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显示】                                                    【算法配置信息】
                                          【算法执行前结果】
                                             【算法执行后结果】





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值