Для разработки расширений можно использовать язык программирования Python. По сравнению с классическими расширениями, написанными на C++, их легче разрабатывать, понимать, поддерживать и распространять в силу динамической природы самого Python.
Расширения на Python перечисляются в Менеджере модулей QGIS наравне с расширениями на C++. Поиск расширений выполняется в следующих каталогах:
- UNIX/Mac: ~/.qgis/python/plugins и (qgis_prefix)/share/qgis/python/plugins
- Windows: ~/.qgis/python/plugins и (qgis_prefix)/python/plugins
В Windows домашний каталог (обозначенный выше как ~) обычно выглядит как C:\Documents and Settings\(user). Вложенные каталоги в этих папках рассматриваются как пакеты Python, которые можно загружать в QGIS как расширения.
Шаги:
С момента введения поддержки Python в QGIS появилось множество расширений — на странице Plugin Repositories можно найти некоторые из них. Исходный код этих расширений можно использовать, чтобы узнать больше о программировании с PyQGIS, а также для того, чтобы удостовериться, что разработка не дублируется. Готовы к созданию расширения, но отсутствует идея? На странице Python Plugin Ideas собрано много идей и пожеланий!
Ниже показано содержимое каталога нашего демонстрационного расширения:
PYTHON_PLUGINS_PATH/
testplug/
__init__.py
plugin.py
metadata.txt
resources.qrc
resources.py
form.ui
form.py
Для чего используются файлы:
Здесь и вот здесь можно найти два способа автоматической генерации базовых файлов (скелета) типового Python расширения для QGIS. Кроме того, существует модуль Plugin Builder, который создает шаблон модуля прямо из QGIS и не требует соединения с Интернет. Это упростит работу и поможет быстрее начать разработку типового расширения.
Прежде всего, Менеджер модулей должен получить основные сведения о расширении, такие как его название, описание и т.д. Файл __init__.py именно то место, где должна быть эта информация:
def name():
return "My testing plugin"
def description():
return "This plugin has no real use."
def version():
return "Version 0.1"
def qgisMinimumVersion():
return "1.0"
def authorName():
return "Developer"
def classFactory(iface):
# загружаем класс TestPlugin из файла testplugin.py
from testplugin import TestPlugin
return TestPlugin(iface)
В QGIS 1.9.90 модули могут быть помещены не только в меню Модули, но и в меню Растр, Вектор, База данных и Web. Поэтому было введено новое поле метаданных “category”. Это поле используется в качестве подсказки для пользователей и сообщает где (в каком меню) искать модуль. Допустимыми значениями для параметра “category” являются Vector, Raster, Database, Web и Layers. Например, если модуль должен быть доступен из меню Растр, добавьте в __init__.py следующие строки:
def category():
return "Raster"
Для QGIS >= 1.8 необходимо создать файл metadata.txt (см. также) Пример :file:`metadata.txt’:
; the next section is mandatory
[general]
name=HelloWorld
qgisMinimumVersion=1.8
description=This is a plugin for greeting the
(going multiline) world
category=Raster
version=version 1.2
; end of mandatory metadata
; start of optional metadata
changelog=this is a very
very
very
very
very
very long multiline changelog
; tags are in comma separated value format, spaces are allowed
tags=wkt,raster,hello world
; these metadata can be empty
; in a future version of the web application it will
; be probably possible to create a project on redmine
; if they are not filled
homepage=http://www.itopen.it
tracker=http://bugs.itopen.it
repository=http://www.itopen.it/repo
icon=icon.png
; experimental flag
experimental=True
; deprecated flag (applies to the whole plugin and not only to the uploaded version)
deprecated=False
Стоит сказать несколько слов о функции classFactory(), которая вызывается когда расширение загружается в QGIS. Она получает ссылку на экземпляр класса QgisInterface и должна вернуть экземпляр класса вашего расширения — в нашем случае этот класс называется``TestPlugin``. Ниже показано он должен выглядеть (например, testplugin.py):
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
# загружаем ресурсы Qt из файла resouces.py
import resources
class TestPlugin:
def __init__(self, iface):
# сохраняем ссылку на интерфейс QGIS
self.iface = iface
def initGui(self):
# создаем действия для запуска расширения или его настройки
self.action = QAction(QIcon(":/plugins/testplug/icon.png"), "Test plugin", self.iface.mainWindow())
self.action.setWhatsThis("Configuration for test plugin")
self.action.setStatusTip("This is status tip")
QObject.connect(self.action, SIGNAL("triggered()"), self.run)
# добавляем кнопку на панель и пункт в меню
self.iface.addToolBarIcon(self.action)
self.iface.addPluginToMenu("&Test plugins", self.action)
# подключаемся к сигналу renderComplete, который посылается по завершению отрисовки карты
QObject.connect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), self.renderTest)
def unload(self):
# удаляем пункт меню и кнопку на панели
self.iface.removePluginMenu("&Test plugins",self.action)
self.iface.removeToolBarIcon(self.action)
# отключаемся от сигнала карты
QObject.disconnect(self.iface.MapCanvas(), SIGNAL("renderComplete(QPainter *)"), self.renderTest)
def run(self):
# создаем и показываем диалог настройки или выполняем что-то еще
print "TestPlugin: run called!"
def renderTest(self, painter):
# рисуем на карте, используя painter
print "TestPlugin: renderTest called!"
Если используется QGIS 1.9.90 или старше и необходимо разместить модуль в одном из новых меню (Растр, Вектор, База данных или Web), нужно модифицировать код функций initGui() и unload(). Так как эти новые пункты меню доступны только в QGIS 1.9.90, прежде всего необходимо проверить, что используемая версия QGIS имеет все необходимые функции. Если новые пункты меню доступны, мы можем разместить модуль в нужном месте, в противном случае будем использовать меню Модули как и раньше. Вот пример для меню Растр:
def initGui(self):
# создаем действия для запуска расширения или его настройки
self.action = QAction(QIcon(":/plugins/testplug/icon.png"), "Test plugin", self.iface.mainWindow())
self.action.setWhatsThis("Configuration for test plugin")
self.action.setStatusTip("This is status tip")
QObject.connect(self.action, SIGNAL("triggered()"), self.run)
# проверяем, доступно ли меню Растр
if hasattr(self.iface, "addPluginToRasterMenu"):
# меню Растр и одноименная панель доступны
self.iface.addRasterToolBarIcon(self.action)
self.iface.addPluginToRasterMenu("&Test plugins", self.action)
else:
# меню Растр отсутствует, размещаем модуль в меню Модули, как и раньше
self.iface.addToolBarIcon(self.action)
self.iface.addPluginToMenu("&Test plugins", self.action)
# одключаемся к сигналу renderComplete, который посылается по завершению отрисовки карты
QObject.connect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), self.renderTest)
def unload(self):
# проверям доступно ли меню Растр и удаляем наши кнопки из соответствующего
# меню и панели
if hasattr(self.iface, "addPluginToRasterMenu"):
self.iface.removePluginRasterMenu("&Test plugins",self.action)
self.iface.removeRasterToolBarIcon(self.action)
else:
self.iface.removePluginMenu("&Test plugins",self.action)
self.iface.removeToolBarIcon(self.action)
# отключаемся от сигнала карты
QObject.disconnect(self.iface.MapCanvas(), SIGNAL("renderComplete(QPainter *)"), self.renderTest)
Полный список методов, которые можно использовать для размещения модуля в новых меню и на новых панелях инструментов доступен в описании API.
В расширении обязательно должны присутствовать функции initGui() и unload(). Эти функции вызываются когда расширение загружается и выгружается.
Как видно в примере выше, в initGui() мы использовали иконку из файла ресурсов (в нашем случае он называется resources.qrc):
<RCC>
<qresource prefix="/plugins/testplug" >
<file>icon.png</file>
</qresource>
</RCC>
Хорошим тоном считается использование префикса, это позволит избежать конфликтов с другими расширениями или с частями QGIS. Если префикс не задан, можно получить не те ресурсы, которые нужны. Теперь сгенерируем файл ресурсов на Python. Это делается командой pyrcc4:
pyrcc4 -o resources.py resources.qrc
Вот и все... ничего сложного :) Если всё сделано правильно, то расширение должно отобразиться в Менеджере модулей и загружаться в QGIS без ошибок. После его загрузки на панели появится кнопка, а в меню — новый пункт, нажатие на которые приведет к выводу сообщения на терминал.
При работе над реальным расширением удобно вести разработку в другом (рабочем) каталоге и создать makefile, который будет генерировать файлы интерфейса и ресурсов, а также выполнять копирование расширения в каталог QGIS.
Этот способ создания документации требует наличия Qgis версии 1.5.
Документацию к расширению можно готовить в виде файлов HTML. Модуль qgis.utils предоставляет функцию showPluginHelp(), которая откроет файл справки в браузере, точно так же как другие файлы справки QGIS.
Функция showPluginHelp`() ищет файлы справки в том же каталоге, где находится вызвавший её модуль. Она по очереди будет искать файлы index-ll_cc.html, index-ll.html, index-en.html, index-en_us.html и index.html, и отобразит первый найденный. Здесь ll_cc — язык интерфейса QGIS. Это позволяет включать в состав расширения документацию на разных языках.
Кроме того, функция showPluginHelp() может принимать параметр packageName, идентифицирующий расширение, справку которого нужно отобразить; filename, который используется для переопределения имени файла с документацией; и section, для передачи имени якоря (закладки) в документе, на который браузер должен перейти.
Здесь собраны фрагменты кода, полезные при разработке расширений.
Добавьте в initGui():
self.keyAction = QAction("Test Plugin", self.iface.mainWindow())
self.iface.registerMainWindowAction(self.keyAction, "F7") # action1 is triggered by the F7 key
self.iface.addPluginToMenu("&Test plugins", self.keyAction)
QObject.connect(self.keyAction, SIGNAL("triggered()"),self.keyActionF7)
И в unload():
self.iface.unregisterMainWindowAction(self.keyAction)
Метод, вызываемый по нажатию F7:
def keyActionF7(self):
QMessageBox.information(self.iface.mainWindow(),"Ok", "You pressed F7")
Примечание: в QGIS 1.5 появился класс QgsLegendInterface, позволяющий управлять списком слоёв легенды.
Так как в настоящее время методы прямого доступа к слоям легенды отсутствуют, в качестве временно решения для управления видимостью слоёв можно использовать решение на основе изменения прозрачности слоя:
def toggleLayer(self, lyrNr):
lyr = self.iface.mapCanvas().layer(lyrNr)
if lyr:
cTran = lyr.getTransparency()
lyr.setTransparency(0 if cTran > 100 else 255)
self.iface.mapCanvas().refresh()
Метод принимает номер слоя в качестве параметры (0 соответствует самому верхнему) и вызывается так:
self.toggleLayer(3)
def changeValue(self, value):
layer = self.iface.activeLayer()
if(layer):
nF = layer.selectedFeatureCount()
if (nF > 0):
layer.startEditing()
ob = layer.selectedFeaturesIds()
b = QVariant(value)
if (nF > 1):
for i in ob:
layer.changeAttributeValue(int(i),1,b) # 1 соответствует второй колонке
else:
layer.changeAttributeValue(int(ob[0]),1,b) # 1 соответствует второй колонке
layer.commitChanges()
else:
QMessageBox.critical(self.iface.mainWindow(),"Error", "Please select at least one feature from current layer")
else:
QMessageBox.critical(self.iface.mainWindow(),"Error","Please select a layer")
Метод принимает один параметр (новое значения атрибута выделенного объекта(ов)) и вызывается как:
self.changeValue(50)
Сначала добавьте следующий код в место, которое будет отлаживаться:
# для отладки используем pdb
import pdb
# устанавливаем точку останова
pyqtRemoveInputHook()
pdb.set_trace()
Затем запускаем QGIS из командной строки.
В Linux:
$ ./Qgis
В Mac OS X:
$ /Applications/Qgis.app/Contents/MacOS/Qgis
Когда приложение достигнет точки останова, консоль станет доступной и можно будет вводить команды!
Если после создания расширения вы решите, что оно может быть полезно и другим пользователям — не бойтесь загрузить его в репозиторий QGIS plugin repository. На этой же странице можно найти инструкции по подготовке пакета, следование которым избавит от проблем с установкой расширения через Установщик модулей. В случае, когда нужно настроить собственный репозиторий, создайте простой XML документ, описывающий все расширения и их метаданные. Пример файла можно найти на странице Python plugin repositories.
При использовании Linux разработка расширений не требует дополнительных настроек. Но в при использовании Windows необходимо убедиться, что и QGIS, и интерпретатор используют одни и те же переменные окружения и библиотеки. Наиболее простой и быстрый способ сделать это — модифицировать командный файл для запуска QGIS.
Если используется установщик OSGeo4W, командный файл можно найти в каталоге bin папки, куда выполнена установка OSGeo4W. Ищите что-то похожее на C:\OSGeo4W\bin\qgis-unstable.bat.
Далее будет описана настройка Pyscripter IDE. Настройка других сред разработки может несколько отличаться:
Сделайте копию qgis-unstable.bat и переименуйте её в pyscripter.bat.
Откройте это файл в редакторе. Удалите последнюю строку, которая отвечает за запуск QGIS.
Добавьте строку для запуска pyscripter с параметром, указывающим на используемую версию Python. QGIS 1.5 использует Python 2.5.
Добавьте еще один аргумент, указывающий на каталог, где pyscripter должен искать библиотеки Python, используемые qgis. Обычно это каталог bin папки, куда установлен OSGeo4W:
@echo off
SET OSGEO4W_ROOT=C:\OSGeo4W
call "%OSGEO4W_ROOT%"\bin\o4w_env.bat
call "%OSGEO4W_ROOT%"\bin\gdal16.bat
@echo off
path %PATH%;%GISBASE%\bin
start C:\pyscripter\pyscripter.exe --python25 --pythondllpath=C:\OSGeo4W\bin
Теперь при запуске этого командного файла установятся необходимые переменные окружения и будет запущен pyscripter.