出于研究sextante代码的需要,抽空查了下QGIS下python插件的开发流程。具体的操作参考英文的PyQGIS 的开发帮助文档。
QGIS是用C++开发的,传统QGIS下开发插件也多是用C++写的,然而用Python可不可以呢?当然可以!并且,由于Python语言的动态编程特性,用Python进行QGIS插件开发相比C++而言要快捷方便很多,也易于理解和发布。
实质上,在QGIS的插件管理器中,Python插件和C++插件是一视同仁的。Python插件的存放路径有两个,在UNIX或者Mac操作系统下为~/.qgis/python/plugins和(qgis_prefix)/share/qgis/python/plugin;在windows下为~/.qgis/python/plugin和(qgis_prefix)/python/plugin。在windows下插件存放根目录一般为C:\Documents and
Settings\(user)。在插件目录(根目录+上述路径)下的任何文件夹将会被视为QGIS的Python插件包,但是该目录下并非所有文件夹都能够被QGIS识别和安装,只有符合一定要求才可以,也即具备必要的文件组成。
开发Python插件思路大体分为4步:
自从QGIS发布官方的Python插件开发文档后,到目前已经有很多很优秀的插件被共享出来了,例如sextante。具体请浏览Plugin Repositories wiki page,可以下载下来研究下源码哦,适合的的话修改一下说不定就成了你的插件了呢(太坏了,起码要跟原作者说一声嘛)。要是你只是想试下手又没啥好的想法,到Python Plugin Ideas wiki page去吧,那里有。
说这么多话终于到了编码的节奏了呢。先看个例子吧,了解下它的结构组成:
PYTHON_PLUGINS_PATH/testplug/
__init__.py
plugin.py
metadata.txt
resources.qrc
resources.py
form.ui
form.py
其中:
若嫌手动创建这些插件框架文件太繁琐,这里有两种方案你可以选择:1)http://hub.qgis.org/projects/plugin-builder2)http://www.dimitrisk.gr/qgis/creator/。此外,你也可以下载一款叫Plugin Builder的离线插件来帮助你创建插件模板。在开始编写自己的插件之前,建议最好还是找个典型的插件案例来研究一下吧,例如sextante啦。
QGIS 插件管理器在加载一款插件前需要获取一些插件的基本信息,例如插件名(插件标志,类似ID子类的)、插件描述信息(显示)等。__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): # load TestPlugin class from file testplugin.py from testplugin import TestPlugin return TestPlugin(iface)
在旧版本中QGIS外部插件只能显示在“插件/Plugins”菜单栏下,但在1.9.90版本后,在__init__.py中新增了函数“category()”使得插件在菜单栏Raster、Vector、Database和Web下都可以了。该函数的作用就是定义插件的显示菜单,但目前其值只能选“Vector”、“Raster”、“Database”、“Web”和“Layers”中的一个。例如,加入你想在“Raster”菜单栏下显示你的插件,参照下面的代码:
def category(): return "Raster"
在1.8版本之后,你必须添加该文件以描述你的插件信息。关于该文件的详细信息请参考https://github.com/qgis/qgis-django/blob/master/qgis-app/plugins/docs/introduction.rst。一个简单的例子如下:
; 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 versi
deprecated=False
...
在该类中定义插件操作。值得提醒的是该类中的classFactory()函数。该函数会在QGIS加载外部插件时调用,用来接收QgisInterface类实例的引用和(必须)返回自定义插件类的实例,即你的插件实例。举个简单的例子,如插件TestPlugin,参照下面的代码(testplugin.py):
from PyQt4.QtCore import * from PyQt4.QtGui import * from qgis.core import *
# initialize Qt resources from file resouces.py import resources
class TestPlugin:
def __init__(self, iface):
# save reference to the QGIS interface self.iface = iface def initGui(self): # create action that will start plugin configuration self.action = QAction(QIcon(":/plugins/testplug/icon.png"), "Test plugin", self self.action.setWhatsThis("Configuration for test plugin") self.action.setStatusTip("This is status tip") QObject.connect(self.action, SIGNAL("triggered()"), self.run)
# add toolbar button and menu item self.iface.addToolBarIcon(self.action) self.iface.addPluginToMenu("&Test plugins", self.action)
# connect to signal renderComplete which is emitted when canvas rendering is done QObject.connect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"),
def unload(self): # remove the plugin menu item and icon self.iface.removePluginMenu("&Test plugins",self.action) self.iface.removeToolBarIcon(self.action)
# disconnect form signal of the canvas QObject.disconnect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"
def run(self): # create and show a configuration dialog or something similar print "TestPlugin: run called!"
def renderTest(self, painter): # use painter for drawing to map canvas print "TestPlugin: renderTest called!
...
如果你用的是1.9.90以上的版本,并且想修改插件显示的位置,那你必须修改得修改initGui()函数和unload()函数部分代码。首先,检查QGIS的版本,若版本支持,则参照下面代码进行适当的修改:
def initGui(self): # create action that will start plugin configuration self.action = QAction(QIcon(":/plugins/testplug/icon.png"), "Test plugin", self self.action.setWhatsThis("Configuration for test plugin") self.action.setStatusTip("This is status tip") QObject.connect(self.action, SIGNAL("triggered()"), self.run)
# check if Raster menu available if hasattr(self.iface, "addPluginToRasterMenu"): # Raster menu and toolbar available self.iface.addRasterToolBarIcon(self.action) self.iface.addPluginToRasterMenu("&Test plugins", self.action) else: # there is no Raster menu, place plugin under Plugins menu as usual self.iface.addToolBarIcon(self.action) self.iface.addPluginToMenu("&Test plugins", self.action)
# connect to signal renderComplete which is emitted when canvas rendering is done QObject.connect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), self
def unload(self): # check if Raster menu available and remove our buttons from appropriate # menu and toolbar 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)
# disconnect form signal of the canvas QObject.disconnect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"),
...
在自定义插件显示菜单位置时可参考 API docs中列举的函数。
在initGui()函数中我们会使用到某些资源,例如上例中的icon.png,而这些资源是在resources中定义的,例如(resources.qrc):
<RCC> <qresource prefix="/plugins/testplug" > <file>icon.png</file> </qresource> </RCC>
为了避免与其他外部插件或者QGIS部件发生命名冲突,建议最好在资源文件中添加路径前缀,例如上例<qresource prefix="/plugins/testplug">。
最后呢,调用pyrcc4.exe将resources.qrc文件转换成resources.py文件即可,如下:
pyrcc4 -o resources.py resources.qrc
至此,万事具备,运行下看下结果把。
另附一张归纳图方便查阅:
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/xiluoduyu/article/details/9992179。