Разработка простого расширения для QGIS на Python: различия между версиями

Материал из GIS-Lab
Перейти к навигации Перейти к поиску
Нет описания правки
 
(не показано 18 промежуточных версий 4 участников)
Строка 2: Строка 2:
{{Аннотация|Описание процесса создания элементарного расширения для QGIS.}}
{{Аннотация|Описание процесса создания элементарного расширения для QGIS.}}


QGIS — свободная пользовательская ГИС, обладающая развитым API и продвинутой системой расширений (модулей), которые могут быть использованы для расширения функциональности программы. QGIS написана на объектно-ориентированном языке высокого уровня C++. Так же в QGIS встроены привязки (bindings), которые реализуют практически весь функционал QGIS на языке Python. Графический интерфейс пользователя QGIS базируется на библиотеках Qt4. Разработка для QGIS может вестись на двух языках, Python и C++. Для разработки на языке Python необходимы привязки [http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/index.html PyQt4], обеспечивающие взаимодействия Pythonс Qt4. В PyQt4 реализовано 440 классов Qt4 в виде модулей Python. Помимо расширений библиотеки QGIS могут быть также использованы для разработки отдельных приложений, что подробно описано в статье «[http://gis-lab.info/qa/qgis-standalone.html Создание приложения на базе набора библиотек QGIS на Python]».
QGIS — свободная пользовательская ГИС, обладающая развитым API и продвинутой системой расширений (модулей). Модули создаются для расширения функциональности программы. QGIS написана на объектно-ориентированном языке высокого уровня C++. Так же в QGIS встроены привязки (bindings), которые реализуют практически весь функционал QGIS на языке Python. Графический интерфейс пользователя QGIS базируется на библиотеках Qt4. Разработка для QGIS может вестись на двух языках, Python и C++. Для разработки на языке Python необходимы привязки [http://www.riverbankcomputing.co.uk/software/pyqt/intro PyQt], обеспечивающие взаимодействия Python с Qt4. Помимо расширений библиотеки QGIS могут быть также использованы для разработки отдельных приложений, что подробно описано в статье «[http://gis-lab.info/qa/qgis-standalone.html Создание приложения на базе набора библиотек QGIS на Python]».


Данная статья описывает процесс создания каркаса расширения на языке Python. Разработанный каркас можно в дальнейшем использовать как основу для других расширений, выполняющих нужные функции.
Данная статья описывает процесс создания каркаса расширения на языке Python. Разработанный каркас можно в дальнейшем использовать как основу для других расширений, выполняющих нужные функции.
Строка 9: Строка 9:


* Идея
* Идея
* Создание файлов
* Создание каркаса
* Разработка
* Разработка необходимой функциональности.
* Тестирование
* Тестирование
* Публикация
* Публикация


В данной статье идея расширения достаточно проста: создать расширение, которое добавит новую кнопку на панель инструментов и подменю в меню расширений и будет выполнять какое-либо простое действие. Это расширение можно будет использовать как каркас для других будущих, более функциональных расширений. Далее мы более подробно разберем остальные шаги.
Задача этой статьи - иллюстрация процесса создания простого расширения, которое:
* добавит новую кнопку на панель инструментов
* добавит подменю в меню расширений
* выполнит простое действие
* будет доступно на двух языках.  
 
Это расширение можно будет использовать как каркас для других будущих, более функциональных расширений. Далее мы более подробно разберем процесс создания по шагам.


=== Получение исходного кода ===
=== Получение исходного кода ===


Код расширения, проверенно работающий под QGIS 1.0 можно получить несколькими способами.
Код расширения, работающий под QGIS 2.0 можно получить в [https://github.com/nextgis/testplugin репозитории GitHub]:
 
Его можно скачать в виде [http://gis-lab.info/programs/qgis/testplugingl.zip архива].
 
Его можно получить через SVN
 
<pre>svn co http://svn.gis-lab.info/testplugin testplugin</pre>
 
Рабочий вариант расширения можно загрузить через репозиторий расширений GIS-Lab, открыв Установщик модулей QGIS («Модули → Загрузить модули») и добавив сторонний репозиторий:


<pre>
<pre>git clone git@github.com:nextgis/testplugin.git</pre>
Name: GIS-Lab
URL: http://gis-lab.info/programs/qgis/qgis-repo.xml
</pre>


=== Подготовка ===
=== Подготовка ===
Строка 38: Строка 33:


* Любой текстовый редактор
* Любой текстовый редактор
* Python, лучше версии 2.5
* Python, лучше версии 2.5-2.7.х (3.x не подойдет)
* PyQt
* QGIS версии 2.0 и выше, для проверки работоспособности расширения.
* Qt Opensource
* собственно QGIS соответствующей версии, для проверки работоспособности расширения.


Разработку можно вести прямо в папке, где хранятся расширения или переместить его в нее после разработки, в QGIS эта папка либо <tt>QGIS\python\plugins</tt> либо </tt>C:\Documents and Settings\username\.qgis\python\plugins\</tt>.
Если вы установили QGIS с помощью установщика [http://gis-lab.info/qa/qgis-osgeo4w.html osgeo4w] или [http://nextgis.ru/nextgis-qgis NextGIS QGIS], то у вас уже есть все необходимое для разработки. Ничего дополнительно устанавливать не нужно.


В папке с плагинами необходимо создать новую, назвав ее так, чтобы бы по ее названию было бы легко определить, что именно расширение делает. Назовем нашу папку testplugin. Это название следует запомнить, так как оно будет фигурировать в различных компонентах кода.
Разработку можно вести прямо в папке, где хранятся расширения или переместить его в нее после разработки, в QGIS эта папка либо <tt>QGIS\python\plugins</tt> либо </tt>C:\Documents and Settings\username\.qgis2\python\plugins\</tt>.


Перед собственно разработкой, нам понадобится создать в этой папке несколько новых, пока пустых текстовых файлов, называемых следующим образом:
В папке с плагинами необходимо создать новую, назвав ее так, чтобы бы по названию можно было легко определить, что именно делает расширение. Назовем нашу папку <code>testplugin</code>. Это название следует запомнить, так как оно будет фигурировать в различных компонентах кода.


* <tt>__init__.py</tt> — начальная точка, содержит информацию о расширении, версию, имя разработчика, в нем создается экземпляр основного класса, он передается в qgis
Перед разработкой, нам нужно создать в этой папке несколько новых, пока пустых текстовых файлов:
* <tt>plugin.py</tt> — основной код расширения. Содержит всю информацию о всех действиях расширения и их код.
 
* <tt>resources.qrc</tt> — xml файл, создаваемый Qt Designer или вручную и содержащий относительные пути к ресурсам расширения, формам, иконкам и т.п.
* <tt>__init__.py</tt> — начальная точка, создаёт экземпляр основного класса, который передается в QGIS
* <tt>testplugin.py</tt> — основной код расширения. Содержит всю информацию о всех действиях расширения.
* <tt>resources.qrc</tt> — xml файл, создаваемый Qt Designer или вручную и содержащий относительные пути к ресурсам расширения, формам, иконкам и т.п. Компилируется в py перед созданием пакета плагина.
* <tt>metadata.txt</tt> — описание модуля, содержит информацию о расширении, версию, имя разработчика


Помимо этих файлов, если расширение использует формы, могут также присутствовать файлы:
Помимо этих файлов, если расширение использует формы, могут также присутствовать файлы:


* <tt>form.ui</tt> — форма созданная с помощью Qt Designer
* <tt>form.ui</tt> — форма созданная с помощью Qt Designer
* <tt>form.py</tt> — она же, сохраненная в виде программы на языке Python
* <tt>form.py</tt> — она же, скомпилированная в виде программы на языке Python


Для примера из данной статьи эти файлы не понадобятся.
Для примера из данной статьи эти файлы не понадобятся.
Строка 62: Строка 58:
Итак, пустые файлы созданы и лежат в нужной папке.
Итак, пустые файлы созданы и лежат в нужной папке.


=== Разработка ===
Перед разработкой, отредактируем файл metadata.txt. Этот файл содержит информацию о расширении, такую как его название <tt>name</tt>, т.е. то, как он будет показываться в Менеджере модулей, его описание <tt>description</tt>, показывается там же. Нужно также указать минимальную версию QGIS, для которой разработано это расширение (при попытке загрузки в QGIS меньшей версии плагин будет отключен) и другие параметры:
 
<pre>[general]
name=TestPlugin
description=This plugin is for testing and templating purposes
category=Vector
version=0.0.2
qgisMinimumVersion=2.0
 
author=NextGIS
email=info@nextgis.org
 
icon=icons/icon.png
 
tags=testing,template
 
homepage=http://gis-lab.info/qa/qgis-dev-python.html
tracker=https://github.com/nextgis/testplugin/issues
repository=https://github.com/nextgis/testplugin


Разработка начинается с файла <tt>__init__.py</tt>, содержащего некоторые основные параметры расширения, такие как его название <tt>name</tt>, т.е. то, как он будет показываться в Менеджере модулей, его описание <tt>description</tt>, показывается там же. Если мы хотим использовать в коде кириллицу, в том числе в комментариях, необходимо в самом его начале объявить кодировку utf-8. Нужно также указать минимальную версию QGIS, для которой разработано это расширение (при попытке загрузки в QGIS меньшей версии плагин будет отключен) и другие параметры:
experimental=True
deprecated=False</pre>


<syntaxhighlight lang="python">
=== Разработка ===
# -*- coding: utf-8 -*-
def name():
  return "My testing plugin"
def description():
  return "This plugin has no real use."
def qgisMinimumVersion():
  return "1.0"
def version():
  return "Version " + "0.1.21"
def authorName():
  return "Ivan Developer"
...
</syntaxhighlight>


Важным фрагментом кода <tt>__init__.py</tt> является импорт исполняемой части расширения, содержащейся в файле <tt>plugin.py</tt> (поэтому <tt>from plugin</tt>) и содержащей всю содержательную часть кода.
Разработка начинается с файла <tt>__init__.py</tt>. Главным фрагментом кода является импорт исполняемой части расширения, содержащейся в файле <tt>testplugin.py</tt> (поэтому <tt>from testplugin</tt>) и содержащей всю содержательную часть кода.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
...
def classFactory(iface):
def classFactory(iface):
   # загрузка класса TestPlugin из файла plugin.py
   # Import class TestPlugin from file testplugin.py
   from plugin import TestPlugin
   from testplugin import TestPlugin
   return TestPlugin(iface)
   return TestPlugin(iface)
</syntaxhighlight>
</syntaxhighlight>


Название главного импортируемого класса <tt>TestPlugin</tt> должно быть равно названию класса в коде <tt>plugin.py</tt>, причем название — регистрозависимое, если вызывается <tt>TestPlugin</tt>, а класс носит название <tt>testplugin</tt>, будет выдано сообщение об ошибке.
Название главного импортируемого класса <tt>TestPlugin</tt> должно быть равно названию класса в коде <tt>testplugin.py</tt>, название — регистрозависимое, если вызывается <tt>TestPlugin</tt>, а класс носит название <tt>testplugin</tt>, будет выдано сообщение об ошибке.


Рассмотрим <tt>plugin.py</tt>.
Рассмотрим <tt>testplugin.py</tt>.


Как и во всех программах на языке Python, все начинается с импорта необходимых для работы классов, в нашем случае это будут классы PyQt — обертки для Qt на языке Python: PyQt4.QtCore и PyQt4.QtGui, активно используемые QGIS и сами классы QGIS: qgis.core
Как и во всех программах на языке Python, все начинается с импорта необходимых для работы классов, в нашем случае это будут классы PyQt — обертки для Qt на языке Python: PyQt4.QtCore и PyQt4.QtGui, активно используемые QGIS и сами классы QGIS: qgis.core
Строка 118: Строка 119:


   def __init__(self, iface):
   def __init__(self, iface):
     # сохраним ссылку на интерфейс QGIS
    """Initialize class"""
     # сохраним ссылку на интерфейс QGIS//save reference to QGIS interface
     self.iface = iface
     self.iface = iface
    self.qgsVersion = unicode(QGis.QGIS_VERSION_INT)
</syntaxhighlight>
</syntaxhighlight>


Строка 125: Строка 128:


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
import resources def initGui(self):
import resources
     """Иннициализируем графический интерфейс пользователя"""
def initGui(self):
     # создадим действие, которое будет запускать конфигурацию расширения
     """Инициализируем графический интерфейс пользователя"""
     self.action = QAction(QIcon(":/plugins/testplugin/icon.png"), "Test plugin", self.iface.mainWindow())
     #проверим, не пытаются ли запустить плагин из QGIS версии ниже 2.0//check if the plugin is ran below 2.0
    if int(self.qgsVersion) < 10900:
        qgisVersion = self.qgsVersion[0] + "." + self.qgsVersion[2] + "." + self.qgsVersion[3]
        QMessageBox.warning(self.iface.mainWindow(),
                            "TestPlugin", "Error",
                            "TestPlugin", "QGIS %s detected.\n" % (qgisVersion) +
                            "TestPlugin", "This version of TestPlugin requires at least QGIS version 2.0.\nPlugin will not be enabled.")
        return None
 
    # create action that will be run by the plugin//создадим действие, которое будет запускать конфигурацию расширения
     self.action = QAction("Test plugin", self.iface.mainWindow())
    self.action.setIcon(QIcon(":/icons/icon.png"))
     self.action.setWhatsThis("Configuration for test plugin")
     self.action.setWhatsThis("Configuration for test plugin")
    self.action.setStatusTip("This is status tip")
    # добавим пункт в меню инструментов Vector//add plugin menu to Vector toolbar
    self.iface.addPluginToVectorMenu("TestPlugin",self.action)
      
      
    # связь действия с функцией run
     # добавим кнопку на панель инструментов Vector//add icon to new menu item in Vector toolbar
    QObject.connect(self.action, SIGNAL("activated()"), self.run)
     self.iface.addVectorToolBarIcon(self.action)
   
 
     # добавим кнопку на панель инструментов
     # связь действия с функцией run//connect action to the run method
     self.iface.addToolBarIcon(self.action)
     self.action.triggered.connect(self.run)
   
     
     # добавим строку вызова в новое подменю
     self.iface.addPluginToMenu("&Test plugin", self.action)
</syntaxhighlight>
</syntaxhighlight>


Строка 146: Строка 162:
   def unload(self):
   def unload(self):
     """Действия при выгрузке расширения"""
     """Действия при выгрузке расширения"""
     # удалить меню расширения и иконку
     # удалить меню расширения и иконку//remove menu and icon from the menu
     self.iface.removePluginMenu("&Test plugin",self.action)
     self.iface.removeVectorToolBarIcon(self.action)
     self.iface.removeToolBarIcon(self.action)
     self.iface.removePluginVectorMenu("TestPlugin",self.action)
    # отключить сигнал обновления вида
    QObject.disconnect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), self.renderTest)
</syntaxhighlight>
</syntaxhighlight>


И наконец функция выполняющая единственное действие в нашем расширении — <tt>run()</tt>. Функция использует функцию <tt>QString</tt> для создания строки сообщения и <tt>QMessageBox</tt> для показа этого сообщения в главном окне программы.
И наконец функция выполняющая единственное действие в нашем расширении — <tt>run()</tt>. Функция создаёт строку сообщения и показывает ее в <tt>QMessageBox</tt> в главном окне программы.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
   def run(self):
   def run(self):
     """Действия при запуске расширения"""
     """Действия при запуске расширения"""
     # создать и показать сообщение
     # создать и показать сообщение//create a string and show it
     infoString = QString("This is a test")
     infoString = "This is a test"
     QMessageBox.information(self.iface.mainWindow(),"About",infoString)
     QMessageBox.information(self.iface.mainWindow(),"About",infoString)
</syntaxhighlight>
</syntaxhighlight>
=== Интернационализация ===
Интернационализация позволяет сделать ваш плагин доступным на разных языках. [https://ru.wikipedia.org/wiki/Интернационализация Об интернационализации на wiki]
В качестве примера рассмотрим добавление перевода на русский язык. Команды в данном разделе указаны для систем Ubuntu/Debian.
Для начала нам потребуется внести изменения в код файла <tt>testplugin.py</tt>.
Добавим импорт нового модуля <tt>os</tt>, который позволит нам обращаться к файловой системе. Добавьте перед существующими "импортами":
<syntaxhighlight lang="python">
import os
</syntaxhighlight>
Воспользуемся новым модулем. Метод <tt>__init__</tt> теперь будет выглядеть так:
<syntaxhighlight lang="python">
    def __init__(self, iface):
        """Initialize class"""
        # save reference to QGIS interface
        self.iface = iface
        self.qgsVersion = unicode(QGis.QGIS_VERSION_INT)
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'TestPlugin_{}.qm'.format(locale))
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)
</syntaxhighlight>
Что изменилось? Мы добавили атрибут <tt>self.plugin_dir</tt>, хранящий путь к папке с текущим файлом (<tt>testplugin.py</tt>). Получили из настроек QGIS текущую локаль (<tt>locale</tt>), которую пытаемся использовать для обращения к пути файла перевода (<tt>locale_path</tt>). Если такой файл существует, то загружаем из него перевод.
Сразу после метода <tt>__init__</tt> создадим новый:
<syntaxhighlight lang="python">
    def tr(self, message):
        return QCoreApplication.translate("TestPlugin", message)
</syntaxhighlight>
В Qt4 для перевода строки необходимо использовать метод <tt>QCoreApplication.translate()</tt>. Для каждой строки, требующей перевода, писать такую конструкцию неудобно, поэтому мы и добавили новый метод <tt>tr</tt>. В нашем примере есть только две строки, требующие перевода, и без данного метода можно было и обойтись, но вам он должен упростить жизнь. Далее используем новый метод для того, чтобы "пометить" строки для перевода:
<syntaxhighlight lang="python">
    def run(self):
        """Action to run"""
        # create a string and show it
        infoString = self.tr("This is a test")
        QMessageBox.information(self.iface.mainWindow(), self.tr("About"), infoString)
</syntaxhighlight>
С кодом почти закончили, перейдем к заключительным действиям. Создайте папку <tt>i18n</tt> рядом с файлом <tt>testplugin.py</tt>. В папке <tt>i18n</tt> создайте файл <tt><ИмяВашегоПлагина>.pro</tt>. В нашем примере это будет файл <tt>TestPlugin.pro</tt>. Содержимое файла должно быть таким:
<pre>SOURCES += ../testplugin.py
TRANSLATIONS += TestPlugin_ru.ts</pre>
В <tt>SOURCES</tt> перечисляются файлы, в которых требуется искать строки для перевода. Таких источников может быть несколько, и необходимо перечислить их все, добавляя новые строки <tt>SOURCES += ../<ИмяФайла></tt>. В нашем примере ресурс один. В <tt>TRANSLATIONS</tt> перечисляются файлы, содержащие перевод в виде разметки XML, которые необходимо сгенерировать. В имени файла <tt>ru</tt> значит, что это файл перевода на русский. Если хотите добавить другой язык, то после нижнего подчеркивания следует указать соответствующие две буквы. Например, <tt>uk</tt> — украинский.
Далее необходимо установить инструменты разработчика. В частности, нам понадобятся <tt>pylupdate4</tt> и <tt>linguist</tt>. Это делается следующей командой:
<pre>sudo apt-get install pyqt4-dev-tools</pre>
В терминале выполняем команды:
<pre>cd i18n
pylupdate4 TestPlugin.pro
linguist TestPlugin_ru.ts</pre>
Если требуется переводить сразу на несколько языков, то можно открыть несколько файлов, перечислив их:
<pre>linguist TestPlugin_ru.ts TestPlugin_uk.ts</pre>
Или открыть все доступные файлы переводов:
<pre>linguist *.ts</pre>
Всплывающее окно заполните так:
[[Файл:Linguist_settings.jpg]]
Если языков несколько, то настройте каждый отдельно в меню "Правка -> Параметры файла перевода".
В поле "Русский перевод" введите желаемый перевод — "Это тест" и нажмите <tt>Ctrl+Enter</tt>.
[[Файл:Linguist_translation_example.jpg]]
Теперь введите "О Плагине" и нажмите <tt>Ctrl+Enter</tt>. Далее "Файл -> Сохранить" и "Файл -> Скомпилировать". Последняя операция создаст бинарный файл  <tt>TestPlugin_ru.qm</tt>, содержащий перевод. Именно этот файл и будет использоваться при отображении плагина на русском языке.
Осталось проверить результат. Для перезагрузки разрабатываемых плагинов удобно использовать плагин с говорящим названием "Plugin Reloader". В QGIS откройте меню "Модули -> Управление модулями". Во вкладке "Параметры" поставьте галочку напротив "Разрешить установку экспериментальных модулей". Во вкладке "Все" введите "Plugin Reloader" и нажмите "Установить модуль". В настройках появившейся иконки плагина "Plugin Reloader" нажмите "Choose plugin to be reloaded" и выберите плагин "testplugin" из списка. Нажмите на иконку плагина "Plugin Reloader", откройте "testplugin" и посмотрите результат.
<gallery>
Файл:Testplugin_en.jpg‎|До перевода
Файл:Testplugin_ru.jpg‎|После перевода
</gallery>


=== Какие функции из API доступны через Python ===
=== Какие функции из API доступны через Python ===


К сожалению, не все функции API QGIS доступны через Python (через C++ доступны все). Чтобы определить, доступны ли конкретные классы и функции, необходимо сначала найти их в документации к API GIS [http://doc.qgis.org/ http://doc.qgis.org]. А затем в консоли Python определить с помощью команды <tt>dir</tt> видны ли эти классы и функции из Python.
К сожалению, не все функции API QGIS доступны через Python (через C++ доступны все). Чтобы определить, доступны ли конкретные классы и функции, необходимо сначала найти их в документации к API GIS [http://doc.qgis.org/ http://doc.qgis.org]. А затем в консоли Python (Модули\Консоль Python) определить с помощью команды <tt>dir</tt> видны ли эти классы и функции из Python.


Например, определим доступные классы:
Например, определим доступные классы:
Строка 176: Строка 281:
Результат:
Результат:
<pre>
<pre>
['DEFAULT_LINE_WIDTH', 'DEFAULT_POINT_SIZE', 'ELLPS_PREFIX_LEN', 'GEOCRS_ID', 'GEOPROJ4', 'GEOSRID', 'GEOWkt',
['DEFAULT_LINE_WIDTH', 'DEFAULT_POINT_SIZE', 'DEFAULT_SEGMENT_EPSILON', 'ELLPS_PREFIX_LEN', 'GEOCRS_ID', 'GEOPROJ4', 'GEOSRID', 'GEOWKT', 'GEO_EPSG_CRS_AUTHID', 'GEO_EPSG_CRS_ID', 'GEO_NONE', 'LAT_PREFIX_LEN', 'MINIMUM_POINT_SIZE', 'NULL', 'PROJECT_SCALES', 'PROJ_PREFIX_LEN', 'QGis', 'QgsAbstractCacheIndex', 'QgsAbstractFeatureIterator', 'QgsAction', 'QgsAddRemoveItemCommand', 'QgsAddRemoveMultiFrameCommand', 'QgsApplication', 'QgsAtlasComposition', 'QgsAttributeAction', 'QgsAttributeEditorContainer', 'QgsAttributeEditorElement', 'QgsAttributeEditorField', 'QgsBilinearRasterResampler', 'QgsBrightnessContrastFilter', 'QgsBrowserModel', 'QgsCRSCache', 'QgsCacheIndexFeatureId', 'QgsCachedFeatureIterator', 'QgsCachedFeatureWriterIterator', 'QgsCategorizedSymbolRendererV2', 'QgsCentroidFillSymbolLayerV2', 'QgsClipToMinMaxEnhancement', 'QgsClipper', 'QgsColorBrewerPalette', 'QgsColorRampShader', 'QgsComposerArrow', 'QgsComposerAttributeTable', 'QgsComposerAttributeTableCompare', 'QgsComposerEffect', 'QgsComposerFrame', 'QgsComposerGroupItem', 'QgsComposerHtml', 'QgsComposerItem', 'QgsComposerItemCommand', 'QgsComposerItemGroup', 'QgsComposerLabel', 'QgsComposerLayerItem', 'QgsComposerLegend', 'QgsComposerLegendItem', 'QgsComposerLegendStyle', 'QgsComposerMap', 'QgsComposerMergeCommand', 'QgsComposerMultiFrame', 'QgsComposerMultiFrameCommand', 'QgsComposerPicture', 'QgsComposerRasterSymbolItem', 'QgsComposerScaleBar', 'QgsComposerShape', 'QgsComposerSymbolV2Item', 'QgsComposerTable', 'QgsComposerTextTable', 'QgsComposition', 'QgsContextHelp', 'QgsContrastEnhancement', 'QgsContrastEnhancementFunction', 'QgsCoordinateReferenceSystem', 'QgsCoordinateTransform', 'QgsCoordinateTransformCache', 'QgsCptCityArchive', 'QgsCptCityBrowserModel', 'QgsCptCityCollectionItem', 'QgsCptCityColorRampItem', 'QgsCptCityColorRampV2', 'QgsCptCityDataItem', 'QgsCptCityDirectoryItem', 'QgsCptCitySelectionItem', 'QgsCredentials', 'QgsCredentialsConsole', 'QgsCsException', 'QgsCubicRasterResampler', 'QgsDataCollectionItem', 'QgsDataDefined', 'QgsDataItem', 'QgsDataProvider', 'QgsDataSourceURI', 'QgsDbFilterProxyModel', 'QgsDiagram', 'QgsDiagramInterpolationSettings', 'QgsDiagramLayerSettings', 'QgsDiagramRendererV2', 'QgsDiagramSettings', 'QgsDirectoryItem', 'QgsDirectoryParamWidget', 'QgsDistanceArea', 'QgsDoubleBoxScaleBarStyle', 'QgsEllipseSymbolLayerV2', 'QgsError', 'QgsErrorItem', 'QgsErrorMessage', 'QgsException', 'QgsExpression', 'QgsFavouritesItem', 'QgsFeature', 'QgsFeatureIterator', 'QgsFeatureRendererV2', 'QgsFeatureRequest', 'QgsFeatureStore', 'QgsField', 'QgsFields', 'QgsFillSymbolLayerV2', 'QgsFillSymbolV2', 'QgsFontMarkerSymbolLayerV2', 'QgsFontUtils', 'QgsGPSConnection', 'QgsGPSConnectionRegistry', 'QgsGPSDetector', 'QgsGPSInformation', 'QgsGeometry', 'QgsGeometryCache', 'QgsGeometryValidator', 'QgsGml', 'QgsGmlFeatureClass', 'QgsGmlSchema', 'QgsGpsdConnection', 'QgsGradientStop', 'QgsGraduatedSymbolRendererV2', 'QgsHistogramDiagram', 'QgsHttpTransaction', 'QgsHueSaturationFilter', 'QgsImageFillSymbolLayer', 'QgsLabel', 'QgsLabelAttributes', 'QgsLabelCandidate', 'QgsLabelComponent', 'QgsLabelPosition', 'QgsLabelSearchTree', 'QgsLabelingEngineInterface', 'QgsLayerItem', 'QgsLegendModel', 'QgsLinePatternFillSymbolLayer', 'QgsLineSymbolLayerV2', 'QgsLineSymbolV2', 'QgsLinearMinMaxEnhancement', 'QgsLinearMinMaxEnhancementWithClip', 'QgsLinearlyInterpolatedDiagramRenderer', 'QgsLogger', 'QgsMapLayer', 'QgsMapLayerRegistry', 'QgsMapRenderer', 'QgsMapToPixel', 'QgsMarkerLineSymbolLayerV2', 'QgsMarkerSymbolLayerV2', 'QgsMarkerSymbolV2', 'QgsMessageLog', 'QgsMessageLogConsole', 'QgsMessageOutput', 'QgsMessageOutputConsole', 'QgsMimeDataUtils', 'QgsMultiBandColorRenderer', 'QgsNMEAConnection', 'QgsNetworkAccessManager', 'QgsNumericScaleBarStyle', 'QgsOWSConnection', 'QgsOfflineEditing', 'QgsOgcUtils', 'QgsPaintEngineHack', 'QgsPalLabeling', 'QgsPalLayerSettings', 'QgsPalettedRasterRenderer', 'QgsPaperItem', 'QgsPieDiagram', 'QgsPluginLayer', 'QgsPluginLayerRegistry', 'QgsPluginLayerType', 'QgsPoint', 'QgsPointDisplacementRenderer', 'QgsPointPatternFillSymbolLayer', 'QgsProject', 'QgsProjectBadLayerDefaultHandler', 'QgsProjectBadLayerHandler', 'QgsProjectFileTransform', 'QgsProjectVersion', 'QgsProperty', 'QgsPropertyKey', 'QgsPropertyValue', 'QgsProviderCountCalcEvent', 'QgsProviderExtentCalcEvent', 'QgsProviderMetadata', 'QgsProviderRegistry', 'QgsPseudoColorShader', 'QgsPythonRunner', 'QgsRaster', 'QgsRasterBandStats', 'QgsRasterBlock', 'QgsRasterChecker', 'QgsRasterDataProvider', 'QgsRasterDrawer', 'QgsRasterFileWriter', 'QgsRasterHistogram', 'QgsRasterIdentifyResult', 'QgsRasterInterface', 'QgsRasterIterator', 'QgsRasterLayer', 'QgsRasterNuller', 'QgsRasterPipe', 'QgsRasterProjector', 'QgsRasterPyramid', 'QgsRasterRange', 'QgsRasterRenderer', 'QgsRasterResampleFilter', 'QgsRasterResampler', 'QgsRasterShader', 'QgsRasterShaderFunction', 'QgsRasterTransparency', 'QgsRasterViewPort', 'QgsRectangle', 'QgsRenderChecker', 'QgsRenderContext', 'QgsRendererCategoryV2', 'QgsRendererRangeV2', 'QgsRendererV2AbstractMetadata', 'QgsRendererV2Metadata', 'QgsRendererV2Registry', 'QgsRuleBasedRendererV2', 'QgsRunProcess', 'QgsSVGFillSymbolLayer', 'QgsSatelliteInfo', 'QgsScaleBarStyle', 'QgsScaleCalculator', 'QgsScaleUtils', 'QgsSimpleFillSymbolLayerV2', 'QgsSimpleLineSymbolLayerV2', 'QgsSimpleMarkerSymbolLayerV2', 'QgsSingleBandColorDataRenderer', 'QgsSingleBandGrayRenderer', 'QgsSingleBandPseudoColorRenderer', 'QgsSingleBoxScaleBarStyle', 'QgsSingleCategoryDiagramRenderer', 'QgsSingleSymbolRendererV2', 'QgsSnapper', 'QgsSnappingResult', 'QgsSpatialIndex', 'QgsStyleV2', 'QgsSvgCache', 'QgsSvgCacheEntry', 'QgsSvgMarkerSymbolLayerV2', 'QgsSymbolLayerV2', 'QgsSymbolLayerV2AbstractMetadata', 'QgsSymbolLayerV2Metadata', 'QgsSymbolLayerV2Registry', 'QgsSymbolLayerV2Utils', 'QgsSymbolV2', 'QgsSymbolV2LevelItem', 'QgsSymbolV2RenderContext', 'QgsSymbologyV2Conversion', 'QgsTextDiagram', 'QgsTicksScaleBarStyle', 'QgsTolerance', 'QgsVectorColorBrewerColorRampV2', 'QgsVectorColorRampV2', 'QgsVectorDataProvider', 'QgsVectorFieldSymbolLayer', 'QgsVectorFileWriter', 'QgsVectorGradientColorRampV2', 'QgsVectorJoinInfo', 'QgsVectorLayer', 'QgsVectorLayerCache', 'QgsVectorLayerEditBuffer', 'QgsVectorLayerEditUtils', 'QgsVectorLayerFeatureIterator', 'QgsVectorLayerImport', 'QgsVectorLayerJoinBuffer', 'QgsVectorRandomColorRampV2', 'QgsZipItem', 'USER_CRS_START_ID', '__doc__', '__file__', '__name__', '__package__']
'GEO_EPSG_CRS_ID', 'LAT_PREFIX_LEN', 'MINIMUM_POINT_SIZE', 'PROJ_PREFIX_LEN', 'QGis', 'QgsApplication', 'QgsContextHelp',
'QgsContinuousColorRenderer', 'QgsContrastEnhancement', 'QgsContrastEnhancementFunction', 'QgsCoordinateReferenceSystem',  
'QgsCoordinateTransform', 'QgsDataProvider', 'QgsDataSourceURI', 'QgsDistanceArea', 'QgsFeature', 'QgsField', 'QgsGeometry',
'QgsGraduatedSymbolRenderer', 'QgsLabel', 'QgsLabelAttributes', 'QgsLogger', 'QgsMapLayer', 'QgsMapLayerRegistry',
'QgsMapRenderer', 'QgsMapToPixel', 'QgsMarkerCatalogue', 'QgsMessageOutput', 'QgsMessageOutputConsole', 'QgsPoint',
'QgsProject', 'QgsProviderMetadata', 'QgsProviderRegistry', 'QgsRasterBandStats', 'QgsRasterDataProvider', 'QgsRasterLayer',
'QgsRasterPyramid', 'QgsRasterShader', 'QgsRasterShaderFunction', 'QgsRasterTransparency', 'QgsRasterViewPort', 'QgsRectangle',
'QgsRenderContext', 'QgsRenderer', 'QgsScaleCalculator', 'QgsSingleSymbolRenderer', 'QgsSnapper', 'QgsSnappingResult',
'QgsSpatialIndex', 'QgsSymbol', 'QgsSymbologyUtils', 'QgsUniqueValueRenderer', 'QgsVectorDataProvider',
'QgsVectorFileWriter', 'QgsVectorLayer', 'USER_CRS_START_ID', '__doc__', '__file__', '__name_]
</pre>
</pre>


Строка 198: Строка 293:


<pre>
<pre>
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__',
['__class__', '__delattr__', '__delitem__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'attribute', 'attributes', 'deleteAttribute', 'fieldNameIndex', 'fields', 'geometry', 'geometryAndOwnership', 'id', 'initAttributes', 'isValid', 'setAttribute', 'setAttributes', 'setFeatureId', 'setFields', 'setGeometry', 'setGeometryAndOwnership', 'setValid']
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'addAttribute',
'attributeMap', 'changeAttribute', 'clean', 'deleteAttribute', 'geometry', 'geometryAndOwnership', 'id',
'isDirty', 'isValid', 'setAttributeMap', 'setFeatureId', 'setGeometry', 'setGeometryAndOwnership', 'setTypeName',
'setValid', 'typeName']
</pre>
</pre>


Строка 211: Строка 302:
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<RCC>
<RCC>
<qresource prefix="/plugins/testplugin" >
<qresource prefix="" >
     <file>icon.png</file>
     <file>icons/icon.png</file>
</qresource>
</qresource>
</RCC>
</RCC>
Строка 225: Строка 316:
</pre>
</pre>


И как уже упоминалось выше, импортировать получившиеся ресурсы в основном коде <tt>plugin.py</tt>.
И как уже упоминалось выше, импортировать получившиеся ресурсы в основном коде <tt>testplugin.py</tt>.


=== Заключение ===
=== Заключение ===


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


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


=== Ссылки по теме ===
=== Ссылки по теме ===

Текущая версия от 21:10, 5 ноября 2018

Эта страница опубликована в основном списке статей сайта
по адресу http://gis-lab.info/qa/qgis-dev-python.html


Описание процесса создания элементарного расширения для QGIS.

QGIS — свободная пользовательская ГИС, обладающая развитым API и продвинутой системой расширений (модулей). Модули создаются для расширения функциональности программы. QGIS написана на объектно-ориентированном языке высокого уровня C++. Так же в QGIS встроены привязки (bindings), которые реализуют практически весь функционал QGIS на языке Python. Графический интерфейс пользователя QGIS базируется на библиотеках Qt4. Разработка для QGIS может вестись на двух языках, Python и C++. Для разработки на языке Python необходимы привязки PyQt, обеспечивающие взаимодействия Python с Qt4. Помимо расширений библиотеки QGIS могут быть также использованы для разработки отдельных приложений, что подробно описано в статье «Создание приложения на базе набора библиотек QGIS на Python».

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

Создание расширения состоит из нескольких этапов:

  • Идея
  • Создание каркаса
  • Разработка необходимой функциональности.
  • Тестирование
  • Публикация

Задача этой статьи - иллюстрация процесса создания простого расширения, которое:

  • добавит новую кнопку на панель инструментов
  • добавит подменю в меню расширений
  • выполнит простое действие
  • будет доступно на двух языках.

Это расширение можно будет использовать как каркас для других будущих, более функциональных расширений. Далее мы более подробно разберем процесс создания по шагам.

Получение исходного кода

Код расширения, работающий под QGIS 2.0 можно получить в репозитории GitHub:

git clone git@github.com:nextgis/testplugin.git

Подготовка

Для разработки расширения понадобится

  • Любой текстовый редактор
  • Python, лучше версии 2.5-2.7.х (3.x не подойдет)
  • QGIS версии 2.0 и выше, для проверки работоспособности расширения.

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

Разработку можно вести прямо в папке, где хранятся расширения или переместить его в нее после разработки, в QGIS эта папка либо QGIS\python\plugins либо C:\Documents and Settings\username\.qgis2\python\plugins\.

В папке с плагинами необходимо создать новую, назвав ее так, чтобы бы по названию можно было легко определить, что именно делает расширение. Назовем нашу папку testplugin. Это название следует запомнить, так как оно будет фигурировать в различных компонентах кода.

Перед разработкой, нам нужно создать в этой папке несколько новых, пока пустых текстовых файлов:

  • __init__.py — начальная точка, создаёт экземпляр основного класса, который передается в QGIS
  • testplugin.py — основной код расширения. Содержит всю информацию о всех действиях расширения.
  • resources.qrc — xml файл, создаваемый Qt Designer или вручную и содержащий относительные пути к ресурсам расширения, формам, иконкам и т.п. Компилируется в py перед созданием пакета плагина.
  • metadata.txt — описание модуля, содержит информацию о расширении, версию, имя разработчика

Помимо этих файлов, если расширение использует формы, могут также присутствовать файлы:

  • form.ui — форма созданная с помощью Qt Designer
  • form.py — она же, скомпилированная в виде программы на языке Python

Для примера из данной статьи эти файлы не понадобятся.

Итак, пустые файлы созданы и лежат в нужной папке.

Перед разработкой, отредактируем файл metadata.txt. Этот файл содержит информацию о расширении, такую как его название name, т.е. то, как он будет показываться в Менеджере модулей, его описание description, показывается там же. Нужно также указать минимальную версию QGIS, для которой разработано это расширение (при попытке загрузки в QGIS меньшей версии плагин будет отключен) и другие параметры:

[general]
name=TestPlugin
description=This plugin is for testing and templating purposes
category=Vector
version=0.0.2
qgisMinimumVersion=2.0

author=NextGIS
email=info@nextgis.org

icon=icons/icon.png

tags=testing,template

homepage=http://gis-lab.info/qa/qgis-dev-python.html
tracker=https://github.com/nextgis/testplugin/issues
repository=https://github.com/nextgis/testplugin

experimental=True
deprecated=False

Разработка

Разработка начинается с файла __init__.py. Главным фрагментом кода является импорт исполняемой части расширения, содержащейся в файле testplugin.py (поэтому from testplugin) и содержащей всю содержательную часть кода.

def classFactory(iface):
  # Import class TestPlugin from file testplugin.py
  from testplugin import TestPlugin
  return TestPlugin(iface)

Название главного импортируемого класса TestPlugin должно быть равно названию класса в коде testplugin.py, название — регистрозависимое, если вызывается TestPlugin, а класс носит название testplugin, будет выдано сообщение об ошибке.

Рассмотрим testplugin.py.

Как и во всех программах на языке Python, все начинается с импорта необходимых для работы классов, в нашем случае это будут классы PyQt — обертки для Qt на языке Python: PyQt4.QtCore и PyQt4.QtGui, активно используемые QGIS и сами классы QGIS: qgis.core

# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *

Помимо этого нужно также импортировать ресурсы самого расширения, в нашем случае они будут задаваться через resources.py, как его создать мы разберем чуть позже.

import resources

После импорта идет раздел главного класса, импортируемого в __init__.py и содержащего все нужные функции. Необходимо обратить внимание, что название класса должно быть равно названию используемому для его вызова в __init__.py. Определение функции начинается с ключевого слова def, за которым следует имя функции и ее аргументы.

Объявим наш основной класс и функцию инициализации:

class TestPlugin:

  def __init__(self, iface):
    """Initialize class"""
    # сохраним ссылку на интерфейс QGIS//save reference to QGIS interface
    self.iface = iface
    self.qgsVersion = unicode(QGis.QGIS_VERSION_INT)

Одна из важнейших функций — создание элементов интерфейса расширения initGui. При загрузке расширения к пользовательскому интерфейсу iface.self могут добавляться кнопки addToolBarIcon или строки в меню «Модули», addPluginToMenu. Не забываем документировать функции с помощью тройных кавычек.

import resources
def initGui(self):
    """Инициализируем графический интерфейс пользователя"""
    #проверим, не пытаются ли запустить плагин из QGIS версии ниже 2.0//check if the plugin is ran below 2.0
    if int(self.qgsVersion) < 10900:
        qgisVersion = self.qgsVersion[0] + "." + self.qgsVersion[2] + "." + self.qgsVersion[3]
        QMessageBox.warning(self.iface.mainWindow(),
                            "TestPlugin", "Error",
                            "TestPlugin", "QGIS %s detected.\n" % (qgisVersion) +
                            "TestPlugin", "This version of TestPlugin requires at least QGIS version 2.0.\nPlugin will not be enabled.")
        return None

    # create action that will be run by the plugin//создадим действие, которое будет запускать конфигурацию расширения
    self.action = QAction("Test plugin", self.iface.mainWindow())
    self.action.setIcon(QIcon(":/icons/icon.png"))
    self.action.setWhatsThis("Configuration for test plugin")
    self.action.setStatusTip("This is status tip")

    # добавим пункт в меню инструментов Vector//add plugin menu to Vector toolbar
    self.iface.addPluginToVectorMenu("TestPlugin",self.action)
    
    # добавим кнопку на панель инструментов Vector//add icon to new menu item in Vector toolbar
    self.iface.addVectorToolBarIcon(self.action)

    # связь действия с функцией run//connect action to the run method
    self.action.triggered.connect(self.run)

Следующая важная функция расширения описывает то, что происходит при его выгрузке, выключении через Менеджер модулей — unload(self)

  def unload(self):
    """Действия при выгрузке расширения"""
    # удалить меню расширения и иконку//remove menu and icon from the menu
    self.iface.removeVectorToolBarIcon(self.action)
    self.iface.removePluginVectorMenu("TestPlugin",self.action)

И наконец функция выполняющая единственное действие в нашем расширении — run(). Функция создаёт строку сообщения и показывает ее в QMessageBox в главном окне программы.

  def run(self):
    """Действия при запуске расширения"""
    # создать и показать сообщение//create a string and show it
    infoString = "This is a test"
    QMessageBox.information(self.iface.mainWindow(),"About",infoString)

Интернационализация

Интернационализация позволяет сделать ваш плагин доступным на разных языках. Об интернационализации на wiki В качестве примера рассмотрим добавление перевода на русский язык. Команды в данном разделе указаны для систем Ubuntu/Debian. Для начала нам потребуется внести изменения в код файла testplugin.py. Добавим импорт нового модуля os, который позволит нам обращаться к файловой системе. Добавьте перед существующими "импортами":

import os

Воспользуемся новым модулем. Метод __init__ теперь будет выглядеть так:

    def __init__(self, iface):
        """Initialize class"""
        # save reference to QGIS interface
        self.iface = iface
        self.qgsVersion = unicode(QGis.QGIS_VERSION_INT)
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'TestPlugin_{}.qm'.format(locale))
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

Что изменилось? Мы добавили атрибут self.plugin_dir, хранящий путь к папке с текущим файлом (testplugin.py). Получили из настроек QGIS текущую локаль (locale), которую пытаемся использовать для обращения к пути файла перевода (locale_path). Если такой файл существует, то загружаем из него перевод. Сразу после метода __init__ создадим новый:

    def tr(self, message):
        return QCoreApplication.translate("TestPlugin", message)

В Qt4 для перевода строки необходимо использовать метод QCoreApplication.translate(). Для каждой строки, требующей перевода, писать такую конструкцию неудобно, поэтому мы и добавили новый метод tr. В нашем примере есть только две строки, требующие перевода, и без данного метода можно было и обойтись, но вам он должен упростить жизнь. Далее используем новый метод для того, чтобы "пометить" строки для перевода:

    def run(self):
        """Action to run"""
        # create a string and show it
        infoString = self.tr("This is a test")
        QMessageBox.information(self.iface.mainWindow(), self.tr("About"), infoString)

С кодом почти закончили, перейдем к заключительным действиям. Создайте папку i18n рядом с файлом testplugin.py. В папке i18n создайте файл <ИмяВашегоПлагина>.pro. В нашем примере это будет файл TestPlugin.pro. Содержимое файла должно быть таким:

SOURCES += ../testplugin.py
TRANSLATIONS += TestPlugin_ru.ts

В SOURCES перечисляются файлы, в которых требуется искать строки для перевода. Таких источников может быть несколько, и необходимо перечислить их все, добавляя новые строки SOURCES += ../<ИмяФайла>. В нашем примере ресурс один. В TRANSLATIONS перечисляются файлы, содержащие перевод в виде разметки XML, которые необходимо сгенерировать. В имени файла ru значит, что это файл перевода на русский. Если хотите добавить другой язык, то после нижнего подчеркивания следует указать соответствующие две буквы. Например, uk — украинский. Далее необходимо установить инструменты разработчика. В частности, нам понадобятся pylupdate4 и linguist. Это делается следующей командой:

sudo apt-get install pyqt4-dev-tools

В терминале выполняем команды:

cd i18n
pylupdate4 TestPlugin.pro
linguist TestPlugin_ru.ts

Если требуется переводить сразу на несколько языков, то можно открыть несколько файлов, перечислив их:

linguist TestPlugin_ru.ts TestPlugin_uk.ts

Или открыть все доступные файлы переводов:

linguist *.ts

Всплывающее окно заполните так:

Linguist settings.jpg

Если языков несколько, то настройте каждый отдельно в меню "Правка -> Параметры файла перевода". В поле "Русский перевод" введите желаемый перевод — "Это тест" и нажмите Ctrl+Enter.

Linguist translation example.jpg

Теперь введите "О Плагине" и нажмите Ctrl+Enter. Далее "Файл -> Сохранить" и "Файл -> Скомпилировать". Последняя операция создаст бинарный файл TestPlugin_ru.qm, содержащий перевод. Именно этот файл и будет использоваться при отображении плагина на русском языке. Осталось проверить результат. Для перезагрузки разрабатываемых плагинов удобно использовать плагин с говорящим названием "Plugin Reloader". В QGIS откройте меню "Модули -> Управление модулями". Во вкладке "Параметры" поставьте галочку напротив "Разрешить установку экспериментальных модулей". Во вкладке "Все" введите "Plugin Reloader" и нажмите "Установить модуль". В настройках появившейся иконки плагина "Plugin Reloader" нажмите "Choose plugin to be reloaded" и выберите плагин "testplugin" из списка. Нажмите на иконку плагина "Plugin Reloader", откройте "testplugin" и посмотрите результат.

Какие функции из API доступны через Python

К сожалению, не все функции API QGIS доступны через Python (через C++ доступны все). Чтобы определить, доступны ли конкретные классы и функции, необходимо сначала найти их в документации к API GIS http://doc.qgis.org. А затем в консоли Python (Модули\Консоль Python) определить с помощью команды dir видны ли эти классы и функции из Python.

Например, определим доступные классы:

import qgis.core
dir(qgis.core)

Результат:

['DEFAULT_LINE_WIDTH', 'DEFAULT_POINT_SIZE', 'DEFAULT_SEGMENT_EPSILON', 'ELLPS_PREFIX_LEN', 'GEOCRS_ID', 'GEOPROJ4', 'GEOSRID', 'GEOWKT', 'GEO_EPSG_CRS_AUTHID', 'GEO_EPSG_CRS_ID', 'GEO_NONE', 'LAT_PREFIX_LEN', 'MINIMUM_POINT_SIZE', 'NULL', 'PROJECT_SCALES', 'PROJ_PREFIX_LEN', 'QGis', 'QgsAbstractCacheIndex', 'QgsAbstractFeatureIterator', 'QgsAction', 'QgsAddRemoveItemCommand', 'QgsAddRemoveMultiFrameCommand', 'QgsApplication', 'QgsAtlasComposition', 'QgsAttributeAction', 'QgsAttributeEditorContainer', 'QgsAttributeEditorElement', 'QgsAttributeEditorField', 'QgsBilinearRasterResampler', 'QgsBrightnessContrastFilter', 'QgsBrowserModel', 'QgsCRSCache', 'QgsCacheIndexFeatureId', 'QgsCachedFeatureIterator', 'QgsCachedFeatureWriterIterator', 'QgsCategorizedSymbolRendererV2', 'QgsCentroidFillSymbolLayerV2', 'QgsClipToMinMaxEnhancement', 'QgsClipper', 'QgsColorBrewerPalette', 'QgsColorRampShader', 'QgsComposerArrow', 'QgsComposerAttributeTable', 'QgsComposerAttributeTableCompare', 'QgsComposerEffect', 'QgsComposerFrame', 'QgsComposerGroupItem', 'QgsComposerHtml', 'QgsComposerItem', 'QgsComposerItemCommand', 'QgsComposerItemGroup', 'QgsComposerLabel', 'QgsComposerLayerItem', 'QgsComposerLegend', 'QgsComposerLegendItem', 'QgsComposerLegendStyle', 'QgsComposerMap', 'QgsComposerMergeCommand', 'QgsComposerMultiFrame', 'QgsComposerMultiFrameCommand', 'QgsComposerPicture', 'QgsComposerRasterSymbolItem', 'QgsComposerScaleBar', 'QgsComposerShape', 'QgsComposerSymbolV2Item', 'QgsComposerTable', 'QgsComposerTextTable', 'QgsComposition', 'QgsContextHelp', 'QgsContrastEnhancement', 'QgsContrastEnhancementFunction', 'QgsCoordinateReferenceSystem', 'QgsCoordinateTransform', 'QgsCoordinateTransformCache', 'QgsCptCityArchive', 'QgsCptCityBrowserModel', 'QgsCptCityCollectionItem', 'QgsCptCityColorRampItem', 'QgsCptCityColorRampV2', 'QgsCptCityDataItem', 'QgsCptCityDirectoryItem', 'QgsCptCitySelectionItem', 'QgsCredentials', 'QgsCredentialsConsole', 'QgsCsException', 'QgsCubicRasterResampler', 'QgsDataCollectionItem', 'QgsDataDefined', 'QgsDataItem', 'QgsDataProvider', 'QgsDataSourceURI', 'QgsDbFilterProxyModel', 'QgsDiagram', 'QgsDiagramInterpolationSettings', 'QgsDiagramLayerSettings', 'QgsDiagramRendererV2', 'QgsDiagramSettings', 'QgsDirectoryItem', 'QgsDirectoryParamWidget', 'QgsDistanceArea', 'QgsDoubleBoxScaleBarStyle', 'QgsEllipseSymbolLayerV2', 'QgsError', 'QgsErrorItem', 'QgsErrorMessage', 'QgsException', 'QgsExpression', 'QgsFavouritesItem', 'QgsFeature', 'QgsFeatureIterator', 'QgsFeatureRendererV2', 'QgsFeatureRequest', 'QgsFeatureStore', 'QgsField', 'QgsFields', 'QgsFillSymbolLayerV2', 'QgsFillSymbolV2', 'QgsFontMarkerSymbolLayerV2', 'QgsFontUtils', 'QgsGPSConnection', 'QgsGPSConnectionRegistry', 'QgsGPSDetector', 'QgsGPSInformation', 'QgsGeometry', 'QgsGeometryCache', 'QgsGeometryValidator', 'QgsGml', 'QgsGmlFeatureClass', 'QgsGmlSchema', 'QgsGpsdConnection', 'QgsGradientStop', 'QgsGraduatedSymbolRendererV2', 'QgsHistogramDiagram', 'QgsHttpTransaction', 'QgsHueSaturationFilter', 'QgsImageFillSymbolLayer', 'QgsLabel', 'QgsLabelAttributes', 'QgsLabelCandidate', 'QgsLabelComponent', 'QgsLabelPosition', 'QgsLabelSearchTree', 'QgsLabelingEngineInterface', 'QgsLayerItem', 'QgsLegendModel', 'QgsLinePatternFillSymbolLayer', 'QgsLineSymbolLayerV2', 'QgsLineSymbolV2', 'QgsLinearMinMaxEnhancement', 'QgsLinearMinMaxEnhancementWithClip', 'QgsLinearlyInterpolatedDiagramRenderer', 'QgsLogger', 'QgsMapLayer', 'QgsMapLayerRegistry', 'QgsMapRenderer', 'QgsMapToPixel', 'QgsMarkerLineSymbolLayerV2', 'QgsMarkerSymbolLayerV2', 'QgsMarkerSymbolV2', 'QgsMessageLog', 'QgsMessageLogConsole', 'QgsMessageOutput', 'QgsMessageOutputConsole', 'QgsMimeDataUtils', 'QgsMultiBandColorRenderer', 'QgsNMEAConnection', 'QgsNetworkAccessManager', 'QgsNumericScaleBarStyle', 'QgsOWSConnection', 'QgsOfflineEditing', 'QgsOgcUtils', 'QgsPaintEngineHack', 'QgsPalLabeling', 'QgsPalLayerSettings', 'QgsPalettedRasterRenderer', 'QgsPaperItem', 'QgsPieDiagram', 'QgsPluginLayer', 'QgsPluginLayerRegistry', 'QgsPluginLayerType', 'QgsPoint', 'QgsPointDisplacementRenderer', 'QgsPointPatternFillSymbolLayer', 'QgsProject', 'QgsProjectBadLayerDefaultHandler', 'QgsProjectBadLayerHandler', 'QgsProjectFileTransform', 'QgsProjectVersion', 'QgsProperty', 'QgsPropertyKey', 'QgsPropertyValue', 'QgsProviderCountCalcEvent', 'QgsProviderExtentCalcEvent', 'QgsProviderMetadata', 'QgsProviderRegistry', 'QgsPseudoColorShader', 'QgsPythonRunner', 'QgsRaster', 'QgsRasterBandStats', 'QgsRasterBlock', 'QgsRasterChecker', 'QgsRasterDataProvider', 'QgsRasterDrawer', 'QgsRasterFileWriter', 'QgsRasterHistogram', 'QgsRasterIdentifyResult', 'QgsRasterInterface', 'QgsRasterIterator', 'QgsRasterLayer', 'QgsRasterNuller', 'QgsRasterPipe', 'QgsRasterProjector', 'QgsRasterPyramid', 'QgsRasterRange', 'QgsRasterRenderer', 'QgsRasterResampleFilter', 'QgsRasterResampler', 'QgsRasterShader', 'QgsRasterShaderFunction', 'QgsRasterTransparency', 'QgsRasterViewPort', 'QgsRectangle', 'QgsRenderChecker', 'QgsRenderContext', 'QgsRendererCategoryV2', 'QgsRendererRangeV2', 'QgsRendererV2AbstractMetadata', 'QgsRendererV2Metadata', 'QgsRendererV2Registry', 'QgsRuleBasedRendererV2', 'QgsRunProcess', 'QgsSVGFillSymbolLayer', 'QgsSatelliteInfo', 'QgsScaleBarStyle', 'QgsScaleCalculator', 'QgsScaleUtils', 'QgsSimpleFillSymbolLayerV2', 'QgsSimpleLineSymbolLayerV2', 'QgsSimpleMarkerSymbolLayerV2', 'QgsSingleBandColorDataRenderer', 'QgsSingleBandGrayRenderer', 'QgsSingleBandPseudoColorRenderer', 'QgsSingleBoxScaleBarStyle', 'QgsSingleCategoryDiagramRenderer', 'QgsSingleSymbolRendererV2', 'QgsSnapper', 'QgsSnappingResult', 'QgsSpatialIndex', 'QgsStyleV2', 'QgsSvgCache', 'QgsSvgCacheEntry', 'QgsSvgMarkerSymbolLayerV2', 'QgsSymbolLayerV2', 'QgsSymbolLayerV2AbstractMetadata', 'QgsSymbolLayerV2Metadata', 'QgsSymbolLayerV2Registry', 'QgsSymbolLayerV2Utils', 'QgsSymbolV2', 'QgsSymbolV2LevelItem', 'QgsSymbolV2RenderContext', 'QgsSymbologyV2Conversion', 'QgsTextDiagram', 'QgsTicksScaleBarStyle', 'QgsTolerance', 'QgsVectorColorBrewerColorRampV2', 'QgsVectorColorRampV2', 'QgsVectorDataProvider', 'QgsVectorFieldSymbolLayer', 'QgsVectorFileWriter', 'QgsVectorGradientColorRampV2', 'QgsVectorJoinInfo', 'QgsVectorLayer', 'QgsVectorLayerCache', 'QgsVectorLayerEditBuffer', 'QgsVectorLayerEditUtils', 'QgsVectorLayerFeatureIterator', 'QgsVectorLayerImport', 'QgsVectorLayerJoinBuffer', 'QgsVectorRandomColorRampV2', 'QgsZipItem', 'USER_CRS_START_ID', '__doc__', '__file__', '__name__', '__package__']

Далее, после выбора нужного класса, например QgsFeature, посмотрим его методы:

dir(qgis.core.QgsFeature)

Результат:

['__class__', '__delattr__', '__delitem__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'attribute', 'attributes', 'deleteAttribute', 'fieldNameIndex', 'fields', 'geometry', 'geometryAndOwnership', 'id', 'initAttributes', 'isValid', 'setAttribute', 'setAttributes', 'setFeatureId', 'setFields', 'setGeometry', 'setGeometryAndOwnership', 'setValid']

Ресурсы

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

<RCC>
<qresource prefix="" >
    <file>icons/icon.png</file>
</qresource>
</RCC>

Рекомендуется использовать уникальный qresource prefix, который не будет конфликтовать с другими расширениями.

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

pyrcc4 -o resources.py resources.qrc 

И как уже упоминалось выше, импортировать получившиеся ресурсы в основном коде testplugin.py.

Заключение

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

Помните, что лучшим пособием по разработке для QGIS являются расширения созданные другими авторами.

Ссылки по теме