本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/xiluoduyu/。
在上一篇中介紹了sextante的架構,這次主要介紹如何在sextante中自定義算法。sextante自定義算法途徑有二:1)編寫腳本文件;2)編寫算法類。
編寫腳本文件
sextante的腳本文件有兩種:1)Script腳本,2)R腳本。兩種腳本差別蠻大,前者的編寫只要懂pyqt即可,而後者還得對R(一款強大的科學統計軟件,據說畫圖比SPSS更帥)的代碼編寫有一定的熟悉程度。平時一般用SPSS,R用的不多,因而在此主要講述如何通過編寫Script腳本自定義算法。sextante提供了script腳本和R腳本樣例,在toolbox中右鍵選擇編輯腳本即可查看腳本源碼,如[Example ]下的“Save selected features ”,該算法用於將矢量文件中選擇的要素另存爲矢量文件。首先導入需要用到的類,如下:
from qgis.core import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from sextante.core.QGisLayers import QGisLayers
from sextante.core.SextanteVectorWriter import SextanteVectorWriter
然後定義輸入輸出參數,如下:##[Example scripts]=group
##input=vector
##output=output vector
雖然該算法中先導入所需文件再定義參數和輸出,實際上這兩者的順序如何毫不影響。腳本文件中定義輸入參數和輸出有一定的規則,正如前篇所言,也即被模板化了,或者說對象化了,每一種輸出都是一種對象。具體參照QGIS 幫助文檔Sextante部分。參數定義如下:
## [參數名] = [參數類型] [默認值/初始化值]
其中,“##”爲輸入參數或輸出標誌符,參數類型有:
- raster,柵格文件;
- vector,矢量文件;
- table,表單;
- number,數字,必須提供默認值,例如dept =number 2.4;
- string,字符串,必須提供默認值,例如name=string xiluoduyu;
- boolean,布爾值,可選True或False,例如,verbose=boolean True,默認爲False;
- multiple raster,柵格數據集合,即可以同時選擇輸入多個柵格文件;
- multiple vector,矢量數據集合,即可以同時選擇輸入多個矢量文件;
- field,矢量數據屬性中的字段,在field後面必須跟着矢量文件名,例如若在前面有選擇輸入矢量文件:myLayer=vector,則可通過myfield=field myLayer來選擇矢量數據中的要素;
- folder,文件夾;
- file,文件名。
爲了直觀,sextante中顯示算法參數時用空格取代下劃線,源碼如下:
def createDescriptiveName(self, s):
return s.replace("_", " ")
因此,如果你想用戶看到類似A numeric value的參數的話,可選擇A_numeric_value作爲參數名。通過定義上述參數方式獲取的參數值中例如Layer、Table,其值其實只是文件對應的路徑而已,因而必須通過Sextante.getObjectFromUri()將其轉化爲QGIS對象;多文件輸入時,每個文件由分號隔開。
輸出定義與輸入參數的定義方式類似,只是沒有默認或初始化值,輸出定義如下:
##[輸出名] = [輸出類型]
其中,“##”意義同上,輸出類型有:
- output raster,輸出柵格文件;
- output vector,輸出矢量文件;
- output table,輸出表格文件,可用於輸出矢量數據屬性;
- output html,輸出html文件,在生成幫助等信息時可用;
- output file,輸出文件,格式不定,具體用法未明;
- output number,輸出數字;
- output string,輸出字符串,最直觀的就是用來顯示執行信息。
定義完畢輸入參數和輸出後即可進行算法的執行,實例代碼如下:
#首先獲取輸入的矢量文件。輸入的文件input只是文件對應的路徑,
#因而需要通過Sextante.getObjectFromUri()將input轉換爲QGIS矢量圖層對象.
#實際上Sextante.getObjectFromUri()只是簡單調用了QGisLayers.getObjectFromUri(input)而已。
vectorLayer = QGisLayers.getObjectFromUri(input)
#接着創建輸出文件,可通過SextanteVectorWriter對象來進行,該對象實爲模板化的矢量輸出類
provider = vectorLayer.dataProvider()
writer = SextanteVectorWriter(output, None, provider.fields(), provider.geometryType(), provider.crs() )
#然後將選擇的字段添加到輸出文件
selection = vectorLayer.selectedFeatures()
for feat in selection:
writer.addFeature(feat)
del writer
通過腳本文件創建的算法會自動加載到QGIS裏面去,在modeler中腳本算法也會得到相應的處理。sextante的開發者們對這已經做了足夠多的工作讓它好用,不用我們操心。腳本算法執行的效果如下:
sextante官網沒有提供漢化版,圖中漢化內容是我自己最新漢化的,完整的漢化版本在我的資源裏面可以下載。
必須注意的是有時候在sextante中選擇本地文件再執行算法可能執行不成功,若遇到這種情況可選擇先在QGIS中加載影像再選擇QGIS中已打開的影像執行,這應該是sextante1.0.9版本的bug,其他版本沒測試過。sextante中的script腳本編輯器不具備代碼識別和自動縮進功能,所以最好簡單的算法用腳本語言,可以先用其他編輯器如IDLE編寫好,然後複製到script編輯面板中去;而複雜一點的算法寫算法類完成。定義參數和輸出時最好嚴格按照給定的格式進行比較編寫,這是因爲sextante的腳本算法類定義在代碼裏默認嚴格按上述格式編寫腳本代碼,例如“##num=number
2”寫成“## num = number 2”就會出錯。當然你完全可以改動源碼以適應你的需求,比如我爲了解決它在定義數值類型參數時“##”後和參數名後不能帶空格的限制,我直接把源碼改了,調試沒問題,改動代碼如下:
def processParameterLine(self,line):
param = None
out = None
line = line.replace("#", "");
...
tokens = line.split("=");
desc = self.createDescriptiveName(tokens[0])
if tokens[1].lower().strip() == "group":
self.group = tokens[0]
return
...
elif tokens[1].lower().strip().startswith("number"):
tolens[0] = tokens[0].strip() #新增語句,修改是爲了提出參數部分不必要的空格,解決定義數字參數執行時提示變量未定義的錯誤問題。
default = tokens[1].strip()[len("number")+1:].strip()
param = ParameterNumber(tokens[0], desc, default=default)#tokens[0]爲參數名,desc爲描述信息,用於顯示
return
腳本算法的執行過程也是首先生成算法調用命令行字符串,然後調用Python的exec()函數執行有效的script腳本代碼,也即是說咱們編寫的腳本代碼會被當做一行行的python代碼進行處理,與平時編寫正常的python代碼一樣,不同之處在於腳本代碼中的未定義的參數的值是從“參數字典”這一全局名字空間中獲取,源碼如下:def processAlgorithm(self, progress):
script = "import sextante\n" #導入sextante文件
#------------生成算法調用命令行字符串--------------#
ns = {}
ns['progress'] = progress
for param in self.parameters:
#script += param.name + "=" + param.getValueAsCommandLineParameter() + "\n"
ns[param.name] = param.value
for out in self.outputs:
ns[out.name] = out.value
#script += out.name + "=" + out.getValueAsCommandLineParameter() + "\n"
script+=self.script
#-----------------執行算法----------------#
exec(script) in ns #將參數字典作爲全局名字空間
#將輸出結果保存到算法的輸出列表中,這樣後面sextante即可自動將算法輸出結果加載到QGIS中
for out in self.outputs:
out.setValue(ns[out.name])
編寫算法類
編寫算法類自定義算法,首先得繼承GeoAlgorithm基類進行定義,然後定義對應的算法提供者基類AlgorithmProvider的派生類,也可以不定義AlgorithmProvider派生類而只是將算法分到已有的provider中去,但此時得注意算法類中的provider屬性必須與其所屬的provider的名稱name對應,否則會有問題。爲了介紹完整的自定義算法類流程,雖然只是定義了一個算法類,我仍然爲其定義了對應的provider類。完整的自定義算法需要定義幾個必要的文件:- __init__.py,定義該文件主要是爲了進行provider加載前的某些操作,但sextante中一般第三方應用程序算法提供者,如OTB、SAGA、Grass等均只是簡單的定義了該文件而沒有實現任何內容。這也說明,該文件只是sextante的硬性規定,相當於協議而已,目前實際作用不明顯;
- [算法類名(Algeorithm)].py,該文件定義了算法類及其參數等屬性;
- [算法提供者(AlgorithmProvider)].py,該文件定義了算法提供者類及其相關屬性;
- CMakeList.txt,該文件聲明需要編譯那些文件;
首先在sextante目錄下新建文件夾preprocess,文件夾中定義上述文件。每個文件的具體內容如下:
__init__.py
保留空白。
CmakeList.txt
FILE(GLOB PY_FILES *.py)
FILE(GLOB DESCR_FILES description/*.txt)
FILE(GLOB HELP_FILES help/*.html)
PLUGIN_INSTALL(sextante preprocess ${PY_FILES})
PLUGIN_INSTALL(sextante preprocess/description ${DESCR_FILES})
PLUGIN_INSTALL(sextante preprocess/help ${HELP_FILES})
testalg.py (算法基類派生類文件)
因爲只是爲了介紹自定義算法流程,因而在該算法中我並沒有實現自己的算法,只是簡單的將sextante中的算法“Save selected features”進行簡單的修改套用而已。不過,這也是一種非常好的學習方法,就簡單的情況而言,直接參考別人的代碼往往比看別人的文章來的直接快捷。
# -*- coding: utf-8 -*-
'''描述信息,省略...'''
from sextante.core.GeoAlgorithm import GeoAlgorithm
from sextante.outputs.OutputVector import OutputVector
from sextante.parameters.ParameterVector import ParameterVector
from qgis.core import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from sextante.core.QGisLayers import QGisLayers
#自定義算法案例
class testalg(GeoAlgorithm):
'''這是個算法樣例,算法實現的是從矢量數據中提取感興趣的要素。該算法樣例的目的主要是向開發者說明在sextante中自定義算法的流程, 開發者可以參照該算法 樣例定義自己的算法,同時你可以在sextante中像使用其他算法一樣使用自己定義的算法,不需要做任何額外的工作。'''
#輸入參數和輸出的常量引用,在算法被第三方算法或QGIS python控制檯調用時會用到。
OUTPUT_LAYER = "OUTPUT_LAYER"
INPUT_LAYER = "INPUT_LAYER"
#************************************************************************#
# 必須定義的兩個函數: #
# 1)defineCharacteristics(self),該函數用於設置算法參數等屬性 #
# 2)processALgorithm(self,progress),該函數定義算法的執行 #
#************************************************************************#
def defineCharacteristics(self):
'''在此定義算法的輸入輸出以及其他屬性'''
#在Toolbox算法樹節點顯示的算法名
self.name = "Save selected features"
#Toolbox算法樹中子目錄名(組名)
self.group = unicode("測試算法",'utf-8')
#添加矢量文件輸入參數,在此因爲該參數是必須的,也即對用戶是可見的,因而最後一個參數必須設爲False,即hiden=False
self.addParameter(ParameterVector(self.INPUT_LAYER, "Input layer", ParameterVector.VECTOR_TYPE_ANY, False))
# 添加矢量文件輸出
self.addOutput(OutputVector(self.OUTPUT_LAYER, "Output layer with selected features"))
def processAlgorithm(self, progress):
'''在此定義算法的執行過程'''
#首先獲取用戶的輸入內容
inputFilename = self.getParameterValue(self.INPUT_LAYER)
output = self.getOutputFromName(self.OUTPUT_LAYER)
#將輸入的文件路徑轉換爲QGIS對象
vectorLayer = QGisLayers.getObjectFromUri(inputFilename)
#開始執行 #創建輸出矢量文件
provider = vectorLayer.dataProvider()
writer = output.getVectorWriter( provider.fields(), provider.geometryType(), provider.crs() )
#獲取選擇的矢量要素數據並添加到結果矢量文件中
features = QGisLayers.features(vectorLayer)
total = len(features)
i = 0
for feat in features:
writer.addFeature(feat)
progress.setPercentage(100 * i / float(total))
i += 1
del writer
PreprocessAlgorithmProvider.py(算法提供者基類派生類文件)
# -*- coding: utf-8 -*-
'''描述信息,省略...'''
import os
from PyQt4 import QtGui
from sextante.core.SextanteLog import SextanteLog
from sextante.core.AlgorithmProvider import AlgorithmProvider
from sextante.preprocess.testalg import testalg
class PreprocessAlgorithmProvider(AlgorithmProvider):
"""必須重定義以下函數以提供算法提供者派生類的相關信息"""
#初始化函數,類似C++中的構造函數
def __init__(self):
AlgorithmProvider.__init__(self)
self.active = False
#可在此設置在配置窗口中顯示的算法提供者的相關設置,例如OTB、SAGA的文件夾路徑等,默認添加“是否激活”設置,
#即配置窗口目錄樹下Active一項,在此僅添加默認設置。
def initializeSettings(self):
AlgorithmProvider.initializeSettings(self)
#卸載算法提供者函數,類似C++中的析構函數
def unload(self):
AlgorithmProvider.unload(self)
#設置/獲取算法提供者名稱
def getName(self):
return "preprocess"
#設置/獲取算法提供者在Toolbox算法目錄樹中的節點名稱
def getDescription(self):
return unicode("自定義預處理算法",'utf-8')
#設置對應的算法樹節點圖標
def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/preprocess.png")
#加載算法提供者下屬算法,在此添加自定義的算法testalg
def _loadAlgorithms(self):
self.alglist = [testalg()]
self.algs = self.alglist
#設置/獲取是否支持非文件類型的結果輸出
def supportsNonFileBasedOutput(self):
return True
進行到這裏之後我們還需要進行最後的一步操作,即將PreprocessAlgorithmProvider添加到core文件夾下Sextante.py文件中Sextante類的providers列表中去,過程如下:1、在文件開頭引入自定義provider文件:
from sextante.preprocess.PreprocessAlgorithmProvider import PreprocessAlgorithmProvider
2、在initialize函數中添加provider:Sextante.addProvider(PreprocessAlgorithmProvider())
至此,大功告成!結果截圖如下:
【Toolbox顯示】 【算法配置信息】
【算法執行前結果】
【算法執行後結果】