撰写 Python 脚本并在处理框架下执行

警告

本教学已有新的版本,请前往 编写用于处理框架的Python脚本(QGIS3)

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

内容说明

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

注解

如果你想要执行融合操作并附带统计输出,可以使用已经完成的 Dissolve with stats 这个附加元件。我们要弄的脚本会示范如何使用处理框架的脚本达到类似的功能。

取得资料

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

下载 Admin 0 - 国界(Countries)shapefile

资料来源 [NATURALEARTH]

为了方便起见,你也可以直接用下面的连结下载:

ne_10_admin_0_countries.zip

操作流程

  1. 在 QGIS 中选择 图层 ‣ 加入向量图层,选择刚下载的 ne_10_admin_0_countries.zip 档案,选择 ne_10_admin_0_countries 图层载入。打开 地理运算 ‣ 工具箱

../_images/1183.png
  1. 地理运算工具箱 中展开 脚本 项目,选择 建立新脚本

../_images/2140.png
  1. 为了让此脚本能够被电脑辨认为要在处理框架下执行的脚本,在开头的部分必须要指定输入跟输出才行。这个部分的资讯会被用在脚本的使用者介面上,你可以参考 QGIS Processing 文件 以了解更多有关这部分的格式说明。在 脚本编辑器 中输入以下几行,其中有 3 个使用者指定的输入:dissolve_layerdissolve_fieldsum_field。注意在后两者中,我们也加入了 dissolve_layer,意味着这两个输入栏位将会从 dissolve_layer 中挑选。另外,我们也指定了 output_layer 作为输出向量档。完成后按下 储存 钮。

##dissolve_layer=vector
##dissolve_field=field dissolve_layer
##sum_field=field dissolve_layer
##output_layer=output vector
../_images/378.png
  1. 把脚本命名为 dissolve_with_sum 然后存至预设的 .qgis2 ‣ processing ‣ scripts 资料夹路径。

../_images/456.png
  1. 回到 脚本编辑器,按下 执行演算法 按钮,预览使用者介面。

../_images/557.png
  1. 你可以看到脚本虽然只有几行,就已经具有一个不错的介面,让使用者可以指定输入了。由于其他的地理运算演算法都是使用相同介面,所以就算是自己设计演算法,也可以马上上手。

../_images/654.png
  1. 脚本编辑器 中输入以下程式码。你会看到我们其实使用了一些特殊方法,像是 processing.getObject()processing.features() 等,这些方法可以帮你更快速的拆解、存取资料,如需更多说明可以参考 QGIS 地理运算文件中的 用于处理资料的额外函数 一节的内容。按下 储存 把刚修改的内容存档,然后再使用视窗右上的 X 钮把编辑器关起来。

注解

本脚本大量使用了 Python 的串列综合运算(list comprehensions),如果你对其语法不太熟悉,可参考 串列综合运算的教学

from qgis.core import *
from PyQt4.QtCore import *

inlayer = processing.getObject(dissolve_layer)
dissolve_field_index = inlayer.fieldNameIndex(dissolve_field)
sum_field_index = inlayer.fieldNameIndex(sum_field)

# Find unique values present in the dissolve field
unique_values = set([f[dissolve_field] for f in
processing.features(inlayer)])

print unique_values
../_images/753.png
  1. 在写程式码的过程中,接收和处理程式产生的错误是非常重要的。地理运算的脚本可以直接在内建的 Python 主控台中除错,只要点选 附加元件 ‣ Python 主控台 即可打开它。开启之后,在 地理运算工具箱 中找到我们的脚本,然后点两下以开始执行程式。

../_images/851.png
  1. dissolve field 中选择 SUBREGION,接下来因为我们还没有在脚本中加入任何有关加总的处理,sum field 那边可以随便选择一个栏位。按下 Run

../_images/950.png
  1. 你会看到错误讯息出现,不过这是可预期的,因为我们的脚本还没完成,并不会产生任何输出档。

../_images/1049.png
  1. 在 QGIS 主视窗中,可以看到脚本输出到 Python 主控台的讯息。在程式执行时,列印出一些新产生的变数对于除错非常有帮助。

../_images/1184.png
  1. 让我们继续编辑脚本吧。以右键点选脚本,选择 编辑脚本

../_images/1249.png
  1. 输入下面的程式码,就可完成脚本。注意我们使用的是 QGIS 提供的地理运算库 processing.runalg() 中的「融合」演算法。

# Create a dictionary to hold values from the sum field
sum_unique_values = {}
attrs = [f.attributes() for f in processing.features(inlayer)]

for unique_value in unique_values:
    val_list = [ f_attr[sum_field_index] for f_attr in attrs if f_attr[dissolve_field_index] == unique_value]
    sum_unique_values[unique_value] = sum(val_list)

# Run the regular Dissolve algorithm
processing.runalg("qgis:dissolve", dissolve_layer, "false",
    dissolve_field, output_layer)

# Add a new attribute called 'SUM' in the output layer
outlayer = processing.getObject(output_layer)
provider = outlayer.dataProvider()
provider.addAttributes([QgsField('SUM', QVariant.Double)])
outlayer.updateFields()

# Set the value of the 'SUM' field for each feature
outlayer.startEditing()
new_field_index = outlayer.fieldNameIndex('SUM')
for f in processing.features(outlayer):
  outlayer.changeAttributeValue(f.id(), new_field_index, sum_unique_values[f[dissolve_field]])
outlayer.commitChanges()
../_images/1347.png
  1. 重新启动脚本,在 dissolve field 中选择 SUBREGIONsum field 选择 POP_EST,按下 Run

注解

处理所花费的时间依照你的作业系统而定,可能要花上 10 分钟。

../_images/1446.png
  1. 处理完成后,可以使用 识别图征 工具在任一多边形上按一下,你就会看到新的 SUM 属性值就是所有原本的多边形的 POP_EST 值的总和。

../_images/1544.png
  1. 现在你会看到在输出档中,其他的栏位依旧存在,但因为我们执行的是融合处理,这些栏位代表的值会与新的多边形图征无法对上。因此,我们要再次回到 脚本编辑器 中,然后加入以下的程式码以删除除了 SUM 与用来融合的属性栏位名称之外的所有属性栏位。按下 储存 钮,然后关闭视窗。

# Delete all fields except dissolve field and the newly created 'SUM' field.
outlayer.startEditing()

fields_to_delete = [fid for fid in range(len(provider.fields())) if fid != new_field_index and fid != dissolve_field_index]
provider.deleteAttributes(fields_to_delete)
outlayer.updateFields()

outlayer.commitChanges()
../_images/1640.png
  1. 处理框架还有一个隐藏功能,那就是我们可以只针对图层中选取的区域做处理,当你只想要处理图层中的某个子集合时非常方便。其原理是我们的脚本使用 processing.features() 来读取图征,但这个方法其实只会读取选取的图征。接下来就让我们来示范看看,首先要建立选取范围,请点选 使用表示式选取图征

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

"CONTINENT" = 'North America' OR "CONTINENT" = 'South America'
../_images/1837.png
  1. 你会看到选取的图征已经变成黄色显示。在 dissolve_with_sum 脚本上按右键然后选择 执行

../_images/1931.png
  1. 选择与之前相同的输入参数,然后按下 Run

../_images/2024.png
  1. 新的 output_layer 会加入 QGIS 中,它只包含了在输入图层选取范围中融合的图征。另外,新的 output_layer 也如预期所示,只含有 2 个栏位。

../_images/2141.png
  1. 最后的工作是要为此演算法添加说明文字(很重要!),处理框架本身提供了良好的工具作为协助。前往 脚本编辑器 然后点选 编辑脚本说明 钮。

../_images/2225.png
  1. 填上每个不同元素的说明细节,然后按下 确定。现在这些说明就可以在开启演算法后显示的视窗中的 Help 分页出现,以供所有使用者查阅。

../_images/2322.png

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

##dissolve_layer=vector
##dissolve_field=field dissolve_layer
##sum_field=field dissolve_layer
##output_layer=output vector

from qgis.core import *
from PyQt4.QtCore import *

inlayer = processing.getObject(dissolve_layer)
dissolve_field_index = inlayer.fieldNameIndex(dissolve_field)
sum_field_index = inlayer.fieldNameIndex(sum_field)

# Find unique values present in the dissolve field
unique_values = set([f[dissolve_field] for f in processing.features(inlayer)])

# Create a dictionary to hold values from the sum field
sum_unique_values = {}
attrs = [f.attributes() for f in processing.features(inlayer)]
 
for unique_value in unique_values:
    val_list = [ f_attr[sum_field_index] for f_attr in attrs if f_attr[dissolve_field_index] == unique_value]
    sum_unique_values[unique_value] = sum(val_list)

# Run the regular Dissolve algorithm
processing.runalg("qgis:dissolve", dissolve_layer, "false",
    dissolve_field, output_layer)

# Add a new attribute called 'SUM' in the output layer
outlayer = processing.getObject(output_layer)
provider = outlayer.dataProvider()
provider.addAttributes([QgsField('SUM', QVariant.Double)])
outlayer.updateFields()

# Set the value of the 'SUM' field for each feature
outlayer.startEditing()
new_field_index = outlayer.fieldNameIndex('SUM')
for f in processing.features(outlayer):
  outlayer.changeAttributeValue(f.id(), new_field_index, sum_unique_values[f[dissolve_field]])
outlayer.commitChanges()

# Delete all fields except dissolve field and the newly created 'SUM' field
outlayer.startEditing()
fields_to_delete = [fid for fid in range(len(provider.fields())) if fid != new_field_index and fid != dissolve_field_index]
provider.deleteAttributes(fields_to_delete)
outlayer.updateFields()
outlayer.commitChanges()