Quantcast
Channel: CSDN博客推荐文章
Viewing all articles
Browse latest Browse all 35570

sextante源码剖析(三)之自定义算法

$
0
0

        在上一篇中介绍了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"):
        tokens[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 GeoAlgorithmfrom sextante.outputs.OutputVector import OutputVectorfrom sextante.parameters.ParameterVector import ParameterVectorfrom 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 osfrom PyQt4 import QtGuifrom sextante.core.SextanteLog import SextanteLogfrom sextante.core.AlgorithmProvider import AlgorithmProviderfrom sextante.preprocess.testalg import testalgclass 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显示】                                                    【算法配置信息】
                                                                                                                      【算法执行前结果】
                                                                                                     【算法执行后结果】





作者:xiluoduyu 发表于2013-8-21 13:54:14 原文链接
阅读:13 评论:0 查看评论

Viewing all articles
Browse latest Browse all 35570

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>