執行 QGIS 工作排程¶
使用 Python 腳本 (PyQGIS) 搭配處理框架,許多工作可以在 QGIS 中自動處理。在一般情況下,我們是在 QGIS 開啟時,手動執行這些腳本;不過有些時候,你或許會希望有方法可以讓這些腳本在不開啟 QGIS 的情況下,直接於指令列中執行。沒問題,在本教學中,我們就來看看要怎麼使用獨立的 Python 環境,使用 QGIS 提供的函數庫與處理框架,撰寫腳本和工作排程,並直接在命令列中執行。
內容說明¶
讓我們假設我們要為某區域的 shapefile 進行分析,此 shapefile 每天會更新一次,而我們隨時都要使用最新的檔案,而且在我們使用檔案內的資料之前,還要稍微「清理」一下它們。因此,我們要設計一個 QGIS 流程,可以每日自動執行以上工作,這樣我們隨時都能把我們的資料分析保持在最新的狀態。以下我們要撰寫一個獨立的 Python 腳本,每日下載最新的 shapefile,然後對其執行拓樸清理(topological cleaning)運算。
你還會學到這些¶
使用 Python 下載檔案並解壓縮
使用 PyQGIS 執行地理運算
修正向量圖層中的拓撲誤差
取得資料¶
Geofabrik 提供每日更新的 OpenStreetMap 資料集的 shapefile。
我們在本練習中要使用 斐濟的 shapefiles。下載 fiji-latest.shp.zip 然後解壓到硬碟中。
資料來源 [GEOFABRIK]
操作流程¶
首先我們來手動清理此 shapefile,熟悉一下我們等一下要在 Python 腳本中加入的指令。打開 QGIS,選擇
。
選擇下載後解壓縮的內容中的
roads.shp
並按下 確定。
第一件事是重投影道路圖層到專案的 CRS,這樣一來我們就可以使用 公尺 而不是角度來當作等下分析使用的單位。選擇
。
尋找 Reproject layer 工具,點兩下開啟對話窗。
在 Reproject layer 視窗中,輸入圖層 選擇
roads
,目標 CRS 選擇EPSG:3460 Fiji 1986 / Fiji Map Grid
,按下 Run。
處理完成後可以看到重投影的圖層已載入到 QGIS 中。選擇
。
在 歷程與日誌 視窗中,展開 Algorithm 資料夾然後選擇最新一筆的紀錄,就會看到完整的處理指令顯示在下方面板中。這就是我們等下要使用在腳本內的指令。
回到 QGIS 視窗,按下右下角的 CRS 按鈕。
在 專案屬性 | CRS 視窗中,勾選 開啟即時 CRS 轉換,然後選擇
EPSG:3460 Fiji 1986 / Fiji Map Grid
為專案 CRS,這樣就能確保原本的圖層和重投影過的圖層都會正確顯示。
現在我們要來執行清理操作了。GRASS 具有強大的拓樸清理工具,在 QGIS 中可透過
v.clean
運算法存取。在 地理運算工具箱 中尋找此演算法,然後點兩下開啟視窗。
有關此工具的更多說明,可以在 Help 分頁中找到,而在此教學中,我們要使用
snap
工具來清除任何在 1 公尺之內多餘的線條頂點。在 Layer to clean 中選擇Reprojected layer
,然後在 Cleaning tool 中選擇snap
,在 Threshold 中輸入1.00
,其他欄位留白,按下 Run。
處理完成後,會有 2 個新圖層加入 QGIS 中。
Cleaned
是經過拓樸誤差修正後的圖層,而Errors
則顯示了有修改過的圖徵,因此你可以參考 Errors 圖層,縮放地圖以查看被移除的頂點們。
選擇
,然後查看我們等一下要使用的命令列指令。
現在我們已經做好寫程式碼的準備了!如果你需要設定文字編輯器或 IDE,請參考在 以 Python 製作附加元件 一章中的 編輯器或 Python IDE 的說明。為了要在獨立的 Python 腳本中使用 QGIS,我們必須要先設定好系統配置才行。有個方便的方法是透過
.bat
檔案來執行,此檔案會先設定好正確的配置選項,然後再呼叫 Python 腳本。因此,請建立稱為launch.bat
的新檔案然後輸入以下文字,記得根據你的 QGIS 配置改動其中的一些參數值,也別忘記把存取 Python 檔案的路徑中的使用者名稱替換成你自己的。如果你是透過OSGeo4W Installer
安裝 QGIS 的話,在此檔案中的路徑會與你的系統路徑相同。最後存檔到你的桌面。
備註
Linux 和 Mac 的使用者則需要使用 shell 腳本來設定路徑和環境變數。
REM Change OSGEO4W_ROOT to point to the base install folder
SET OSGEO4W_ROOT=C:\OSGeo4W64
SET QGISNAME=qgis
SET QGIS=%OSGEO4W_ROOT%\apps\%QGISNAME%
set QGIS_PREFIX_PATH=%QGIS%
REM Gdal Setup
set GDAL_DATA=%OSGEO4W_ROOT%\share\gdal\
REM Python Setup
set PATH=%OSGEO4W_ROOT%\bin;%QGIS%\bin;%PATH%
SET PYTHONHOME=%OSGEO4W_ROOT%\apps\Python27
set PYTHONPATH=%QGIS%\python;%PYTHONPATH%
REM Launch python job
python c:\Users\Ujaval\Desktop\download_and_clean.py
pause
建立新的 Python 檔然後輸入以下程式碼,檔名取為
download_and_clean.py
然後儲存至桌面。
from qgis.core import *
print 'Hello QGIS!'
切換到桌面,找到
launch.bat
然後點它兩下,腳本會開始執行,並會同時開啟一個命令列視窗。如果你在命令列視窗中看到了Hello QGIS!
字樣出現,那就表示之前的設定和操作一切順利。如果你發現錯誤或沒有看到以上文字的話,請檢查launch.bat
的內容,然後確認所有的路徑在你的作業系統中都是正確的。
回到文字編輯器,編輯
download_and_clean.py
並加入以下的程式碼。這個段落用來快速啟動 QGIS,如果你在 QGIS 中執行腳本,並不需要此段落;但由於我們要在 QGIS 不開啟的狀況下執行,程式最前端就必須放上這幾行,同時要注意有把使用者名稱改成你自己的才行。完成之後,存檔然後再次執行launch.bat
,如果你看到Hello QGIS!
列印出來,就表示可以開始在腳本中加入地理運算的流程了。
import sys
from qgis.core import *
# Initialize QGIS Application
QgsApplication.setPrefixPath("C:\\OSGeo4W64\\apps\\qgis", True)
app = QgsApplication([], True)
QgsApplication.initQgis()
# Add the path to Processing framework
sys.path.append('c:\\Users\\Ujaval\\.qgis2\\python\\plugins')
# Import and initialize Processing framework
from processing.core.Processing import Processing
Processing.initialize()
import processing
print 'Hello QGIS!'
我們從地理運算的歷程日誌中看到的第一個運算指令,就是要放在這邊的重投影指令。因此,貼上此段指令到腳本中,然後前後加入如下所示的幾行。注意運算指令的回傳值為字典(dict)變數,其中包含了輸出圖層的路徑,因此我們在這邊把字典存成
ret
然後再列印出重投影後圖層的路徑。
roads_shp_path = "C:\\Users\\Ujaval\\Downloads\\fiji-latest.shp\\roads.shp"
ret = processing.runalg('qgis:reprojectlayer', roads_shp_path, 'EPSG:3460',
None)
output = ret['OUTPUT']
print output
透過
launch.bat
執行此腳本,就可以看到新建立的重投影圖層的路徑。
接下來要增加拓樸清理的程式碼。由於這會是我們的最終輸出,所以我們要在
grass.v.clean
函數中的最後 2 個參數加上輸出檔路徑。如果這兩個參數是空白的話,輸出檔就只會放在一個暫時資料夾之中。
processing.runalg("grass:v.clean",
output,
1,
1,
None,
-1,
0.0001,
'C:\\Users\\Ujaval\\Desktop\\clean.shp',
'C:\Users\\Ujaval\\Desktop\\errors.shp')
執行腳本後可以看到 2 個新的 shapefiles 出現在你的桌面了,到此為止我們已完成腳本的地理運算部分,接下來我們來加入從網頁中下載資料和自動解壓縮的程式碼。我們也順便把解壓縮後的檔案路徑儲存起來,給之後的地理運算函數使用。要做到這些得需要一些額外的模組才行。(本教學最後附有完整的腳本檔案供參考)
import os
import urllib
import zipfile
import tempfile
temp_dir = tempfile.mkdtemp()
download_url = 'http://download.geofabrik.de/australia-oceania/fiji-latest.shp.zip'
print 'Downloading file'
zip, headers = urllib.urlretrieve(download_url)
with zipfile.ZipFile(zip) as zf:
files = zf.namelist()
for filename in files:
if 'roads' in filename:
file_path = os.path.join(temp_dir, filename)
f = open(file_path, 'wb')
f.write(zf.read(filename))
f.close()
if filename == 'roads.shp':
roads_shp_path = file_path
執行完成的腳本。每次執行腳本之後,就會有一份新的資料被下載並進行處理。
為了要每日自動執行腳本,我們可以使用 Windows 中的
工作排程器
。開啟工作排程器之後,按下 建立基本工作。
備註
Linux 和 Mac 的使用者可以使用 crontab 執行排程工作。
把工作命名為
Daily Download and Cleanup
然後按下 下一步。
在 觸發程序 設定中選擇
每天
,然後按 下一步。
選擇你喜歡的時段然後按 下一步。
在 執行 設定中選擇 啟動程式,按 下一步。
按下 瀏覽 然後選擇
launch.bat
腳本,按下 下一步。
在最後一個視窗按下 完成,就完成了例行排程的設定。從現在開始腳本就會每天在你選擇的時段執行,然後新的、處理過的資料就會每天產生。
以下放上完整的 download_and_clean.py
檔做為參考。
import sys
from qgis.core import *
import os
import urllib
import zipfile
import tempfile
# Initialize QGIS Application
QgsApplication.setPrefixPath("C:\\OSGeo4W64\\apps\\qgis", True)
app = QgsApplication([], True)
QgsApplication.initQgis()
# Add the path to Processing framework
sys.path.append('c:\\Users\\Ujaval\\.qgis2\\python\\plugins')
# Import and initialize Processing framework
from processing.core.Processing import Processing
Processing.initialize()
import processing
# Download and unzip the latest shapefile
temp_dir = tempfile.mkdtemp()
download_url = 'http://download.geofabrik.de/australia-oceania/fiji-latest.shp.zip'
print 'Downloading file'
zip, headers = urllib.urlretrieve(download_url)
with zipfile.ZipFile(zip) as zf:
files = zf.namelist()
for filename in files:
if 'roads' in filename:
file_path = os.path.join(temp_dir, filename)
f = open(file_path, 'wb')
f.write(zf.read(filename))
f.close()
if filename == 'roads.shp':
roads_shp_path = file_path
print 'Downloaded file to %s' % roads_shp_path
# Reproject the Roads layer
print 'Reprojecting the roads layer'
ret = processing.runalg('qgis:reprojectlayer', roads_shp_path, 'EPSG:3460', None)
output = ret['OUTPUT']
# Clean the Roads layer
print 'Cleaning the roads layer'
processing.runalg("grass:v.clean",
output,
1,
1,
None,
-1,
0.0001,
'C:\\Users\\Ujaval\\Desktop\\clean.shp',
'C:\Users\\Ujaval\\Desktop\\errors.shp')
print 'Success'
If you want to give feedback or share your experience with this tutorial, please comment below. (requires GitHub account)