Создание приложения на базе набора библиотек QGIS на Python

Данная статья объясняет процесс создания самостоятельного отдельного приложения на основе QGIS. Одной из целей QGIS, помимо предоставления пользовательского приложения ГИС является также поддержка набора библиотек, которые могут быть использованы для создания новых приложений. Начиная с версии 0.9 разработка может вестись на языках C++ и Python.

Статья основана на переводе соответствующей главы руководства пользователя QGIS (Глава 12. Creating Applications) и обучающих примерах, разработанных Tim Sutton для C++ и портированных на Python Martin Dobias. В статье более подробно с иллюстрациями разъяснены некоторые моменты процесса. Пример приведенный в руководстве пользователя QGIS нуждается в основательной доработке, из-за специфической формы предоставления кода (нумерация линий и смещение отступов) его сложно правильно воспроизвести не имея навыков работы с Python.

Рассмотрим процесс создания приложения, которое будет:
загружать векторный слой
обеспечивать возможность сдвига, увеличения/уменьшения
показывать слой полностью
менять цветовую легенду слоя

Подготовка

Перед началом разработки с использованием библиотек QGIS необходимо установить несколько переменных среды.

Переменные среды в Windows XP доступны через: Пуск\Настройки\Панель управления\Система\Дополнительно\Переменные среды (Strat\Settings\Control Panel\System\Advanced\Environmental Variables).

К системной переменной PATH необходимо добавить путь к папке куда установлен QGIS:
set PATH=C:\qgis;%PATH%

Новая переменная PYTHONPATH должна содержать путь к python bindings, эта переменная должна содержать путь именно к подпапке python в папке установки QGIS, а не к папке где установлен сам Python.
set PYTHONPATH=C:\qgis\python

Неправильная установка переменных среды может вызвать следующую ошибку:
>>> from qgis.core import *

Traceback (most recent call last):
File "", line 1, in
from qgis.core import *
ImportError: No module named qgis.core

Разработка графического интерфейса

Создание основного окна

Интерфейс для нашего приложения, как и сам QGIS разрабатывается с помощью QT (скачать QT для некоммерческого использования можно здесь). В состав QT входит программа QT Designer, которая позволяет создавать и изменять формы без редактирования кода. Интерфейсы созданные таким образом можно затем сконвертировать в код на Python. Также нам понадобится PyQT (скачать).

Откроем QT Designer, обычно он устанавливается в отдельную от QGIS папку, в нашем случае это C:\Gis\Python.

Создадим простейшую форму без меню и панелей инструментов. Для этого либо выберем из окна помощника появляющегося при старте QT Designer пункт Main Window или если помощник не появился, то запустим его из меню File\New Form и нажмем на Create.

Выберем из списка элементов (widgets) элемент Frame находящийся в группе Containers. После этого — щелкнем мышью где-либо на самой форме (вне контейнера который мы только что добавили) и выберем Lay Out in a Grid, эта операция должна увеличить размер добавленного контейнера до размеров формы в целом. Это также свяжет их размеры, так что при изменении размера формы будет меняться и размер контейнера.

Сохраним созданную форму как mainwindow.ui

Добавление меню

Для добавления меню, необходимо выбрать пункт Type Here находящийся в верхнем левом углу окна нового приложения и ввести название пункта, например Map, а затем, последовательно, последовательно добавить команды меню нажимая на Type Here. Введем 5 пунктов, как это показано на иллюстрации.

Создание файла ресурсов

Помимо меню, нашему приложению понадобится также панель инструментов, соответственно, на панели должны будут располагаться некие иконки. Назначить иконки можно будет через файл ресурсов. Чтобы создать этот файл, выберем Resource Editor (редактор ресурсов) и в пункте Current resource (текущий ресурсный файл) выберем New. Создадим ресурсный файл с названием resources.qrc в той же директории, где находится mainwindow.ui

После этого, создадим в ресурсном файле директорию / и добавим в нее 5 иконок для инструментов. Сами иконки можно взять из папки themes\default установочной папки QGIS (C:\Gis\QGIS\themes\default\).

Назначение действий

Для удобства, воспользуемся окном Action Editor (Редактор команд) и переименуем команды, соответствующие каждому пункту меню. Так, назовем команду для пункта Zoom In: mpActionZoomIn, Zoom Out: mpActionZoomOut и т.д. В итоге, у нас должен получиться список из 5 команд:
mpActionZoomIn
mpActionZoomOut
mpActionPan
mpActionAddLayer
mpActionZoomFullExtent

Назначим каждому действию соответствующую иконку, для этого дважды щелкнем на действие и выберем иконку из созданного на предыдущем этапе ресурсного файла. Пустые квадраты, обозначающие действия должны замениться на иконки:

Компиляция формы и ресурсного файла

Компиляция формы

Сохраним созданную форму как mainwindow.ui и скомпилируем ее командой:
pyuic4 -o mainwindow_ui.py mainwindow.ui

Перед запуском, может оказаться необходимым изменить пути к программам запускаемым pyuic4. Для этого нужно найти и отредактировать pyuic4, который является простым bat-файлом. Файл pyuic4 обычно хранится в той же папке, что и python.exe (например: c:\Program Files\Python\).

Результатом компиляции будет представление только что созданной формы на языке Python, следующего содержания:
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Sun Jun 22 18:37:53 2008
# by: PyQt4 UI code generator 4.3.3
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(QtCore.QSize(QtCore.QRect(0,0,579,330).size()).expandedTo(MainWindow.minimumSizeHint()))

self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")

self.gridlayout = QtGui.QGridLayout(self.centralwidget)
self.gridlayout.setMargin(9)
self.gridlayout.setSpacing(6)
self.gridlayout.setObjectName("gridlayout")

self.frameMap = QtGui.QFrame(self.centralwidget)
self.frameMap.setFrameShape(QtGui.QFrame.StyledPanel)
self.frameMap.setFrameShadow(QtGui.QFrame.Raised)
self.frameMap.setObjectName("frameMap")
self.gridlayout.addWidget(self.frameMap,0,0,1,1)
MainWindow.setCentralWidget(self.centralwidget)

self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0,0,579,22))
self.menubar.setObjectName("menubar")

self.menuMap = QtGui.QMenu(self.menubar)
self.menuMap.setObjectName("menuMap")
MainWindow.setMenuBar(self.menubar)

self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)

self.mpActionZoomIn = QtGui.QAction(MainWindow)
self.mpActionZoomIn.setIcon(QtGui.QIcon(":/mActionZoomIn.png"))
self.mpActionZoomIn.setObjectName("mpActionZoomIn")

self.mpActionZoomOut = QtGui.QAction(MainWindow)
self.mpActionZoomOut.setIcon(QtGui.QIcon(":/mActionZoomOut.png"))
self.mpActionZoomOut.setObjectName("mpActionZoomOut")

self.mpActionPan = QtGui.QAction(MainWindow)
self.mpActionPan.setIcon(QtGui.QIcon(":/mActionPan.png"))
self.mpActionPan.setObjectName("mpActionPan")

self.mpActionAddLayer = QtGui.QAction(MainWindow)
self.mpActionAddLayer.setIcon(QtGui.QIcon(":/mActionAddLayer.png"))
self.mpActionAddLayer.setObjectName("mpActionAddLayer")
self.menuMap.addAction(self.mpActionZoomIn)
self.menuMap.addAction(self.mpActionZoomOut)
self.menuMap.addAction(self.mpActionPan)
self.menuMap.addAction(self.mpActionAddLayer)
self.menubar.addAction(self.menuMap.menuAction())

self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)

def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.menuMap.setTitle(QtGui.QApplication.translate("MainWindow", "Map", None, QtGui.QApplication.UnicodeUTF8))
self.mpActionZoomIn.setText(QtGui.QApplication.translate("MainWindow", "Zoom In", None, QtGui.QApplication.UnicodeUTF8))
self.mpActionZoomOut.setText(QtGui.QApplication.translate("MainWindow", "Zoom Out", None, QtGui.QApplication.UnicodeUTF8))
self.mpActionPan.setText(QtGui.QApplication.translate("MainWindow", "Pan", None, QtGui.QApplication.UnicodeUTF8))
self.mpActionAddLayer.setText(QtGui.QApplication.translate("MainWindow", "Add Layer", None, QtGui.QApplication.UnicodeUTF8))

import resources_rc

Компиляция ресурсного файла

Как можно видеть из последней строки скопилированного файла формы (mainwindow_ui.py) туда автоматически была включена строка import resources_rc, однако такого файла еще не существует и его необходимо создать, скомпилировав файл ресурсов:
pyrcc4 -o resources_rc.py resources.qrc

Результаты компиляции формы и ресурсного файла (mainwindow_ui.py и resources_rc.py) необходимо скопировать в ту же папку, в которой хранятся python bindings, например c:\Gis\QGIS\python\

Файл resources.qrc должен иметь следующее содержание:

images/mActionAddLayer.png
images/mActionPan.png
images/mActionZoomFullExtent.png
images/mActionZoomIn.png
images/mActionZoomOut.png

Создание приложения

Помимо формы также понадобится программа которая будет ей управлять. Она будет написана на языке Python. Создать сам файл программы можно в любом редакторе (Notepad и т.п.) или в PythonWin. Разберем ее по частям, код целиком можно посмотреть здесь.

Не забудем упомянуть авторов исходного примера и всех поучаствовавших, а так же указать лицензию. Первая строка позволяет использовать русскоязычные комментарии далее по тексту.
# -*- coding: utf-8 -*-
# Пример основан на C++ Tutorial 2. Автор: Tim Sutton, 2006
# Порт на Python: Martin Dobias, при участии Gary Sherman, изменения для FOSS4G2007
# Адаптация, перевод, изменения
# Лицензия GNU GPL 2

Импортируем модули QT, QGIS и созданную нами форму.
from PyQt4 import QtCore, QtGui
from qgis.core import *
from qgis.gui import *
import sys,os
# Импорт созданного интерфейса
from mainwindow_ui import Ui_MainWindow

Перед запуском приложения, должен быть указан путь к папке установки QGIS .9
qgis_prefix = "c:/Gis/QGIS/Quantum GIS/"

Создадим новый класс MainWindow, который будет описывать наше приложение:
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):

def __init__(self):
QtGui.QMainWindow.__init__(self)

# Требуется Qt4 для иннициализации пользовательского интерфейса
self.setupUi(self)

Установим заголовок окна, создадим карту и зададим светло-голубой цвет фона:
self.setWindowTitle("QGIS Demo")

self.canvas = QgsMapCanvas()

self.canvas.setCanvasColor(QtGui.QColor(200,200,255))
self.canvas.enableAntiAliasing(True)
self.canvas.useQImageToRender(False)
self.canvas.show()

Расположить компоненты-виджеты в окне приложения, окно карты так же является одним из компонентов:
self.layout = QtGui.QVBoxLayout(self.frame)
self.layout.addWidget(self.canvas)

Далее необходимо задать действия для каждого из инструментов и соединить их с соответствующими методами, которые будут определены чуть позднее в этом же классе:
self.connect(self.mpActionAddLayer, QtCore.SIGNAL("triggered()"), self.addLayer);
self.connect(self.mpActionZoomIn, QtCore.SIGNAL("triggered()"), self.zoomIn);
self.connect(self.mpActionZoomOut, QtCore.SIGNAL("triggered()"), self.zoomOut);
self.connect(self.mpActionPan, QtCore.SIGNAL("triggered()"), self.pan);
self.connect(self.mpActionZoomFullExtent, QtCore.SIGNAL("triggered()"), self.zoomFull)

Создадим панель инструментов Map на которую добавим наши действия:
self.toolbar = self.addToolBar("Map");
self.toolbar.addAction(self.mpActionAddLayer);
self.toolbar.addAction(self.mpActionZoomIn);
self.toolbar.addAction(self.mpActionZoomOut);
self.toolbar.addAction(self.mpActionPan);
self.toolbar.addAction(self.mpActionZoomFullExtent);

Создадим инструменты:
self.toolPan = QgsMapToolPan(self.canvas)
self.toolPan.setAction(self.mpActionPan)
self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = уменьшить
self.toolZoomIn.setAction(self.mpActionZoomIn)
self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = увеличить
self.toolZoomOut.setAction(self.mpActionZoomOut)

И соответствующие им процедуры для обычных инструментов они будут выглядеть вот так:
def zoomIn(self):
self.canvas.setMapTool(self.toolZoomIn)

def zoomOut(self):
self.canvas.setMapTool(self.toolZoomOut)

def pan(self):
self.canvas.setMapTool(self.toolPan)

def zoomFull(self):
self.canvas.zoomFullExtent()

Для инструмента добаляющего слой, описание будет более развернутым, необходимо определить какой слой будет загружен и откуда, а так же сделать так, чтобы охват карты был равен охвату загруженного слоя, чтобы показать его полностью. Сами данные - файл test.shp можно загрузить вместе с исходным кодом и вспомогательными материалами, располагаться они должны в той же папке, откуда будет запускаться программа.
def addLayer(self):

layerPath = "test.shp"
layerName = "test"
layerProvider = "ogr"

layer = QgsVectorLayer(layerPath, layerName, layerProvider)

if not layer.isValid():
return

QgsMapLayerRegistry.instance().addMapLayer(layer);

self.canvas.setExtent(layer.extent())

cl = QgsMapCanvasLayer(layer)
layers = [cl]
self.canvas.setLayerSet(layers)

Далее, создадим процедуру инициализирующую библиотеки QGIS и запускающую нашу программу:
def main(app):

QgsApplication.setPrefixPath(qgis_prefix, True)
QgsApplication.initQgis()

wnd = MainWindow()
wnd.show()

retval = app.exec_()

QgsApplication.exitQgis()
sys.exit(retval)

И запустим ее на выполнение:
if __name__ == "__main__":

# создать приложение Qt
app = QtGui.QApplication(sys.argv)

main(app)

После создания нашей программы, остается только ее запустить.

http://gis-lab.info/qa/qgis-standalone.html

Яндекс.Метрика