중요

번역은 여러분이 참여할 수 있는 커뮤니티 활동입니다. 이 페이지는 현재 100.00% 번역되었습니다.

9. 맵 캔버스 사용하기

힌트

PyQGIS 콘솔을 사용하지 않는 경우 이 페이지에 있는 코드 조각들을 다음과 같이 가져와야 합니다:

 1from qgis.PyQt.QtGui import (
 2    QColor,
 3)
 4
 5from qgis.PyQt.QtCore import Qt, QRectF
 6
 7from qgis.PyQt.QtWidgets import QMenu
 8
 9from qgis.core import (
10    QgsVectorLayer,
11    QgsPoint,
12    QgsPointXY,
13    QgsProject,
14    QgsGeometry,
15    QgsMapRendererJob,
16    QgsWkbTypes,
17)
18
19from qgis.gui import (
20    QgsMapCanvas,
21    QgsVertexMarker,
22    QgsMapCanvasItem,
23    QgsMapMouseEvent,
24    QgsRubberBand,
25)

맵 캔버스가 중첩하는 맵 레이어들로 구성된 맵을 보여주고 맵과 레이어들과 쌍방향 작업을 할 수 있게 해주기 때문에, 맵 캔버스 위젯은 아마도 QGIS에서 가장 중요한 위젯일 겁다. 맵 캔버스는 항상 현재 캔버스 범위로 정의되는 맵의 일부분을 보여줍니다. 쌍방향 작업은 맵 도구 를 사용해서 이루어집니다: 이동(pan), 확대/축소(zoom), 레이어 식별, 측정, 벡터 편집, 그리고 기타 작업들을 위한 도구들이 있습니다. 다른 그래픽 프로그램들처럼, 도구 하나는 항상 활성화되어 있는 상태이며 여러분은 사용할 수 있는 다른 도구들로 전환할 수 있습니다.

맵 캔버스는 qgis.gui 모듈에 있는 QgsMapCanvas 클래스로 구현됩니다. 이 구현은 Qt 그래픽스 뷰 프레임워크를 기반으로 합니다. 이 프레임워크는 일반적으로 사용자 정의 그래픽 항목을 배치하고 쌍방향 작업을 할 수 있는 표면(surface)과 뷰(view)를 제공합니다. 여러분이 그래픽 신(scene), 뷰, 항목이라는 개념을 충분히 이해할 만큼 Qt에 익숙하다고 가정할 것입니다. 그렇지 않을 경우, 프레임워크의 개요 를 읽어보시기 바랍니다.

맵을 이동시키거나 확대/축소할 (또는 새로고침을 촉발하는 기타 다른 액션을 취할) 때마다, 현재 범위 안에 있는 맵을 다시 렌더링합니다. 레이어를 (QgsMapRendererJob 클래스를 사용해서) 이미지로 렌더링해서, 캔버스에 이 이미지를 보이는 것입니다. QgsMapCanvas 클래스는 렌더링된 맵의 새로고침도 제어합니다. 배경 역할을 하는 이 항목 외에, 더 많은 맵 캔버스 항목들 이 있을 수도 있습니다.

전형적인 맵 캔버스 항목이라고 하면 (측정, 벡터 편집 등등에 쓰이는) 고무줄(rubber band) 또는 꼭짓점 마커 등등이 있습니다. 이런 캔버스 항목들은 보통 맵 도구들을 위한 가시적 피드백을 주기 위해 사용됩니다. 예를 들면 새 폴리곤을 생성할 때 맵 도구가 폴리곤의 현재 형태를 보여주는 고무줄 캔버스 항목을 생성합니다. 모든 맵 캔버스 항목은 기본 QGraphicsItem 객체에 더 많은 기능을 추가하는 QgsMapCanvasItem 클래스의 하위 클래스입니다.

요약하면 맵 캔버스 아키텍처는 다음 3가지 개념으로 이루어집니다.

  • 맵 캔버스 — 맵을 보여주는 데 쓰입니다.

  • 맵 캔버스 항목 — 맵 캔버스에 보일 수 있는 추가적인 항목들입니다.

  • 맵 도구 — 맵 캔버스와 쌍방향 작업을 하는 데 쓰입니다.

9.1. 맵 캔버스 내장시키기

맵 캔버스는 다른 모든 Qt 위젯과 마찬가지이기 때문에, 이를 사용하는 방법도 생성하고 보이는 방법처럼 간단합니다.

canvas = QgsMapCanvas()
canvas.show()

이 코드는 맵 캔버스를 가진 독립형(standalone) 창을 생성합니다. 기존 위젯이나 창에 맵 캔버스 창을 내장시킬 수도 있습니다. .ui 파일과 Qt 설계자를 사용할 때, 양식 상에 QWidget 을 배치하고 새 클래스로 승격시키십시오: QgsMapCanvas 를 클래스 이름으로 그리고 qgis.gui 를 헤더 파일로 설정하십시오. pyuic5 유틸리티가 이를 처리할 것입니다. 캔버스를 내장시킬 수 있는 매우 편리한 방법입니다. 다른 방법은 맵 캔버스와 기타 위젯들을 (메인 창 또는 대화창의 하위 창으로써) 작성하는 코드를 직접 입력한 다음 조판(layout)을 생성하는 것입니다.

맵 캔버스는 기본적으로 검은색 배경이고 위신호 제거(anti-aliasing)를 사용하지 않습니다. 배경을 하얀색으로 설정하고 매끄러운 렌더링을 위해 위신호 제거를 활성화하려면:

canvas.setCanvasColor(Qt.white)
canvas.enableAntiAliasing(True)

(여러분이 궁금할 경우를 위해 알려드리자면, QtPyQt.QtCore 모듈에서 나온 것이고 Qt.white 는 사전 정의된 QColor 인스턴스들 가운데 하나입니다.)

이제 맵 레이어 몇 개를 추가할 때입니다. 먼저 레이어 하나를 열어서 현재 프로젝트에 추가할 것입니다. 그 다음 캔버스 범위를 설정하고 캔버스에 보일 레이어 목록을 설정할 것입니다.

 1vlayer = QgsVectorLayer('testdata/airports.shp', "Airports layer", "ogr")
 2if not vlayer.isValid():
 3    print("Layer failed to load!")
 4
 5# add layer to the registry
 6QgsProject.instance().addMapLayer(vlayer)
 7
 8# set extent to the extent of our layer
 9canvas.setExtent(vlayer.extent())
10
11# set the map canvas layer set
12canvas.setLayers([vlayer])

이 명령어들을 실행하면, 사용자가 불러온 레이어가 캔버스에 보일 것입니다.

9.2. 고무줄과 꼭짓점 마커

캔버스에 있는 맵 최상단에 추가 데이터를 보이려면 맵 캔버스 항목을 사용하십시오. (`writing-custom-map-canvas-items`_ 에서 설명할) 사용자 정의 캔버스 항목 클래스를 생성할 수 있지만, 사용자 편의를 위한 유용한 캔버스 항목 클래스 2개가 있습니다: 폴리라인 또는 폴리곤을 그리기 위한 QgsRubberBand 클래스와 포인트를 그리기 위한 QgsVertexMarker 클래스입니다. 두 클래스 모두 맵 좌표를 사용해서 작업하기 때문에, 캔버스를 이동시키거나 확대/축소할 때 형태를 자동으로 이동/크기 조정합니다.

폴리라인을 보이려면:

r = QgsRubberBand(canvas, QgsWkbTypes.LineGeometry)  # line
points = [QgsPoint(-100, 45), QgsPoint(10, 60), QgsPoint(120, 45)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

폴리곤을 보이려면:

r = QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry)  # polygon
points = [[QgsPointXY(-100, 35), QgsPointXY(10, 50), QgsPointXY(120, 35)]]
r.setToGeometry(QgsGeometry.fromPolygonXY(points), None)

폴리곤의 포인트들이 평범한 목록이 아니라는 점을 기억하십시오. 사실 이 목록은 폴리곤의 선형 고리들을 담고 있는 고리 목록입니다. 첫 번째 고리는 외곽 경계이며, 그 다음 (있을 수도 있고 없을 수도 있는) 고리들은 폴리곤 내부의 구멍에 해당합니다.

고무줄을 사용자 정의할 수 있습니다. 즉 색상 및 선 너비를 변경할 수 있습니다.

r.setColor(QColor(0, 0, 255))
r.setWidth(3)

캔버스 항목은 캔버스 신(scene)에 바인딩되어 있습니다. 캔버스 항목을 일시적으로 숨기려면 (그리고 다시 보이려면) hide()show() 함수 조합을 사용하십시오. 어떤 항목을 완전히 제거하려면, 캔버스 신에서 제거해야 합니다:

canvas.scene().removeItem(r)

(C++의 경우 항목을 그냥 삭제할 수 있지만, 파이썬의 경우 del r 명령어는 참조만 삭제할 뿐 실제 객체는 캔버스가 소유하고 있기 때문에 계속 남아 있을 것입니다.)

포인트도 고무줄을 사용해서 그릴 수 있지만, 이 작업에는 QgsVertexMarker 클래스가 더 적합합니다. (QgsRubberBand 클래스는 원하는 지점 주위에 직사각형을 그릴 뿐입니다.)

꼭짓점 마커는 다음과 같이 사용할 수 있습니다:

m = QgsVertexMarker(canvas)
m.setCenter(QgsPointXY(10,40))

이 코드는 [10,45] 위치에 빨간색 십자표를 그릴 것입니다. 아이콘의 유형, 크기, 색상, 그리고 펜 너비를 사용자 정의할 수 있습니다:

m.setColor(QColor(0, 255, 0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X
m.setPenWidth(3)

꼭짓점 마커를 일시적으로 숨기거나 캔버스에서 제거하려면, 고무줄의 경우와 동일한 메소드들을 사용하십시오.

9.3. 캔버스에서 맵 도구 사용하기

다음 예시는 맵 캔버스와 맵 이동 및 확대/축소를 위한 기본 맵 도구를 담고 있는 창일 작성합니다. 각 도구의 활성화를 위한 액션을 생성합니다: QgsMapToolPan 클래스는 이동을 수행하고, QgsMapToolZoom 클래스 인스턴스의 쌍으로 확대/축소를 수행합니다. 이 액션들을 체크할 수 있게 설정해서, 향후 액션의 체크/체크 해제 상태를 자동으로 처리할 수 있도록 도구에 할당합니다 — 어떤 맵 도구를 활성화시킬 때, 해당 도구의 액션을 선택 상태로 표시하고 이전 맵 도구의 액션을 선택 해제합니다. 맵 도구를 활성화시키는 메소드는 setMapTool() 입니다.

 1from qgis.gui import *
 2from qgis.PyQt.QtWidgets import QAction, QMainWindow
 3from qgis.PyQt.QtCore import Qt
 4
 5class MyWnd(QMainWindow):
 6    def __init__(self, layer):
 7        QMainWindow.__init__(self)
 8
 9        self.canvas = QgsMapCanvas()
10        self.canvas.setCanvasColor(Qt.white)
11
12        self.canvas.setExtent(layer.extent())
13        self.canvas.setLayers([layer])
14
15        self.setCentralWidget(self.canvas)
16
17        self.actionZoomIn = QAction("Zoom in", self)
18        self.actionZoomOut = QAction("Zoom out", self)
19        self.actionPan = QAction("Pan", self)
20
21        self.actionZoomIn.setCheckable(True)
22        self.actionZoomOut.setCheckable(True)
23        self.actionPan.setCheckable(True)
24
25        self.actionZoomIn.triggered.connect(self.zoomIn)
26        self.actionZoomOut.triggered.connect(self.zoomOut)
27        self.actionPan.triggered.connect(self.pan)
28
29        self.toolbar = self.addToolBar("Canvas actions")
30        self.toolbar.addAction(self.actionZoomIn)
31        self.toolbar.addAction(self.actionZoomOut)
32        self.toolbar.addAction(self.actionPan)
33
34        # create the map tools
35        self.toolPan = QgsMapToolPan(self.canvas)
36        self.toolPan.setAction(self.actionPan)
37        self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
38        self.toolZoomIn.setAction(self.actionZoomIn)
39        self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
40        self.toolZoomOut.setAction(self.actionZoomOut)
41
42        self.pan()
43
44    def zoomIn(self):
45        self.canvas.setMapTool(self.toolZoomIn)
46
47    def zoomOut(self):
48        self.canvas.setMapTool(self.toolZoomOut)
49
50    def pan(self):
51        self.canvas.setMapTool(self.toolPan)

파이썬 콘솔 편집기에서 앞의 코드를 시도해볼 수 있습니다. 캔버스 창을 열려면, MyWnd 클래스를 초기 설정하는 다음 줄들을 추가하십시오. 새로 생성한 캔버스 위에 현재 선택된 레이어를 렌더링할 것입니다:

w = MyWnd(iface.activeLayer())
w.show()

9.3.1. QgsMapToolIdentifyFeature를 사용해서 피처 선택하기

사용자에게 콜백(callback) 함수로 전송될 피처 하나를 선택하도록 요청하려면, QgsMapToolIdentifyFeature 맵 도구를 사용하면 됩니다.

 1def callback(feature):
 2  """Code called when the feature is selected by the user"""
 3  print("You clicked on feature {}".format(feature.id()))
 4
 5canvas = iface.mapCanvas()
 6feature_identifier = QgsMapToolIdentifyFeature(canvas)
 7
 8# indicates the layer on which the selection will be done
 9feature_identifier.setLayer(vlayer)
10
11# use the callback as a slot triggered when the user identifies a feature
12feature_identifier.featureIdentified.connect(callback)
13
14# activation of the map tool
15canvas.setMapTool(feature_identifier)

9.3.2. 맵 캔버스 컨텍스트 메뉴에 항목 추가하기

contextMenuAboutToShow 신호를 사용해서 맵 캔버스의 컨텍스트 메뉴에 추가할 수도 있는 항목들을 통해서도 맵 캔버스와의 쌍방향 작업을 수행할 수 있습니다.

다음은 맵 캔버스를 오른쪽 클릭할 때 나타나는 기본 항목에 My menu ► My Action 액션을 추가하는 코드입니다.

1# a slot to populate the context menu
2def populateContextMenu(menu: QMenu, event: QgsMapMouseEvent):
3    subMenu = menu.addMenu('My Menu')
4    action = subMenu.addAction('My Action')
5    action.triggered.connect(lambda *args:
6                             print(f'Action triggered at {event.x()},{event.y()}'))
7
8canvas.contextMenuAboutToShow.connect(populateContextMenu)
9canvas.show()

9.4. 사용자 정의 맵 도구 작성하기

여러분이 캔버스 상에서 수행하는 액션에 사용자 정의 습성을 구현하기 위한 사용자 정의 도구를 작성할 수 있습니다.

맵 도구는 클래스든 파생 클래스든 QgsMapTool 클래스로부터 상속받아야 하며, 이미 앞에서 본 바와 같이 캔버스에서 setMapTool() 메소드를 사용해서 활성 도구로써 선택되어야 합니다.

다음 예시는 캔버스 상에서 클릭&드래그로 직사각형 범위를 정의하도록 해주는 맵 도구의 코드입니다. 직사각형을 정의하면, 콘솔에 그 경계 좌표를 출력합니다. 앞에서 설명했던 고무줄 기능을 사용해서 정의되고 있는 직사각형을 보일 것입니다.

 1class RectangleMapTool(QgsMapToolEmitPoint):
 2  def __init__(self, canvas):
 3    self.canvas = canvas
 4    QgsMapToolEmitPoint.__init__(self, self.canvas)
 5    self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
 6    self.rubberBand.setColor(Qt.red)
 7    self.rubberBand.setWidth(1)
 8    self.reset()
 9
10  def reset(self):
11    self.startPoint = self.endPoint = None
12    self.isEmittingPoint = False
13    self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
14
15  def canvasPressEvent(self, e):
16    self.startPoint = self.toMapCoordinates(e.pos())
17    self.endPoint = self.startPoint
18    self.isEmittingPoint = True
19    self.showRect(self.startPoint, self.endPoint)
20
21  def canvasReleaseEvent(self, e):
22    self.isEmittingPoint = False
23    r = self.rectangle()
24    if r is not None:
25      print("Rectangle:", r.xMinimum(),
26            r.yMinimum(), r.xMaximum(), r.yMaximum()
27           )
28
29  def canvasMoveEvent(self, e):
30    if not self.isEmittingPoint:
31      return
32
33    self.endPoint = self.toMapCoordinates(e.pos())
34    self.showRect(self.startPoint, self.endPoint)
35
36  def showRect(self, startPoint, endPoint):
37    self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
38    if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
39      return
40
41    point1 = QgsPointXY(startPoint.x(), startPoint.y())
42    point2 = QgsPointXY(startPoint.x(), endPoint.y())
43    point3 = QgsPointXY(endPoint.x(), endPoint.y())
44    point4 = QgsPointXY(endPoint.x(), startPoint.y())
45
46    self.rubberBand.addPoint(point1, False)
47    self.rubberBand.addPoint(point2, False)
48    self.rubberBand.addPoint(point3, False)
49    self.rubberBand.addPoint(point4, True)    # true to update canvas
50    self.rubberBand.show()
51
52  def rectangle(self):
53    if self.startPoint is None or self.endPoint is None:
54      return None
55    elif (self.startPoint.x() == self.endPoint.x() or \
56          self.startPoint.y() == self.endPoint.y()):
57      return None
58
59      return QgsRectangle(self.startPoint, self.endPoint)
60
61  def deactivate(self):
62    QgsMapTool.deactivate(self)
63    self.deactivated.emit()

9.5. 사용자 정의 맵 캔버스 항목 작성하기

다음은 원을 그리는 사용자 정의 캔버스 항목의 예시입니다:

 1class CircleCanvasItem(QgsMapCanvasItem):
 2  def __init__(self, canvas):
 3    super().__init__(canvas)
 4    self.center = QgsPoint(0, 0)
 5    self.size   = 100
 6
 7  def setCenter(self, center):
 8    self.center = center
 9
10  def center(self):
11    return self.center
12
13  def setSize(self, size):
14    self.size = size
15
16  def size(self):
17    return self.size
18
19  def boundingRect(self):
20    return QRectF(self.center.x() - self.size/2,
21      self.center.y() - self.size/2,
22      self.center.x() + self.size/2,
23      self.center.y() + self.size/2)
24
25  def paint(self, painter, option, widget):
26    path = QPainterPath()
27    path.moveTo(self.center.x(), self.center.y());
28    path.arcTo(self.boundingRect(), 0.0, 360.0)
29    painter.fillPath(path, QColor("red"))
30
31
32# Using the custom item:
33item = CircleCanvasItem(iface.mapCanvas())
34item.setCenter(QgsPointXY(200,200))
35item.setSize(80)