Робота з картою

Віджет «карта» (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), що відповідає за відображення карти, є клас QgsMapCanvasItem. Цей клас також відповідає за оновлення візуалізованої карти. Окрім цього елемента, який використовується як фон, може існувати довільна кількість додаткових елементів карти. Типовими елементами карти є так звані «гумові» нитки (використовуються під час вимірювань та редагування) або маркери вершин. Елементи карти зазвичай використовуються для відображення роботи інструментів карти. Наприклад, під час створення нового полігону, інструмент карти генерує «гумову» нитку, що показує поточну форму полігону. Всі елементи карти є підкласом QgsMapCanvasItem, який розширяє можливості базового об’єкту QGraphicsItem.

Таким чином, архітектура карти базується на трьох концепціях:

  • карта — для візуалізації даних

  • елементи карти — додаткові елементи, що відображаються на карті

  • інструменти карти — для взаємодії з картою

Вкладення карти

Оскільки карта це такий же віджет як і будь-який інший елемент інтерфейсу Qt, її використання, створення та відображення дуже просте

canvas = QgsMapCanvas()
canvas.show()

Цей код створить нове вікно з картою. Карту також можна вкласти в наявний віджет чи вікно. При використанні Qt Designer та файлів .ui зручно використовувати наступний підхід: на формі розмістити QWidget, та перетворити його у новий клас встановивши ім’я класу в QgsMapCanvas та вказавши в якості заголовного файлу qgis.gui. Все інше зробить програма pyuic4. Це дуже зручний спосіб вкладання карти. Інший спосіб — створити карту та інші елементи інтерфейсу динамічно (в якості дочірніх об’єктів головного вікна або діалогу) та розмістити їх у вікні.

За замовчанням фон карти чорний, згладжування під час візуалізації відсутнє. Щоб зробити фон білим та увімкнути згладжування використовується код

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.

Створити полілінію можна так

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)

Зверніть увагу, вершини полігону представлені не простим списком, це список меж полігону: перше кільце є зовнішньою межею, всі інші (необов’язкові) кільця відповідають діркам у полігоні.

Гумові полоси можна налаштовувати, а саме змінювати їх колір та товщину

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

Елементи карти прив’язуються до графічної сцени. Щоб тимчасово сховати їх (а потім знову показати) використовуються методи hide() та show(). Щоб остаточно видалити елемент необхідно видалити його з графічної сцени

canvas.scene().removeItem(r)

(при використанні C++ достатньо просто видалити елемент, однак у Python 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()"))

Створення власних елементів карти

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)