在QGIS下开发python插件

出于研究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步:

  1. 点子。想清楚自己到底要干嘛。为什么要这样做?有没有必要这样做?想好了再动手吧,别弄了半天才发现根本就没那必要或者早就有人把你的需求给实现和共享出来了,那你就悲剧了,劳神费力,伤财伤精啊。
  2. 创建必要的文件。首先,创建初始化文件__init__.py文件,用来设置插件加载前的某些属性值。其次,创建插件主体文件plugin.py,你所有的插件操作将会在此文件中完成。最后,用QT-Designer设计插件界面,生成界面文件form.ui,伴随ui文件一般会有一份资源文件生成,即resources.qrc。
  3. 在plugin.py中填写必要的代码。下面会详细阐述。
  4. 测试。重启QGIS看能不能将插件加载进来并顺利运行。
  5. 发布你的插件。这一步其实看你心情咯,爱发布就发布,但若要发布千万得记住:别写的太烂!毕竟有可能比人会用到你的插件。程序员要则:不接受坑爹,但也别坑人。

插件编写

自从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

其中:

  • __init__.py,是插件调用的起点,一般例如版本号、插件名、插件主类等信息会在这里面定义;
  • plugin.py,插件主体啦,所有的插件操作都必须在这里完成,也是写代码最多的部分;
  • resources.qrc,QT-Designer创建的XML文档,包含了界面资源的相对路径;
  • resources.py,利用pyrcc4.bat转换resources.qrc所得;
  • form.ui,QT-Designer创建的界面文件;
  • form.py,利用pyucc4.bat转换form.ui所得;
  • meradata.txt,QGIS>=1.8.0版本要求提供,是插件元数据,描述了插件的基本信息,如版本号,插件名和其他一些插件网址,框架信息等。在__init__.py文件中会用到,用来获取上述插件相关信息。然而从QGIS2.0开始,所有的插件元数据信息只能在metadata.txt中设置了,在__init__.py中的设置将被视为无效。

        若嫌手动创建这些插件框架文件太繁琐,这里有两种方案你可以选择:1)http://hub.qgis.org/projects/plugin-builder2)http://www.dimitrisk.gr/qgis/creator/。此外,你也可以下载一款叫Plugin Builder的离线插件来帮助你创建插件模板。在开始编写自己的插件之前,建议最好还是找个典型的插件案例来研究一下吧,例如sextante啦。

编写代码

__init__.py

 

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”菜单栏下显示你的插件,参照下面的代码:

__init__.py:

def category():
    return "Raster"

metadata.txt

在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
...

plugin.py

在该类中定义插件操作。值得提醒的是该类中的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中列举的函数。

Resources File

在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




原文地址:https://www.cnblogs.com/bbsno1/p/3263255.html