编写用于处理框架的Python脚本(QGIS3)

独立的 Pyqgis 程式码可以从 QGIS 中的 Python 主控台执行,不过只要经过些微调整,它也可以在处理框架中执行。这么做的好处有两个,首先因为处理框架提供了标准化的介面,所以指定输出入档变容易了;再来就是你可以指定你的脚本成为处理建模中的一部份,这样一来就可以使用批次执行一次处理许多输入档。本教学将示范如何撰写可以当成 QGIS 中处理框架的一部份的 Python 程式码。

注解

在QGIS3中彻底修改了Processing API。请 参阅本指南 了解最佳做法和提示。

内容说明

本脚本会依照使用者挑选的属性,进行融合(Dissolve)操作;除此之外,还会把所有被融合的图征的另一个属性值进行加总。在本例中,我们要依照全球范围的 shapefile 的 CONTINENT 属性进行融合,然后依照 POP_EST 属性加总估计融合后的新区域的总人口。

取得资料

我们要使用 Natural Earth 提供的 Admin 0 - 国界 资料集。

下载 Admin 0 - 国界(Countries)shapefile

资料来源 [NATURALEARTH]

为了方便起见,你也可以直接用下面的连结下载包含此图层的地理数据集:

ne_global.gpkg

操作流程

  1. 在QGIS浏览器面板中,找到保存下载数据的目录。展开 zipgpkg 条目,然后选择 ne_10m_admin_0_countries 层。将图层拖到画布上。

../../_images/1108.png
  1. 转到 Processing ‣ Toolbox。单击工具栏上的 脚本 按钮,然后选择 Create New Script from Template

../../_images/268.png
  1. 该模板包含处理框架将其识别为处理脚本并管理输入/输出所需的所有样板代码。让我们开始根据需要定制示例模板。首先将类名称从 ExampleProcessingAlgorithm 更改为 DissolveProcessingAlgorithm 。此名称也需要在 createInstance 方法中进行更新。在该类中添加一个文档字符串,以说明算法的作用。

../../_images/339.png
  1. 向下滚动时,您将看到为脚本分配名称,组,描述等的方法。 将 name 方法的返回值更改为 dissolve_with_sum,将 displayName 方法更改为 Sum溶解,将 groupgroup 方法和 groupId 方法更改为scripts。将 shortHelpString 方法的返回值更改为将显示给用户的描述。点击 Save 按钮。

../../_images/424.png
  1. 将脚本命名为 dissolve_with_sum 并将其保存在 profiles ‣ default ‣ processing ‣ scripts folder默认位置。

../../_images/524.png
  1. 现在,我们将定义脚本的输入。该模板已经包含 输入 矢量层和 输出 层的定义。我们将添加2个新输入,允许用户选择 DISSOLVE_FIELDSUM_FIELD 。在 initAlgorithm 方法的顶部和下面的代码中添加一个新的导入。单击 Run 按钮以预览更改。

from qgis.core import QgsProcessingParameterField

self.addParameter(
        QgsProcessingParameterField(
                self.DISSOLVE_FIELD,
                'Choose Dissolve Field',
                '',
                self.INPUT))

self.addParameter(
        QgsProcessingParameterField(
                self.SUM_FIELD,
                'Choose Sum Field',
                '',
                self.INPUT))
../../_images/6a.png ../../_images/6b.png
  1. 您将看到一个带有我们新定义的输入的 Dissum with Sum 对话框。选择 ne_10m_admin_0_countries 层作为 Input layer。由于 Dissolve FieldSum Fields 都是基于输入层进行过滤的,因此它们将被输入层中的现有字段预先填充。点击 Close 按钮。

../../_images/723.png
  1. 现在,我们在 processAlgorithm 方法中定义用于处理数据的自定义逻辑。该方法通过了一个名为 parameters 的字典。它包含用户已选择的输入。有一些帮助程序方法,使您可以接受这些输入并创建适当的对象。我们首先使用 parameterAsSourceparameterAsString 方法获取输入。接下来,我们要创建一个特征接收器,在其中写入输出。QGIS3有一个名为 QgsFeatureSink 的新类,它是创建可以接受新功能的对象的首选方法。输出仅需要2个字段-一个用于溶解字段的值,另一个用于所选字段的总和。

from PyQt5.QtCore import QVariant
from qgis.core import QgsField, QgsFields

source = self.parameterAsSource(
        parameters,
        self.INPUT,
        context)
dissolve_field = self.parameterAsString(
        parameters,
        self.DISSOLVE_FIELD,
        context)
sum_field = self.parameterAsString(
        parameters,
        self.SUM_FIELD,
        context)

fields = QgsFields()
fields.append(QgsField(dissolve_field, QVariant.String))
fields.append(QgsField('SUM_' + sum_field, QVariant.Double))

(sink, dest_id) = self.parameterAsSink(
        parameters,
        self.OUTPUT,
        context, fields, source.wkbType(), source.sourceCrs())
../../_images/8a.png ../../_images/8b.png
  1. 现在,我们将准备输入功能,并创建一个字典来保存dissolve_field中的唯一值和sum_field中的值之和。注意使用 feedback.pushInfo() 方法与用户进行状态通信。

feedback.pushInfo('Extracting unique values from dissolve_field and computing sum')

features = source.getFeatures()
unique_values = set(f[dissolve_field] for f in features)

# Get Indices of dissolve field and sum field
dissolveIdx = source.fields().indexFromName(dissolve_field)
sumIdx = source.fields().indexFromName(sum_field)

# Find all unique values for the given dissolve_field and
# sum the corresponding values from the sum_field
sum_unique_values = {}
attrs = [{dissolve_field: f[dissolveIdx], sum_field: f[sumIdx]} for f in source.getFeatures()]
for unique_value in unique_values:
        val_list = [ f_attr[sum_field] for f_attr in attrs if f_attr[dissolve_field] == unique_value]
        sum_unique_values[unique_value] = sum(val_list)
../../_images/923.png
  1. 接下来,我们将在输入层上将内置处理算法称为 native:dissolve 以生成溶解的几何体。一旦有了可溶的几何形状,我们便会遍历可溶算法的输出,并创建要添加到输出中的新特征。最后,我们返回 dest_id FeatureSink作为输出。现在脚本已准备就绪。点击 Run 按钮。

注解

注意使用 parameters [self.INPUT] 直接从参数字典中获取输入层,而没有将其定义为源。由于我们无需对输入对象进行任何处理就将其传递给算法,因此不必将其定义为源。

from qgis.core import QgsFeature

# Running the processing dissolve algorithm
feedback.pushInfo('Dissolving features')
dissolved_layer = processing.run("native:dissolve", {
        'INPUT': parameters[self.INPUT],
        'FIELD': dissolve_field,
        'OUTPUT': 'memory:'
        }, context=context, feedback=feedback)['OUTPUT']

# Read the dissolved layer and create output features
for f in dissolved_layer.getFeatures():
        new_feature =  QgsFeature()
        # Set geometry to dissolved geometry
        new_feature.setGeometry(f.geometry())
        # Set attributes from sum_unique_values dictionary that we had computed
        new_feature.setAttributes([f[dissolve_field], sum_unique_values[f[dissolve_field]]])
        sink.addFeature(new_feature, QgsFeatureSink.FastInsert)

return {self.OUTPUT: dest_id}
../../_images/10a.png ../../_images/10b.png
  1. Dissolve with Sum 对话框中,选择 ne_10m_admin_0_countries 作为 Input layer,以 CONTINENT 作为 Dissolve field 并且 POP_EST 作为 Sum field 字段。点击 Run

../../_images/1126.png
  1. 处理完成后,单击 Close 按钮,然后切换到QGIS主窗口。

../../_images/1223.png
  1. 您将看到每个大陆具有一个要素的已分解输出层,以及该大陆各个国家的总人口合计。

../../_images/1322.png
  1. 编写处理脚本的另一个优点是,处理框架中的方法知道图层选择,并自动过滤输入以仅使用所选功能。发生这种情况是因为我们将输入定义为 QgsProcessingParameterFeatureSource 。特征源允许使用包含向量特征的任何对象,而不仅仅是向量层,因此当层中有选定特征并要求Processing使用选定特征时,输入将作为 QgsProcessingFeatureSource 传递到脚本中 包含所选要素而不是完整矢量层的对象。这是此功能的快速演示。假设我们只想溶解某些大洲。让我们使用表达式工具的 Select feature by Expression 来创建选择。

../../_images/1421.png
  1. 输入以下的表达式,按下 Select 之后,就可把南北美洲选取起来。

"CONTINENT" = 'North America' OR "CONTINENT" = 'South America'
../../_images/1521.png
  1. 您将看到以黄色突出显示的所选功能。找到 dissolve_with_sum 脚本并双击运行它。

../../_images/1619.png
  1. Dissolve with Sum 对话框中,选择 ne_10m_admin_0_countries 作为 Input layer。 这次,请确保选中 Selected features only 框。 选择SUBSUBION作为Dissolve字段,选择 POP_EST 作为 Sum field

../../_images/1720.png
  1. 处理完成后,单击 Close,然后切换回QGIS主窗口。您会注意到一个新图层,其中只有选定的特征被分解。单击 Identify 按钮,然后单击一项功能以检查并验证脚本是否正常运行。

../../_images/1819.png

以下放上完整的脚本供各位参考,你也可以依照个人需求编辑调整此档案。

# -*- coding: utf-8 -*-

"""
***************************************************************************
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************
"""

from PyQt5.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsFeature,
                       QgsField,
                       QgsFields,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterField,
                       )
import processing


class DissolveProcessingAlgorithm(QgsProcessingAlgorithm):
    """
    Dissolve algorithm that dissolves features based on selected
    attribute and summarizes the selected field by cumputing the
    sum of dissolved features.
    """
    INPUT = 'INPUT'
    OUTPUT = 'OUTPUT'
    DISSOLVE_FIELD = 'dissolve_field'
    SUM_FIELD = 'sum_field'

    def tr(self, string):
        """
        Returns a translatable string with the self.tr() function.
        """
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return DissolveProcessingAlgorithm()

    def name(self):
        """
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'dissolve_with_sum'

    def displayName(self):
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return self.tr('Dissolve with Sum')

    def group(self):
        """
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        """
        return self.tr('scripts')

    def groupId(self):
        """
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'scripts'

    def shortHelpString(self):
        """
        Returns a localised short helper string for the algorithm. This string
        should provide a basic description about what the algorithm does and the
        parameters and outputs associated with it..
        """
        return self.tr("Dissolves selected features and creates and sums values of features that were dissolved")

    def initAlgorithm(self, config=None):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """
        # We add the input vector features source. It can have any kind of
        # geometry.
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT,
                self.tr('Input layer'),
                [QgsProcessing.TypeVectorAnyGeometry]
            )
        )
        self.addParameter(
            QgsProcessingParameterField(
                self.DISSOLVE_FIELD,
                'Choose Dissolve Field',
                '',
                self.INPUT))
        self.addParameter(
            QgsProcessingParameterField(
                self.SUM_FIELD,
                'Choose Sum Field',
                '',
                self.INPUT))
        # We add a feature sink in which to store our processed features (this
        # usually takes the form of a newly created vector layer when the
        # algorithm is run in QGIS).
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Output layer')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        source = self.parameterAsSource(
            parameters,
            self.INPUT,
            context
        )
        dissolve_field = self.parameterAsString(
            parameters,
            self.DISSOLVE_FIELD,
            context)
        sum_field = self.parameterAsString(
            parameters,
            self.SUM_FIELD,
            context)
        
        fields = QgsFields()
        fields.append(QgsField(dissolve_field, QVariant.String))
        fields.append(QgsField('SUM_' + sum_field, QVariant.Double))
        
        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context, fields, source.wkbType(), source.sourceCrs())
        
        # Create a dictionary to hold the unique values from the 
        # dissolve_field and the sum of the values from the sum_field
        feedback.pushInfo('Extracting unique values from dissolve_field and computing sum')
        features = source.getFeatures()
        unique_values = set(f[dissolve_field] for f in features)
        # Get Indices of dissolve field and sum field
        dissolveIdx = source.fields().indexFromName(dissolve_field)
        sumIdx = source.fields().indexFromName(sum_field)
        
        # Find all unique values for the given dissolve_field and
        # sum the corresponding values from the sum_field
        sum_unique_values = {}
        attrs = [{dissolve_field: f[dissolveIdx], sum_field: f[sumIdx]}
                for f in source.getFeatures()]
        for unique_value in unique_values:
            val_list = [ f_attr[sum_field] 
                for f_attr in attrs if f_attr[dissolve_field] == unique_value]
            sum_unique_values[unique_value] = sum(val_list)
        
        # Running the processing dissolve algorithm
        feedback.pushInfo('Dissolving features')
        dissolved_layer = processing.run("native:dissolve", {
            'INPUT': parameters[self.INPUT],
            'FIELD': dissolve_field,
            'OUTPUT': 'memory:'
        }, context=context, feedback=feedback)['OUTPUT']
        
        # Read the dissolved layer and create output features
        for f in dissolved_layer.getFeatures():
            new_feature =  QgsFeature()
            # Set geometry to dissolved geometry
            new_feature.setGeometry(f.geometry())
            # Set attributes from sum_unique_values dictionary that we had computed
            new_feature.setAttributes([f[dissolve_field], sum_unique_values[f[dissolve_field]]])
            sink.addFeature(new_feature, QgsFeatureSink.FastInsert)
        
        return {self.OUTPUT: dest_id}