撰寫 Python 腳本並在處理框架下執行¶
警告
本教學已有新的版本,請前往 撰寫 Python 腳本並在處理框架下執行(QGIS3)。
獨立的 Pyqgis 程式碼可以從 QGIS 中的 Python 主控台執行,不過只要經過些微調整,它也可以在處理框架中執行。這麼做的好處有兩個,首先因為處理框架提供了標準化的介面,所以指定輸出入檔變容易了;再來就是你可以指定你的腳本成為處理建模中的一部份,這樣一來就可以使用批次執行一次處理許多輸入檔。本教學將示範如何撰寫可以當成 QGIS 中處理框架的一部份的 Python 程式碼。
內容說明¶
Our script will perform a dissolve operation based on a field chosen by the
user. It will also sum up values of another field for the dissolved features.
In the example, we will dissolve a World shapefile based on a SUBREGION
attribute and sum up POP_EST
field to calculate total population in the
dissolved region.
備註
如果你想要執行融合操作並附帶統計輸出,可以使用已經完成的 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()
If you want to give feedback or share your experience with this tutorial, please comment below. (requires GitHub account)