撰写 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]
为了方便起见,你也可以直接用下面的连结下载:
操作流程¶
在 QGIS 中选择
,选择刚下载的ne_10_admin_0_countries.zip
档案,选择ne_10_admin_0_countries
图层载入。打开 。
在 地理运算工具箱 中展开 脚本 项目,选择 建立新脚本。
为了让此脚本能够被电脑辨认为要在处理框架下执行的脚本,在开头的部分必须要指定输入跟输出才行。这个部分的资讯会被用在脚本的使用者介面上,你可以参考 QGIS Processing 文件 以了解更多有关这部分的格式说明。在 脚本编辑器 中输入以下几行,其中有 3 个使用者指定的输入:
dissolve_layer
、dissolve_field
和sum_field
。注意在后两者中,我们也加入了dissolve_layer
,意味着这两个输入栏位将会从dissolve_layer
中挑选。另外,我们也指定了output_layer
作为输出向量档。完成后按下 储存 钮。
##dissolve_layer=vector
##dissolve_field=field dissolve_layer
##sum_field=field dissolve_layer
##output_layer=output vector
把脚本命名为
dissolve_with_sum
然后存至预设的 资料夹路径。
回到 脚本编辑器,按下 执行演算法 按钮,预览使用者介面。
你可以看到脚本虽然只有几行,就已经具有一个不错的介面,让使用者可以指定输入了。由于其他的地理运算演算法都是使用相同介面,所以就算是自己设计演算法,也可以马上上手。
在 脚本编辑器 中输入以下程式码。你会看到我们其实使用了一些特殊方法,像是
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
在写程式码的过程中,接收和处理程式产生的错误是非常重要的。地理运算的脚本可以直接在内建的 Python 主控台中除错,只要点选 地理运算工具箱 中找到我们的脚本,然后点两下以开始执行程式。
即可打开它。开启之后,在
在 dissolve field 中选择
SUBREGION
,接下来因为我们还没有在脚本中加入任何有关加总的处理,sum field 那边可以随便选择一个栏位。按下 Run。
你会看到错误讯息出现,不过这是可预期的,因为我们的脚本还没完成,并不会产生任何输出档。
在 QGIS 主视窗中,可以看到脚本输出到 Python 主控台的讯息。在程式执行时,列印出一些新产生的变数对于除错非常有帮助。
让我们继续编辑脚本吧。以右键点选脚本,选择 编辑脚本。
输入下面的程式码,就可完成脚本。注意我们使用的是 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()
重新启动脚本,在 dissolve field 中选择
SUBREGION
,sum field 选择POP_EST
,按下 Run。
注解
处理所花费的时间依照你的作业系统而定,可能要花上 10 分钟。
处理完成后,可以使用 识别图征 工具在任一多边形上按一下,你就会看到新的
SUM
属性值就是所有原本的多边形的POP_EST
值的总和。
现在你会看到在输出档中,其他的栏位依旧存在,但因为我们执行的是融合处理,这些栏位代表的值会与新的多边形图征无法对上。因此,我们要再次回到 脚本编辑器 中,然后加入以下的程式码以删除除了
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()
处理框架还有一个隐藏功能,那就是我们可以只针对图层中选取的区域做处理,当你只想要处理图层中的某个子集合时非常方便。其原理是我们的脚本使用
processing.features()
来读取图征,但这个方法其实只会读取选取的图征。接下来就让我们来示范看看,首先要建立选取范围,请点选 使用表示式选取图征。
输入以下的表达式,按下 选取 之后,就可把南北美洲选取起来,
"CONTINENT" = 'North America' OR "CONTINENT" = 'South America'
你会看到选取的图征已经变成黄色显示。在
dissolve_with_sum
脚本上按右键然后选择 执行。
选择与之前相同的输入参数,然后按下 Run。
新的
output_layer
会加入 QGIS 中,它只包含了在输入图层选取范围中融合的图征。另外,新的output_layer
也如预期所示,只含有 2 个栏位。
最后的工作是要为此演算法添加说明文字(很重要!),处理框架本身提供了良好的工具作为协助。前往 脚本编辑器 然后点选 编辑脚本说明 钮。
填上每个不同元素的说明细节,然后按下 确定。现在这些说明就可以在开启演算法后显示的视窗中的 Help 分页出现,以供所有使用者查阅。
以下放上完整的脚本供各位参考,你也可以依照个人需求编辑调整此档案。
##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()