Работа с картой

Виджет “карта” (Map Canvas) является одним из наиболее важных, так как именно он отвечает за отображение карты, состоящей из наложенных друг на друга слоёв, и позволяет взаимодействовать как со всей картой, так и с отдельными слоями. Виджет отображает только часть карты, заданную текущим охватом. Взаимодействие выполняется при помощи инструментов карты (map tools): среди которых присутствуют инструменты панорамирования, масштабирования, определения слоёв, измерения, редактирования и другие. Как и в других программах, активным в каждый момент времени может быть только один инструмент, при необходимости выполняется переключение между ними.

Map canvas is implemented as QgsMapCanvas class in 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 make sure to read the overview of the framework.

Всякий раз, когда пользователь выполняет панорамирование, масштабирование (или любое другое действие, вызывающее обновление карты), происходит перерисовка карты в пределах текущего охвата. Отрисовка слоёв выполняется в изображение (за это отвечает класс QgsMapRenderer), которое затем отображается на карте. Графическим объектом (в терминах фреймвока Qt — graphics view), отвечающим за отображение карты, является класс QgsMapCanvasMap. Этот же класс следит за обновлением карты. Помимо этого объекта, который служит фоном, может существовать множество элементов карты. Обычно, в роли элементов карты выступают “резиновые” линии (используемые при измерении и редактировании слоёв) или маркеры вершин. Чаще всего элементы карты используются для визуализации работы инструментов карты. Например, при создании нового полигона, инструмент карты создает “резиновый” элемент карты, показывающий текущую форму полигона. Все элементы карты являются наследниками QgsMapCanvasItem и добавляют свой функционал к базовому объекту QGraphicsItem.

Таким образом, архитектурно карта состоит из трёх элементов:

  • карта — для отображения данных

  • элементы карты — дополнительные объекты, которые можно отобразить на карте

  • инструменты карты — обеспечивают взаимодействие с картой

Встраивание карты

Map canvas is a widget like any other Qt widget, so using it is as simple as creating and showing it

canvas = QgsMapCanvas()
canvas.show()

Этот код создаст новое окно с картой. Точно так же можно встраивать карту в существующий виджет или окно. При использовании Qt Designer и файлов .ui удобно делать так: на форму положить QWidget и объявить его новым классом, установив в качестве имени класса QgsMapCanvas и qgis.gui в качестве заголовочного файла. Всё остальное сделает программа pyuic4. Как видите, это очень простой и удобный способ встраивания карты в приложение. Ещё один способ — создать виджет карты и другие элементы интерфейса динамически (в качестве дочерних объектов основного или диалогового окна) и разместить их на компоновке.

By default, map canvas has black background and does not use anti-aliasing. To set white background and enable anti-aliasing for smooth rendering

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

(если вас интересует, то приставка Qt используется модулем PyQt4.QtCore а Qt.white это один из предварительно заданных экземпляров QColor.)

Now it is time to add some map layers. We will first open a layer and add it to the map layer registry. Then we will set the canvas extent and set the list of layers for canvas

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)])

После выполнения этих команд на карте должен отобразиться загруженный слой.

Использование инструментов карты

The following example constructs a window that contains a map canvas and basic map tools for map panning and zooming. Actions are created for activation of each tool: panning is done with QgsMapToolPan, zooming in/out with a pair of QgsMapToolZoom instances. The actions are set as checkable and later assigned to the tools to allow automatic handling of checked/unchecked state of the actions – when a map tool gets activated, its action is marked as selected and the action of the previous map tool is deselected. The map tools are activated using setMapTool() method.

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)

You can put the above code to a file, e.g. mywnd.py and try it out in Python console within QGIS. This code will put the currently selected layer into newly created canvas

import mywnd
w = mywnd.MyWnd(qgis.utils.iface.activeLayer())
w.show()

Just make sure that the mywnd.py file is located within Python search path (sys.path). If it isn’t, you can simply add it: sys.path.insert(0, '/my/path') — otherwise the import statement will fail, not finding the module.

Резиновые полосы и маркеры вершин

To show some additional data on top of the map in canvas, use map canvas items. It is possible to create custom canvas item classes (covered below), however there are two useful canvas item classes for convenience: QgsRubberBand for drawing polylines or polygons, and QgsVertexMarker for drawing points. They both work with map coordinates, so the shape is moved/scaled automatically when the canvas is being panned or zoomed.

To show a polyline

r = QgsRubberBand(canvas, False)  # False = not a polygon
points = [QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

To show a polygon

r = QgsRubberBand(canvas, True)  # True = a polygon
points = [[QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]]
r.setToGeometry(QgsGeometry.fromPolygon(points), None)

Обратите внимание, что узлы полигона представлены не плоским списком: на самом деле это список границ полигона. Первое кольцо описывает внешний контур, все остальные (не обязательные) — соответствуют дыркам в полигоне.

Rubber bands allow some customization, namely to change their color and line width

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

The canvas items are bound to the canvas scene. To temporarily hide them (and show again, use the hide() and show() combo. To completely remove the item, you have to remove it from the scene of the canvas

canvas.scene().removeItem(r)

(при использовании C++ можно просто удалить элемент, однако в Python del r удалит только ссылку, а сам объект останется на месте, т.к. его владельцем является карта)

Rubber band can be also used for drawing points, however QgsVertexMarker class is better suited for this (QgsRubberBand would only draw a rectangle around the desired point). How to use the vertex marker

m = QgsVertexMarker(canvas)
m.setCenter(QgsPoint(0, 0))

This will draw a red cross on position [0,0]. It is possible to customize the icon type, size, color and pen width

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

For temporary hiding of vertex markers and removing them from canvas, the same applies as for the rubber bands.

Создание собственных инструментов карты

You can write your custom tools, to implement a custom behaviour to actions performed by users on the canvas.

Map tools should inherit from the QgsMapTool class or any derived class, and selected as active tools in the canvas using the setMapTool() method as we have already seen.

Here is an example of a map tool that allows to define a rectangular extent by clicking and dragging on the canvas. When the rectangle is defined, it prints its boundary coordinates in the console. It uses the rubber band elements described before to show the selected rectangle as it is being defined.

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):
      QgsMapTool.deactivate(self)
      self.emit(SIGNAL("deactivated()"))

Создание собственных элементов карты

TODO:
how to create a map canvas item
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)