BAB 21: Memanggil Alat dari Skrip: Satu Perintah, Satu Analisis, Satu Resep untuk Banyak Berkas
Studi kasus: "Sempadan sungai, tiga puluh ubin, dan sore yang pendek" #
Kepala Seksi minta dua hal sebelum akhir pekan. Pertama, luas jalur sempadan sungai 30 meter di tiap KPH, lengkap dengan jumlah titik panasnya. Kedua, NDVI untuk semua ubin foto drone yang baru masuk. Mengklik alat satu per satu akan menghabiskan sore Anda. Dengan skrip, Anda memanggil alat Processing lewat tulisan, merangkainya, mengulanginya untuk banyak berkas, dan membiarkan QGIS tetap hidup selagi bekerja.
Konsep: memanggil alat dengan formulir pesanan #
Di QGIS, Anda memakai alat lewat jendela dengan kolom isian. Di skrip, kolom isian itu ditulis sebagai kamus parameter. Bayangkan formulir pesanan di loket: Anda menyebut nama layanan, mengisi kolomnya, lalu petugas mengembalikan lembar jawaban. Lembar jawaban itu adalah kamus hasil.

Nama alat di loket itu disebut ID alat, misalnya native:buffer. Bagian sebelum titik dua adalah nama kelompok alat (native berarti alat bawaan QGIS), dan bagian sesudahnya nama alatnya. Kamus sudah dibahas di Bab 5, jadi yang baru di sini hanyalah isinya.
Lima istilah baru Anda:
- Alat Processing: satu fungsi analisis di QGIS, seperti Buffer, yang juga ada di Processing Toolbox.
- ID alat: nama unik alat untuk skrip, misalnya
native:buffer. - Kamus parameter: isian alat, berupa pasangan nama dan nilai.
- Kamus hasil: jawaban alat, diambil dengan nama keluarannya, misalnya
["OUTPUT"]. - Keluaran sementara (
TEMPORARY_OUTPUT): hasil perantara yang tidak ditulis ke berkas pilihan Anda.
Dua cara menjalankan, dan satu tambahan untuk Processing #
Bab 14 menjelaskan dua cara menjalankan skrip PyQGIS: di dalam QGIS, atau mandiri dengan kerangka QgsApplication. Semua skrip bab ini berjalan dengan cara pertama tanpa tambahan, sebab Konsol Python QGIS sudah memuat qgis.core dan processing.
Untuk cara mandiri, ada tambahan khusus. Python biasa tidak menemukan perkakas processing begitu saja. Pada uji, tulisan import processing di skrip mandiri menghasilkan ModuleNotFoundError: No module named 'processing'. Kerangka berikut sudah diuji pada QGIS 4.0.2.
import sys
from qgis.core import QgsApplication
QgsApplication.setPrefixPath(r"C:/Program Files/QGIS 4.0.2/apps/qgis", True)
app = QgsApplication([], False)
app.initQgis()
sys.path.append(r"C:/Program Files/QGIS 4.0.2/apps/qgis/python/plugins")
from processing.core.Processing import Processing
Processing.initialize()
from qgis.analysis import QgsNativeAlgorithms
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
# ... tempel isi skrip Anda di sini ...
app.exitQgis()| Baris | Artinya |
|---|---|
sys.path.append(...) | Tambahkan folder plugin QGIS ke daftar tempat Python mencari perkakas. Jalur ini milik komputer penulis dan akan berbeda di komputer Anda [CEK]. |
Processing.initialize() | Nyalakan sistem Processing. |
addProvider(QgsNativeAlgorithms()) | Daftarkan alat bawaan (native:) ke buku telepon alat. |
Kerangka ini hanya untuk cara mandiri. Bila bagian Processing.initialize() dan addProvider dibuang, skrip tetap bisa mengimpor processing, tetapi alatnya tidak dikenal. Pada uji, hasilnya QgsProcessingException: Error: Algorithm native:buffer not found.
Skrip 21.1: Memanggil satu alat, Buffer #
Tujuan. Mencari ID alat Buffer, mengintip nama-nama isiannya, menjalankannya pada layer sungai dengan jarak 30 meter, lalu membaca hasilnya.
Bahan. Paket data paket-i4, berkas Sungai.gpkg (dua garis sungai). Berkas b2_01_jalankan_alat.py dijalankan dari Editor Skrip QGIS, atau dengan kerangka mandiri di atas. Ganti DATA di baris 6 dengan lokasi paket-i4 Anda.
Skrip ini bekerja dalam enam tahap. Gambar berikut menunjukkan urutannya.

Skrip utuh:
# [SKRIP 2.1: Menjalankan satu alat Processing dari Python (Buffer)]
# Penulis: Badar Mubarok Yogaswara
import processing
from qgis.core import QgsProject, QgsVectorLayer, QgsApplication
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
sungai = QgsVectorLayer(DATA + "/Sungai.gpkg", "Sungai", "ogr")
# 0. Mencari ID alat dengan kata kunci
for a in sorted(QgsApplication.processingRegistry().algorithms(), key=lambda x: x.id()):
if a.id().startswith("native:") and "buffer" in a.id():
print("ID:", a.id(), "-", a.displayName())
# 1. Mengintip "formulir" alat: nama parameter dan artinya
alat = QgsApplication.processingRegistry().algorithmById("native:buffer")
print("Alat:", alat.displayName())
for p in alat.parameterDefinitions():
print(" ", p.name(), "=", p.description())
# 2. Menjalankan alat. Parameter berupa kamus (dictionary): nama parameter -> nilai.
hasil = processing.run("native:buffer", {
"INPUT": sungai,
"DISTANCE": 30, # satuan meter karena CRS-nya UTM
"SEGMENTS": 8,
"END_CAP_STYLE": 0, # 0 = bulat
"JOIN_STYLE": 0, # 0 = bulat
"MITER_LIMIT": 2,
"DISSOLVE": True, # gabungkan semua penyangga jadi satu bentuk
"OUTPUT": "TEMPORARY_OUTPUT", # keluaran sementara, tidak menulis berkas
})
# 3. Hasilnya juga kamus. Kuncinya nama keluaran. Untuk vektor sementara, isinya sudah berupa layer.
sempadan = hasil["OUTPUT"]
print("Jenis keluaran:", type(sempadan).__name__)
print("Fitur:", sempadan.featureCount())
luas = sum(f.geometry().area() for f in sempadan.getFeatures()) / 10000
print("Luas sempadan sungai:", round(luas, 2), "ha")
# 4. Beri nama yang jelas lalu tampilkan di proyek
sempadan.setName("Sempadan_30m")
QgsProject.instance().addMapLayer(sempadan)
print("Layer di proyek:", [l.name() for l in QgsProject.instance().mapLayers().values()])Penjelasan baris demi baris. Dua blok. Blok pertama mengenali alatnya, blok kedua menjalankannya.
Blok A: mengenali alat (baris 3-20).
| Baris | Tulisan | Artinya |
|---|---|---|
| 3-4 | import processing dan from qgis.core import ... | Panggil perkakas Processing dan tiga perkakas QGIS. QgsProject dan QgsVectorLayer dibahas di Bab 14. |
| 6-7 | DATA dan HASIL | Dua kotak jalur folder. Di skrip ini hanya DATA yang dipakai. |
| 9 | sungai = QgsVectorLayer(...) | Buka layer sungai, seperti di Bab 14. |
| 12-14 | for a in sorted(...algorithms(), key=lambda x: x.id()): | Ambil daftar semua alat, urutkan menurut ID. lambda x: x.id() adalah resep mini yang berbunyi "ambil ID-nya" (dibahas di Bab 7). Untuk tiap alat yang ID-nya diawali native: dan memuat kata buffer, cetak ID dan nama tampilannya. |
| 17-18 | alat = ...algorithmById("native:buffer") | Ambil satu alat dengan ID-nya, lalu cetak nama tampilannya. |
| 19-20 | for p in alat.parameterDefinitions(): | Ulangi untuk tiap isian alat. Cetak nama isian (p.name()) dan artinya (p.description()). Inilah "formulir" alat itu. |
Blok B: menjalankan dan membaca hasil (baris 22-44).
| Baris | Tulisan | Artinya |
|---|---|---|
| 23-32 | hasil = processing.run("native:buffer", {...}) | Jalankan alat. Isian pertama adalah ID alat, isian kedua kamus parameter. Hasilnya disimpan di kotak hasil. |
| 24 | "INPUT": sungai | Layer masukan. |
| 25 | "DISTANCE": 30 | Jarak penyangga. Satuannya mengikuti CRS layer. Di data ini CRS-nya UTM, jadi 30 meter. |
| 26-29 | "SEGMENTS", "END_CAP_STYLE", "JOIN_STYLE", "MITER_LIMIT" | Pengaturan bentuk ujung dan sudut. Angka 0 berarti "bulat" (lihat tabel pilihan di bawah). |
| 30 | "DISSOLVE": True | Gabungkan semua penyangga menjadi satu bentuk. |
| 31 | "OUTPUT": "TEMPORARY_OUTPUT" | Simpan hasil sebagai keluaran sementara. |
| 35-37 | sempadan = hasil["OUTPUT"] | Ambil jawaban dari kamus hasil dengan kunci OUTPUT. Untuk vektor sementara, isinya sudah berupa layer. Cetak jenisnya dengan type(...).__name__ dan jumlah fitur dengan featureCount(). |
| 38 | luas = sum(f.geometry().area() for f in ...) / 10000 | Jumlahkan luas semua fitur, lalu ubah meter persegi menjadi hektare. |
| 42-43 | sempadan.setName(...) lalu addMapLayer(...) | Beri nama yang jelas, lalu tampilkan di proyek. Tanpa addMapLayer, layer sementara tidak muncul di panel Layers. |
| 44 | [l.name() for l in ...mapLayers().values()] | Cetak nama semua layer di proyek. |
Angka pilihan untuk END_CAP_STYLE dan JOIN_STYLE bisa dilihat dengan processing.algorithmHelp("native:buffer"). Pada uji, bantuan itu mencatat END_CAP_STYLE: 0 Round, 1 Flat, 2 Square. Untuk JOIN_STYLE: 0 Round, 1 Miter, 2 Bevel.
Hasil. Layar menampilkan (berdasarkan uji pada QGIS 4.0.2):
ID: native:buffer - Buffer
ID: native:bufferbym - Variable width buffer (by M value)
ID: native:multiringconstantbuffer - Multi-ring buffer (constant distance)
ID: native:singlesidedbuffer - Single sided buffer
ID: native:taperedbuffer - Tapered buffers
ID: native:wedgebuffers - Create wedge buffers
Alat: Buffer
INPUT = Input layer
DISTANCE = Distance
SEGMENTS = Segments
END_CAP_STYLE = End cap style
JOIN_STYLE = Join style
MITER_LIMIT = Miter limit
DISSOLVE = Dissolve result
SEPARATE_DISJOINT = Keep disjoint results separate
OUTPUT = Buffered
Jenis keluaran: QgsVectorLayer
Fitur: 1
Luas sempadan sungai: 4.48 ha
Layer di proyek: ['Sempadan_30m']Ada enam alat yang ID-nya memuat kata "buffer". Alat Buffer punya sembilan isian, dan skrip hanya mengisi delapan. Isian SEPARATE_DISJOINT tidak diisi, jadi alat memakai nilai bawaannya. Hasil: satu fitur seluas 4,48 hektare.
Bagian yang boleh diubah.
- Baris 6, lokasi data. Ganti sesuai komputer Anda.
- Baris 13, kata
buffer. Ganti dengan kata lain, misalnyaclipatauraster, untuk mencari alat lain. - Baris 17, ID alat. Ganti dengan ID lain, lalu baca isiannya di blok yang mengintip formulir.
- Baris 25, jarak. Ganti angka 30. Satuan mengikuti CRS.
- Baris 30,
DISSOLVE.Falsemenghasilkan satu fitur per sungai. - Baris 42, nama layer. Ganti sesuai kebutuhan.
Jebakan. Semua kesalahan di bawah sudah dicoba pada QGIS 4.0.2.
- Nama isian salah ketik tidak menimbulkan galat.
"DISTANC": 30menggantikan"DISTANCE": 30. Skrip jalan mulus, tetapi alat memakai nilai bawaannya sendiri. Luas menjadi1.44 ha, bukan4.48 ha. Tidak ada pesan apa pun. Salin nama isian dari hasil blok yang mengintip formulir. - Layer masukan gagal dibuka tidak menimbulkan galat. Bila
Sungai.gpkgdigantiSungay.gpkg, hasilnyaFitur: 0danLuas sempadan sungai: 0.0 ha. Alat bekerja pada layer kosong. Periksasungai.isValid()lebih dulu (Bab 14). - ID alat salah ketik menimbulkan galat.
"native:buffr"menghasilkanQgsProcessingException: Error: Algorithm native:buffr not found. - Keluaran ke berkas bukan layer. Bila
"OUTPUT"diisi jalur berkas,hasil["OUTPUT"]berisi teks jalur itu. Baris 36 mencetakJenis keluaran: str, lalu baris 37 berhenti denganAttributeError: 'str' object has no attribute 'featureCount'. - Satuan jarak mengikuti CRS.
Sungai.gpkgdiubah ke EPSG:4326 (derajat), lalu skrip dijalankan. HasilnyaLuas sempadan sungai: 0.28 ha, angka yang tidak bermakna, karena 30 dibaca sebagai 30 derajat. Pastikan CRS berupa meter sebelum memakaiDISTANCE.
Latihan variasi.
- Jalankan lagi dengan
DISSOLVEbernilaiFalse. Berapa fitur dan berapa luasnya? - Mengapa luasnya berbeda dari 4,48 hektare?
Contoh jawaban (sudah diuji). Untuk nomor 1, ubah baris 30 menjadi "DISSOLVE": False,. Layar menampilkan Fitur: 2 dan Luas sempadan sungai: 4.82 ha. Untuk nomor 2, kemungkinan besar bagian yang tumpang tindih di pertemuan dua sungai terhitung dua kali bila tidak digabung. Selisihnya 0,34 hektare [kemungkinan, belum diukur terpisah].
Skrip 21.2: Merangkai alat vektor #
Keluaran satu alat bisa langsung menjadi masukan alat berikutnya, seperti ban berjalan di pabrik. Skrip ini merangkai tiga alat, lalu menggabungkan hasilnya dalam satu tabel kecil.
Tujuan. Menghitung luas sempadan sungai 30 meter di tiap KPH, persennya terhadap luas KPH, dan jumlah titik panas per KPH.
Bahan. Paket data paket-i4: Batas_KPH.gpkg, Sungai.gpkg, dan Hotspot_Nasional.gpkg. Berkas b2_02_rantai_vektor.py. Ganti DATA di baris 6.

Skrip utuh:
# [SKRIP 2.2: Rantai alat vektor: sempadan sungai dan titik panas per KPH]
# Penulis: Badar Mubarok Yogaswara
import processing
from qgis.core import QgsProject, QgsVectorLayer
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
batas = QgsVectorLayer(DATA + "/Batas_KPH.gpkg", "Batas_KPH", "ogr")
sungai = QgsVectorLayer(DATA + "/Sungai.gpkg", "Sungai", "ogr")
hotspot = QgsVectorLayer(DATA + "/Hotspot_Nasional.gpkg", "Hotspot_Nasional", "ogr")
# Langkah 1: penyangga (buffer) 30 m di kiri-kanan sungai
sempadan = processing.run("native:buffer", {
"INPUT": sungai, "DISTANCE": 30, "SEGMENTS": 8, "END_CAP_STYLE": 0, "JOIN_STYLE": 0,
"MITER_LIMIT": 2, "DISSOLVE": True, "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
# Langkah 2: irisan (intersection). Hasil langkah 1 langsung menjadi masukan langkah 2.
irisan = processing.run("native:intersection", {
"INPUT": batas, "OVERLAY": sempadan, "INPUT_FIELDS": ["NAMA_KPH"],
"OVERLAY_FIELDS": [], "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
# Langkah 3: hitung titik panas di tiap KPH
dengan_titik = processing.run("native:countpointsinpolygon", {
"POLYGONS": batas, "POINTS": hotspot, "FIELD": "JML_HOTSPOT", "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
# Langkah 4: gabungkan angkanya dalam satu tabel kecil
luas_sempadan = {}
for f in irisan.getFeatures():
nama = f["NAMA_KPH"]
luas_sempadan[nama] = luas_sempadan.get(nama, 0) + f.geometry().area() / 10000
print("KPH luas(ha) sempadan(ha) persen titik_panas")
for f in dengan_titik.getFeatures():
nama = f["NAMA_KPH"]
luas = f.geometry().area() / 10000
sem = luas_sempadan.get(nama, 0)
print("%-11s %8.2f %13.2f %7.1f %12d" % (nama, luas, sem, 100 * sem / luas, f["JML_HOTSPOT"]))Skrip ini mirip Skrip 21.1, jadi hanya bagian yang baru yang dijelaskan. Baris import, DATA, dan pemuatan layer (baris 3-11) sudah dibahas sebelumnya.
| Baris | Tulisan | Artinya |
|---|---|---|
| 14-16 | sempadan = processing.run("native:buffer", {...})["OUTPUT"] | Sama dengan Skrip 21.1, tetapi ["OUTPUT"] ditulis langsung di ujung. Kotak sempadan langsung berisi layer hasil. |
| 19-21 | irisan = processing.run("native:intersection", {...}) | Iris layer batas KPH dengan sempadan. INPUT adalah batas, OVERLAY adalah sempadan. Hasil langkah 1 menjadi masukan langkah 2. |
| 20 | "INPUT_FIELDS": ["NAMA_KPH"] | Kolom dari INPUT yang dibawa ke hasil. Daftar kosong ([]) pada OVERLAY_FIELDS berarti, menurut keterangan alat, semua kolom dibawa. |
| 24-25 | dengan_titik = processing.run("native:countpointsinpolygon", {...}) | Hitung titik panas di dalam tiap KPH. POLYGONS adalah batas, POINTS adalah titik panas, dan FIELD adalah nama kolom penghitung baru, JML_HOTSPOT. |
| 28 | luas_sempadan = {} | Siapkan kamus kosong. |
| 29-31 | for f in irisan.getFeatures(): | Untuk tiap irisan: ambil nama KPH, lalu tambahkan luasnya (dalam hektare) ke jumlah milik KPH itu. luas_sempadan.get(nama, 0) berarti "ambil jumlah sejauh ini, atau 0 bila belum ada". |
| 33 | print("KPH luas(ha) ...") | Cetak judul tabel. Spasinya diketik tangan agar sejajar dengan baris 38. |
| 34-37 | for f in dengan_titik.getFeatures(): | Untuk tiap KPH: ambil nama, luas, dan luas sempadannya (.get(nama, 0)). |
| 38 | print("%-11s %8.2f %13.2f %7.1f %12d" % (...)) | Cetak satu baris tabel. %-11s teks rata kiri selebar 11 karakter, %8.2f angka dua desimal selebar 8, %d bilangan bulat. Persen dihitung dengan 100 * sem / luas. Format % dibahas di Bab 9. |
Hasil. Layar menampilkan (berdasarkan uji pada QGIS 4.0.2):
KPH luas(ha) sempadan(ha) persen titik_panas
KPH Alpha 4.00 1.18 29.4 2
KPH Beta 4.00 1.39 34.6 5
KPH Gamma 8.00 1.50 18.7 5Dari tabel ini Anda tahu: sempadan paling luas di KPH Gamma (1,50 hektare), tetapi persen terbesar ada di KPH Beta (34,6 persen).
Bagian yang boleh diubah.
- Baris 15,
"DISTANCE": 30. Lebar sempadan. - Baris 25,
"FIELD": "JML_HOTSPOT". Nama kolom penghitung. Bila Anda mengubahnya, ubah jugaf["JML_HOTSPOT"]di baris 38. - Baris 20,
INPUT_FIELDS. Tambahkan nama kolom lain yang ingin dibawa. - Baris 33 dan 38, format tabel. Lebar kolom boleh disesuaikan, tetapi judul dan baris data harus selaras.
Jebakan. Semua dicoba pada QGIS 4.0.2.
- Nama kolom tidak sama.
f["JML_HOTSPOT"]digantif["JML_TITIK"]: judul tabel tercetak, lalu skrip berhenti denganKeyError: 'JML_TITIK'. - Layer sungai gagal dibuka. Dengan
Sungay.gpkg, tidak ada galat. Kolom sempadan berisi0.00dan persen0.0untuk ketiga KPH. Angka tampak wajar, padahal kosong. - Jarak nol.
"DISTANCE": 0menghentikan alat Intersection denganQgsProcessingException: Could not write feature into OUTPUT: Could not add feature with geometry type GeometryCollection to layer of type MultiPolygon. Jarak nol menghasilkan bentuk yang tidak cocok untuk alat berikutnya. - Pembagian dengan nol. Baris 38 membagi dengan
luas. Data latihan tidak punya KPH berluas nol, jadi galatZeroDivisionErrortidak muncul di sini (Bab 10 membahas penangannya).
Latihan variasi. Ubah lebar sempadan menjadi 50 meter. Bagaimana tabelnya berubah?
Contoh jawaban (sudah diuji). Ganti baris 15 menjadi "INPUT": sungai, "DISTANCE": 50, "SEGMENTS": 8, "END_CAP_STYLE": 0, "JOIN_STYLE": 0,. Layar menampilkan:
KPH luas(ha) sempadan(ha) persen titik_panas
KPH Alpha 4.00 1.69 42.2 2
KPH Beta 4.00 2.32 58.0 5
KPH Gamma 8.00 2.54 31.8 5Skrip 21.3: Alat raster, NDVI dan tinggi tajuk #
Raster adalah kisi petak (piksel), jadi alatnya sedikit berbeda dari vektor. Skrip ini memakai dua alat raster dan satu alat statistik. Hasilnya dua angka per KPH.
Dua istilah baru. NDVI adalah indeks kehijauan tanaman dari citra, dihitung (NIR - Merah) / (NIR + Merah). Nilai tinggi berarti vegetasi rapat. CHM adalah tinggi tajuk pohon, dihitung dari raster tinggi permukaan (DSM) dikurangi raster tinggi tanah (DTM). Statistik zonal meringkas nilai raster di dalam tiap poligon.
Tujuan. Menghitung NDVI dan tinggi tajuk, lalu mencetak rata-ratanya untuk tiap KPH.
Bahan. Paket data paket-i4: Batas_KPH.gpkg, Citra_KPH.tif (empat band, band 1 Merah dan band 4 NIR), DSM_Drone.tif, dan DTM_Drone.tif. Berkas b2_03_raster_alat.py. Ganti DATA di baris 6.

Hal yang perlu Anda tahu dulu: keluaran raster sementara berbeda dari vektor sementara.

Skrip utuh:
# [SKRIP 2.3: Alat raster dari Python: NDVI, tinggi tajuk (CHM), dan statistik per KPH]
# Penulis: Badar Mubarok Yogaswara
import processing
from qgis.core import QgsProject, QgsVectorLayer, QgsRasterLayer
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
batas = QgsVectorLayer(DATA + "/Batas_KPH.gpkg", "Batas_KPH", "ogr")
citra = QgsRasterLayer(DATA + "/Citra_KPH.tif", "Citra_KPH", "gdal") # band 1=Merah, 4=NIR
dsm = QgsRasterLayer(DATA + "/DSM_Drone.tif", "DSM", "gdal")
dtm = QgsRasterLayer(DATA + "/DTM_Drone.tif", "DTM", "gdal")
# 1. NDVI = (NIR - Merah) / (NIR + Merah). Dalam ekspresi, band ditulis "NamaLayer@nomorBand".
jalur_ndvi = processing.run("native:rastercalc", {
"EXPRESSION": '("Citra_KPH@4" - "Citra_KPH@1") / ("Citra_KPH@4" + "Citra_KPH@1")',
"LAYERS": [citra], "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
print("Keluaran raster sementara berupa:", type(jalur_ndvi).__name__) # teks jalur, bukan layer
ndvi = QgsRasterLayer(jalur_ndvi, "NDVI_KPH", "gdal") # bungkus jadi layer
print("NDVI sah?", ndvi.isValid())
# 2. CHM = DSM - DTM
jalur_chm = processing.run("native:rastercalc", {
"EXPRESSION": '"DSM@1" - "DTM@1"', "LAYERS": [dsm, dtm], "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
chm = QgsRasterLayer(jalur_chm, "CHM", "gdal")
# 3. Statistik zonal: rata-rata nilai raster di dalam tiap poligon KPH
def rata_rata(raster, awalan):
return processing.run("native:zonalstatisticsfb", {
"INPUT": batas, "INPUT_RASTER": raster, "RASTER_BAND": 1,
"COLUMN_PREFIX": awalan, "STATISTICS": [2], # 2 = Mean
"OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
zn = rata_rata(ndvi, "ndvi_")
zc = rata_rata(chm, "chm_")
chm_per_kph = {f["NAMA_KPH"]: f["chm_mean"] for f in zc.getFeatures()}
print("KPH NDVI rata-rata CHM rata-rata (m)")
for f in zn.getFeatures():
print("%-11s %12.3f %16.2f" % (f["NAMA_KPH"], f["ndvi_mean"], chm_per_kph[f["NAMA_KPH"]]))
QgsProject.instance().addMapLayer(ndvi)
QgsProject.instance().addMapLayer(chm)Penjelasan baris demi baris. Hanya yang baru yang dijelaskan.
| Baris | Tulisan | Artinya |
|---|---|---|
| 9-12 | batas, citra, dsm, dtm | Buka satu layer vektor dan tiga raster. Raster memakai QgsRasterLayer(..., "gdal"). Nama kedua ("Citra_KPH", "DSM", "DTM") penting, karena dipakai di ekspresi. |
| 15-17 | jalur_ndvi = processing.run("native:rastercalc", {...})["OUTPUT"] | Jalankan kalkulator raster. EXPRESSION berisi rumus NDVI. Band ditulis "NamaLayer@nomorBand", misalnya "Citra_KPH@4" untuk band 4. LAYERS berisi daftar layer yang namanya dipakai di rumus. |
| 18 | print(type(jalur_ndvi).__name__) | Cetak jenis hasil. Jawabannya str: teks jalur, bukan layer. |
| 19 | ndvi = QgsRasterLayer(jalur_ndvi, "NDVI_KPH", "gdal") | Bungkus jalur itu menjadi layer raster. |
| 20 | print("NDVI sah?", ndvi.isValid()) | Buktikan pembungkusnya berhasil. |
| 23-25 | jalur_chm = ... dan chm = ... | Hal yang sama untuk CHM, dengan rumus "DSM@1" - "DTM@1". |
| 28-32 | def rata_rata(raster, awalan): | Resep bernama yang menjalankan alat statistik zonal. Dibahas di Bab 7. Ia menerima raster dan awalan nama kolom, lalu mengembalikan layer batas dengan kolom baru. |
| 29-31 | "native:zonalstatisticsfb" | Alat statistik zonal. INPUT poligon, INPUT_RASTER raster, RASTER_BAND nomor band, COLUMN_PREFIX awalan nama kolom hasil, dan STATISTICS daftar statistik. Angka 2 berarti rata-rata (Mean). |
| 34-35 | zn = rata_rata(ndvi, "ndvi_") dan zc = rata_rata(chm, "chm_") | Panggil resep dua kali. Kolom hasil bernama awalan ditambah mean: ndvi_mean dan chm_mean. |
| 36 | chm_per_kph = {f["NAMA_KPH"]: f["chm_mean"] for f in zc.getFeatures()} | Susun kamus: nama KPH sebagai kunci, rata-rata CHM sebagai nilai. Cara menulis ini disebut dictionary comprehension. |
| 37-39 | for f in zn.getFeatures(): | Cetak tabel: nama KPH, ndvi_mean, dan CHM dari kamus tadi. |
| 41-42 | addMapLayer(ndvi) dan addMapLayer(chm) | Tampilkan dua raster di proyek. |
Pada uji, bantuan alat native:zonalstatisticsfb mencatat kode statistik: 0 Count, 1 Sum, 2 Mean, 3 Median, 4 St dev, 5 Minimum, 6 Maximum, dan seterusnya.
Hasil. Layar menampilkan (berdasarkan uji pada QGIS 4.0.2):
Keluaran raster sementara berupa: str
NDVI sah? True
KPH NDVI rata-rata CHM rata-rata (m)
KPH Alpha 0.604 2.60
KPH Beta 0.431 2.69
KPH Gamma 0.329 2.56Dari hasil ini Anda tahu: vegetasi paling hijau ada di KPH Alpha (NDVI 0,604), sedangkan tajuk paling tinggi rata-rata di KPH Beta (2,69 meter).
Bagian yang boleh diubah.
- Baris 16, rumus NDVI. Nomor band harus sesuai urutan band citra Anda. Citra ini: band 1 Merah, band 4 NIR.
- Baris 31,
"STATISTICS": [2]. Ganti kode statistik. Perhatikan: reseprata_ratadipakai untuk NDVI dan CHM sekaligus, jadi perubahannya berlaku untuk keduanya. - Baris 34-36, awalan kolom. Bila Anda mengubah awalan, ubah juga nama kolom yang dibaca.
- Baris 24, rumus CHM. Ganti raster atau rumusnya.
Jebakan. Semua dicoba pada QGIS 4.0.2.
- Band tertukar, tanpa galat. Rumus NDVI ditulis terbalik:
"Citra_KPH@1"di tempat"Citra_KPH@4"dan sebaliknya. Skrip jalan mulus, tetapi tabel menampilkan-0.604,-0.431, dan-0.329. Tanda minus adalah petunjuk bahwa band terbalik. - Nama layer di rumus tidak cocok.
"DSM@1"diganti"DSM_Drone@1", padahal layer bernama"DSM". HasilnyaQgsProcessingException: Error occurred while performing calculation. - Nama kolom hasil salah. Menulis
f["chm_rata"]menghasilkanKeyError: 'chm_rata'. Kolom hasil selalu awalan ditambah nama statistik (mean,max, dan seterusnya). - Hasil raster adalah teks. Memanggil
jalur_ndvi.width()gagal, karenastrtidak punya metode itu. Bungkus dulu denganQgsRasterLayer.
Latihan variasi. Ganti kolom CHM menjadi nilai tertinggi per KPH, bukan rata-rata.
Contoh jawaban (sudah diuji). Ubah tiga hal: pada baris 31 jadi "STATISTICS": [2, 6] (rata-rata dan tertinggi sekaligus), pada baris 36 jadi f["chm_max"], dan pada baris 37 ganti judul menjadi CHM tertinggi (m). Layar menampilkan:
KPH NDVI rata-rata CHM tertinggi (m)
KPH Alpha 0.604 13.94
KPH Beta 0.431 13.96
KPH Gamma 0.329 13.96Konsep: batch, satu resep untuk banyak berkas #
Tiga skrip berikutnya membahas batch: pekerjaan yang sama diulang untuk banyak masukan. Bayangkan Anda memasak resep yang sama untuk tiga puluh porsi. Resepnya ditulis sekali, lalu perulangan for (Bab 6) memasaknya satu porsi demi satu porsi. Buku I4 Bab 3 menunjukkan cara bawaan QGIS (Execute as batch process). Cara itu cocok untuk beberapa berkas. Untuk puluhan berkas, skrip lebih ringkas.

Istilah baru bagian ini:
- Batch: pekerjaan sama, banyak masukan.
- Pola berkas (
glob): cara mencari banyak berkas sekaligus, misalnya semua yang berakhiran.tif. - Latar belakang: jalur kerja terpisah, supaya QGIS tidak membeku.
Skrip 21.4: Batch sederhana, NDVI untuk semua ubin #
Tujuan. Menghitung NDVI untuk semua ubin ortofoto di satu folder dengan satu perulangan for, menyimpan tiap hasil sebagai berkas, dan mencetak rata-ratanya.
Bahan. Paket data paket-i4, folder Ortofoto_Mentah (tiga ubin; pada data ini band 1 adalah NIR dan band 2 adalah Merah). Berkas b3_01_batch_ndvi.py. Ganti DATA di baris 8 dan HASIL di baris 9. Skrip membuat folder NDVI_ubin di dalam HASIL.

Skrip utuh:
# [SKRIP 3.1: Batch sederhana: NDVI untuk semua ubin ortofoto dalam satu folder]
# Penulis: Badar Mubarok Yogaswara
import glob
import os
import processing
from qgis.core import QgsRasterLayer
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
folder_masuk = DATA + "/Ortofoto_Mentah"
folder_keluar = HASIL + "/NDVI_ubin"
os.makedirs(folder_keluar, exist_ok=True) # buat folder bila belum ada
# 1. Kumpulkan semua berkas .tif, diurutkan agar hasilnya selalu berurutan sama
daftar = sorted(glob.glob(folder_masuk + "/*.tif"))
print("Ditemukan", len(daftar), "ubin")
# 2. Ulangi pekerjaan yang sama untuk tiap berkas. Pada ubin ini band 1 = NIR dan band 2 = Merah.
berhasil = []
for jalur in daftar:
nama = os.path.basename(jalur) # contoh: ubin_01.tif
keluar = folder_keluar + "/NDVI_" + nama
ubin = QgsRasterLayer(jalur, "ubin", "gdal") # nama "ubin" dipakai di ekspresi
processing.run("native:rastercalc", {
"EXPRESSION": '("ubin@1" - "ubin@2") / ("ubin@1" + "ubin@2")',
"LAYERS": [ubin], "OUTPUT": keluar})
hasil = QgsRasterLayer(keluar, "hasil", "gdal")
rata = processing.run("native:rasterlayerstatistics", {"INPUT": hasil, "BAND": 1})["MEAN"]
print(" %s -> %s | NDVI rata-rata %.3f" % (nama, os.path.basename(keluar), rata))
berhasil.append(keluar)
print("Selesai:", len(berhasil), "dari", len(daftar), "berkas")Penjelasan baris demi baris.
| Baris | Tulisan | Artinya |
|---|---|---|
| 3-6 | import glob, import os, import processing, from qgis.core ... | glob untuk mencari berkas, os untuk urusan folder dan nama berkas. |
| 10-12 | folder_masuk, folder_keluar, os.makedirs(...) | Tentukan folder masukan dan keluaran, lalu buat folder keluaran bila belum ada. |
| 15 | daftar = sorted(glob.glob(folder_masuk + "/*.tif")) | glob.glob mengembalikan semua jalur berkas berakhiran .tif. sorted mengurutkannya agar hasil selalu berurutan sama. |
| 16 | print("Ditemukan", len(daftar), "ubin") | Cetak jumlah berkas yang ditemukan. |
| 19 | berhasil = [] | Daftar kosong untuk mencatat hasil. |
| 20 | for jalur in daftar: | Mulai perulangan. Di tiap putaran, jalur berisi satu berkas. |
| 21-22 | nama = os.path.basename(jalur) lalu keluar = ... | Ambil nama berkas saja (misalnya ubin_01.tif), lalu susun jalur keluaran NDVI_ubin_01.tif. |
| 23 | ubin = QgsRasterLayer(jalur, "ubin", "gdal") | Buka ubin sebagai layer bernama ubin. Nama ini dipakai di rumus. |
| 24-26 | processing.run("native:rastercalc", {...}) | Hitung NDVI. Band 1 adalah NIR dan band 2 adalah Merah, jadi rumusnya ("ubin@1" - "ubin@2") / ("ubin@1" + "ubin@2"). OUTPUT berisi jalur berkas, jadi hasilnya ditulis ke sana. |
| 27 | hasil = QgsRasterLayer(keluar, "hasil", "gdal") | Buka berkas hasil sebagai layer. |
| 28 | rata = processing.run("native:rasterlayerstatistics", {...})["MEAN"] | Jalankan alat statistik raster. Hasilnya kamus dengan beberapa kunci. Skrip mengambil kunci MEAN. |
| 29-30 | print(...) dan berhasil.append(keluar) | Cetak satu baris ringkas, lalu catat berkas ke daftar. |
| 32 | print("Selesai:", len(berhasil), "dari", len(daftar), "berkas") | Cetak ringkasan akhir. |
Hasil. Layar menampilkan (berdasarkan uji pada QGIS 4.0.2):
Ditemukan 3 ubin
ubin_01.tif -> NDVI_ubin_01.tif | NDVI rata-rata 0.471
ubin_02.tif -> NDVI_ubin_02.tif | NDVI rata-rata 0.471
ubin_03.tif -> NDVI_ubin_03.tif | NDVI rata-rata 0.467
Selesai: 3 dari 3 berkasDi folder NDVI_ubin kini ada tiga berkas NDVI. QGIS dapat menambahkan berkas pendamping berakhiran .aux.xml. Berkas itu aman dibiarkan.
Bagian yang boleh diubah.
- Baris 10, folder masukan. Ganti
/Ortofoto_Mentahdengan folder lain. - Baris 15, pola
"/*.tif". Ganti ke"/ubin_0*.tif"untuk memilih sebagian berkas. - Baris 25, rumus. Urutan band harus sesuai data Anda. Perhatikan: di
Citra_KPH.tif(Skrip 21.3), band 4 adalah NIR, sedangkan di ubin ini band 1. - Baris 22, awalan
"NDVI_". Ganti sesuai selera.
Jebakan. Semua dicoba pada QGIS 4.0.2.
- Folder salah, tanpa galat.
Ortofoto_MentahdigantiOrtofoto_Mentahh. Layar menampilkanDitemukan 0 ubindanSelesai: 0 dari 0 berkas. Perulangan tidak berputar sama sekali. Baca baris pertama hasil. - Satu berkas rusak menghentikan semuanya. Folder
Ortofoto_Campurberisi empat ubin, dan ubin keempat rusak. Layar menampilkanDitemukan 4 ubin, tiga baris hasil, laluERROR 1: Attempt to create -2147483648x-2147483648 dataset is illegal,sizes must be larger than zero.dan berakhir denganQgsProcessingException: Error creating output file.Tiga ubin pertama sudah selesai, tetapi ringkasan akhir tidak pernah tercetak. Penanganannya (trydanexcept) dibahas di Bab 10. - Band terbalik, tanpa galat. Rumus ditulis
("ubin@2" - "ubin@1") / .... Hasilnya-0.471,-0.471, dan-0.467. Tanda minus menandakan band terbalik. - Band yang tidak ada. Menulis
"ubin@4"pada ubin dua band menghasilkanQgsProcessingException: Error occurred while performing calculation. berhasilselalu sama panjang. Daftarberhasilselalu terisi setiap putaran, sehingga ringkasan "3 dari 3" belum berarti ada penyaringan. Ia baru berguna bila disertai penanganan galat.
Latihan variasi. Tambahkan satu baris di dalam perulangan yang mencetak ukuran piksel tiap hasil.
Contoh jawaban (sudah diuji). Sisipkan sebelum berhasil.append(keluar):
print(" ukuran:", hasil.width(), "x", hasil.height(), "piksel")Di bawah tiap baris NDVI, layar menampilkan ukuran: 100 x 100 piksel.
Skrip 21.5: Satu putaran untuk tiap fitur #
Perulangan tidak harus atas berkas. Skrip ini berputar atas fitur: untuk tiap KPH, ia memilih satu poligon lalu memotong raster tinggi tajuk (CHM) dengannya. Hasilnya satu raster per KPH.
Tujuan. Menghasilkan raster CHM terpisah untuk tiap KPH, lalu mencetak ukuran, rata-rata, dan nilai tertingginya.
Bahan. Paket data paket-i4: Batas_KPH.gpkg, DSM_Drone.tif, dan DTM_Drone.tif. Berkas b3_02_batch_per_kph.py. Ganti DATA di baris 7 dan HASIL di baris 8. Skrip membuat folder CHM_per_KPH.

Skrip utuh:
# [SKRIP 3.2: Batch per fitur: potong raster tinggi tajuk (CHM) untuk tiap KPH]
# Penulis: Badar Mubarok Yogaswara
import os
import processing
from qgis.core import QgsVectorLayer, QgsRasterLayer
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
folder_keluar = HASIL + "/CHM_per_KPH"
os.makedirs(folder_keluar, exist_ok=True)
batas = QgsVectorLayer(DATA + "/Batas_KPH.gpkg", "Batas_KPH", "ogr")
dsm = QgsRasterLayer(DATA + "/DSM_Drone.tif", "DSM", "gdal")
dtm = QgsRasterLayer(DATA + "/DTM_Drone.tif", "DTM", "gdal")
# 1. CHM untuk seluruh area (satu kali saja, di luar pengulangan)
chm_jalur = processing.run("native:rastercalc", {
"EXPRESSION": '"DSM@1" - "DTM@1"', "LAYERS": [dsm, dtm], "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
# 2. Satu putaran untuk tiap KPH: pilih satu poligon, lalu potong CHM dengannya
for fitur in batas.getFeatures():
nama = fitur["NAMA_KPH"] # contoh: KPH Alpha
aman = nama.replace(" ", "_") # untuk nama berkas: KPH_Alpha
satu_kph = processing.run("native:extractbyexpression", {
"INPUT": batas, "EXPRESSION": '"NAMA_KPH" = \'%s\'' % nama,
"OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
keluar = folder_keluar + "/CHM_" + aman + ".tif"
processing.run("gdal:cliprasterbymasklayer", {
"INPUT": chm_jalur, "MASK": satu_kph, "CROP_TO_CUTLINE": True,
"NODATA": -9999, "OUTPUT": keluar})
hasil = QgsRasterLayer(keluar, aman, "gdal")
st = processing.run("native:rasterlayerstatistics", {"INPUT": hasil, "BAND": 1})
print("%-10s %4d x %4d piksel | rata-rata %.2f m | tertinggi %.2f m" % (
aman, hasil.width(), hasil.height(), st["MEAN"], st["MAX"]))
print("Berkas di folder:", sorted(os.listdir(folder_keluar)))Penjelasan baris demi baris. Hanya yang baru yang dijelaskan.
| Baris | Tulisan | Artinya |
|---|---|---|
| 17-18 | chm_jalur = processing.run("native:rastercalc", {...})["OUTPUT"] | Hitung CHM untuk seluruh area satu kali saja, di luar perulangan. Menghitung ulang di tiap putaran hanya membuang waktu. |
| 21 | for fitur in batas.getFeatures(): | Satu putaran untuk tiap KPH. |
| 22-23 | nama = fitur["NAMA_KPH"] lalu aman = nama.replace(" ", "_") | Ambil nama KPH, lalu ganti spasi dengan garis bawah untuk nama berkas: KPH_Alpha. |
| 24-26 | satu_kph = processing.run("native:extractbyexpression", {...})["OUTPUT"] | Ambil satu poligon saja dengan ekspresi. '"NAMA_KPH" = \'%s\'' % nama menyisipkan nama ke dalam teks ekspresi, dengan kutip tunggal di sekelilingnya. |
| 27 | keluar = folder_keluar + "/CHM_" + aman + ".tif" | Susun nama berkas keluaran. |
| 28-30 | processing.run("gdal:cliprasterbymasklayer", {...}) | Potong CHM dengan poligon tadi. INPUT raster yang dipotong, MASK poligon pemotong, CROP_TO_CUTLINE berarti ukuran hasil mengikuti poligon, dan NODATA nilai untuk piksel di luar poligon. |
| 31-32 | hasil = QgsRasterLayer(...) lalu st = ...rasterlayerstatistics... | Buka hasil, lalu hitung statistiknya. Kali ini seluruh kamus disimpan di st, bukan hanya satu kuncinya. |
| 33-34 | print("%-10s %4d x %4d piksel ..." % (...)) | Cetak satu baris: nama, lebar, tinggi, rata-rata (st["MEAN"]), dan nilai tertinggi (st["MAX"]). Tanda garis tegak di dalam tulisan yang dicetak hanyalah pemisah tampilan. |
| 35 | print("Berkas di folder:", sorted(os.listdir(folder_keluar))) | Cetak isi folder keluaran, diurutkan. |
Hasil. Layar menampilkan (berdasarkan uji pada QGIS 4.0.2):
KPH_Alpha 400 x 400 piksel | rata-rata 2.60 m | tertinggi 13.94 m
KPH_Beta 400 x 400 piksel | rata-rata 2.69 m | tertinggi 13.96 m
KPH_Gamma 800 x 400 piksel | rata-rata 2.56 m | tertinggi 13.96 m
Berkas di folder: ['CHM_KPH_Alpha.tif', 'CHM_KPH_Alpha.tif.aux.xml', 'CHM_KPH_Beta.tif', 'CHM_KPH_Beta.tif.aux.xml', 'CHM_KPH_Gamma.tif']Perhatikan daftar berkas di baris terakhir. Berkas .aux.xml dibuat oleh QGIS saat menghitung statistik, bukan oleh skrip secara sengaja. Isi daftar di komputer Anda bisa berbeda, misalnya CHM_KPH_Gamma.tif.aux.xml ikut muncul bila skrip dijalankan untuk kedua kalinya. KPH Gamma berukuran 800 kali 400 piksel karena areanya dua kali lebih luas.
Bagian yang boleh diubah.
- Baris 9-10, folder keluaran. Ganti nama folder.
- Baris 25, kolom
NAMA_KPH. Ganti bila data Anda memakai kolom lain. - Baris 29,
CROP_TO_CUTLINE.Truememotong ukuran hasil mengikuti KPH. - Baris 30,
NODATA. Nilai -9999 menandai piksel kosong. Nilai ini ikut dikenali saat statistik dihitung. - Baris 17-18, rumus CHM. Ganti rumus atau raster, misalnya untuk NDVI.
Jebakan. Semua dicoba pada QGIS 4.0.2.
- Nama KPH yang memuat kutip tunggal. Pada uji, nama
KPH D'Alphadisisipkan ke baris 25. Alat berhenti denganIncomplete expression. You might not have finished the full expression.Kutip tunggal di tengah nama mengakhiri teks ekspresi lebih awal. Data latihan tidak memuat nama seperti itu. - Nama kolom salah.
fitur["NAMA_KPH"]digantifitur["NAMA"]:KeyError: 'NAMA'. CROP_TO_CUTLINEbernilaiFalse. Tidak ada galat. Ketiga hasil berukuran800 x 800 piksel, karena ukuran tidak dipotong mengikuti KPH. Rata-rata Beta dan Gamma di layar tetap2.69dan2.56.- Berkas pendamping di daftar. Karena baris 35 mencetak semua isi folder, berkas
.aux.xmlikut tampil.
Latihan variasi. Jalankan hanya untuk KPH Gamma. Bagian mana yang cukup ditambah?
Contoh jawaban (sudah diuji). Sisipkan dua baris setelah baris 22:
if nama != "KPH Gamma":
continuecontinue berarti "lewati sisa putaran ini, lanjut ke fitur berikutnya". Layar menampilkan satu baris KPH_Gamma 800 x 400 piksel | rata-rata 2.56 m | tertinggi 13.96 m, disusul daftar isi folder (yang juga memuat berkas dari uji sebelumnya).
Konsep: latar belakang dan fungsi selesai #
Skrip 21.4 dan 21.5 membuat QGIS terasa macet selama bekerja. Bayangkan Anda pelayan restoran yang juga harus memasak. Selama Anda memasak, tamu tidak terlayani. Solusinya: serahkan masakan ke juru masak di dapur belakang, sementara Anda tetap melayani. QGIS menyediakan juru masak ini sebagai QgsTask, yaitu tugas latar belakang.

Pembagian kerjanya begini. Fungsi tugas berjalan di dapur belakang dan hanya boleh memasak (menghitung). Fungsi selesai berjalan di depan setelah masakan jadi, dan aman untuk menyentuh proyek, misalnya menambah layer. Fungsi selesai disebut juga callback, yaitu fungsi yang dipanggil kembali setelah tugas berakhir.
Tiga istilah baru:
- QgsTask: tugas latar belakang milik QGIS.
- Fungsi tugas: fungsi yang dikerjakan di latar belakang.
- Fungsi selesai: fungsi yang dipanggil di program utama setelah tugas berakhir.
Skrip 21.6: Batch di latar belakang dengan QgsTask #
Tujuan. Menjalankan batch NDVI di latar belakang, lengkap dengan bilah kemajuan dan kemungkinan pembatalan, lalu menambahkan hasilnya ke proyek.
Bahan. Paket data paket-i4, folder Ortofoto_Mentah. Berkas b3_03_batch_latar_belakang.py. Ganti DATA di baris 8 dan HASIL di baris 9. Skrip membuat folder NDVI_latar. Skrip ini paling pas dijalankan di Editor Skrip QGIS yang terbuka, karena tugas latar belakang butuh QGIS yang hidup.

Skrip utuh:
# [SKRIP 3.3: Batch di latar belakang dengan QgsTask supaya QGIS tidak macet]
# Penulis: Badar Mubarok Yogaswara
import glob
import os
import processing
from qgis.core import QgsApplication, QgsTask, QgsMessageLog, Qgis, QgsRasterLayer, QgsProject
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
folder_masuk = DATA + "/Ortofoto_Mentah"
folder_keluar = HASIL + "/NDVI_latar"
os.makedirs(folder_keluar, exist_ok=True)
def hitung_ndvi(task, folder_in, folder_out):
"""Berjalan di LATAR BELAKANG. Jangan menyentuh proyek atau antarmuka di sini."""
daftar = sorted(glob.glob(folder_in + "/*.tif"))
keluar = []
for i, jalur in enumerate(daftar):
if task.isCanceled(): # pengguna menekan Cancel
return None
tujuan = folder_out + "/NDVI_" + os.path.basename(jalur)
ubin = QgsRasterLayer(jalur, "ubin", "gdal")
processing.run("native:rastercalc", {
"EXPRESSION": '("ubin@1" - "ubin@2") / ("ubin@1" + "ubin@2")',
"LAYERS": [ubin], "OUTPUT": tujuan})
keluar.append(tujuan)
task.setProgress(100 * (i + 1) / len(daftar)) # bilah kemajuan di pojok kanan bawah QGIS
return keluar
def selesai(exception, hasil=None):
"""Berjalan di PROGRAM UTAMA setelah tugas berakhir. Aman untuk menyentuh proyek."""
if exception is not None:
QgsMessageLog.logMessage("Tugas gagal: %s" % exception, "Batch NDVI", Qgis.Critical)
elif hasil is None:
QgsMessageLog.logMessage("Tugas dibatalkan", "Batch NDVI", Qgis.Warning)
else:
for jalur in hasil:
nama = os.path.basename(jalur)[:-4]
QgsProject.instance().addMapLayer(QgsRasterLayer(jalur, nama, "gdal"))
QgsMessageLog.logMessage("Selesai: %d berkas" % len(hasil), "Batch NDVI", Qgis.Success)
print("Tugas selesai:", len(hasil), "berkas NDVI ditambahkan ke proyek")
tugas = QgsTask.fromFunction("Hitung NDVI semua ubin", hitung_ndvi, on_finished=selesai,
folder_in=folder_masuk, folder_out=folder_keluar)
QgsApplication.taskManager().addTask(tugas)
print("Tugas dikirim ke latar belakang. QGIS tetap bisa dipakai.")Penjelasan baris demi baris. Skrip ini terdiri atas dua fungsi dan tiga baris pengiriman. Rumus NDVI di baris 25 sama dengan Skrip 21.4.
Blok A: fungsi tugas, yang bekerja di latar belakang (baris 15-29).
| Baris | Tulisan | Artinya |
|---|---|---|
| 15 | def hitung_ndvi(task, folder_in, folder_out): | Fungsi tugas. Parameter pertama task diisi otomatis oleh QGIS. Dua lainnya diisi dari baris 47. |
| 16 | """Berjalan di LATAR BELAKANG...""" | Catatan di awal fungsi (disebut docstring, Bab 7). Isinya peringatan: jangan menyentuh proyek atau antarmuka di sini. |
| 17-18 | daftar = ... dan keluar = [] | Kumpulkan berkas, siapkan daftar hasil. |
| 19 | for i, jalur in enumerate(daftar): | Ulangi tiap ubin. enumerate menambahkan nomor urut i mulai dari 0 (Bab 6). |
| 20-21 | if task.isCanceled(): return None | Bila pengguna menekan Cancel, berhenti dan kembalikan None. |
| 22-27 | tujuan = ... sampai keluar.append(tujuan) | Hitung NDVI satu ubin ke berkas, lalu catat jalurnya. |
| 28 | task.setProgress(100 * (i + 1) / len(daftar)) | Isi bilah kemajuan, 0 sampai 100 persen. |
| 29 | return keluar | Kembalikan daftar berkas hasil. Nilai ini akan diterima fungsi selesai. |
Blok B: fungsi selesai, yang bekerja di depan (baris 32-43).
| Baris | Tulisan | Artinya |
|---|---|---|
| 32 | def selesai(exception, hasil=None): | Fungsi selesai. exception berisi galat bila tugas gagal. hasil berisi daftar dari return keluar. =None adalah nilai bawaan (Bab 7). |
| 34-35 | if exception is not None: | Bila ada galat, catat ke log sebagai Qgis.Critical. |
| 36-37 | elif hasil is None: | Bila tidak ada hasil, tugas dianggap dibatalkan. Catat Qgis.Warning. |
| 38-43 | else: | Bila sukses: untuk tiap jalur, buat layer dan tambahkan ke proyek (baris 39-41), catat Qgis.Success (baris 42), lalu cetak pesan (baris 43). [:-4] membuang empat huruf terakhir (.tif) dari nama berkas. |
Blok C: membuat dan mengirim tugas (baris 46-49).
| Baris | Tulisan | Artinya |
|---|---|---|
| 46-47 | tugas = QgsTask.fromFunction("Hitung NDVI semua ubin", hitung_ndvi, on_finished=selesai, folder_in=..., folder_out=...) | Bungkus fungsi tugas menjadi tugas. Isian: judul, fungsi tugas, fungsi selesai (on_finished), lalu argumen bernama yang diteruskan ke fungsi tugas. Fungsi dipakai sebagai nilai tanpa kurung. |
| 48 | QgsApplication.taskManager().addTask(tugas) | Serahkan tugas ke manajer tugas QGIS. Tugas mulai berjalan. |
| 49 | print("Tugas dikirim ...") | Cetak pesan. Pesan ini muncul sebelum tugas selesai, karena skrip tidak menunggu. |
Hasil. Layar menampilkan (berdasarkan uji pada QGIS 4.0.2):
Tugas dikirim ke latar belakang. QGIS tetap bisa dipakai.
Tugas selesai: 3 berkas NDVI ditambahkan ke proyekPesan pertama muncul langsung. Pesan kedua muncul beberapa saat kemudian, setelah tugas selesai. Di dalam QGIS, kemajuan tugas tampil di bilah status dan pesan lognya di panel Log Messages dengan tag Batch NDVI [CEK: tampilan visualnya, tidak dapat diuji tanpa antarmuka QGIS]. Tiga layer baru NDVI_ubin_01 sampai NDVI_ubin_03 masuk ke proyek.
Bagian yang boleh diubah.
- Baris 8-9, jalur. Sesuaikan dengan komputer Anda.
- Baris 17, daftar berkas. Ubah pola berkas atau batasi jumlahnya.
- Baris 25, rumus. Sesuaikan urutan band.
- Baris 35, 37, dan 42, tag log
"Batch NDVI". Ganti dengan nama yang Anda suka. - Baris 46, judul tugas. Judul ini tampil di daftar tugas QGIS.
Jebakan.
- Skrip tidak menunggu tugas. Pesan "Tugas dikirim" tampil sebelum NDVI selesai. Itu normal di dalam QGIS.
- Skrip mandiri harus menunggu sendiri. Pada uji, skrip ini dijalankan dengan kerangka mandiri, lalu dibiarkan habis begitu saja. Layar hanya menampilkan
Tugas dikirim ..., kemudian proses berhenti mendadak (kode keluar 139, tanda proses mogok). Pesan "Tugas selesai" tidak pernah muncul. Perbaikannya: sebelumapp.exitQgis(), tunggu tugas dengan perulangan berikut. Dengan perulangan ini, uji berjalan normal dan menampilkan kedua pesan.
import time
while QgsApplication.taskManager().countActiveTasks() > 0:
app.processEvents()
time.sleep(0.05)
for _ in range(20):
app.processEvents()
time.sleep(0.02)- Fungsi tugas tidak boleh menyentuh proyek atau antarmuka. Komentar di baris 16 dan 33 dan Buku I4 mengingatkan bahwa menambah layer dari fungsi tugas dapat membuat QGIS macet atau tertutup sendiri. Itu sebabnya
addMapLayerada di fungsi selesai. Bagian ini tidak diuji [CEK]. - Berkas pendamping. Seperti Skrip 21.4, QGIS dapat membuat berkas
.aux.xmldi folderNDVI_latar.
Latihan variasi. Batasi tugas hanya untuk dua ubin pertama.
Contoh jawaban (sudah diuji). Ubah baris 17 menjadi:
daftar = sorted(glob.glob(folder_in + "/*.tif"))[:2][:2] mengambil dua isi pertama dari daftar. Layar menampilkan Tugas selesai: 2 berkas NDVI ditambahkan ke proyek, dan folder NDVI_latar berisi NDVI_ubin_01.tif dan NDVI_ubin_02.tif.
Cek paham #
- Apa isi
hasil["OUTPUT"]bilaOUTPUTberupa"TEMPORARY_OUTPUT"pada alat vektor? Bagaimana bila alatnya raster? - Mengapa CHM dihitung di luar perulangan pada Skrip 21.5?
- Fungsi mana yang boleh menambahkan layer ke proyek pada Skrip 21.6, dan mengapa?
Jawaban.
- Pada alat vektor, isinya layer (
QgsVectorLayer) yang siap dipakai. Pada alat raster, isinya teks jalur berkas, yang harus dibungkus denganQgsRasterLayer. - CHM seluruh area sama untuk semua KPH. Menghitungnya sekali cukup, dan menghitungnya di tiap putaran hanya membuang waktu.
- Fungsi selesai. Ia berjalan di program utama, sedangkan fungsi tugas berjalan di latar belakang dan tidak boleh menyentuh proyek.
Kesalahan umum #
- Salah ketik nama isian atau kolom. Nama isian yang tak dikenal diabaikan diam-diam (Skrip 21.1), sedangkan nama kolom yang salah memicu
KeyError. Perbaikan: salin nama dariparameterDefinitions()dan dari daftar kolom layer. - Mengira layer yang gagal dibuka akan menimbulkan galat. Alat tetap jalan dengan layer kosong dan menghasilkan angka nol. Perbaikan: periksa
isValid()dan baca baris pertama hasil. - Urutan band terbalik. NDVI menjadi negatif tanpa pesan apa pun. Perbaikan: cek urutan band tiap dataset dan curigai tanda minus.
- Memakai satuan jarak yang salah.
DISTANCEmengikuti CRS, jadi 30 pada CRS derajat berarti 30 derajat. Perbaikan: pastikan CRS metrik. - Skrip mandiri yang tidak menunggu tugas latar belakang. Prosesnya berhenti mendadak sebelum tugas selesai. Perbaikan: tambahkan perulangan tunggu.
Ringkasan dan latihan #
processing.run("ID_ALAT", {kamus parameter})memanggil alat Processing. Hasilnya kamus, dan jawabannya diambil dengan kunci seperti["OUTPUT"].- Vektor sementara berupa layer. Raster sementara dan keluaran berupa berkas berupa teks jalur.
- Merangkai alat berarti memakai hasil satu alat sebagai masukan alat berikutnya.
- Batch berarti satu resep, banyak masukan: perulangan atas berkas (
glob) atau atas fitur (getFeatures). QgsTaskmenjalankan pekerjaan berat di latar belakang. Fungsi tugas menghitung, fungsi selesai menyentuh proyek.
Latihan. Gabungkan Skrip 21.2 dan 21.5. Untuk tiap KPH, buat sempadan sungai 30 meter, lalu potong CHM dengan sempadan itu, dan cetak rata-rata tinggi tajuk di sempadan. Alat apa yang Anda butuhkan di setiap langkah, dan bagian mana yang dipinjam dari tiap skrip?
Tabel perbandingan: memanggil alat dengan klik dan dengan skrip #
| Pekerjaan | Dengan klik di QGIS | Dengan skrip |
|---|---|---|
| Memilih alat | Cari di Processing Toolbox | ID alat, misalnya native:buffer |
| Mengisi kolom | Isi dialog | Kamus parameter |
| Menjalankan | Tombol Run | processing.run(ID, kamus) |
| Mengambil hasil | Layer baru di panel Layers | hasil["OUTPUT"], lalu addMapLayer |
| Banyak berkas | Execute as batch process | Perulangan for dengan glob |
| Pekerjaan berat | Alat berjalan, QGIS terasa macet | QgsTask di latar belakang |