BAB 28: Pola dan Improvisasi: Kerangka Siap Salin dan Cara Mengubahnya
Studi kasus: "Datanya berubah. Skripnya bagaimana?" #
Analis Muda sudah menulis puluhan skrip. Lalu datang data baru: folder berbeda, nama kolom berbeda, jumlah berkas bertambah. Ia tidak ingin menulis dari nol. Kepala Seksi berpesan sederhana: "Jangan hafalkan skripnya. Kenali polanya." Bab ini memberi Anda pola-pola itu, siap disalin, lengkap dengan petunjuk bagian mana yang diganti.
Konsep: improvisasi dalam tiga kalimat #
Memasak dari resep itu mudah. Berimprovisasi artinya Anda tahu fungsi tiap bahan, jadi bisa menggantinya. Skrip GIS sama: hampir semuanya tersusun dari beberapa pola dasar yang disambung-sambung. Bila Anda mengenal polanya, data baru hanya berarti mengganti beberapa nama dan angka.

Setiap pola di bab ini dibangun dari bagian yang sudah Anda kenal. Lima istilah baru:
- Pola: kerangka skrip yang berulang di banyak tempat.
- Kerangka: pola yang sudah dibersihkan dari isi, siap Anda isi dengan data sendiri.
- Jebakan: kesalahan yang sering terjadi dan tidak selalu menimbulkan pesan galat.
- Konsol Python QGIS: tempat mengetik dan menjalankan Python di dalam QGIS (Bab 3).
- python-qgis: cara menjalankan Python berisi QGIS dari terminal, tanpa membuka jendela QGIS (Bab 3).
Menyiapkan dulu: data dan cara menjalankan #
Pola di bab ini memakai dua paket data: paket-b2 (petak, titik plot, tabel inventarisasi) dan paket-i4 (batas KPH, sungai, titik panas, kiriman lapangan, ubin foto drone). Letakkan keduanya di satu folder, misalnya D:/Latihan.
Di tiap skrip, dua baris ini menentukan lokasi:
DATA: folder yang berisipaket-b2danpaket-i4.HASIL: folder untuk berkas keluaran. Dibuat otomatis bila belum ada.
Kedua lokasi dicari lebih dulu di variabel lingkungan PY_DATA dan PY_HASIL. Bila variabel itu tidak ada, jalur contoh D:/Latihan dipakai (Bab 8). Ganti jalur contoh itu sesuai komputer Anda. Semua kode di bab ini diuji dengan python-qgis.bat di QGIS 4.0.2. Di Konsol Python QGIS, kode yang sama belum saya jalankan dari sini. [CEK]
Pola 28.0: Kepala skrip untuk terminal #
Tujuan. Menyalakan QGIS bila skrip dijalankan dari terminal, supaya processing.run dan QgsVectorLayer bisa dipakai.
Bahan. Tidak ada data. Di Konsol Python QGIS, tidak perlu kepala ini, karena QGIS sudah menyala.
Skrip utuh:
# Kepala skrip: hanya untuk terminal (python-qgis). Di Konsol Python QGIS tidak perlu.
import os
from qgis.core import QgsApplication
if QgsApplication.instance() is None:
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
app = QgsApplication([], False)
app.initQgis()
import sys
sys.path.append(os.path.join(QgsApplication.prefixPath(), "python", "plugins"))
from processing.core.Processing import Processing
Processing.initialize()
import processing
print("QGIS siap.")| Baris | Tulisan | Artinya |
|---|---|---|
| 4 | if QgsApplication.instance() is None: | Tanya: apakah QGIS sudah menyala? Di Konsol jawabannya ya, jadi blok di bawahnya dilewati. Dari terminal jawabannya tidak. |
| 5 | os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") | Nyalakan tanpa jendela. |
| 6-7 | app = QgsApplication([], False) dan app.initQgis() | Buat dan nyalakan mesin QGIS. |
| 9 | sys.path.append(os.path.join(QgsApplication.prefixPath(), "python", "plugins")) | Tambahkan folder tempat modul processing berada. Lokasi dihitung dari tempat QGIS terpasang, jadi tidak perlu menulis jalur komputer. |
| 10-11 | Processing.initialize() | Daftarkan semua alat Processing. |
| 12 | import processing | Panggil perkakas processing. |
Cara memakainya: tempel isi kepala ini di atas pola mana pun yang dijalankan dari terminal. Cara praktis lain: simpan kepala sebagai kepala.py dan pola sebagai berkas lain, lalu gabungkan.
cat kepala.py pola_28_3.py > jalankan.py
"C:/Program Files/QGIS 4.0.2/bin/python-qgis.bat" jalankan.py(Di Windows PowerShell, gunakan Get-Content kepala.py, pola_28_3.py | Set-Content jalankan.py.) Semua hasil di bawah diawali satu baris QGIS siap. dari terminal. Baris itu saya hilangkan dari contoh.
Jebakan. Jalur C:/Program Files/QGIS 4.0.2 di perintah terminal adalah lokasi QGIS di komputer uji. Ganti sesuai pemasangan Anda.
Pola 28.1: Muat, ubah, simpan #
Tujuan. Membuka satu layer, mengubahnya (di sini: menyaring), lalu menyimpan hasilnya ke berkas baru. Hampir semua skrip dasar di Bab 14 sampai 17 memakai pola ini.
Bahan. paket-b2/Petak_KPH.gpkg, layer Petak (16 petak).
Analogi: ini fotokopi dengan stabilo. Ambil dokumen (muat), tandai bagian yang Anda perlukan (ubah), simpan salinannya (simpan). Dokumen asli tidak disentuh.
Skrip utuh:
import os
from qgis.core import QgsVectorLayer, QgsVectorFileWriter, QgsProject
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
HASIL = os.environ.get("PY_HASIL", r"D:/Latihan/hasil")
os.makedirs(HASIL, exist_ok=True)
# 1. Muat
petak = QgsVectorLayer(DATA + "/paket-b2/Petak_KPH.gpkg|layername=Petak", "Petak", "ogr")
assert petak.isValid(), "Layer tidak terbuka. Cek DATA dan nama berkas."
print("Muat:", petak.featureCount(), "petak")
# 2. Ubah: saring jenis Jati
petak.setSubsetString("\"JENIS\" = 'Jati'")
print("Setelah disaring:", petak.featureCount(), "petak")
# 3. Simpan
opsi = QgsVectorFileWriter.SaveVectorOptions()
opsi.driverName = "GPKG"
opsi.layerName = "Petak_Jati"
hasil = QgsVectorFileWriter.writeAsVectorFormatV3(
petak, HASIL + "/petak_jati.gpkg", QgsProject.instance().transformContext(), opsi)
print("Simpan berhasil:", hasil[0] == QgsVectorFileWriter.WriterError.NoError)
petak.setSubsetString("")| Baris | Tulisan | Artinya |
|---|---|---|
| 4-6 | DATA, HASIL, os.makedirs(...) | Lokasi data dan keluaran. exist_ok=True berarti tidak apa-apa bila folder sudah ada. |
| 9 | QgsVectorLayer(DATA + "/paket-b2/Petak_KPH.gpkg ..." , "Petak", "ogr") | Buka layer. Tiga isian: sumber, nama layer, dan pembaca "ogr" (Bab 15). Sumber berupa jalur berkas, lalu satu tanda garis tegak, lalu layername=Petak, yaitu nama layer di dalam berkas GeoPackage. |
| 10 | assert petak.isValid(), "..." | Hentikan skrip bila layer gagal dibuka, dengan pesan yang jelas (Bab 10). |
| 14 | petak.setSubsetString("\"JENIS\" = 'Jati'") | Saring: hanya petak berjenis Jati yang terlihat. Teks saringan memakai kutip ganda untuk nama kolom dan kutip tunggal untuk nilai. |
| 18-20 | opsi = QgsVectorFileWriter.SaveVectorOptions() dst. | "Formulir" penyimpanan: format GPKG dan nama layer Petak_Jati. |
| 21-22 | QgsVectorFileWriter.writeAsVectorFormatV3(petak, ..., opsi) | Tulis layer ke berkas. Hasilnya daftar kecil; isian pertamanya adalah kode keberhasilan. |
| 23 | hasil[0] == QgsVectorFileWriter.WriterError.NoError | Bandingkan kode itu dengan "tidak ada galat". |
| 24 | petak.setSubsetString("") | Kembalikan layer seperti semula. Saringan kosong berarti semua petak terlihat. |
Hasil. Layar menampilkan (berdasarkan uji pada QGIS 4.0.2):
Muat: 16 petak
Setelah disaring: 5 petak
Simpan berhasil: TrueBerkas petak_jati.gpkg terbentuk di folder HASIL.
Bagian yang boleh diubah.
- Baris 4-5, lokasi data dan hasil.
- Baris 9, berkas dan layer. Ganti untuk layer lain.
- Baris 14, saringan. Kolom dan nilai yang disaring.
- Baris 19,
driverName. Format keluaran."GPKG"untuk GeoPackage. Nama format lain mengikuti daftar format GDAL. [CEK] - Baris 20 dan 22, nama layer dan berkas keluaran.
Jebakan.
- Saringan menempel pada layer.
setSubsetStringmengubah layer sampai dikosongkan lagi. Dalam uji: dengan saringan 5 petak, tanpa saringan 16. Bila skrip berhenti sebelum baris 24, saringan tetap menempel. - Jalur salah tidak memunculkan galat. Baca bagian "Jebakan lintas bab" di bawah.
Latihan variasi.
- Saring petak yang ditanam sebelum tahun 2010.
- Saring petak berjenis Mahoni.
Contoh jawaban, hanya bagian yang berubah:
petak.setSubsetString("\"TAHUN_TANAM\" < 2010")Hasil uji: 4 petak. Untuk Mahoni, ganti baris itu menjadi petak.setSubsetString("\"JENIS\" = 'Mahoni'"); hasil uji: 4 petak.
Pola 28.2: Loop fitur #
Tujuan. Memeriksa tiap fitur satu per satu, mengumpulkan hasilnya ke kamus, dan memilih fitur yang memenuhi syarat.
Bahan. Layer Petak yang sama.
Analogi: petugas dengan buku catatan yang berjalan dari petak ke petak. Tiap petak ia catat, lalu di akhir ia menjumlahkan catatannya.
Skrip utuh:
import os
from qgis.core import QgsVectorLayer, QgsFeatureRequest
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
petak = QgsVectorLayer(DATA + "/paket-b2/Petak_KPH.gpkg|layername=Petak", "Petak", "ogr")
assert petak.isValid(), "Layer tidak terbuka. Cek DATA dan nama berkas."
# Tiap petak diperiksa satu per satu; hasilnya dikumpulkan ke kamus
luas_per_kph = {}
for f in petak.getFeatures():
kph = f["NAMA_KPH"]
ha = f.geometry().area() / 10000
luas_per_kph[kph] = luas_per_kph.get(kph, 0) + ha
for kph, ha in sorted(luas_per_kph.items()):
print("%-10s %6.2f ha" % (kph, ha))
# Hanya fitur yang memenuhi syarat
syarat = QgsFeatureRequest().setFilterExpression('"TAHUN_TANAM" < 2010')
tua = [f["KODE_PETAK"] for f in petak.getFeatures(syarat)]
print("Ditanam sebelum 2010:", tua)| Baris | Tulisan | Artinya |
|---|---|---|
| 9 | luas_per_kph = {} | Buku catatan kosong: kamus yang kuncinya nama KPH. |
| 10 | for f in petak.getFeatures(): | Ulangi untuk tiap petak. f adalah petak yang sedang diperiksa (Bab 6 dan 15). |
| 11 | kph = f["NAMA_KPH"] | Ambil isi kolom NAMA_KPH petak ini. |
| 12 | ha = f.geometry().area() / 10000 | Luas bentuk petak, dalam satuan peta, dibagi 10000 menjadi hektare. Benar hanya bila peta berbasis meter. |
| 13 | luas_per_kph[kph] = luas_per_kph.get(kph, 0) + ha | Tambahkan ke catatan KPH itu. .get(kph, 0) berarti "bila belum ada, anggap 0". |
| 14-15 | for kph, ha in sorted(luas_per_kph.items()): | Cetak catatan berurutan. %-10s %6.2f mengatur lebar kolom: -10s teks rata kiri 10 huruf, 6.2f angka dua desimal selebar 6. |
| 18 | QgsFeatureRequest().setFilterExpression('"TAHUN_TANAM" < 2010') | Buat permintaan khusus: hanya petak yang ditanam sebelum 2010. |
| 19 | [f["KODE_PETAK"] for f in petak.getFeatures(syarat)] | Ambil kode petak yang lolos saringan, dalam satu baris (list comprehension, Bab 6). |
Hasil. Layar menampilkan:
KPH Alpha 4.00 ha
KPH Beta 4.00 ha
KPH Gamma 8.00 ha
Ditanam sebelum 2010: ['P-01', 'P-02', 'P-05', 'P-13']Bagian yang boleh diubah.
- Baris 11, nama kolom pengelompok. Ganti
NAMA_KPHdenganJENISatauSTATUSuntuk pengelompokan lain. - Baris 12, pembagi.
10000untuk hektare. Hapus pembagi untuk meter persegi. - Baris 13, penjumlahan. Ganti dengan
+ 1untuk menghitung jumlah petak, bukan luas. - Baris 18, syarat. Ekspresi QGIS biasa.
Jebakan.
- Nilai kolom kosong. Bila kolom berisi
NULLatau kosong, penjumlahan atau perbandingan bisa gagal. Periksa denganif nilai is not None. [CEK] - Pembagi sesuai satuan. Lihat "Jebakan lintas bab", butir satuan luas.
Latihan variasi.
- Hitung luas per jenis tanaman, bukan per KPH.
- Tampilkan kode petak milik KPH Gamma.
Contoh jawaban:
kph = f["JENIS"]pada baris 11 (nama kotak tidak perlu diganti). Hasil uji: {'Akasia': 2.94, 'Jati': 4.96, 'Mahoni': 4.04, 'Sengon': 4.06}. Untuk nomor 2, ganti syarat baris 18 menjadi '"NAMA_KPH" = \'KPH Gamma\''. Hasil uji: ['P-09', 'P-10', 'P-11', 'P-12', 'P-13', 'P-14', 'P-15', 'P-16'].
Pola 28.3: Memanggil alat Processing #
Tujuan. Menjalankan alat bawaan QGIS dari Python, dan merantai keluarannya menjadi masukan alat berikutnya. Ini inti Bab 18 sampai 23.
Bahan. paket-i4/Sungai.gpkg (2 garis sungai) dan paket-i4/Batas_KPH.gpkg (3 KPH).
Analogi: ban berjalan di pabrik. Hasil satu mesin langsung jadi bahan mesin berikutnya.
Skrip utuh:
import os
import processing
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
sungai = DATA + "/paket-i4/Sungai.gpkg"
batas = DATA + "/paket-i4/Batas_KPH.gpkg"
# Keluaran alat pertama menjadi masukan alat kedua
sempadan = processing.run("native:buffer", {
"INPUT": sungai, "DISTANCE": 30, "DISSOLVE": True,
"OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
irisan = processing.run("native:intersection", {
"INPUT": batas, "OVERLAY": sempadan,
"OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
for f in irisan.getFeatures():
print(f["NAMA_KPH"], round(f.geometry().area() / 10000, 2), "ha sempadan")| Baris | Tulisan | Artinya |
|---|---|---|
| 2 | import processing | Panggil perkakas pemanggil alat. |
| 5-6 | sungai = ... dan batas = ... | Jalur dua berkas. Alat Processing boleh diberi jalur berkas langsung. |
| 9-11 | processing.run("native:buffer", {...})["OUTPUT"] | Jalankan alat native:buffer. Isian alat diberikan sebagai kamus: nama isian lalu nilainya. "OUTPUT" di ujung mengambil hasil dari kamus jawaban. |
| 10 | "DISTANCE": 30 | Lebar sempadan: 30 satuan peta (meter bila peta berbasis meter). |
| 11 | "OUTPUT": "TEMPORARY_OUTPUT" | Simpan hasil di memori, sementara. Hasilnya layer siap pakai. |
| 12-14 | processing.run("native:intersection", {...}) | Alat kedua. Masukannya adalah keluaran alat pertama (sempadan). |
| 16-17 | for f in irisan.getFeatures(): print(...) | Ukur luas tiap irisan lalu cetak. |
Hasil. Layar menampilkan:
KPH Alpha 1.18 ha sempadan
KPH Beta 1.39 ha sempadan
KPH Gamma 1.5 ha sempadanBagian yang boleh diubah.
- Baris 9, ID alat.
"native:buffer"diganti dengan alat lain. Cara mencari ID dibahas di "Cara mencari fungsi sendiri". - Baris 10, isian alat. Nama isian harus persis seperti yang diminta alat.
- Baris 11,
"OUTPUT". Ganti dengan jalur berkas, misalnyaHASIL + "/sempadan.gpkg", bila hasil harus tersimpan. - Baris 10,
"DISSOLVE": True.Falsemenghasilkan satu bentuk per garis.
Jebakan.
- Hasil bisa teks, bukan layer. Dengan
"TEMPORARY_OUTPUT", hasilnyaQgsVectorLayer. Dengan jalur berkas, hasilnya hanya teks jalur. Uji:QgsVectorLayer | str D:/.../buf.gpkg. - Nama alat salah ketik.
native:buffrmenghasilkanQgsProcessingException Error: Algorithm native:buffr not found. - Satuan jarak mengikuti CRS. Lihat "Jebakan lintas bab".
Latihan variasi.
- Lebarkan sempadan menjadi 50 meter.
- Apa yang berubah pada luas sempadan?
Contoh jawaban: ganti "DISTANCE": 30 menjadi "DISTANCE": 50. Hasil uji: KPH Alpha 1,69 ha, KPH Beta 2,32 ha, KPH Gamma 2,54 ha. Luas naik karena sempadan makin lebar.
Pola 28.4: Batch, banyak berkas dengan satu putaran #
Tujuan. Menjalankan pekerjaan yang sama pada semua berkas dalam satu folder, lalu merangkum hasilnya. Ini pola Bab 21.
Bahan. paket-i4/Ortofoto_Mentah/ berisi tiga ubin foto drone (ubin_01.tif sampai ubin_03.tif).
Analogi: petugas pos memilah surat satu per satu dengan cara yang sama.
Skrip utuh:
import os, glob
import processing
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
daftar = sorted(glob.glob(DATA + "/paket-i4/Ortofoto_Mentah/*.tif"))
print("Ditemukan", len(daftar), "berkas")
ringkas = []
for jalur in daftar:
hasil = processing.run("native:rasterlayerstatistics", {"INPUT": jalur, "BAND": 1})
ringkas.append((os.path.basename(jalur), round(hasil["MEAN"], 2)))
for nama, rata in ringkas:
print(nama, "rata-rata band 1:", rata)
print("Selesai:", len(ringkas), "dari", len(daftar))| Baris | Tulisan | Artinya |
|---|---|---|
| 5 | daftar = sorted(glob.glob(DATA + "/paket-i4/Ortofoto_Mentah/*.tif")) | glob mencari semua berkas dengan pola nama *.tif (bintang artinya "apa saja"). sorted mengurutkannya. |
| 6 | print("Ditemukan", len(daftar), "berkas") | Cetak jumlah yang ditemukan. Biasakan memeriksa angka ini. |
| 8 | ringkas = [] | Daftar kosong untuk menampung hasil. |
| 9 | for jalur in daftar: | Ulangi tiap berkas. |
| 10 | processing.run("native:rasterlayerstatistics", {"INPUT": jalur, "BAND": 1}) | Hitung statistik band 1. Hasilnya kamus berisi MEAN, MAX, MIN, dan lain-lain. |
| 11 | ringkas.append((os.path.basename(jalur), round(hasil["MEAN"], 2))) | Simpan pasangan: nama berkas (tanpa folder) dan rata-rata dua desimal. |
| 13-15 | for nama, rata in ringkas: dst. | Cetak ringkasan dan hitung berapa yang selesai. |
Hasil. Layar menampilkan:
Ditemukan 3 berkas
ubin_01.tif rata-rata band 1: 2105.11
ubin_02.tif rata-rata band 1: 2097.24
ubin_03.tif rata-rata band 1: 2095.22
Selesai: 3 dari 3Bagian yang boleh diubah.
- Baris 5, folder dan pola nama.
"*.tif"diganti"*.gpkg"untuk vektor. - Baris 10,
"BAND": 1. Nomor band. Salah nomor tidak menimbulkan galat, tetapi angkanya lain. - Baris 11,
hasil["MEAN"]. Ganti kunciMAXuntuk nilai tertinggi. - Baris 10, alat. Ganti dengan alat lain yang menerima satu berkas.
Jebakan.
- Folder kosong. Bila
daftarkosong, putaran tidak jalan dan tidak ada pesan galat. Baris 6 ada untuk menangkap kasus ini. - Huruf besar pada ekstensi. Pola
*.tifdi Windows tetap menemukan.TIF. Di Linux dan macOS tidak. [kemungkinan: perilaku sistem berkas, tidak diuji] - Satu berkas rusak menghentikan semua. Pola ini belum tahan galat. Baca Pola 28.5.
Latihan variasi.
- Hitung rata-rata band 2.
- Tampilkan juga nilai tertinggi (
MAX).
Contoh jawaban: ganti "BAND": 1 menjadi "BAND": 2. Hasil uji: rata-rata 746,82; 743,77; dan 750,6. Nilai MAX band 2 untuk ketiganya 1199,0. Kunci lain yang tersedia: COUNT, MIN, RANGE, STD_DEV, SUM, SUM_OF_SQUARES.
Pola 28.5: Tahan galat, satu berkas rusak tidak menghentikan semua #
Tujuan. Memeriksa tiap berkas sebelum dipakai, mencatat alasan penolakan, dan tetap melanjutkan ke berkas berikutnya. Ini pola Bab 22.
Bahan. paket-i4/Kiriman_Lapangan/: enam berkas kiriman tim. Satu rusak (tim_e), satu kekurangan kolom (tim_f).
Analogi: petugas gerbang memeriksa tiap tamu. Yang tidak lengkap suratnya ditolak dengan catatan alasan, dan antrean tetap jalan.
Skrip utuh:
import os, glob
from qgis.core import QgsVectorLayer
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
def muat_aman(jalur, kolom_wajib):
layer = QgsVectorLayer(jalur, "uji", "ogr")
if not layer.isValid():
raise ValueError("layer tidak terbuka")
kurang = [k for k in kolom_wajib if layer.fields().indexOf(k) < 0]
if kurang:
raise ValueError("kolom kurang: " + ", ".join(kurang))
return layer
diterima, ditolak = [], []
for jalur in sorted(glob.glob(DATA + "/paket-i4/Kiriman_Lapangan/*.gpkg")):
nama = os.path.basename(jalur)
try:
layer = muat_aman(jalur, ["Kondisi", "Tinggi_Phn"])
diterima.append((nama, layer.featureCount()))
except ValueError as galat:
ditolak.append((nama, str(galat)))
print("DITERIMA:", len(diterima))
for nama, n in diterima:
print(" ", nama, n, "titik")
print("DITOLAK:", len(ditolak))
for nama, alasan in ditolak:
print(" ", nama, "->", alasan)| Baris | Tulisan | Artinya |
|---|---|---|
| 7 | def muat_aman(jalur, kolom_wajib): | Fungsi pemeriksa: menerima jalur dan daftar kolom yang wajib ada (Bab 7). |
| 9-10 | if not layer.isValid(): raise ValueError(...) | Bila layer tak terbuka, lempar galat berpesan. raise melempar, ValueError adalah jenis galatnya (Bab 10). |
| 11 | kurang = [k for k in kolom_wajib if layer.fields().indexOf(k) < 0] | Kumpulkan kolom yang tidak ada. indexOf bernilai negatif bila kolom tidak ditemukan. |
| 12-13 | if kurang: raise ValueError(...) | Bila ada yang kurang, lempar galat memuat daftarnya. |
| 14 | return layer | Bila lolos semua, kembalikan layernya. |
| 17 | diterima, ditolak = [], [] | Dua daftar kosong sekaligus. |
| 20-24 | try: ... except ValueError as galat: ... | Coba muat. Bila ada ValueError, catat alasannya di ditolak dan lanjutkan putaran. |
| 26-31 | print(...) | Cetak laporan: yang diterima dan yang ditolak beserta alasannya. |
Hasil. Layar menampilkan:
DITERIMA: 4
tim_a.gpkg 6 titik
tim_b.gpkg 5 titik
tim_c.gpkg 4 titik
tim_d.gpkg 3 titik
DITOLAK: 2
tim_e.gpkg -> layer tidak terbuka
tim_f.gpkg -> kolom kurang: KondisiBagian yang boleh diubah.
- Baris 21, daftar kolom wajib.
["Kondisi", "Tinggi_Phn"]diganti kolom yang harus ada pada data Anda. - Baris 18, folder dan pola nama.
- Baris 9-14, aturan pemeriksaan. Tambahkan aturan baru dengan
if ...: raise ValueError("alasan"). - Baris 23, jenis galat. Tangkap jenis lain bila fungsi Anda melemparnya.
Jebakan.
- Layer rusak tidak melempar galat.
QgsVectorLayeruntuk berkas rusak hanya memberiisValid()bernilaiFalse. Karena itu baris 9-10 perlu. exceptyang terlalu lebar.except Exceptionmenangkap segalanya, termasuk salah ketik di kode Anda sendiri, yang lalu tampak seperti "berkas ditolak". Tangkap jenis galat yang spesifik.
Latihan variasi.
- Tolak juga kiriman yang berisi kurang dari 4 titik.
- Berkas apa yang ditolak sekarang?
Contoh jawaban: tambahkan sebelum return layer pada fungsi muat_aman:
if layer.featureCount() < 4:
raise ValueError("terlalu sedikit titik")Hasil uji: tim_d.gpkg ditolak dengan alasan terlalu sedikit titik, selain tim_e (layer tidak terbuka) dan tim_f (kolom kurang: Kondisi).
Pola 28.6: Ekspresi QGIS dari Python #
Tujuan. Memakai bahasa ekspresi QGIS (yang sama dengan Field Calculator) dari Python, untuk menghitung nilai dan memilih fitur. Ini pola Bab 22.
Bahan. Layer Petak.
Analogi: ekspresi adalah rumus Excel yang dipakai QGIS. Anda menulis rumusnya sebagai teks, QGIS yang menghitung.
Skrip utuh:
import os
from qgis.core import (QgsVectorLayer, QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils)
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
petak = QgsVectorLayer(DATA + "/paket-b2/Petak_KPH.gpkg|layername=Petak", "Petak", "ogr")
assert petak.isValid(), "Layer tidak terbuka. Cek DATA dan nama berkas."
# Ekspresi QGIS dihitung dari Python
ekspresi = QgsExpression("round(area($geometry) / 10000, 2) || ' ha, ' || upper(\"JENIS\")")
assert not ekspresi.hasParserError(), ekspresi.parserErrorString()
konteks = QgsExpressionContext()
konteks.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(petak))
for i, f in enumerate(petak.getFeatures()):
konteks.setFeature(f)
print(f["KODE_PETAK"], "->", ekspresi.evaluate(konteks))
if i == 2:
break
# Ekspresi yang sama untuk memilih fitur
petak.selectByExpression("\"STATUS\" = 'Rehabilitasi'")
print("Terpilih:", petak.selectedFeatureCount(), "petak")
petak.removeSelection()| Baris | Tulisan | Artinya |
|---|---|---|
| 10 | ekspresi = QgsExpression("round(area($geometry) / 10000, 2) ... upper(...)") | Buat ekspresi dari teks: luas dalam hektare, disambung teks " ha, ", disambung nama jenis dalam huruf besar. Penyambung teks dalam ekspresi QGIS berupa dua tanda garis tegak berurutan, bukan operator Python. |
| 11 | assert not ekspresi.hasParserError(), ... | Periksa tata bahasa ekspresi. Bila salah tulis, hentikan dengan pesan penjelasnya. |
| 12-13 | konteks = QgsExpressionContext() dan appendScopes(...) | Siapkan "ruang kerja" ekspresi: ia perlu tahu layer dan proyek yang dipakai. |
| 14-16 | for i, f in enumerate(...), konteks.setFeature(f), ekspresi.evaluate(konteks) | Untuk tiap petak, tunjuk petak itu ke ruang kerja, lalu hitung ekspresinya. |
| 17-18 | if i == 2: break | Berhenti setelah tiga petak (nomor mulai 0). |
| 21-22 | petak.selectByExpression(...) | Pilih petak dengan ekspresi, lalu cetak jumlah yang terpilih. |
| 23 | petak.removeSelection() | Hapus pilihan agar layer bersih. |
Hasil. Layar menampilkan:
P-01 -> 1.01 ha, JATI
P-02 -> 0.93 ha, JATI
P-03 -> 1 ha, MAHONI
Terpilih: 2 petakBagian yang boleh diubah.
- Baris 10, teks ekspresi. Satu-satunya tempat rumus ditulis. Uji dulu di Field Calculator QGIS.
- Baris 21, ekspresi pilihan. Kolom dan nilai.
- Baris 17, angka 2. Jumlah petak yang ditampilkan dikurangi satu.
Jebakan.
- Ekspresi salah tulis.
round(area($geometrymenghasilkanIncomplete expression. You might not have finished the full expression., expecting COMMA or ')'. Itu sebabnya baris 11 ada. - Nama kolom salah. Ekspresi
"JENISS" || 'x'tidak menimbulkan galat Python. Kegagalannya baru terbaca lewathasEvalError()dan pesannyaField 'JENISS' not found. Bila Anda tidak memeriksa, hasilnya diam-diam kosong. $areadanarea($geometry). Keduanya sama (10100,0) pada proyek tanpa pengaturan elipsoid. Setelah elipsoid proyek diatur keEPSG:7030,$areamenjadi 10099,2465 sedangkanarea($geometry)tetap 10100,0.
Latihan variasi.
- Hitung umur tanaman: 2026 dikurangi
TAHUN_TANAM. - Pilih petak berstatus
Lindung.
Contoh jawaban: ganti isi QgsExpression(...) menjadi QgsExpression("2026 - \"TAHUN_TANAM\""). Hasil uji: P-01 umur 18, P-02 umur 18, P-03 umur 14. Untuk nomor 2, ganti baris 21 menjadi petak.selectByExpression("\"STATUS\" = 'Lindung'"); hasil uji: 2 petak. Tahun 2026 tertulis tetap, jadi berubah maknanya pada tahun lain.
Pola 28.7: Baca dan tulis berkas CSV dan JSON #
Tujuan. Membaca tabel dari CSV, merangkumnya, lalu menulis hasilnya ke CSV dan JSON. Ini pola yang dipakai di Bab 14, 22, dan 27.
Bahan. paket-b2/Inventarisasi.csv: volume pohon per petak (16 baris data), dengan kolom KODE_PETAK, JML_POHON, VOLUME_M3, TGL_UKUR, REGU.
Analogi: menyalin dari buku tamu ke buku laporan. Anda membaca satu halaman, merangkum, lalu menulis lembar baru.
Skrip utuh:
import os, csv, json
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
HASIL = os.environ.get("PY_HASIL", r"D:/Latihan/hasil")
os.makedirs(HASIL, exist_ok=True)
# Baca: jumlahkan volume per regu
volume = {}
with open(DATA + "/paket-b2/Inventarisasi.csv", encoding="utf-8") as fh:
for baris in csv.DictReader(fh):
regu = baris["REGU"]
volume[regu] = volume.get(regu, 0) + float(baris["VOLUME_M3"])
# Tulis: CSV
with open(HASIL + "/volume_per_regu.csv", "w", newline="", encoding="utf-8") as fh:
tulis = csv.writer(fh)
tulis.writerow(["REGU", "VOLUME_M3"])
for regu, v in sorted(volume.items()):
tulis.writerow([regu, round(v, 1)])
# Tulis: JSON
volume = {regu: round(v, 1) for regu, v in volume.items()}
with open(HASIL + "/volume_per_regu.json", "w", encoding="utf-8") as fh:
json.dump(volume, fh, ensure_ascii=False, indent=2)
print(volume)| Baris | Tulisan | Artinya |
|---|---|---|
| 1 | import os, csv, json | Tiga perkakas: jalur, tabel teks, dan format JSON. |
| 9-10 | with open(..., encoding="utf-8") as fh: dan csv.DictReader(fh) | Buka CSV dan baca baris demi baris sebagai kamus (Bab 9). |
| 11-12 | regu = baris["REGU"] dan volume[regu] = ... | Jumlahkan VOLUME_M3 per regu. float(...) mengubah teks jadi angka. |
| 15 | open(..., "w", newline="", encoding="utf-8") | Buka berkas untuk menulis ("w"). newline="" mencegah baris kosong ganda di Windows. |
| 16-19 | csv.writer(fh), writerow(...) | Tulis baris judul, lalu satu baris per regu. |
| 22 | volume = {regu: round(v, 1) for regu, v in volume.items()} | Bulatkan semua nilai ke satu desimal. Ini dictionary comprehension (Bab 5 dan 6). |
| 23-24 | json.dump(volume, fh, ensure_ascii=False, indent=2) | Tulis kamus sebagai JSON rapi. indent=2 memberi lekuk dua spasi. |
Hasil. Layar menampilkan:
{'Regu 1': 1107.6, 'Regu 2': 990.5, 'Regu 3': 702.1}Berkas volume_per_regu.csv berisi empat baris (judul dan tiga regu). Berkas volume_per_regu.json berisi kamus yang sama dalam bentuk teks JSON.
Bagian yang boleh diubah.
- Baris 9, nama berkas CSV.
- Baris 11-12, kolom pengelompok dan kolom angka. Nama kolom persis seperti judul CSV.
- Baris 15,
encoding."utf-8-sig"bila CSV akan dibuka di Excel dan memuat huruf non-ASCII. Diuji: berkas diawali tiga byte\xef\xbb\xbf(BOM). [CEK: tampilan di Excel tidak diuji] - Baris 23-24, JSON. Hapus bila tidak diperlukan.
Jebakan.
- Nama kolom CSV peka huruf besar-kecil.
baris["kode_petak"]menghasilkanKeyError: 'kode_petak'. (Diuji.) - Data kotor. Di CSV ini, kode petak
P-09tertulisp-09,P-12tidak ada, danP-17tidak ada di peta. Pola 28.8 menangani ketiganya. - Berkas terkunci. Lihat "Jebakan lintas bab".
Latihan variasi.
- Hitung rata-rata jumlah pohon (
JML_POHON) per regu. - Simpan CSV dengan
utf-8-sig.
Contoh jawaban, mengganti baris 11-12 dengan dua kamus:
jml[regu] = jml.get(regu, 0) + int(baris["JML_POHON"])
n[regu] = n.get(regu, 0) + 1dengan jml, n = {}, {} lebih dulu, lalu {r: round(jml[r] / n[r], 1) for r in sorted(jml)}. Hasil uji: {'Regu 1': 886.3, 'Regu 2': 792.2, 'Regu 3': 900.2}.
Pola 28.8: Membuat laporan dari beberapa sumber #
Tujuan. Menggabungkan layer dan CSV menjadi laporan teks rapi, sambil menangani data kotor. Ini pola penutup Bab 14, 17, dan 22.
Bahan. Layer Petak dan Inventarisasi.csv.
Analogi: meja redaksi. Dua wartawan membawa bahan berbeda. Redaktur mencocokkan, merapikan, lalu menulis satu halaman.
Skrip utuh:
import os, csv
from qgis.core import QgsVectorLayer
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
HASIL = os.environ.get("PY_HASIL", r"D:/Latihan/hasil")
os.makedirs(HASIL, exist_ok=True)
petak = QgsVectorLayer(DATA + "/paket-b2/Petak_KPH.gpkg|layername=Petak", "Petak", "ogr")
assert petak.isValid(), "Layer tidak terbuka. Cek DATA dan nama berkas."
# Bahan 1: KPH tiap petak dan luas per KPH, dari layer
kph_petak, luas = {}, {}
for f in petak.getFeatures():
kph = f["NAMA_KPH"]
kph_petak[f["KODE_PETAK"]] = kph
luas[kph] = luas.get(kph, 0) + f.geometry().area() / 10000
# Bahan 2: volume per petak dari CSV, dijumlahkan per KPH
volume, kode_csv, asing = {}, set(), []
with open(DATA + "/paket-b2/Inventarisasi.csv", encoding="utf-8") as fh:
for b in csv.DictReader(fh):
kode = b["KODE_PETAK"].strip().upper()
if kode not in kph_petak:
asing.append(kode)
continue
kode_csv.add(kode)
kph = kph_petak[kode]
volume[kph] = volume.get(kph, 0) + float(b["VOLUME_M3"])
# Rakit teks laporan
baris = ["LAPORAN RINGKAS KPH CONTOH", "-" * 38,
f"{'KPH':<10}{'Luas (ha)':>12}{'Volume (m3)':>16}"]
for kph in sorted(luas):
baris.append(f"{kph:<10}{luas[kph]:>12.2f}{volume.get(kph, 0):>16.1f}")
baris.append("-" * 38)
baris.append(f"{'Total':<10}{sum(luas.values()):>12.2f}{sum(volume.values()):>16.1f}")
baris.append("Petak tanpa data volume: " + ", ".join(sorted(set(kph_petak) - kode_csv)))
baris.append("Kode di CSV yang tidak ada di peta: " + ", ".join(asing))
teks = "\n".join(baris)
with open(HASIL + "/laporan_kph.txt", "w", encoding="utf-8") as fh:
fh.write(teks + "\n")
print(teks)| Baris | Tulisan | Artinya |
|---|---|---|
| 12-16 | kph_petak, luas = {}, {} dan putaran fitur | Bahan 1: dua kamus. kph_petak menyimpan KPH tiap petak, luas menjumlahkan luas per KPH. |
| 19 | volume, kode_csv, asing = {}, set(), [] | Bahan 2: kamus volume, himpunan (set) kode yang dikenal, dan daftar kode asing. |
| 22 | kode = b["KODE_PETAK"].strip().upper() | Rapikan kode: buang spasi di ujung dan ubah ke huruf besar. Ini yang menyelamatkan p-09. |
| 23-25 | if kode not in kph_petak: asing.append(kode); continue | Bila kode tidak ada di peta, catat sebagai asing dan lompat ke baris berikutnya. |
| 26-28 | kode_csv.add(kode) dst. | Catat bahwa kode ini ada di CSV, lalu tambahkan volumenya ke KPH-nya. |
| 31-32 | baris = [...] | Daftar baris laporan: judul, garis, dan judul kolom. |
| 32 | f"{'KPH':<10}{'Luas (ha)':>12}{'Volume (m3)':>16}" | f-string: teks dengan lubang {...}. <10 rata kiri selebar 10, >12 rata kanan selebar 12 (Bab 9). |
| 33-34 | for kph in sorted(luas): | Satu baris laporan per KPH. :>12.2f berarti angka dua desimal, rata kanan, selebar 12. |
| 36-38 | Total, petak tanpa data, kode asing | Baris total, lalu dua catatan penting tentang data yang tidak cocok. |
| 40-43 | "\n".join(baris), fh.write(...), print(teks) | Sambung semua baris dengan pindah baris, simpan ke berkas, dan tampilkan. |
Hasil. Layar menampilkan:
LAPORAN RINGKAS KPH CONTOH
--------------------------------------
KPH Luas (ha) Volume (m3)
KPH Alpha 4.00 939.2
KPH Beta 4.00 596.0
KPH Gamma 8.00 1226.5
--------------------------------------
Total 16.00 2761.7
Petak tanpa data volume: P-12
Kode di CSV yang tidak ada di peta: P-17Cek silang: jumlah volume di CSV seluruhnya 2800,2. Angka 2761,7 ditambah volume P-17 (38,5) sama dengan 2800,2. Jadi tidak ada volume yang hilang tanpa terhitung.
Bagian yang boleh diubah.
- Baris 8 dan 20, sumber data.
- Baris 14-16, kolom pengelompok.
NAMA_KPHdiganti kolom lain. - Baris 22, perapian kode. Tambah
.replace(" ", "")bila ada spasi di tengah. - Baris 31-38, tata letak laporan. Lebar kolom dalam f-string.
- Baris 41, nama berkas laporan.
Jebakan.
- Tanpa perapian kode, skrip berhenti. Bila baris 22 ditulis
kode = b["KODE_PETAK"]dan dipakai langsung sebagai kunci dikph_petak, hasilnyaKeyError: 'p-09'. (Terjadi pada percobaan pertama bab ini.) - Data yang hilang tidak menimbulkan galat. P-12 tidak muncul di CSV dan P-17 tidak muncul di peta. Tanpa baris 37-38 Anda tidak akan tahu.
- Total tidak sama dengan seluruh CSV. Laporan hanya menjumlahkan yang cocok dengan peta. Itu disengaja, tetapi harus dicatat.
Latihan variasi.
- Tambahkan kolom volume per hektare.
Contoh jawaban, mengganti baris 31-34:
baris = ["LAPORAN RINGKAS KPH CONTOH", "-" * 50,
f"{'KPH':<10}{'Luas (ha)':>12}{'Volume (m3)':>16}{'m3 per ha':>12}"]
for kph in sorted(luas):
vol = volume.get(kph, 0)
baris.append(f"{kph:<10}{luas[kph]:>12.2f}{vol:>16.1f}{vol / luas[kph]:>12.1f}")Garis pemisah di baris 35 diubah menjadi "-" * 50. Hasil uji: KPH Alpha 234,8; KPH Beta 149,0; KPH Gamma 153,3 meter kubik per hektare. Angka ini dihitung dari luas seluruh petak, termasuk P-12 yang tidak punya data volume, jadi cenderung terlalu rendah untuk KPH Gamma.
Tabel: bila data saya berubah, bagian mana yang saya ganti? #
Tabel ini rujukan cepat. Cari baris yang cocok dengan keadaan Anda.
| Bila data saya berubah begini | Bagian yang diganti | Ada di pola |
|---|---|---|
| Folder data pindah ke komputer lain | DATA (dan HASIL) di awal skrip | Semua |
| Berkas atau layer berganti nama | Teks sumber pada QgsVectorLayer(...) | 28.1, 28.2, 28.6, 28.8 |
| Nama kolom berbeda | Teks di dalam f["..."] dan ekspresi | 28.2, 28.6, 28.7, 28.8 |
| Satu GeoPackage berisi banyak layer | Bagian sesudah garis tegak: layername=... | 28.1 |
| Data berada di CRS lain | Ubah dulu ke CRS meter dengan native:reprojectlayer | 28.3 dan semua yang mengukur |
| Satuan hasil perlu beda (meter persegi, hektare) | Pembagi 10000 | 28.2, 28.8 |
| Jumlah berkas bertambah | Tidak ada. Putaran mengikuti isi folder | 28.4, 28.5 |
| Ekstensi berkas berbeda | Pola nama di glob | 28.4, 28.5 |
| Raster punya urutan band lain | Angka BAND | 28.4 |
| Kolom wajib bertambah | Daftar kolom_wajib | 28.5 |
| Aturan seleksi berubah | Teks saringan atau ekspresi | 28.1, 28.2, 28.6 |
| Alat Processing diganti | ID alat dan nama isiannya | 28.3, 28.4 |
| CSV memuat huruf besar-kecil tidak seragam | Rapikan dengan .strip().upper() | 28.8 |
| Laporan perlu kolom tambahan | Daftar judul dan baris f-string | 28.8 |
Aturan praktisnya: ubah satu hal, jalankan, periksa hasilnya, baru ubah hal berikutnya. Bila hasil salah, Anda langsung tahu penyebabnya.

Jebakan lintas bab #
Berikut kesalahan yang berulang di banyak bab. Setiap butir sudah diuji, dengan pesan nyata.
1. Jalur salah, tanpa galat. Membuka layer dari jalur yang salah tidak langsung gagal:
x = QgsVectorLayer(DATA + "/paket-b2/Petak_KPH_X.gpkg|layername=Petak", "Petak", "ogr")
print(x.isValid(), x.featureCount())
print(next(x.getFeatures())["JENIS"])False -2
StopIterationAngka -2 artinya jumlah tidak diketahui. Skrip baru gagal di baris berikutnya dengan StopIteration. Karena itu selalu periksa isValid() (Pola 28.1). Dijumpai di Bab 14, 15, dan 16.
2. CRS dan satuan jarak. Jarak 30 pada alat native:buffer berarti 30 satuan peta. Pada peta UTM itu 30 meter. Pada peta berderajat (EPSG:4326) itu 30 derajat:
buffer 30 pada derajat -> extent 79.4,-30.0 : 139.2,29.9
buffer 30 pada UTM -> extent 311970.1,9996030.1 : 312429.9,9996299.8Sungai di data latihan hanya membentang 400 meter. Dengan CRS derajat, sempadannya selebar benua. Tidak ada pesan galat. Perbaikan: ubah dulu ke CRS berbasis meter. Dijumpai di Bab 18, 20, dan 27 (kerapatan titik panas).
3. Huruf besar dan kecil. Aturannya tidak sama di semua tempat:
| Tempat | JENIS ditulis jenis | Hasil pada uji |
|---|---|---|
Kolom fitur QGIS: f["jenis"] | Berhasil | Jati |
Ekspresi QGIS: "jenis" | Berhasil | Jati |
fields().indexOf("jenis") | Gagal | -1 |
fields().lookupField("jenis") | Berhasil | 3 |
Kolom CSV: baris["kode_petak"] | Gagal | KeyError: 'kode_petak' |
Nilai teks: "p-09" == "P-09" | Gagal | False |
Nama kolom salah ketik: f["JENISS"] | Gagal | KeyError: 'JENISS' |
Kesimpulannya: jangan mengandalkan toleransi QGIS. Tulis nama kolom persis seperti aslinya. Nilai teks dari sumber luar, seperti kode di CSV, rapikan dengan .strip().upper() (Pola 28.8).
4. Satuan luas. area() mengembalikan luas dalam satuan peta. Petak P-01 memberi 10100.0, dan dibagi 10000 menjadi 1,01 hektare. Kolom LUAS_LAMA_HA di data yang sama berisi 1.0, yaitu angka nominal lama, bukan hasil ukur. Jangan campur keduanya. Pada ekspresi, $area ikut pengaturan elipsoid proyek, sedangkan area($geometry) tidak (lihat Pola 28.6).
5. Indeks mulai dari 0. Daftar berisi 16 petak punya nomor 0 sampai 15:
jumlah 16 | pertama: P-01 | terakhir: P-16
IndexError list index out of rangePesan kedua muncul bila Anda meminta semua[16]. Indeks [-1] mengambil yang terakhir. Dijumpai di Bab 4, 5, dan 15.
6. File terkunci. Selama sebuah berkas masih terbuka di QGIS atau program lain, Windows menolak menghapus atau menimpanya:
PermissionError [WinError 32] The process cannot access the file because it is being used by another process: 'C:\temp\tmpmd9xbtg9/petak_salin.gpkg'Perbaikan: tutup layer atau lepaskan dari proyek, tutup program lain yang membukanya (misalnya Excel untuk CSV), lalu ulangi. Pesan ini khas Windows.
proyek.clear()mengosongkan proyek Anda. Beberapa skrip contoh memanggilQgsProject.instance().clear(). Di Konsol QGIS, itu membuang semua layer proyek yang sedang terbuka:
sebelum: 2 layer
sesudah: 0 layerJangan jalankan skrip seperti itu di proyek penting. Simpan proyek lebih dulu, atau pakai proyek kosong. Dijumpai di Bab 14, 17, dan 23.
8. Hasil yang bukan layer. Hasil processing.run dengan "OUTPUT" jalur berkas adalah teks, bukan layer. Dengan "TEMPORARY_OUTPUT" hasilnya layer: QgsVectorLayer | str. Dijumpai di Bab 21.
9. Saringan yang menempel. setSubsetString tetap berlaku sampai dikosongkan. Hasil uji: 5 petak dengan saringan, 16 tanpa.
10. Data yang kotor. Kode p-09 huruf kecil, kode yang hilang, dan kode yang berlebih adalah kejadian sehari-hari. Selalu cetak apa yang tidak cocok (Pola 28.8).
Cara mencari fungsi sendiri #
Tidak ada yang hafal semua fungsi QGIS. Pembuat skrip yang mahir hanya tahu cara mencarinya. Bab 13 membahas kebiasaan belajar mandiri ini lebih panjang. Berikut ringkasannya, empat langkah yang cukup.

Skrip berikut menjalankan langkah 1 sampai 3 pada layer petak, dan juga mencari alat Processing.
import os
from qgis.core import QgsVectorLayer, QgsApplication
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
petak = QgsVectorLayer(DATA + "/paket-b2/Petak_KPH.gpkg|layername=Petak", "Petak", "ogr")
# 1. Apa yang bisa dilakukan sebuah layer? Saring daftar namanya dengan kata kunci.
print([n for n in dir(petak) if "select" in n.lower()][:8])
# 2. Baca petunjuk satu fungsi (tiga baris pertama)
print("\n".join(petak.selectByExpression.__doc__.splitlines()[:3]))
# 3. Cari alat Processing dari kata kunci
for alat in QgsApplication.processingRegistry().algorithms():
if "buffer" in alat.id().lower():
print(alat.id(), "->", alat.displayName())
# 4. Lihat "formulir" satu alat
alat = QgsApplication.processingRegistry().algorithmById("native:buffer")
for p in alat.parameterDefinitions():
print(p.name(), "|", p.description())| Baris | Tulisan | Artinya |
|---|---|---|
| 8 | dir(petak) | Daftar semua nama yang dimiliki benda petak. |
| 8 | [n for n in dir(petak) if "select" in n.lower()][:8] | Saring daftar itu: hanya nama berisi "select". [:8] mengambil delapan pertama. |
| 11 | petak.selectByExpression.__doc__ | Teks petunjuk bawaan sebuah fungsi. splitlines()[:3] mengambil tiga baris pertama. |
| 14-16 | QgsApplication.processingRegistry().algorithms() | Daftar semua alat Processing. Saring dengan kata kunci "buffer". |
| 19-21 | algorithmById("native:buffer") dan parameterDefinitions() | Ambil satu alat, lalu lihat semua isian formulirnya. |
Hasil. Layar menampilkan (daftar alat bisa berbeda di komputer Anda, bergantung penyedia yang terpasang, misalnya GRASS):
['AddToSelection', 'IntersectSelection', 'RemoveFromSelection', 'SelectBehavior', 'SetSelection', 'boundingBoxOfSelected', 'deleteSelectedFeatures', 'deselect']
selectByExpression(self, expression: str|None, behavior: Qgis.SelectBehavior = Qgis.SelectBehavior.SetSelection, context: QgsExpressionContext|None = None)
Selects matching features using an expression.
gdal:buffervectors -> Buffer vectors
gdal:onesidebuffer -> One side buffer
grass:r.buffer -> r.buffer
grass:r.buffer.lowmem -> r.buffer.lowmem
grass:v.buffer -> v.buffer
native:buffer -> Buffer
native:bufferbym -> Variable width buffer (by M value)
native:multiringconstantbuffer -> Multi-ring buffer (constant distance)
native:singlesidedbuffer -> Single sided buffer
native:taperedbuffer -> Tapered buffers
native:wedgebuffers -> Create wedge buffers
qgis:variabledistancebuffer -> Variable distance 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 | BufferedDari hasil ini Anda tahu: untuk memilih fitur ada fungsi selectByExpression, dan alat buffer standar berID native:buffer dengan sembilan isian. Nama isian di kolom kiri (INPUT, DISTANCE, dan seterusnya) adalah kunci kamus pada processing.run.
Dua cara lain melihat petunjuk sebuah alat lengkap dengan jenis tiap isian dan nilai yang diterima. Dari Python: processing.algorithmHelp("native:buffer"). Dari terminal: qgis_process-qgis.bat help native:buffer. Keduanya diuji dan mencetak deskripsi alat serta daftar isian.
Cara membaca dokumentasi API QGIS #
API adalah daftar semua fungsi yang boleh Anda panggil. Dokumentasi PyQGIS ada di https://qgis.org/pyqgis/master/. Halaman utamanya mengelompokkan modul: Core, GUI, Analysis, Server, Processing, dan 3D, dengan kotak pencarian. Halaman itu menyediakan beberapa versi (pengembangan, rilis stabil, dan LTR). Pilih versi yang sesuai QGIS Anda. [CEK: daftar versi dibaca pada saat penulisan, bisa berubah]
Contoh jalur membaca untuk soal "bagaimana memilih fitur dengan ekspresi?":
- Dari
dir(petak)Anda tahu benda itu adalahQgsVectorLayer. Cetaktype(petak)bila ragu. - Cari halaman kelas
QgsVectorLayerdi bagian Core:https://qgis.org/pyqgis/master/core/QgsVectorLayer.html. - Cari metode
selectByExpressiondi halaman itu (Ctrl+F). Halaman kelas memuat metodegetFeatures,featureCount,setSubsetString, danselectByExpressionyang sudah Anda pakai di bab ini. - Baca tanda tangan metodenya, seperti yang tadi dicetak oleh
__doc__.
Cara membaca satu tanda tangan:
| Bagian | Artinya |
|---|---|
selectByExpression( | Nama metode. |
self | Benda yang memanggil. Anda tidak mengisinya. |
expression: str | Isian pertama, wajib, berjenis teks. |
behavior: Qgis.SelectBehavior = Qgis.SelectBehavior.SetSelection | Isian kedua, boleh dilewat. Nilai sesudah tanda sama dengan adalah nilai bawaan. |
context: ... = None | Isian ketiga, juga boleh dilewat. |
-> sebuah jenis | Bila ada, jenis nilai yang dikembalikan, misalnya -> int pada featureCount. |
.. seealso:: | Rujukan ke fungsi terkait, berguna untuk menemukan tetangganya. |
since QGIS 3.26 | Fungsi atau isian itu baru ada sejak versi tersebut. |
Untuk alat Processing, rujukan terbaik adalah petunjuk alatnya sendiri (processing.algorithmHelp). Untuk membangun plugin dan alat sendiri, PyQGIS Developer Cookbook di https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/index.html memuat bab "Using Vector Layers", "Expressions, Filtering and Calculating Values", "Tasks", "Developing Python Plugins", dan "Writing a Processing plugin", serta lembar contekan PyQGIS. [CEK: daftar bab dibaca saat penulisan, versi dokumen yang tampil saat itu QGIS 3.44]
Kebiasaan yang membantu:
- Coba kecil dulu. Satu fungsi, satu
print. Jangan menulis tiga puluh baris sebelum mencoba. - Baca pesan galat dari bawah. Baris terakhir memuat jenis dan penjelasannya (Bab 10).
- Cari contoh di bab lain buku ini. Gunakan tabel "Bila data saya berubah".
Latihan akhir: rekap petak dari tiga sumber #
Saatnya menggabungkan pola. Kepala Seksi meminta satu tabel CSV berisi, untuk tiap petak: KPH, luas, volume dari inventarisasi, jumlah titik panas, dan status waspada. Petak dengan paling sedikit satu titik panas berstatus WASPADA. Petak tanpa data volume harus diberi catatan.
Pola yang dibutuhkan: muat layer (28.1), panggil alat native:countpointsinpolygon (28.3), baca CSV yang kotor (28.7), loop fitur (28.2), dan tulis CSV serta cetak ringkasan (28.7 dan 28.8). Coba kerjakan sendiri lebih dulu. Contoh jawaban yang sudah diuji:
import os, csv
import processing
from qgis.core import QgsVectorLayer
DATA = os.environ.get("PY_DATA", r"D:/Latihan")
HASIL = os.environ.get("PY_HASIL", r"D:/Latihan/hasil")
os.makedirs(HASIL, exist_ok=True)
# 1. Muat petak (Pola 28.1)
petak = QgsVectorLayer(DATA + "/paket-b2/Petak_KPH.gpkg|layername=Petak", "Petak", "ogr")
assert petak.isValid(), "Layer tidak terbuka. Cek DATA dan nama berkas."
# 2. Hitung titik panas per petak dengan alat Processing (Pola 28.3)
hitung = processing.run("native:countpointsinpolygon", {
"POLYGONS": petak, "POINTS": DATA + "/paket-i4/Hotspot_Nasional.gpkg",
"FIELD": "TITIK_PANAS", "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
# 3. Baca volume dari CSV, dengan kode yang dirapikan (Pola 28.7)
volume = {}
with open(DATA + "/paket-b2/Inventarisasi.csv", encoding="utf-8") as fh:
for b in csv.DictReader(fh):
volume[b["KODE_PETAK"].strip().upper()] = float(b["VOLUME_M3"])
# 4. Loop fitur dan rakit baris rekap (Pola 28.2)
rekap = []
for f in hitung.getFeatures():
kode = f["KODE_PETAK"]
titik = int(f["TITIK_PANAS"])
vol = volume.get(kode)
rekap.append([kode, f["NAMA_KPH"], round(f.geometry().area() / 10000, 2),
vol if vol is not None else "",
titik,
"WASPADA" if titik >= 1 else "aman",
"" if vol is not None else "tanpa data volume"])
# 5. Tulis CSV dan cetak ringkasan (Pola 28.7 dan 28.8)
judul = ["KODE_PETAK", "NAMA_KPH", "LUAS_HA", "VOLUME_M3", "TITIK_PANAS", "STATUS", "CATATAN"]
with open(HASIL + "/rekap_petak.csv", "w", newline="", encoding="utf-8") as fh:
tulis = csv.writer(fh)
tulis.writerow(judul)
tulis.writerows(rekap)
print("Petak:", len(rekap), "| titik panas:", sum(r[4] for r in rekap))
print("WASPADA:", [r[0] for r in rekap if r[5] == "WASPADA"])
print("Tanpa data volume:", [r[0] for r in rekap if r[6]])Hasil. Layar menampilkan:
Petak: 16 | titik panas: 12
WASPADA: ['P-03', 'P-05', 'P-06', 'P-07', 'P-08', 'P-10', 'P-11', 'P-12', 'P-13', 'P-14']
Tanpa data volume: ['P-12']Berkas rekap_petak.csv memuat tujuh kolom dan 16 baris data. Contoh dua baris pertamanya:
KODE_PETAK,NAMA_KPH,LUAS_HA,VOLUME_M3,TITIK_PANAS,STATUS,CATATAN
P-01,KPH Alpha,1.01,246.8,0,aman,
P-02,KPH Alpha,0.93,235.0,0,aman,Pemeriksaan hasil: 12 titik panas terhitung, sama dengan jumlah titik di Hotspot_Nasional. P-12 punya titik panas tetapi tidak punya data volume, jadi ia muncul di dua daftar.
Tantangan lanjutan.
- Ubah ambang WASPADA menjadi dua titik atau lebih. Petak mana yang tersisa?
- Tambahkan kolom
VOLUME_PER_HA. - Bagaimana skrip ini dibuat tahan galat bila
Inventarisasi.csvtidak ada?
Petunjuk jawaban: untuk nomor 1, ganti titik >= 1 pada baris 33 menjadi titik >= 2. Hanya P-07 (3 titik) yang lolos, karena petak lain berisi satu titik (lihat hasil kerapatan di Skrip 27.4). Untuk nomor 3, bungkus bagian baca CSV dengan try/except FileNotFoundError dan siapkan volume = {} kosong di bagian except.
Cek paham #
- Sebutkan tiga hal yang biasanya diganti bila data baru datang.
- Mengapa pola batch perlu dibungkus tahan galat?
- Apa beda
QgsVectorLayeryang jalurnya salah dengan layer yang jalurnya benar?
Jawaban.
- Lokasi data (
DATA), nama berkas atau layer, dan nama kolom. Kadang ditambah angka batas atau nomor band. - Satu berkas rusak bisa menghentikan seluruh putaran. Dengan
try/exceptdan daftar ditolak, putaran jalan terus dan alasannya tercatat. - Layer berjalur salah tidak menimbulkan galat.
isValid()bernilaiFalsedanfeatureCount()bernilai -2. Galat baru muncul di baris berikutnya.
Kesalahan umum #
- Menyalin pola tanpa mengganti jalur contoh. Perbaikan: ganti
DATAdanHASILdi awal setiap skrip. - Mengubah banyak hal sekaligus. Bila gagal, Anda tidak tahu penyebabnya. Perbaikan: ubah satu hal, jalankan, periksa.
- Percaya pada data tanpa memeriksa. Kode huruf kecil, baris hilang, dan kolom kurang itu biasa. Perbaikan: cetak yang tidak cocok (Pola 28.5 dan 28.8).
- Menjalankan skrip bekas di proyek penting. Skrip dengan
clear()atauremoveAllMapLayers()mengosongkan proyek. Perbaikan: simpan proyek dulu atau pakai proyek kosong.
Ringkasan dan latihan #
- Skrip GIS tersusun dari delapan pola: muat-ubah-simpan, loop fitur, panggil alat, batch, tahan galat, ekspresi, baca-tulis berkas, laporan.
- Data baru biasanya hanya menuntut penggantian lokasi, nama berkas, nama kolom, dan angka.
- Jebakan lintas bab: jalur, CRS dan satuan, huruf besar-kecil, indeks 0, file terkunci, dan
clear(). - Mencari fungsi:
dir,__doc__, daftar alat Processing, lalu dokumentasi API. - Mengubah satu hal, menjalankan, memeriksa, adalah kebiasaan paling berharga.
Latihan. Pilih satu skrip dari Bab 14 sampai 27 yang paling Anda sukai. Tandai dengan pensil: mana bagian pola 28.1 sampai 28.8, dan mana bagian yang khas skrip itu. Lalu jalankan dengan data Anda sendiri, dengan hanya mengganti DATA, nama berkas, dan nama kolom.
Tabel perbandingan: delapan pola sekilas #
| Pola | Pekerjaan | Alat utama | Contoh di bab |
|---|---|---|---|
| 28.1 | Muat, ubah, simpan | QgsVectorLayer, setSubsetString, QgsVectorFileWriter | 14, 15, 16 |
| 28.2 | Loop fitur | getFeatures, kamus, .get | 15, 17, 18 |
| 28.3 | Panggil alat | processing.run | 18, 19, 21 |
| 28.4 | Batch | glob, for | 21, 26 |
| 28.5 | Tahan galat | try/except, raise | 22 |
| 28.6 | Ekspresi | QgsExpression | 22, 23 |
| 28.7 | Baca dan tulis berkas | csv, json | 14, 22, 27 |
| 28.8 | Laporan | f-string, join | 17, 23, 27 |