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

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

Карта реализуется классом QgsMapCanvas модуля qgis.gui. В основе реализации лежит Qt Graphics View framework. Фреймворк предоставляет пользователю поверхность для рисования и объект для отображения пользовательских элементов, а также даёт возможность взаимодействовать с ними. Предполагается, что читатель достаточно знаком с Qt чтобы разобраться в основных понятиях сцены, вида и элементов. Если это не так, пожалуйста, ознакомьтесь с описанием фреймворка.

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

Теперь можно добавить несколько слоёв. Сначала слой необходимо открыть и добавить его к списку слоёв карты. Затем нужно установить охват и добавить слои к карте:

layer = QgsVectorLayer(path, name, provider)
if not layer.isValid():
  raise IOError, "Failed to open the layer"

# добавляем слой к списку
QgsMapLayerRegistry.instance().addMapLayer(layer)

# устанавливаем охват карты равный охвату слоя
canvas.setExtent(layer.extent())

# добавляем слои на карту
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)

    # создаем инструменты карты
    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 и попробовать выполнить в Консоли Python QGIS. Код ниже показывает как поместить текущий выделенный слой на созданную только что карту:

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

Перед этим необходимо убедиться, что файл mywnd.py находится в каталоге где Python ищет модули (sys.path). Если это не так, просто добавьте его: sys.path.insert(0, '/my/path') — иначе импорт завершится с ошибкой, из-за того, что модуль не найден.

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

Для отображения дополнительных данных поверх карты используются элементы карты. Можно как создавать свои собственные элементы карты (рассматривается дальше), так и использовать существующие классы: QgsRubberBand для рисования полигонов или полилиний, и QgsVertexMarker для рисования точек. Оба этих класса работают в координатах карты, поэтому фигуры автоматически перемещаются/масштабируются при панорамировании и масштабировании карты

Показать полилинию можно так:

r = QgsRubberBand(canvas, False)  # False = не полигон
points = [ QgsPoint(-1,-1), QgsPoint(0,1), QgsPoint(1,-1) ]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

Отобразить полигон:

r = QgsRubberBand(canvas, True)  # True = полигон
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)

Элементы карты связанны с графической сценой карты. Их можно скрыть (а потом снова отобразить) вызывая функции func: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) # или ICON_CROSS, ICON_X
m.setPenWidth(3)

Для временно скрытия и последующего отображения маркеров используется тот же подход, что и для резиновых полос.

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

TODO: how to create a map tool

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

TODO: how to create a map canvas item