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

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

内容说明

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

注解

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

取得资料

我们要使用 Natural Earth 提供的`Admin 0 - Countries <http://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-admin-0-countries/>`_ 资料集。

下载`Admin 0 - countries shapefile. <http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_countries.zip>`_。

资料来源 [NATURALEARTH]

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

ne_10_admin_0_countries.zip

操作流程

  1. 在 QGIS 中选择:menuselection:Layers –> Add Vector –> Add Vector Layer,选择刚下载的``ne_10_admin_0_countries.zip`` f档案,选择``ne_10_admin_0_countries`` 图层载入。打开:menuselection:Processing –> Toolbox
../_images/1108.png
  1. 在:guilabel:Processing Toolbox`中展开:guilabel:`Scripts`项目,选择 :guilabel:`Create a new script
../_images/260.png
  1. 为了让此脚本能够被电脑辨认为要在处理框架下执行的脚本,在开头的部分必须要指定输入跟输出才行。这个部分的资讯会被用在脚本的使用者介面上,你可以参考`QGIS Processing Documentation <https://docs.qgis.org/2.8/en/docs/user_manual/processing/scripts.html>`_以了解更多有关这部分的格式说明。在:guilabel:Script editor`中输入以下几行,其中有: ``dissolve_layer`, dissolve_field``和``sum_field。注意在后两者中,我们也加入了 dissolve_layer``意味着这两个输入栏位将会从 ``dissolve_layer``中挑选。另外,我们也指定了``output_layer 作为输出向量档。完成后按下:guilabel:Save 钮。
##dissolve_layer=vector
##dissolve_field=field dissolve_layer
##sum_field=field dissolve_layer
##output_layer=output vector
../_images/335.png
  1. 把脚本命名为``dissolve_with_sum`` 然后存至预设的:menuselection:.qgis2 –> processing –> scripts 资料夹路径。
../_images/425.png
  1. 回到:guilabel:Script editor, 按下:guilabel:`Run algorithm`按钮,预览使用者介面。
../_images/526.png
  1. 你可以看到脚本虽然只有几行,就已经具有一个不错的介面,让使用者可以指定输入了。由于其他的地理运算演算法都是使用相同介面,所以就算是自己设计演算法,也可以马上上手。
../_images/624.png
  1. 在:guilabel:Script editor 中输入以下程序代码。你会看到我们其实使用了一些特殊方法,像是``processing.getObject()``和 ``processing.features()``等,这些方法可以帮你更快速的拆解、存取资料,如需更多说明可以参考 QGIS 地理运算文件中的`Additional functions for handling data <https://docs.qgis.org/2.8/en/docs/user_manual/processing/console.html#additional-functions-for-handling-data>`_ 一节的内容。按下 储存 把刚修改的内容存档,然后再使用视窗右上的 :guilabel:`X`钮把编辑器关起来。

注解

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

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/723.png
  1. 在写程序代码的过程中,接收和处理程序产生的错误是非常重要的。地理运算的脚本可以直接在内建的 Python 主控台中除错,只要点选 go to Plugins ‣ Python Console`即可打开它。开启之后,在 :guilabel:`Processing Toolbox 中找到我们的脚本,然后点两下以开始执行程序。
../_images/822.png
  1. 在:guilabel:dissolve field`中选择``SUBREGION`,接下来因为我们还没有在脚本中加入任何有关加总的处理,guilabel:sum field 那边可以随便选择一个栏位。按下:guilabel:Run
../_images/920.png
  1. 你会看到错误讯息出现,不过这是可预期的,因为我们的脚本还没完成,并不会产生任何输出档。
../_images/1021.png
  1. 在 QGIS 主视窗中,可以看到脚本输出到 Python 主控台的讯息。在程序执行时,列印出一些新产生的变数对于除错非常有帮助。
../_images/1125.png
  1. 让我们继续编辑脚本吧。以右键点选脚本,选择:guilabel:Edit script
../_images/1222.png

13. 输入下面的程序代码,就可完成脚本。注意我们使用的是 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/1320.png
  1. 重新启动脚本,在:guilabel:dissolve field`中选择``SUBREGION` guilabel:sum field`选择``POP_EST`,按下:guilabel:Run

注解

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

../_images/1419.png
  1. 处理完成后,可以使用:guilabel:Identify 工具在任一多边形上按一下,你就会看到新的``SUM`` 属性值就是所有原本的多边形的``POP_EST`` 值的总和。
../_images/1518.png
  1. 现在你会看到在输出档中,其他的栏位依旧存在,但因为我们执行的是融合处理,这些栏位代表的值会与新的多边形图征无法对上。因此,我们要再次回到:guilabel:Script editor 中,然后加入以下的程序代码以删除除了``SUM`` 与用来融合的属性栏位名称之外的所有属性栏位。按下 Save 钮,然后关闭视窗。
# 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/1617.png
  1. 处理框架还有一个隐藏功能,那就是我们可以只针对图层中选取的区域做处理,当你只想要处理图层中的某个子集合时非常方便。其原理是我们的脚本使用 processing.features() 来读取图征,但这个方法其实只会读取选取的图征。接下来就让我们来示范看看,首先要建立选取范围,请点选:guilabel:Select features using an expression 钮。
../_images/1716.png
  1. 输入以下的表达式,按下:guilabel:`Select`之后,就可把南北美洲选取起来。
"CONTINENT" = 'North America' OR "CONTINENT" = 'South America'
../_images/1816.png
  1. 你会看到选取的图征已经变成黄色显示。在 dissolve_with_sum 脚本上按右键然后选择:guilabel:Execute
../_images/1914.png
  1. 选择与之前相同的输入参数,然后按下:guilabel:Run
../_images/2011.png
  1. 新的``output layer`` 会加入 QGIS 中,它只包含了在输入图层选取范围中融合的图征。另外,新的 ``output layer``也如预期所示,只含有 2 个栏位。
../_images/2116.png
  1. 最后的工作是要为此演算法添加说明文字(很重要!),处理框架本身提供了良好的工具作为协助。前往 Script editor 然后点选:guilabel:`Edit script help`钮。
../_images/2215.png

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

../_images/2312.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()