Day 2 — Hands-on
2 jam · dari citra mentah sampai berkas untuk laporan
Hari ini semua orang mengetik. Skripnya tumbuh sedikit demi sedikit — tidak ada langkah yang dibuang, dan yang kita bangun adalah satu alur utuh:
Kawasan tambang nikel Konawe Utara, Sulawesi Tenggara: berapa hektar vegetasi hilang antara 2023 dan 2026, tutupan lahannya seperti apa, dan bagaimana mengeluarkannya sebagai berkas yang bisa dilampirkan.
Setengah kiri halaman ini, setengah kanan Code Editor. Peserta yang bolak-balik antar tab kehilangan banyak waktu.
Area kerja
// Kawasan tambang nikel, Konawe Utara. Ganti ke wilayah Anda kalau mau —
// semua bagian selanjutnya mengikuti variabel ini.
var area = ee.Geometry.Rectangle([121.90, -3.30, 122.05, -3.15]);
Map.centerObject(area, 12);Untuk wilayah Anda sendiri: cari di Google Maps, klik kanan, salin koordinatnya — lalu ingat membaliknya menjadi [bujur, lintang]. Kalau peta Anda mendarat di Somalia, keduanya tertukar.
1 · Komposit bebas awan
20 menit
Satu citra jarang cukup di Indonesia, dan pesertanya biasanya sudah tahu alasannya sebelum kita menyebutkannya: awan. Menunggu satu citra yang benar-benar bersih di atas Sulawesi bisa berarti menunggu berbulan-bulan.
Solusinya bukan mencari citra sempurna, melainkan menggabungkan banyak citra tidak sempurna. Untuk setiap piksel: buang yang tertutup awan, lalu ambil nilai tengah (median) dari sisanya. Awan di citra A ditambal piksel bersih dari citra F — otomatis, per piksel.
// Membuang piksel awan dengan band SCL (Scene Classification Layer)
function maskAwan (citra) {
var scl = citra.select('SCL');
var bersih = scl.neq(3) // 3 = bayangan awan
.and(scl.neq(8)) // 8 = awan probabilitas sedang
.and(scl.neq(9)) // 9 = awan probabilitas tinggi
.and(scl.neq(10)) // 10 = cirrus tipis
.and(scl.neq(11)); // 11 = salju / es
return citra.updateMask(bersih);
}
// Satu komposit median untuk satu rentang tanggal
function komposit (mulai, selesai) {
return ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
.filterBounds(area)
.filterDate(mulai, selesai)
.filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 60))
.map(maskAwan)
.median();
}
var sebelum = komposit('2023-01-01', '2023-12-31');
var sesudah = komposit('2026-01-01', '2026-06-30');
Map.addLayer(sebelum.clip(area), {bands:['B4','B3','B2'], min:0, max:3000}, 'Sebelum 2023');
Map.addLayer(sesudah.clip(area), {bands:['B4','B3','B2'], min:0, max:3000}, 'Sesudah 2026');Jalankan, lalu bandingkan kedua lapisan dengan menyalakan–mematikannya lewat panel Layers. Perbedaannya seharusnya sudah terlihat dengan mata telanjang.
max: 3000, bukan 255?
Sentinel-2 Level-2A menyimpan pantulan permukaan dalam bilangan bulat yang dikalikan 10.000. Jadi nilai 3000 berarti pantulan 0,30 alias 30% — kira-kira batas atas yang wajar untuk vegetasi dan tanah. Kalau citra tampak terlalu gelap, naikkan; terlalu terang, turunkan.
Tambahkan juga versi warna semu, lalu nyalakan bergantian:
Map.addLayer(sesudah.clip(area), {bands:['B8','B4','B3'], min:0, max:3000}, 'Warna semu');Pada warna semu, vegetasi sehat menyala merah terang — karena daun memantulkan inframerah-dekat jauh lebih kuat daripada cahaya tampak. Sifat inilah yang dipakai NDVI di bagian berikutnya.
2 · NDVI
20 menit
function ndvi (citra) {
return citra.normalizedDifference(['B8', 'B4']).rename('NDVI');
}
var ndviSebelum = ndvi(sebelum).clip(area);
var ndviSesudah = ndvi(sesudah).clip(area);
var visNDVI = {min: -0.2, max: 0.8,
palette: ['#c8553d', '#f0e6c8', '#2d6a4f']};
Map.addLayer(ndviSebelum, visNDVI, 'NDVI 2023');
Map.addLayer(ndviSesudah, visNDVI, 'NDVI 2026');| NDVI | Biasanya |
|---|---|
| < 0 | air |
| 0 – 0.2 | lahan terbuka, terbangun, tambang |
| 0.2 – 0.4 | vegetasi jarang, semak |
| 0.4 – 0.7 | pertanian, perkebunan |
| > 0.7 | hutan rapat |
Verifikasi dengan Inspector sebelum lanjut. Klik di badan air — harus negatif. Klik di hutan — harus di atas 0,6. Kalau tidak sesuai, ada yang salah pada komposit Anda, dan lebih baik ketahuan sekarang daripada tiga bagian kemudian.
Grafik deret waktu
var setahun = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
.filterBounds(area).filterDate('2025-01-01', '2025-12-31')
.map(maskAwan)
.map(function (img) {
return img.addBands(ndvi(img)).copyProperties(img, ['system:time_start']);
});
print(ui.Chart.image.series({
imageCollection: setahun.select('NDVI'),
region: area, reducer: ee.Reducer.mean(), scale: 100
}).setOptions({title: 'NDVI sepanjang 2025', vAxis: {title: 'NDVI'}}));Grafik muncul di Console. Klik ikon panah di pojoknya untuk membukanya di tab baru, lalu unduh sebagai PNG untuk laporan atau CSV kalau angkanya mau diolah di Excel.
3 · Klasifikasi tutupan lahan
30 menit · bagian baru, dan yang paling sering diminta
NDVI memberi tahu seberapa hijau. Klasifikasi memberi tahu apa — hutan, tambang, air, terbangun — dan itu yang biasanya diminta dalam laporan.
Skrip lengkapnya: 05-land-cover-classification.js.
Langkah 1 · Siapkan band untuk klasifikasi
Menambahkan indeks hampir selalu menaikkan akurasi, karena air dan lahan terbuka jadi lebih mudah dibedakan dari vegetasi.
var citra = sesudah.select(['B2','B3','B4','B8','B11','B12'])
.addBands(ndvi(sesudah))
.addBands(sesudah.normalizedDifference(['B3','B8']).rename('NDWI'))
.addBands(sesudah.normalizedDifference(['B11','B8']).rename('NDBI'))
.clip(area);Langkah 2 · Gambar titik latih — bagian yang tidak bisa disalin
Komputer tidak tahu seperti apa “tambang” di wilayah Anda. Anda yang memberi tahu.
Di panel Geometry Imports (kiri atas peta): klik + new layer, ganti namanya, klik ikon roda gigi → Import as: FeatureCollection, tambahkan property kelas berisi angka. Lalu gambar 20–40 titik di area yang jelas kelas itu.
| Kelas | Nilai | Warna |
|---|---|---|
| air | 0 | biru tua |
| hutan | 1 | hijau tua |
| pertanian / semak | 2 | hijau muda |
| terbangun | 3 | merah |
| lahan terbuka / tambang | 4 | coklat |
Mulai dari sedikit kelas. Lima kelas yang benar jauh lebih berguna daripada dua belas kelas yang saling tertukar.
Langkah 3 · Bagi 70/30, latih, uji
var contoh = citra.sampleRegions({
collection: titikLatih, properties: ['kelas'], scale: 10, tileScale: 2
});
var acak = contoh.randomColumn('acak', 42);
var latih = acak.filter(ee.Filter.lt('acak', 0.7)); // 70% melatih
var uji = acak.filter(ee.Filter.gte('acak', 0.7)); // 30% menguji
var model = ee.Classifier.smileRandomForest(100)
.train({features: latih, classProperty: 'kelas',
inputProperties: citra.bandNames()});
var hasil = citra.classify(model);
Map.addLayer(hasil, {min:0, max:4,
palette:['1f4e79','1a7d3a','9acd32','c0392b','d9b38c']}, 'Tutupan lahan');Langkah 4 · Baca akurasinya sebelum melihat petanya
var matriks = uji.classify(model).errorMatrix('kelas', 'classification');
print('Overall accuracy:', matriks.accuracy());
print('Kappa:', matriks.kappa());
print('Confusion matrix:', matriks);Apa pun yang Anda masukkan, hasilnya berupa peta berwarna rapi yang terlihat profesional. Itu sebabnya pembagian 70/30 tidak boleh dilewati.
Kalau model diuji dengan titik yang sama seperti yang dipakai melatih, akurasinya hampir selalu tinggi — persis seperti murid yang sudah diberi bocoran soal. Angka itu tidak berarti apa-apa.
| Overall accuracy | Artinya |
|---|---|
| > 0.85 | layak dipakai untuk laporan |
| 0.70 – 0.85 | cukup; sebutkan angkanya, waspadai kelas yang lemah |
| < 0.70 | jangan dipakai; tambah titik latih dulu |
Kalau rendah, lihat confusion matrix: baris yang nilainya tersebar menunjukkan kelas mana tertukar dengan apa. Yang paling sering di Indonesia: pertanian tertukar dengan lahan terbuka, karena sawah bera secara spektral memang mirip tanah kosong.
Kalau muncul “User memory limit exceeded”, inilah batas yang disebut kemarin. Tiga cara mengatasinya, dari yang paling ringan: naikkan tileScale (2 → 4 → 8), perkecil AOI, atau naikkan scale dari 10 ke 20 selagi masih bereksperimen.
4 · Deteksi perubahan
25 menit
var selisih = ndviSesudah.subtract(ndviSebelum);
Map.addLayer(selisih, {min:-0.3, max:0.3,
palette:['#b2182b','#f7f7f7','#1a9850']}, 'Perubahan NDVI');
var AMBANG = -0.15; // turun lebih dari 0,15 dianggap kehilangan
var hilang = selisih.lt(AMBANG);
Map.addLayer(hilang.selfMask().clip(area), {palette:['red']}, 'Kehilangan vegetasi');.selfMask() menyembunyikan piksel bernilai nol, sehingga yang tergambar hanya area yang benar-benar berubah.
Lalu ubah peta menjadi angka — bagian yang biasanya paling ditunggu:
var luas = hilang.multiply(ee.Image.pixelArea()) // luas tiap piksel, m2
.divide(10000) // m2 -> hektar
.reduceRegion({
reducer: ee.Reducer.sum(), geometry: area,
scale: 20, maxPixels: 1e10
});
print('Luas kehilangan vegetasi (ha):', luas);reduceRegion meringkas seluruh piksel dalam sebuah wilayah menjadi satu angka. Parameter scale menentukan resolusi perhitungan, dan berpengaruh besar pada waktu proses; untuk latihan 20 m sudah memadai.
Tidak ada yang menurunkannya dari langit. Ubah menjadi −0,10 dan area merah melebar; −0,25 dan ia menyempit.
Karena itu, saat melaporkan hasil, selalu sebutkan ambang yang dipakai. Pernyataan “wilayah A dua kali wilayah B pada ambang yang sama” jauh lebih kokoh daripada “wilayah A rusak 37,2%”.
Untuk pemateri — hasil yang seharusnya muncul
Sudah diuji lebih dulu pada AOI seluas 11.175 ha (30 citra pada 2023, 13 citra pada 2026):
| Ambang | Luas terdeteksi | Porsi AOI |
|---|---|---|
| −0,10 | 1.681 ha | 15,0% |
| −0,15 (nilai di skrip) | 1.178 ha | 10,5% |
| −0,25 | 560 ha | 5,0% |
Inilah inti latihan berikutnya, dan sebaiknya ditulis di papan: menggeser satu angka dari −0,10 ke −0,25 mengubah jawaban tiga kali lipat — 1.681 ha versus 560 ha — pada data yang sama persis. Tidak ada yang keliru, tidak ada bug. Yang berubah cuma satu keputusan yang biasanya tidak pernah disebutkan dalam laporan.
Kalau angka peserta meleset agak jauh dari tabel ini, penyebabnya kemungkinan besar arsip yang bertambah — citra 2026 masih terus masuk — bukan kesalahan pengetikan.
5 · Ekspor untuk laporan
25 menit
Peta di layar bagus untuk diskusi, tapi cepat atau lambat hasilnya perlu masuk QGIS, ArcGIS, atau lampiran laporan.
Export.image.toDrive({
image: hasil.byte(), // byte: berkas jauh lebih kecil
description: 'tutupan_lahan_2026',
folder: 'GEE_webinar',
region: area,
scale: 10,
maxPixels: 1e10,
fileFormat: 'GeoTIFF'
});Export tidak langsung mengekspor
Menjalankan skrip hanya mengantre tugasnya. Anda masih harus membuka tab Tasks di panel kanan dan menekan RUN pada tiap tugas. Ini keluhan nomor satu peserta webinar GEE, tanpa terkecuali.
| Keperluan | Format |
|---|---|
| dibuka di QGIS/ArcGIS | GeoTIFF |
| gambar untuk laporan Word | GeoTIFF hasil .visualize() |
| angka untuk tabel | CSV lewat Export.table |
| grafik | PNG dari Console |
Untuk peta yang benar-benar siap cetak — dengan skala, arah utara dan kotak judul — ekspor GeoTIFF-nya, lalu susun tata letaknya di QGIS. Earth Engine unggul untuk analisis; QGIS lebih baik untuk kartografi.
Tugas · 10 menit
a. Ganti AMBANG menjadi −0,10 lalu −0,25. Catat luasnya masing-masing. Seberapa besar pengaruh satu keputusan kecil itu terhadap angka yang akan Anda kutip dalam laporan?
b. Ganti koordinat area ke wilayah kerja Anda sendiri, dan jalankan seluruh skrip dari awal. Apakah kelas-kelas yang Anda pilih masih masuk akal di sana?
c. Lihat overall accuracy klasifikasi Anda. Kalau di bawah 0,70, tambah titik latih pada kelas yang paling tertukar di confusion matrix, lalu ulangi.
Setelah webinar
- Jalankan ulang untuk wilayah Anda minggu ini juga, selagi masih ingat. Menunda sebulan berarti mengulang dari nol.
- Ganti satu hal, lihat akibatnya. Memahami datang dari mengubah, bukan dari menjalankan.
- Simpan skrip Anda lewat Scripts → NEW → File, dan pakai Get Link untuk membagikannya.
Kalau ada yang tidak berjalan, bawa pesan error persisnya — pesan itu hampir selalu menunjukkan baris yang bermasalah, dan sebagian besar sudah pernah ditanyakan orang lain di EE Developers Group.