BAB 5: Ekspresi: Bahasa Rumus yang Dipakai di Mana-Mana
#Studi kasus: "Kalimat laporan yang menulis dirinya sendiri"
Kepala Seksi ingin satu kalimat ringkas untuk tiap petak, misalnya "KPH Beta, 4 ha, 5 titik panas". Setiap bulan angkanya berubah, dan Anda mengetik ulang semuanya. Bulan ini Anda mencoba cara lain: menuliskan rumusnya sekali. QGIS yang menghitung dan menyusun kalimatnya untuk setiap petak.
#Konsep: ekspresi dalam tiga kalimat
Ekspresi adalah rumus tertulis yang dihitung QGIS untuk setiap fitur, mirip rumus di sel lembar kerja. Rumus yang sama dipakai di banyak tempat: seleksi, kalkulator field, label, atlas, dan model. Agregasi menambah satu kemampuan: menghitung ringkasan dari layer lain, misalnya jumlah titik panas yang jatuh di dalam tiap petak.
Istilah baru bab ini:
- Ekspresi: rumus yang dihitung per fitur.
- Fungsi ekspresi: perintah kecil di dalam rumus, seperti
round()atauupper(). - Konteks: "lingkungan" tempat rumus dihitung, yaitu fitur yang sedang dibaca dan variabel proyek.
- Agregasi (
aggregate): ringkasan dari layer lain, seperti jumlah, rata-rata, atau gabungan teks. - `@parent`: fitur yang sedang dibaca di layer luar, dipakai di dalam agregasi.

#Bagian A: QGIS
#Bagian A: Ekspresi di QGIS
#A.1 Ekspresi dasar
Dua aturan tanda kutip harus dihafal. Tanda kutip ganda ("NAMA_KPH") menunjuk nama kolom. Tanda kutip tunggal ('KPH Beta') menulis teks. Operator || menyambung teks. Hasil uji di QGIS 4.0.2 untuk KPH Alpha:
| Ekspresi | Hasil |
|---|---|
round($area / 10000, 2) | 4 |
upper("NAMA_KPH") | KPH ALPHA |
replace("NAMA_KPH", 'KPH ', '') | Alpha |
if($area > 50000, 'besar', 'kecil') | kecil |
CASE WHEN $area > 50000 THEN 'besar' ELSE 'kecil' END | kecil |
Gabungan teks ditulis dengan ||. Contoh yang menghasilkan KPH Alpha (4 ha):
"NAMA_KPH" || ' (' || round($area / 10000, 1) || ' ha)'#A.2 Menghitung ekspresi dari Python
Skrip 5.1 membangun "lingkungan" (konteks) lalu menghitung ekspresi untuk tiap petak. Skrip ini juga menunjukkan dua jenis galat ekspresi: galat tulis (rumus tidak lengkap) dan galat hitung (rumus benar tetapi nilainya tidak bisa dihitung).
# [SKRIP 5.1: Mengevaluasi ekspresi QGIS dari Python, termasuk agregasi]
# Penulis: Badar Mubarok Yogaswara
from qgis.core import (QgsProject, QgsVectorLayer, QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils, QgsCoordinateReferenceSystem)
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
proyek = QgsProject.instance()
batas = QgsVectorLayer(DATA + "/Batas_KPH.gpkg", "Batas_KPH", "ogr")
hotspot = QgsVectorLayer(DATA + "/Hotspot_Nasional.gpkg", "Hotspot_Nasional", "ogr")
survei = QgsVectorLayer(DATA + "/Survei.gpkg", "Titik_Survei", "ogr")
for lyr in (batas, hotspot, survei):
proyek.addMapLayer(lyr) # fungsi aggregate() mencari layer lain berdasarkan NAMA di proyek
def konteks(layer, fitur):
"""Membuat 'lingkungan' tempat ekspresi dihitung: variabel proyek, layer, dan fitur saat ini."""
c = QgsExpressionContext()
c.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))
c.setFeature(fitur)
return c
def hitung(teks, layer, fitur):
e = QgsExpression(teks)
if e.hasParserError(): # salah tulis ekspresi
return "GALAT TULIS: " + e.parserErrorString()
nilai = e.evaluate(konteks(layer, fitur))
if e.hasEvalError(): # salah saat dihitung
return "GALAT HITUNG: " + e.evalErrorString()
return nilai
# 1. Ekspresi dasar: rumus, teks, dan pilihan (CASE)
dasar = {
"luas (ha)": "round($area / 10000, 2)",
"nama huruf besar": 'upper("NAMA_KPH")',
"ukuran": "CASE WHEN $area > 50000 THEN 'besar' ELSE 'kecil' END",
}
for fitur in batas.getFeatures():
print(fitur["NAMA_KPH"], {k: hitung(v, batas, fitur) for k, v in dasar.items()})
# 2. Agregasi: menghitung dari LAYER LAIN. @parent = fitur KPH yang sedang dibaca.
titik_panas = ("aggregate(layer:='Hotspot_Nasional', aggregate:='count', expression:=\"ID_Titik\", "
"filter:=intersects($geometry, geometry(@parent)))")
tinggi_rata = ("round(aggregate(layer:='Titik_Survei', aggregate:='mean', expression:=\"Tinggi_Phn\", "
"filter:=intersects($geometry, geometry(@parent))), 1)")
print("--- agregasi per KPH ---")
for fitur in batas.getFeatures():
print("%-10s titik panas = %s | tinggi pohon rata-rata = %s" % (
fitur["NAMA_KPH"], hitung(titik_panas, batas, fitur), hitung(tinggi_rata, batas, fitur)))
# 3. Agregasi tanpa filter: satu angka untuk seluruh layer
fitur = next(batas.getFeatures())
total = hitung("aggregate('Hotspot_Nasional', 'count', \"ID_Titik\")", batas, fitur)
print("Total titik panas (semua KPH):", total)
# 4. Ekspresi yang salah dikenali dengan aman
print(hitung("round($area / 10000, ", batas, fitur))
print(hitung("to_int('abc') + 1", batas, fitur))
# 5. Dua cara lain menghitung titik: daftar dan gabungan teks
lain = {
"jumlah lewat overlay": "array_length(overlay_intersects('Hotspot_Nasional', \"ID_Titik\"))",
"daftar ID": ("aggregate('Hotspot_Nasional', 'concatenate', \"ID_Titik\", "
"filter:=intersects($geometry, geometry(@parent)), concatenator:=', ')"),
}
for fitur in batas.getFeatures():
print(fitur["NAMA_KPH"], {k: hitung(v, batas, fitur) for k, v in lain.items()})
# 6. Awas: $area mengikuti pengaturan ellipsoid proyek, area($geometry) memakai bidang datar peta
fitur = next(batas.getFeatures())
print("Tanpa ellipsoid :", hitung("$area", batas, fitur), hitung("area($geometry)", batas, fitur))
proyek.setCrs(QgsCoordinateReferenceSystem("EPSG:32749"))
proyek.setEllipsoid("EPSG:7030") # WGS 84, pengaturan umum pada proyek baru
print("Dengan ellipsoid :", round(hitung("$area", batas, fitur), 2), hitung("area($geometry)", batas, fitur))Hasil uji di QGIS 4.0.2:
KPH Alpha {'luas (ha)': 4.0, 'nama huruf besar': 'KPH ALPHA', 'ukuran': 'kecil'}
KPH Beta {'luas (ha)': 4.0, 'nama huruf besar': 'KPH BETA', 'ukuran': 'kecil'}
KPH Gamma {'luas (ha)': 8.0, 'nama huruf besar': 'KPH GAMMA', 'ukuran': 'besar'}
--- agregasi per KPH ---
KPH Alpha titik panas = 2 | tinggi pohon rata-rata = 11.3
KPH Beta titik panas = 5 | tinggi pohon rata-rata = 9.6
KPH Gamma titik panas = 5 | tinggi pohon rata-rata = 8.9
Total titik panas (semua KPH): 12
GALAT TULIS:
Incomplete expression. You might not have finished the full expression.
GALAT HITUNG: Cannot convert 'abc' to int
KPH Alpha {'jumlah lewat overlay': 2, 'daftar ID': 'HS-008, HS-012'}
KPH Beta {'jumlah lewat overlay': 5, 'daftar ID': 'HS-004, HS-005, HS-006, HS-009, HS-011'}
KPH Gamma {'jumlah lewat overlay': 5, 'daftar ID': 'HS-001, HS-002, HS-003, HS-007, HS-010'}
Tanpa ellipsoid : 40000.0 40000.0
Dengan ellipsoid : 39997.03 40000.0#A.3 Agregasi: menghitung dari layer lain
Perhatikan satu petak, misalnya KPH Beta. Rumusnya berbunyi: "dari semua titik panas, hitung hanya yang bersinggungan dengan petak ini". Bagian "petak ini" ditulis geometry(@parent). Bagian "bersinggungan" ditulis intersects($geometry, ...), di mana $geometry adalah titik panas yang sedang diperiksa.
Hasil uji: KPH Alpha 2 titik, KPH Beta 5 titik, dan KPH Gamma 5 titik. Totalnya 12 titik, sama dengan jumlah titik di layer. Dua cara lain yang memberi hasil sama: array_length(overlay_intersects('Hotspot_Nasional', "ID_Titik")) untuk jumlah, dan agregasi 'concatenate' untuk daftar ID titik.
#A.4 Ekspresi di dalam alat Processing
Ekspresi tidak hanya untuk label. Skrip 5.2 memakainya di Field calculator (menambah kolom luas dan jumlah titik panas) dan di Extract by expression (memilih petak dengan titik panas minimal 5). Terakhir, skrip menyusun teks laporan untuk tiap petak.
# [SKRIP 5.2: Ekspresi di dalam alat Processing: Field calculator, Extract by expression, label]
# Penulis: Badar Mubarok Yogaswara
import processing
from qgis.core import (QgsProject, QgsVectorLayer, QgsExpression, QgsExpressionContext,
QgsExpressionContextUtils)
DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
proyek = QgsProject.instance()
batas = QgsVectorLayer(DATA + "/Batas_KPH.gpkg", "Batas_KPH", "ogr")
hotspot = QgsVectorLayer(DATA + "/Hotspot_Nasional.gpkg", "Hotspot_Nasional", "ogr")
proyek.addMapLayer(batas)
proyek.addMapLayer(hotspot) # wajib ada di proyek agar aggregate() menemukannya
# 1. Menambah dua kolom baru dengan ekspresi
tahap1 = processing.run("native:fieldcalculator", {
"INPUT": batas, "FIELD_NAME": "LUAS_HA", "FIELD_TYPE": 0, # 0 = desimal
"FIELD_LENGTH": 10, "FIELD_PRECISION": 2,
"FORMULA": "round($area / 10000, 2)", "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
tahap2 = processing.run("native:fieldcalculator", {
"INPUT": tahap1, "FIELD_NAME": "JML_HOTSPOT", "FIELD_TYPE": 1, # 1 = bilangan bulat
"FIELD_LENGTH": 5, "FIELD_PRECISION": 0,
"FORMULA": "aggregate('Hotspot_Nasional', 'count', \"ID_Titik\", "
"intersects($geometry, geometry(@parent)))",
"OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
tahap2.setName("Batas_KPH_Ringkas")
proyek.addMapLayer(tahap2)
print("Kolom:", [f.name() for f in tahap2.fields()])
for f in tahap2.getFeatures():
print(" ", f["NAMA_KPH"], f["LUAS_HA"], "ha;", f["JML_HOTSPOT"], "titik panas")
# 2. Memilih fitur dengan ekspresi lewat alat
rawan = processing.run("native:extractbyexpression", {
"INPUT": tahap2, "EXPRESSION": '"JML_HOTSPOT" >= 5', "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
print("KPH dengan titik panas >= 5:", sorted(f["NAMA_KPH"] for f in rawan.getFeatures()))
# 3. Teks laporan satu KPH, dibentuk dengan ekspresi (cara yang sama dipakai label di layout)
teks = ("'LAPORAN ' || upper(\"NAMA_KPH\") || '\n' || 'Luas: ' || \"LUAS_HA\" || ' ha\n' || "
"'Titik panas: ' || \"JML_HOTSPOT\" || ' titik'")
ekspresi = QgsExpression(teks)
for f in tahap2.getFeatures():
c = QgsExpressionContext()
c.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(tahap2))
c.setFeature(f)
print(ekspresi.evaluate(c))
print("---")Hasil uji di QGIS 4.0.2:
Kolom: ['fid', 'NAMA_KPH', 'LUAS_HA', 'JML_HOTSPOT']
KPH Alpha 4.0 ha; 2 titik panas
KPH Beta 4.0 ha; 5 titik panas
KPH Gamma 8.0 ha; 5 titik panas
KPH dengan titik panas >= 5: ['KPH Beta', 'KPH Gamma']
LAPORAN KPH ALPHA
Luas: 4 ha
Titik panas: 2 titik
---
LAPORAN KPH BETA
Luas: 4 ha
Titik panas: 5 titik
---
LAPORAN KPH GAMMA
Luas: 8 ha
Titik panas: 5 titik
---Hasilnya: kolom LUAS_HA dan JML_HOTSPOT terbentuk, KPH Beta dan KPH Gamma terpilih, dan teks laporan tersusun sendiri untuk tiap petak.
#Bagian B: ArcGIS Pro
#Bagian B: Ekspresi di ArcGIS Pro
ArcGIS Pro memakai Arcade untuk ekspresi bergaya QGIS. Alat Calculate Field menerima ekspresi Arcade, dan lewat ArcPy jenis ekspresinya ditulis "ARCADE". [CEK] Untuk menghitung titik dalam poligon, Arcade memakai FeatureSetByName, Intersects, dan Count. Pada Calculate Field, pencarian layer lain dilaporkan memakai $datastore, bukan $map. Bentuk persisnya perlu diuji pada geodatabase Anda. [CEK]
import arcpy
arcpy.management.CalculateField("Batas_KPH", "LUAS_HA", "Round(Area($feature, 'hectares'), 2)", "ARCADE")
arcpy.management.CalculateField("Batas_KPH", "HOTSPOT",
"Count(Intersects($feature, FeatureSetByName($datastore, 'Hotspot_Nasional')))", "ARCADE")Cara yang lebih pasti adalah Spatial Join, yang menambah kolom Join_Count (lihat Skrip 2.1-Pro). Ekspresi Python pada Calculate Field juga tersedia, misalnya !shape.area@hectares!. [CEK]
#Bagian C: ArcMap 10.8
#Bagian C: Ekspresi di ArcMap 10.8
ArcMap tidak punya Arcade. Field Calculator menerima ekspresi VB Script atau Python. Untuk luas hektare, ekspresi Python-nya !shape.area@hectares!. [CEK] Agregasi lintas layer tidak ada padanan langsung di kalkulator field. Pakai Spatial Join (kolom Join_Count) atau Summary Statistics, seperti pada Skrip 2.1-ArcMap.
import arcpy
arcpy.CalculateField_management("Batas_KPH", "LUAS_HA", "!shape.area@hectares!", "PYTHON_9.3")#Cek paham
- Apa arti
@parentdi dalamaggregate? - Mengapa layer
Hotspot_Nasionalharus ada di proyek saat rumus agregasi dihitung? - Apa beda
$areadanarea($geometry)?
Jawaban:
- Fitur yang sedang dibaca pada layer luar, misalnya petak KPH yang sedang dihitung.
- Karena
aggregatemencari layer lain berdasarkan nama di proyek. Bila tidak ketemu, hasilnyaNULLdengan pesan galat. $areamengikuti ellipsoid pengaturan proyek.area($geometry)memakai bidang datar peta.
#Kesalahan umum
- Tertukar tanda kutip.
"KPH Alpha"dibaca sebagai nama kolom dan menghasilkan galatField 'KPH Alpha' not found. Teks ditulis'KPH Alpha'. - Menyambung dengan nilai kosong.
'a' || NULLmenghasilkanNULL, bukana. Bungkus dengancoalesce(nilai, 0). - Layer agregasi tidak ada di proyek. Tambahkan layer ke proyek dengan nama yang sama persis.
- Lupa memeriksa galat ekspresi. Pakai
hasParserError()danhasEvalError()sebelum memakai hasil.
#Ringkasan dan latihan
Ringkasan: ekspresi adalah rumus per fitur. Kutip ganda untuk kolom, kutip tunggal untuk teks. aggregate menghitung dari layer lain dengan filter intersects($geometry, geometry(@parent)). Ekspresi dapat dihitung dari Python (QgsExpression) maupun di dalam alat (Field calculator, Extract by expression).
Latihan:
- Tulis ekspresi yang menghasilkan "KPH Gamma: 8 ha, 5 titik panas". Hasil yang benar untuk Gamma memakai luas 8 dan jumlah titik 5.
- Pakai
aggregatedengan'mean'padaSurvei.gpkg(layerTitik_Survei) untuk menghitung tinggi pohon rata-rata tiap petak. Hasil pada Skrip 5.1: Alpha 11,3, Beta 9,6, dan Gamma 8,9.
#Tabel perbandingan: ekspresi
| Hal | QGIS | ArcGIS Pro | ArcMap 10.8 |
|---|---|---|---|
| Bahasa ekspresi | Ekspresi QGIS | Arcade atau Python [CEK] | VB Script atau Python [CEK] |
| Kolom hitungan | Field calculator | Calculate Field [CEK] | Field Calculator [CEK] |
| Agregasi layer lain | aggregate(...) | Arcade FeatureSetByName [CEK] atau Spatial Join | Tidak ada padanan langsung; Spatial Join |
| Hitung dari Python | QgsExpression | CalculateField dengan "ARCADE" [CEK] | CalculateField_management [CEK] |
| Dipakai di label dan atlas | Ya | Label Arcade [CEK] | Tidak seperti QGIS [CEK] |