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.

TipSusun layar Anda sekarang

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.

CatatanKenapa 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);
PentingPeta klasifikasi selalu terlihat meyakinkan

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.

PeringatanDi sinilah kuota itu terasa

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.

PentingAngka −0,15 itu pilihan, bukan kebenaran

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'
});
PentingExport 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

  1. Jalankan ulang untuk wilayah Anda minggu ini juga, selagi masih ingat. Menunda sebulan berarti mengulang dari nol.
  2. Ganti satu hal, lihat akibatnya. Memahami datang dari mengubah, bukan dari menjalankan.
  3. 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.


← Day 1 — Concepts · Semua skrip · Rujukan