중요

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

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), 레이어 식별, 측정, 벡터 편집, 그리고 기타 작업들을 위한 도구들이 있습니다. 다른 그래픽 프로그램들처럼, 도구 하나는 항상 활성화되어 있는 상태이며 여러분은 사용할 수 있는 다른 도구들로 전환할 수 있습니다.

The map canvas is implemented with the QgsMapCanvas class in the qgis.gui module. The implementation is based on the Qt Graphics View framework. This framework generally provides a surface and a view where custom graphics items are placed and user can interact with them. We will assume that you are familiar enough with Qt to understand the concepts of the graphics scene, view and items. If not, please read the overview of the framework.

맵을 이동시키거나 확대/축소할 (또는 새로고침을 촉발하는 기타 다른 액션을 취할) 때마다, 현재 범위 안에 있는 맵을 다시 렌더링합니다. 레이어를 (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/data/data.gpkg|layername=airports", "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)