맵 캔버스 위젯은 QGIS에서 가장 중요한 위젯이라고 할 수 있습니다. 맵 레이어들을 층층이 쌓아올려 만들어진 맵을 보여주며 맵과 레이어가 상호작용할 수 있도록 해주기 때문입니다. 캔버스는 항상 현재 캔버스 범위에 따라 정의된 맵의 일부분을 보여줍니다. 상호작용은 맵 도구 를 통해 이루어집니다. 이동, 확대/축소, 레이어 정보조회, 측정, 벡터 편집 외에도 많은 도구들이 있습니다. 다른 그래픽 프로그램과 마찬가지로, 언제나 도구들 중 하나는 활성화되어 있으며 사용자가 사용할 도구를 선택할 수 있습니다.
맵 캔버스는 qgis.gui 모듈의 QgsMapCanvas 클래스로 구현됩니다. 이 구현체는 Qt Graphic View 프레임워크를 기반으로 합니다. 이 프레임워크는 일반적으로 외관(surface)과 사용자 지정 그래픽 아이템이 들어가 사용자와 상호작용할 수 있는 뷰를 제공합니다. 여러분이 그래픽 신(scene), 뷰, 그리고 아이템의 개념을 이해할 정도로 Qt를 잘 알고 있다고 가정할 것입니다. 그렇지 않다면 반드시 프레임워크의 개요 를 읽어보도록 하십시오.
맵을 이동 또는 확대/축소(또는 새로고침이 필요한 다른 작업)시킬 때마다, QGIS는 현재 범위 내에서 맵을 다시 렌더링합니다. 레이어는 (QgsMapRenderer 클래스를 사용해서) 이미지로 렌더링된 다음 캔버스에 표출됩니다. 맵을 표출하는 역할을 하는 (Qt 그래픽 뷰 프레임워크 측면의) 그래픽 아이템이 QgsMapCanvasMap 클래스입니다. 이 클래스는 렌더링된 맵의 새로고침도 제어합니다. 백그라운드로 작동하는 이 아이템 외에도 많은 맵 캔버스 아이템 들이 있을 수 있습니다. 전형적인 맵 캔버스 아이템으로는 버텍스 마커나 (측정, 벡터 편집 등에 사용되는) 고무줄(rubber band)이 있습니다. 보통 맵 도구에 대한 시각적인 피드백을 주는 데 캔버스 아이템을 이용합니다. 예를 들면 새 폴리곤 생성 시 맵 도구는 폴리곤의 현재 형상을 보여주는 캔버스 아이템인 고무줄을 생성합니다. 모든 맵 캔버스 아이템은 QgsMapCanvasItem 클래스의 하위클래스인데, 이 QgsMapCanvasItem 클래스는 기본 QGraphicsItem 오브젝트에 좀 더 많은 기능이 추가되어 있습니다.
요약하면 맵 캔버스 아키텍처는 다음 3가지 개념으로 이루어집니다.
맵 캔버스 — 맵을 보여주는 데 쓰입니다.
맵 캔버스 아이템 — 맵 캔버스에 표출될 수 있는 추가적인 아이템들입니다.
맵 도구 — 맵 캔버스와의 상호작용에 쓰입니다.
맵 캔버스는 다른 Qt 위젯들과 마찬가지로 위젯이므로, 생성, 표출은 물론 사용법도 간단합니다.
canvas = QgsMapCanvas()
canvas.show()
이렇게 하면 맵 캔버스를 가진 독립적인 창이 만들어집니다. 이를 기존 위젯이나 창에 들어가게 할 수도 있습니다. .ui 파일과 Qt 디자이너를 사용해서, 폼 윈도우에 QWidget 을 만든 다음 클래스 명으로 QgsMapCanvas 를 그리고 헤더 파일로 qgis.gui 를 설정해 새로운 클래스로 바꿔주십시오. pyuic4 유틸리티가 이 파일을 변환해 줄 것입니다. 이렇게 하면 매우 편리하게 캔버스를 내장시킬 수 있습니다. 다른 방법으로는 직접 코드를 작성해서 (메인 창 또는 대화상자의 자식으로) 맵 캔버스 및 다른 위젯들을 구성해서 레이아웃을 만들 수도 있습니다.
맵 캔버스의 초기값은 배경은 검은색, 안티알리아싱은 사용하지 않는 것입니다. 배경을 하얀색으로 설정하고 부드러운 렌더링을 위해 안티알리아싱을 활성화하려면 다음과 같이 하십시오.
canvas.setCanvasColor(Qt.white)
canvas.enableAntiAliasing(True)
(추가로 설명하자면 Qt 는 PyQt4.QtCore 모듈에서 나왔고 Qt.white 는 미리 정의된 QColor 인스턴스 가운데 하나입니다.)
이제 맵 레이어 몇 개를 추가할 순서입니다. 먼저 레이어를 불러와 맵 레이어 레지스트리에 추가할 것입니다. 그 다음 캔버스 범위를 설정하고, 캔버스에 표출할 레이어 목록을 설정할 것입니다.
layer = QgsVectorLayer(path, name, provider)
if not layer.isValid():
raise IOError, "Failed to open the layer"
# add layer to the registry
QgsMapLayerRegistry.instance().addMapLayer(layer)
# set extent to the extent of our layer
canvas.setExtent(layer.extent())
# set the map canvas layer set
canvas.setLayerSet([QgsMapCanvasLayer(layer)])
이 명령어들을 실행하면, 사용자가 불러온 레이어가 캔버스에 보일 것입니다.
다음은 맵 캔버스와 이동 및 확대/축소를 위한 기본 맵 도구가 들어있는 윈도우를 만드는 예시 코드입니다. 액션들이 각 툴들의 동작을 위해 만들어집니다. 이동은 QgsMapToolPan 클래스, 축소와 확대는 QgsMapToolZoom 클래스 인스턴스로 동작합니다. 이 액션들은 체크 가능하도록 설정되어, 이후 액션의 체크/해제 상태가 자동적으로 도구에 반영되게 됩니다. 하나의 맵 도구가 활성화되면, 해당 액션이 선택된 것으로 표시되고, 이전 활성 맵 도구는 비활성화 됩니다. 이 맵 도구는 setMapTool() 메소드로 활성화 됩니다.
from qgis.gui import *
from PyQt4.QtGui import QAction, QMainWindow
from PyQt4.QtCore import SIGNAL, Qt, QString
class MyWnd(QMainWindow):
def __init__(self, layer):
QMainWindow.__init__(self)
self.canvas = QgsMapCanvas()
self.canvas.setCanvasColor(Qt.white)
self.canvas.setExtent(layer.extent())
self.canvas.setLayerSet([QgsMapCanvasLayer(layer)])
self.setCentralWidget(self.canvas)
actionZoomIn = QAction(QString("Zoom in"), self)
actionZoomOut = QAction(QString("Zoom out"), self)
actionPan = QAction(QString("Pan"), self)
actionZoomIn.setCheckable(True)
actionZoomOut.setCheckable(True)
actionPan.setCheckable(True)
self.connect(actionZoomIn, SIGNAL("triggered()"), self.zoomIn)
self.connect(actionZoomOut, SIGNAL("triggered()"), self.zoomOut)
self.connect(actionPan, SIGNAL("triggered()"), self.pan)
self.toolbar = self.addToolBar("Canvas actions")
self.toolbar.addAction(actionZoomIn)
self.toolbar.addAction(actionZoomOut)
self.toolbar.addAction(actionPan)
# create the map tools
self.toolPan = QgsMapToolPan(self.canvas)
self.toolPan.setAction(actionPan)
self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
self.toolZoomIn.setAction(actionZoomIn)
self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
self.toolZoomOut.setAction(actionZoomOut)
self.pan()
def zoomIn(self):
self.canvas.setMapTool(self.toolZoomIn)
def zoomOut(self):
self.canvas.setMapTool(self.toolZoomOut)
def pan(self):
self.canvas.setMapTool(self.toolPan)
이 예시 코드를 mywnd.py 같은 파일명으로 저장해서 QGIS의 파이썬 콘솔에서 실행해볼 수 있습니다. 아래 코드는 현재 선택되어 있는 레이어를 새로 만들어진 캔버스에 넣을 것입니다.
import mywnd
w = mywnd.MyWnd(qgis.utils.iface.activeLayer())
w.show()
다만 mywnd.py 파일이 파이썬 검색 경로(sys.path) 안에 위치하는지 확인할 필요가 있습니다. 만약 없다면 sys.path.insert(0, '/my/path') 명령어로 추가하면 됩니다. 파일이 검색 경로 안에 없다면 모듈을 찾지 못 해 임포트 선언이 실패할 것입니다.
캔버스의 맵 상에 추가적인 데이터들을 표시하려면, 맵 캔버스 아이템을 이용하십시오. 사용자 지정 캔버스 아이템 클래스를 (다음 단락에서 설명하는 대로) 생성할 수도 있지만, 편리하게 활용할 수 있는 캔버스 아이템 클래스가 2가지 있습니다. 폴리라인 또는 폴리곤을 그릴 때 쓰이는 QgsRubberBand 클래스와 포인트를 그릴 때 쓰이는 QgsVertexMarker 클래스입니다. 두 클래스 모두 맵 좌표와 함께 동작하므로, 캔버스를 이동하거나 확대/축소할 때마다 자동적으로 형상이 이동되고, 축척에 따라 변합니다.
폴리라인을 표시하는 방법은 다음과 같습니다.
r = QgsRubberBand(canvas, False) # False = not a polygon
points = [QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)
폴리곤을 표시하는 방법은 다음과 같습니다.
r = QgsRubberBand(canvas, True) # True = a polygon
points = [[QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]]
r.setToGeometry(QgsGeometry.fromPolygon(points), None)
폴리곤의 포인트들이 1차원 리스트가 아니라는 점에 주의하십시오. 실제로, 폴리곤의 포인트들은 폴리곤의 선형 폐곡선을 담고 있는 폐곡선 리스트입니다. 첫 번째 폐곡선은 외곽 경계선이고, 그 다음의 (있을 수도 있고 없을 수도 있는) 폐곡선은 폴리곤 내부의 구멍에 해당합니다.
고무줄을 사용자 지정 할 수 있습니다. 즉 색상 및 선 두께를 변경할 수도 있습니다.
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(QgsPoint(0, 0))
이 코드는 [0,0] 위치에 빨강색 십자가를 그릴 것입니다. 아이콘 유형, 크기, 색상, 그리고 펜 두께를 사용자 지정할 수 있습니다.
m.setColor(QColor(0, 255, 0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X
m.setPenWidth(3)
버텍스 마커를 임시적으로 숨기거나 캔버스에서 제거하려면, 고무줄의 경우와 비슷한 과정을 거치면 됩니다.
액션에 사용자 지정 동작방식을 구현해서, 캔버스 상에서 사용자 의도대로 동작하는 여러분만의 사용자 지정 도구를 만들 수 있습니다.
맵 도구들은 QgsMapTool 클래스 또는 그 파생 클래스를 상속 받아야만 합니다. 그리고 이전에 배웠던 대로 setMapTool() 메소드를 통해 캔버스에서 활성 도구로 설정할 수 있습니다.
다음 예시 코드는 캔버스 상에서 클릭과 드래그로 사각형 범위를 정의하도록 해주는 맵 도구입니다. 사각형이 정의되면, 콘솔에 그 범위 좌표를 출력합니다. 이전에 설명했던 고무줄 기능을 사용해서 확정되기 전의 사각형을 표시할 것입니다.
class RectangleMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon)
self.rubberBand.setColor(Qt.red)
self.rubberBand.setWidth(1)
self.reset()
def reset(self):
self.startPoint = self.endPoint = None
self.isEmittingPoint = False
self.rubberBand.reset(QGis.Polygon)
def canvasPressEvent(self, e):
self.startPoint = self.toMapCoordinates(e.pos())
self.endPoint = self.startPoint
self.isEmittingPoint = True
self.showRect(self.startPoint, self.endPoint)
def canvasReleaseEvent(self, e):
self.isEmittingPoint = False
r = self.rectangle()
if r is not None:
print "Rectangle:", r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum()
def canvasMoveEvent(self, e):
if not self.isEmittingPoint:
return
self.endPoint = self.toMapCoordinates(e.pos())
self.showRect(self.startPoint, self.endPoint)
def showRect(self, startPoint, endPoint):
self.rubberBand.reset(QGis.Polygon)
if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
return
point1 = QgsPoint(startPoint.x(), startPoint.y())
point2 = QgsPoint(startPoint.x(), endPoint.y())
point3 = QgsPoint(endPoint.x(), endPoint.y())
point4 = QgsPoint(endPoint.x(), startPoint.y())
self.rubberBand.addPoint(point1, False)
self.rubberBand.addPoint(point2, False)
self.rubberBand.addPoint(point3, False)
self.rubberBand.addPoint(point4, True) # true to update canvas
self.rubberBand.show()
def rectangle(self):
if self.startPoint is None or self.endPoint is None:
return None
elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y():
return None
return QgsRectangle(self.startPoint, self.endPoint)
def deactivate(self):
super(RectangleMapTool, self).deactivate()
self.emit(SIGNAL("deactivated()"))
맵 캔버스 아이템 생성 방법
import sys
from qgis.core import QgsApplication
from qgis.gui import QgsMapCanvas
def init():
a = QgsApplication(sys.argv, True)
QgsApplication.setPrefixPath('/home/martin/qgis/inst', True)
QgsApplication.initQgis()
return a
def show_canvas(app):
canvas = QgsMapCanvas()
canvas.show()
app.exec_()
app = init()
show_canvas(app)