撰寫 Python 腳本並在處理框架下執行

獨立的 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/1108.png
  1. 地理運算工具箱 中展開 腳本 項目,選擇 建立新腳本

../_images/260.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/336.png
  1. 把腳本命名為 dissolve_with_sum 然後存至預設的 .qgis2 ‣ processing ‣ scripts 資料夾路徑。

../_images/426.png
  1. 回到 腳本編輯器,按下 執行演算法 按鈕,預覽使用者介面。

../_images/527.png
  1. 你可以看到腳本雖然只有幾行,就已經具有一個不錯的介面,讓使用者可以指定輸入了。由於其他的地理運算演算法都是使用相同介面,所以就算是自己設計演算法,也可以馬上上手。

../_images/625.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/724.png
  1. 在寫程式碼的過程中,接收和處理程式產生的錯誤是非常重要的。地理運算的腳本可以直接在內建的 Python 主控台中除錯,只要點選 附加元件 ‣ Python 主控台 即可打開它。開啟之後,在 地理運算工具箱 中找到我們的腳本,然後點兩下以開始執行程式。

../_images/823.png
  1. dissolve field 中選擇 SUBREGION,接下來因為我們還沒有在腳本中加入任何有關加總的處理,sum field 那邊可以隨便選擇一個欄位。按下 Run

../_images/921.png
  1. 你會看到錯誤訊息出現,不過這是可預期的,因為我們的腳本還沒完成,並不會產生任何輸出檔。

../_images/1022.png
  1. 在 QGIS 主視窗中,可以看到腳本輸出到 Python 主控台的訊息。在程式執行時,列印出一些新產生的變數對於除錯非常有幫助。

../_images/1125.png
  1. 讓我們繼續編輯腳本吧。以右鍵點選腳本,選擇 編輯腳本

../_images/1223.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/1320.png
  1. 重新啟動腳本,在 dissolve field 中選擇 SUBREGIONsum field 選擇 POP_EST,按下 Run

註解

處理所花費的時間依照你的作業系統而定,可能要花上 10 分鐘。

../_images/1419.png
  1. 處理完成後,可以使用 識別圖徵 工具在任一多邊形上按一下,你就會看到新的 SUM 屬性值就是所有原本的多邊形的 POP_EST 值的總和。

../_images/1518.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/1617.png
  1. 處理框架還有一個隱藏功能,那就是我們可以只針對圖層中選取的區域做處理,當你只想要處理圖層中的某個子集合時非常方便。其原理是我們的腳本使用 processing.features() 來讀取圖徵,但這個方法其實只會讀取選取的圖徵。接下來就讓我們來示範看看,首先要建立選取範圍,請點選 使用表示式選取圖徵

../_images/1716.png
  1. 輸入以下的表達式,按下 選取 之後,就可把南北美洲選取起來,

"CONTINENT" = 'North America' OR "CONTINENT" = 'South America'
../_images/1816.png
  1. 你會看到選取的圖徵已經變成黃色顯示。在 dissolve_with_sum 腳本上按右鍵然後選擇 執行

../_images/1914.png
  1. 選擇與之前相同的輸入參數,然後按下 Run

../_images/2011.png
  1. 新的 output_layer 會加入 QGIS 中,它只包含了在輸入圖層選取範圍中融合的圖徵。另外,新的 output_layer 也如預期所示,只含有 2 個欄位。

../_images/2116.png
  1. 最後的工作是要為此演算法添加說明文字(很重要!),處理框架本身提供了良好的工具作為協助。前往 腳本編輯器 然後點選 編輯腳本說明 鈕。

../_images/2215.png
  1. 填上每個不同元素的說明細節,然後按下 確定。現在這些說明就可以在開啟演算法後顯示的視窗中的 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()
comments powered by Disqus

This work is licensed under a Creative Commons Attribution 4.0 International License