BAB 4: Penanganan Galat: Skrip yang Tidak Mudah Menyerah
#Studi kasus: "Kiriman dari enam tim"
Enam tim lapangan mengirim data titik survei. Malam hari, skrip penggabung Anda berhenti di tengah jalan dengan pesan panjang berbahasa Inggris. Anda tidak tahu berkas mana yang bermasalah, dan berkas sebelumnya sudah terlanjur diproses setengah. Keesokan harinya Kepala Seksi hanya bertanya: berapa kiriman yang diterima, mana yang ditolak, dan mengapa. Skrip yang baik menjawab semua itu sendiri.
#Konsep: penanganan galat dalam tiga kalimat
Galat (exception) adalah tanda dari Python bahwa ada yang salah, dan tanpa penanganan skrip langsung berhenti. Dengan try-except, Anda menangkap galat itu, mencatat sebabnya, lalu meneruskan pekerjaan. Ini seperti petugas gudang yang memeriksa tiap kiriman: barang rusak dikembalikan dengan catatan, bukan seluruh gudang ditutup.
Istilah baru bab ini:
- Galat (exception): sinyal bahwa ada yang gagal.
- try-except: blok yang mencoba sesuatu dan menangkap galatnya.
- Validasi: pemeriksaan masukan sebelum dipakai.
- Log: catatan hasil tiap langkah, yang bisa dibaca belakangan.
- QgsProcessingException: galat khusus yang dikeluarkan alat Processing.

#Bagian A: QGIS
#Bagian A: Penanganan galat di QGIS
#A.1 Memeriksa sebelum memakai
Skrip 4.1 mendefinisikan fungsi muat_vektor yang menolak masukan bermasalah dengan pesan jelas: berkas tidak ada, tidak bisa dibuka, field wajib hilang, atau layer kosong. Fungsi itu lalu dicoba pada empat berkas.
# [SKRIP 4.1: Memeriksa masukan sebelum dipakai dan menangkap galat dengan try-except]
# Penulis: Badar Mubarok Yogaswara
import os
from qgis.core import QgsVectorLayer
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
def muat_vektor(jalur, nama, field_wajib=()):
"""Membuka layer vektor. Bila ada yang tidak beres, berhenti dengan pesan yang jelas."""
if not os.path.exists(jalur):
raise FileNotFoundError("berkas tidak ada: " + jalur)
layer = QgsVectorLayer(jalur, nama, "ogr")
if not layer.isValid():
raise ValueError("berkas tidak bisa dibuka (mungkin rusak): " + os.path.basename(jalur))
kurang = [f for f in field_wajib if layer.fields().indexOf(f) < 0]
if kurang:
raise ValueError("field wajib tidak ada: " + ", ".join(kurang))
if layer.featureCount() == 0:
raise ValueError("layer kosong, tidak ada fitur")
return layer
# Empat berkas dicoba satu per satu. Satu gagal tidak boleh menghentikan yang lain.
kasus = [
DATA + "/Kiriman_Lapangan/tim_a.gpkg", # baik
DATA + "/Kiriman_Lapangan/tim_x.gpkg", # tidak ada
DATA + "/Kiriman_Lapangan/tim_e.gpkg", # rusak
DATA + "/Kiriman_Lapangan/tim_f.gpkg", # field Kondisi tidak ada
]
for jalur in kasus:
nama = os.path.basename(jalur)
try:
layer = muat_vektor(jalur, nama, field_wajib=["Kondisi", "Tinggi_Phn"])
except FileNotFoundError as e:
print("[LEWATI] %s -> %s" % (nama, e))
except ValueError as e:
print("[LEWATI] %s -> %s" % (nama, e))
else:
# blok else: hanya jalan bila TIDAK ada galat
print("[OK] %s -> %d fitur" % (nama, layer.featureCount()))
finally:
# blok finally: selalu jalan, berhasil atau gagal
print(" selesai memeriksa", nama)
# Galat hitungan yang sering terjadi: pembagian dengan nol
def persen(bagian, total):
try:
return 100 * bagian / total
except ZeroDivisionError:
return None # tidak ada jawaban yang masuk akal
print("Persen 3 dari 12:", persen(3, 12))
print("Persen 3 dari 0:", persen(3, 0))Hasil uji di QGIS 4.0.2:
[OK] tim_a.gpkg -> 6 fitur
selesai memeriksa tim_a.gpkg
[LEWATI] tim_x.gpkg -> berkas tidak ada: D:/Latihan/paket-i4/Kiriman_Lapangan/tim_x.gpkg
selesai memeriksa tim_x.gpkg
[LEWATI] tim_e.gpkg -> berkas tidak bisa dibuka (mungkin rusak): tim_e.gpkg
selesai memeriksa tim_e.gpkg
[LEWATI] tim_f.gpkg -> field wajib tidak ada: Kondisi
selesai memeriksa tim_f.gpkg
Persen 3 dari 12: 25.0
Persen 3 dari 0: None#A.2 Batch tahan galat dengan laporan
Skrip 4.2 memproses semua kiriman. Untuk tiap berkas, skrip memeriksa tiga hal berlapis: bisa dibuka, field lengkap, lalu CRS sesuai. Bila CRS berbeda tetapi masih bisa diperbaiki, skrip mengubahnya ke EPSG:32749 dan mencatatnya. Berkas yang tidak bisa diperbaiki ditolak beserta sebabnya. Setelah digabung, skrip memeriksa titik yang jatuh di luar semua KPH.

# [SKRIP 4.2: Batch tahan galat: menggabungkan kiriman lapangan, mencatat yang gagal]
# Penulis: Badar Mubarok Yogaswara
import csv
import glob
import os
import processing
from qgis.core import QgsProject, QgsVectorLayer, QgsCoordinateReferenceSystem
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
os.makedirs(HASIL, exist_ok=True)
CRS_TUJUAN = "EPSG:32749"
FIELD_WAJIB = ["Kondisi", "Tinggi_Phn", "Foto"]
log = [] # satu baris catatan untuk tiap berkas: (berkas, status, keterangan)
bagus = [] # layer yang lolos pemeriksaan
for jalur in sorted(glob.glob(DATA + "/Kiriman_Lapangan/*.gpkg")):
nama = os.path.basename(jalur)
try:
layer = QgsVectorLayer(jalur, nama, "ogr")
if not layer.isValid():
raise ValueError("berkas rusak atau bukan GeoPackage")
kurang = [f for f in FIELD_WAJIB if layer.fields().indexOf(f) < 0]
if kurang:
raise ValueError("field hilang: " + ", ".join(kurang))
catatan = "sesuai"
if layer.crs().authid() != CRS_TUJUAN:
asal = layer.crs().authid()
layer = processing.run("native:reprojectlayer", {
"INPUT": layer, "TARGET_CRS": QgsCoordinateReferenceSystem(CRS_TUJUAN),
"OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
catatan = "CRS %s diubah ke %s" % (asal, CRS_TUJUAN)
bagus.append(layer)
log.append((nama, "DITERIMA", "%d titik; %s" % (layer.featureCount(), catatan)))
except Exception as e: # satu berkas gagal, yang lain tetap diproses
log.append((nama, "DITOLAK", str(e)))
# Gabungkan yang lolos
gabungan = processing.run("native:mergevectorlayers", {
"LAYERS": bagus, "CRS": QgsCoordinateReferenceSystem(CRS_TUJUAN),
"OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
gabungan.setName("Titik_Survei_Gabungan")
QgsProject.instance().addMapLayer(gabungan)
# Pemeriksaan keberadaan: titik yang tidak jatuh di dalam KPH mana pun patut dicurigai
batas = QgsVectorLayer(DATA + "/Batas_KPH.gpkg", "Batas_KPH", "ogr")
cek = processing.run("native:joinattributesbylocation", {
"INPUT": gabungan, "JOIN": batas, "JOIN_FIELDS": ["NAMA_KPH"], "METHOD": 1,
"PREDICATE": [0], "DISCARD_NONMATCHING": False, "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
luar = [f["Foto"] for f in cek.getFeatures() if not f["NAMA_KPH"]]
for foto in luar:
log.append((foto, "CURIGA", "titik jatuh di luar semua KPH"))
# Laporan: layar dan berkas CSV
with open(HASIL + "/log_kiriman.csv", "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["berkas", "status", "keterangan"])
w.writerows(log)
for baris in log:
print("%-22s %-9s %s" % baris)
print("Ringkasan: %d diterima, %d ditolak, %d curiga; total titik gabungan %d" % (
sum(1 for b in log if b[1] == "DITERIMA"), sum(1 for b in log if b[1] == "DITOLAK"),
len(luar), gabungan.featureCount()))Hasil uji di QGIS 4.0.2:
tim_a.gpkg DITERIMA 6 titik; sesuai
tim_b.gpkg DITERIMA 5 titik; sesuai
tim_c.gpkg DITERIMA 4 titik; sesuai
tim_d.gpkg DITERIMA 3 titik; CRS EPSG:4326 diubah ke EPSG:32749
tim_e.gpkg DITOLAK berkas rusak atau bukan GeoPackage
tim_f.gpkg DITOLAK field hilang: Kondisi
b_05.jpg CURIGA titik jatuh di luar semua KPH
Ringkasan: 4 diterima, 2 ditolak, 1 curiga; total titik gabungan 18Angka dapat dicek dari pembuat data: tim A mengirim 6 titik, tim B 5, tim C 4, dan tim D 3 (dikirim dalam derajat). Jumlahnya 18. Tim E mengirim berkas rusak dan tim F tidak punya field Kondisi, jadi keduanya ditolak. Satu titik tim B sengaja diberi koordinat salah ketik, sehingga ditandai curiga.
#A.3 Menangkap galat dari alat Processing
Alat Processing mengeluarkan QgsProcessingException. Skrip 4.3 membungkus processing.run dalam fungsi jalankan_aman. Fungsi itu mengembalikan pasangan (hasil, pesan), sehingga skrip pemanggil tidak perlu menulis try berulang kali.
# [SKRIP 4.3: Menangkap galat dari alat Processing dengan pembungkus kecil]
# Penulis: Badar Mubarok Yogaswara
import traceback
import processing
from qgis.core import QgsVectorLayer, QgsProcessingException, QgsMessageLog, Qgis
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
def jalankan_aman(alat, parameter):
"""Mengembalikan (hasil, pesan). Bila alat gagal: hasil = None dan pesan berisi sebabnya."""
try:
return processing.run(alat, parameter), ""
except QgsProcessingException as e: # galat milik Processing
QgsMessageLog.logMessage("%s gagal: %s" % (alat, e), "Otomasi KPH", Qgis.Warning)
return None, str(e).replace("\n", " | ")
except Exception: # galat Python lain: simpan jejak untuk diagnosis
return None, "galat tak terduga: " + traceback.format_exc().splitlines()[-1]
sungai = QgsVectorLayer(DATA + "/Sungai.gpkg", "Sungai", "ogr")
# Kasus 1: berjalan normal
hasil, pesan = jalankan_aman("native:buffer", {
"INPUT": sungai, "DISTANCE": 30, "OUTPUT": "TEMPORARY_OUTPUT"})
print("Kasus 1:", "OK, %d fitur" % hasil["OUTPUT"].featureCount() if hasil else "GAGAL: " + pesan)
# Kasus 2: masukan tidak ada
hasil, pesan = jalankan_aman("native:buffer", {
"INPUT": DATA + "/tidak_ada.gpkg", "DISTANCE": 30, "OUTPUT": "TEMPORARY_OUTPUT"})
print("Kasus 2:", "OK" if hasil else "GAGAL: " + pesan)
# Kasus 3: nama alat salah ketik
hasil, pesan = jalankan_aman("native:buffr", {})
print("Kasus 3:", "OK" if hasil else "GAGAL: " + pesan)
# Kasus 4: parameter wajib (OVERLAY) tidak diisi
hasil, pesan = jalankan_aman("native:clip", {"INPUT": sungai, "OUTPUT": "TEMPORARY_OUTPUT"})
print("Kasus 4:", "OK" if hasil else "GAGAL: " + pesan)Hasil uji di QGIS 4.0.2:
Kasus 1: OK, 2 fitur
Kasus 2: GAGAL: Unable to execute algorithm | Could not load source layer for INPUT: D:/Latihan/paket-i4/tidak_ada.gpkg not found
Kasus 3: GAGAL: Error: Algorithm native:buffr not found |
Kasus 4: GAGAL: Unable to execute algorithm | Could not load source layer for OVERLAY: no value specified for parameterPesan galat QGIS berbahasa Inggris. Biasakan membaca baris terakhirnya, karena di situlah sebab yang sebenarnya, misalnya Could not load source layer for INPUT. Pada uji, berkas yang tidak ada menghasilkan pesan itu.
| Gejala | Penyebab umum | Tindakan |
|---|---|---|
isValid() bernilai False | Berkas hilang, rusak, atau formatnya salah | Tolak dan catat nama berkas |
IndexError pada [0] | Nama layer tidak ketemu | Periksa ejaan, atau tangani daftar kosong |
Could not load source layer | Masukan alat tidak terbaca | Periksa jalur dan isValid() sebelum memanggil alat |
Algorithm ... not found | ID alat salah ketik | Cari lagi ID dengan Skrip 2.1 |
| Hasil kosong tanpa galat | Filter atau CRS tidak cocok | Periksa jumlah fitur setiap langkah |
#Bagian B: ArcGIS Pro
#Bagian B: Penanganan galat di ArcGIS Pro
Alat geoprocessing yang gagal memicu arcpy.ExecuteError. Pesan alat yang gagal dibaca dengan arcpy.GetMessages(2). Menurut dokumentasi Esri, galat dibagi dua kelompok: galat geoprocessing (arcpy.ExecuteError) dan galat Python lain. Keduanya ditangkap terpisah. Dalam alat skrip, pesan dikirim ke jendela alat dengan arcpy.AddError.
Pemeriksaan masukan memakai arcpy.Exists, arcpy.ListFields, dan arcpy.Describe(...).spatialReference.factoryCode. Bila CRS berbeda, arcpy.management.Project mengubahnya. [CEK]
# [SKRIP 4.1-Pro: Penanganan galat dengan ArcPy: periksa masukan lalu tangkap ExecuteError]
# Penulis: Badar Mubarok Yogaswara
# [CEK] Cocokkan dengan versi Anda. Kiriman lapangan berbentuk Shapefile di folder Kiriman_Lapangan\shp.
import glob
import os
import sys
import arcpy
DATA = r"D:\Latihan\paket-i4"
FIELD_WAJIB = ["Kondisi", "Tinggi_Phn", "Foto"]
log = []
def periksa(jalur):
"""Berhenti dengan pesan jelas bila masukan tidak layak. Mengembalikan kode EPSG data."""
if not arcpy.Exists(jalur):
raise ValueError("data tidak ada atau tidak bisa dibuka")
nama_field = [f.name for f in arcpy.ListFields(jalur)]
kurang = [f for f in FIELD_WAJIB if f not in nama_field]
if kurang:
raise ValueError("field hilang: " + ", ".join(kurang))
return arcpy.Describe(jalur).spatialReference.factoryCode
for jalur in sorted(glob.glob(DATA + r"\Kiriman_Lapangan\shp\*.shp")):
nama = os.path.basename(jalur)
try:
epsg = periksa(jalur)
if epsg != 32749:
arcpy.management.Project(jalur, "memory\\" + nama[:-4], arcpy.SpatialReference(32749))
log.append((nama, "DITERIMA", "CRS %s diubah ke 32749" % epsg))
else:
log.append((nama, "DITERIMA", "sesuai"))
except ValueError as e: # galat milik skrip kita
log.append((nama, "DITOLAK", str(e)))
except arcpy.ExecuteError: # galat dari alat geoprocessing
log.append((nama, "GAGAL ALAT", arcpy.GetMessages(2)))
except Exception: # galat Python lain
log.append((nama, "TAK TERDUGA", str(sys.exc_info()[1])))
for baris in log:
print("%-14s %-12s %s" % baris)#Bagian C: ArcMap 10.8
#Bagian C: Penanganan galat di ArcMap 10.8
Konsepnya sama: arcpy.ExecuteError dan arcpy.GetMessages(2). Perbedaannya hanya nama alat (arcpy.Project_management) dan ruang sementara (in_memory). Bentuk except ValueError as e sudah berlaku di Python 2.7.
# -*- coding: utf-8 -*-
# [SKRIP 4.1-ArcMap: Penanganan galat dengan ArcPy (Python 2.7)]
# Penulis: Badar Mubarok Yogaswara
# [CEK] Cocokkan dengan versi Anda. Kiriman lapangan diandaikan berbentuk Shapefile di folder Kiriman_Lapangan\shp.
import glob
import os
import sys
import arcpy
DATA = r"D:\Latihan\paket-i4"
FIELD_WAJIB = ["Kondisi", "Tinggi_Phn", "Foto"]
log = []
def periksa(jalur):
"""Berhenti dengan pesan jelas bila masukan tidak layak. Mengembalikan kode EPSG data."""
if not arcpy.Exists(jalur):
raise ValueError("data tidak ada atau tidak bisa dibuka")
nama_field = [f.name for f in arcpy.ListFields(jalur)]
kurang = [f for f in FIELD_WAJIB if f not in nama_field]
if kurang:
raise ValueError("field hilang: " + ", ".join(kurang))
return arcpy.Describe(jalur).spatialReference.factoryCode
for jalur in sorted(glob.glob(DATA + r"\Kiriman_Lapangan\shp\*.shp")):
nama = os.path.basename(jalur)
try:
epsg = periksa(jalur)
if epsg != 32749:
arcpy.Project_management(jalur, "in_memory\\" + nama[:-4], arcpy.SpatialReference(32749))
log.append((nama, "DITERIMA", "CRS %s diubah ke 32749" % epsg))
else:
log.append((nama, "DITERIMA", "sesuai"))
except ValueError as e: # galat milik skrip kita
log.append((nama, "DITOLAK", str(e)))
except arcpy.ExecuteError: # galat dari alat geoprocessing
log.append((nama, "GAGAL ALAT", arcpy.GetMessages(2)))
except Exception: # galat Python lain
log.append((nama, "TAK TERDUGA", str(sys.exc_info()[1])))
for baris in log:
print("%-14s %-12s %s" % baris)#Cek paham
- Apa beda blok
elsedanfinallypadatry? - Mengapa
except:kosong sebaiknya dihindari? - Mengapa fungsi
jalankan_amanmengembalikan pasangan (hasil, pesan)?
Jawaban:
elsehanya jalan bila tidak ada galat.finallyselalu jalan.- Karena menelan semua galat, termasuk kesalahan Anda sendiri, sehingga masalah tersembunyi.
- Agar pemanggil bisa memeriksa hasil dan membaca sebab kegagalan tanpa menulis
tryberulang kali.
#Kesalahan umum
- Menangkap galat lalu diam. Selalu catat sebabnya ke layar atau berkas log.
- Memperbaiki data tanpa mencatat. Tulis di log bahwa CRS diubah atau titik digeser.
- Memeriksa terlalu sedikit. Berkas yang bisa dibuka belum tentu field-nya lengkap, dan koordinatnya belum tentu di area yang benar.
- Percaya penuh pada angka "berhasil". Jumlah fitur gabungan harus cocok dengan jumlah kiriman yang diterima.
#Ringkasan dan latihan
Ringkasan: periksa masukan sebelum dipakai, tangkap galat dengan try-except pada jenis yang diperkirakan, dan catat hasil tiap berkas. Batch yang baik tidak berhenti karena satu berkas, dan menutup pekerjaan dengan laporan diterima, ditolak, dan curiga.
Latihan:
- Tambahkan satu pemeriksaan baru ke Skrip 4.2: tolak berkas yang
featureCount()bernilai 0. - Ubah Skrip 3.1 (NDVI ubin) agar tahan galat. Jalankan pada folder
Ortofoto_Campur, lalu pastikan tiga ubin baik tetap diproses dan ubin rusak dicatat.
#Tabel perbandingan: penanganan galat
| Hal | QGIS | ArcGIS Pro | ArcMap 10.8 |
|---|---|---|---|
| Galat alat | QgsProcessingException | arcpy.ExecuteError | arcpy.ExecuteError |
| Pesan galat alat | str(e) | arcpy.GetMessages(2) | arcpy.GetMessages(2) |
| Cek data sah | isValid() | arcpy.Exists() [CEK] | arcpy.Exists() [CEK] |
| Cek CRS | layer.crs().authid() | Describe().spatialReference.factoryCode [CEK] | Describe().spatialReference.factoryCode [CEK] |
| Ubah CRS | native:reprojectlayer | arcpy.management.Project [CEK] | arcpy.Project_management [CEK] |
| Catatan ke pengguna | QgsMessageLog | arcpy.AddError, arcpy.AddMessage | arcpy.AddError, arcpy.AddMessage |