# QGIS python 程式碼編寫(Pyqgis)
### 前言
python腳本除一般的運行模式外,也可以編寫成套件,不管是串接不同的演算法(如迭代演算法),或是自己編寫完整演算法,都大大增加了計算的彈性,與未來應用在其他專案的擴充性。但是與python腳本相關的教學甚少,故有此編。
*Python Qt design又是另外一個東西,如果是想要學Qt相關的,就可以先不用繼續看下去啦。
## 先備知識
1. pyqgis基本操作: 可以參考官方的[vector](https://docs.qgis.org/3.34/en/docs/pyqgis_developer_cookbook/vector.html)與[raster](https://docs.qgis.org/3.34/en/docs/pyqgis_developer_cookbook/raster.html)基本介紹
2. 類別、繼承與物件導向(Class, Inheritance and Object Oriented)
- 套件的編寫基本上是透過繼承QgsProcessingAlgorithm父類來進行操作
- 故基本的類別與繼承行為需要有基本的認識
- pyqgis使用到物件導向概念,需要解析每一個物件的方法(method)與屬性(attribute) (但是官方說明文件中沒有清楚交代)
3. 圖形化套件的編寫(graphic modeler)
- 圖形化套件實現了串接多項不同的演算法,簡化分析工作。
- 可以先用圖形化套件建立初步算法串接流程,再產出腳本進行編輯。
- **通常大部分的工作都可以透過graphic modeler進行分析**,只有在進行比較複雜的運算時,如:調用圖徽屬性進行運算(當演算法不支援時)、資料爬蟲(個人認為實用)等,便需要透過python script來進行分析。
-
## 基本架構
為了讓編寫的程式碼可以被未來不一樣的專案作利用,編寫成"視窗套件"應該最佳解!
視窗套件以類別為主要架構(通常先使用modeler建立input與基本運算可以省下大筆時間):
```python!
class Model(QgsProcessingAlgorithm):
def initAlgorithm(self, config=None):
...
def processAlgorithm(self, parameters, context, model_feedback):
...
def name(self):
return 'modelName'
def displayName(self):
return 'modelDisplayName'
def group(self):
return ''
def groupId(self):
return ''
def createInstance(self):
return Model()
```
- 繼承自QGIS父類別,因此可以做圍套件進行編輯。
- 類別中有幾項必須函式
- initAlgorithm: 定義套件的輸入及輸出,必須傳入self, config
- processAlgorithm: 定義算法,必須傳入self,parameters, context, model_feedback
- name、displayName、group、groupId: 名稱
- createInstance: 建立實作
1. initAlgorithm
- 透過self.addParameter()來進行傳入變數增加(方法來自父類別)[link](https://qgis.org/pyqgis/3.40/core/QgsProcessingAlgorithm.html#qgis.core.QgsProcessingAlgorithm.addParameter)
- 也有self.addOutput() [link](https://qgis.org/pyqgis/3.40/core/QgsProcessingAlgorithm.html#qgis.core.QgsProcessingAlgorithm.addOutput)
- 內部透過傳入QgsProcessingParameterVectorLayer等來進行變數增加
2. processingAlgorithm
## Debug tools
在編寫過程中,可能會需要利用console來進行debug
然而script modeler 沒辦法單純利用print來進行文字輸出
需要透過pushInfo來進行debug
```python=
class Model(QgsProcessingAlgorithm):
...
def processingAlgorithm(self,parameters, context, model_feedback):
model_feedback.pushInfo('the text we would like to print')
```
## ex01: 迴圈編寫
- python model 如果想要對圖徽進行遍歷(for opeation)一般可以透過iterate over feature 的功能來完成,但卻無法調用圖徽屬性(attribute table)來進行計算。
:::info
舉例而言,如果今天我想要對點向量,進行DEM的viewshed分析,分析完成後依照點的屬性名稱重新命名檔案,在目前的qgis框架下後者是無法達成的。
:::
- 如果情況允許,以modeler來執行較容易編輯,但如果碰到複雜的loop over feature時,Pyqgis的使用便會大幅的增加的套件的使用方便程度。
- 通常我會先使用modeler建立input跟模板再生成維python code來進行編輯。
核心使用函式
1. QgsProcessingAlgorithm.parameterAsVectorLayer()
2. getFeatures()
在model class中,可以透過(1)來取得"向量圖層"物件,並透過(2)來取得"圖徽"
- 圖徽可以字典取用屬性表格內容(example code#32)
- 如果需要圖徽的幾何資訊,則可以透過geometry()來取得[QgsGeometry物件](https://qgis.org/pyqgis/3.40/core/QgsGeometry.html#qgis.core.QgsGeometry.asPoint)
- 這邊透過AsPoint回傳[QgsPointXY物件](https://qgis.org/pyqgis/3.40/core/QgsPointXY.html#qgis.core.QgsPointXY)
- 也可以先用type()檢核圖徽種類(point,line,polygon,unknown,null)
### 範例
```python=
from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterVectorLayer,QgsProcessingParameterNumber,QgsProcessingParameterFile,QgsProcessingParameterRasterLayer
from qgis.core import QgsExpression
class Model(QgsProcessingAlgorithm):
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterVectorLayer('pointlayer', 'PointLayer', defaultValue=None))
self.addParameter(QgsProcessingParameterRasterLayer('dem', 'DEM', defaultValue=None))
self.addParameter(QgsProcessingParameterNumber('distance', 'distance', type=QgsProcessingParameterNumber.Double, defaultValue=None))
self.addParameter(QgsProcessingParameterFile('outputFolder', 'outputFolder',behavior=QgsProcessingParameterFile.Folder, fileFilter='All files (*.*)',defaultValue=None))
def processAlgorithm(self, parameters, context, model_feedback):
# Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
# overall progress through the model
feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
results = {}
outputs = {}
folder=parameters['outputFolder']
pointVect= self.parameterAsVectorLayer(parameters,'pointlayer',context)
epsg=pointVect.crs().postgisSrid()
# Viewshed
for i,feature in enumerate(pointVect.getFeatures()):
point=feature.geometry().asPoint()
model_feedback.pushInfo('{0}, {1} [EPSG:{2}]'.format(point.x(), point.y(),epsg))
alg_params = {
'BAND': 1,
'EXTRA': '',
'INPUT': parameters['dem'],
'MAX_DISTANCE': parameters['distance'],
'OBSERVER': '{0}, {1} [EPSG:{2}]'.format(point.x(), point.y(),epsg),
'OBSERVER_HEIGHT': 1,
'OPTIONS': '',
'OUTPUT': path.join(folder,feature['CELLADDR']+'.tif'),
'TARGET_HEIGHT': 1,
}
outputs['Viewshed'] = processing.run('gdal:viewshed', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
return results
## the following code is ommited ...
```