sextante源碼剖析(三)之自定義算法

        本文來自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

以上定義的幾個文件中的函數都是自定義算法流程中不可缺少的,除此之外,我們當然還可以自定義其他函數或者重載基類中可以被重載的函數,具體請參考core文件夾下GeoAlgorithm.py文件中GeoAlgorithm基類的定義。

       進行到這裏之後我們還需要進行最後的一步操作,即將PreprocessAlgorithmProvider添加到core文件夾下Sextante.py文件中Sextante類的providers列表中去,過程如下:1、在文件開頭引入自定義provider文件:
from sextante.preprocess.PreprocessAlgorithmProvider import PreprocessAlgorithmProvider
2、在initialize函數中添加provider:
Sextante.addProvider(PreprocessAlgorithmProvider())
至此,大功告成!
結果截圖如下:
            
                                   【Toolbox顯示】                                                    【算法配置信息】
                                          【算法執行前結果】
                                             【算法執行後結果】





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