在QGIS下開發python插件

本文來自CSDN博客,轉載請標明出處http://blog.csdn.net/xiluoduyu/

出於研究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
至此,萬事具備,運行下看下結果把。


另附一張歸納圖方便查閱:


 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章