Satellite Change Detection

PyPI version Python versions MIT  pip install satchange

A hands-on tutorial. Map deforestation, mining, urbanisation, floods, burns and surface-water change anywhere on Earth from free satellite data — then export georeferenced GeoTIFFs and print-ready maps. All in Python, powered by Google Earth Engine and Microsoft Planetary Computer.

Tutorial praktis. Petakan deforestasi, tambang, urbanisasi, banjir, kebakaran, dan perubahan air permukaan di mana saja dari data satelit gratis — lalu ekspor GeoTIFF tergeoreferensi dan peta siap cetak. Semua dengan Python, ditenagai Google Earth Engine dan Microsoft Planetary Computer.

Python 3.11+ Google Earth Engine Planetary Computer Sentinel-1/2 & Landsat Free & open dataData gratis & terbuka MIT
Example deforestation change map
A finished product: a deforestation map (Sentinel-2 NDVI change) from one command. Red = vegetation loss.Produk jadi: peta deforestasi (perubahan NDVI Sentinel-2) dari satu perintah. Merah = kehilangan vegetasi.

1 · What you'll buildYang akan Anda buat

By the end you'll run a single command like this…

Di akhir tutorial, Anda menjalankan satu perintah seperti ini…

satchange -s deforestation --lat -3.333 --lon 122.25 --radius 6 --map

…and get three things for your chosen area:

…dan mendapat tiga berkas untuk area pilihan Anda:

You pick a scenario (what kind of change) and a location (a coordinate). The tool selects the right remote-sensing method automatically. No prior GIS coding required.

Anda memilih skenario (jenis perubahan) dan lokasi (koordinat). Alat memilih metode penginderaan jauh yang tepat secara otomatis. Tanpa perlu pengalaman pemrograman GIS.

2 · How it works (the science)Cara kerjanya (sainsnya)

“Change detection” compares the landscape at two (or more) points in time and highlights what changed. Different phenomena leave different spectral or radar fingerprints, so each scenario uses a purpose-built index or method:

“Deteksi perubahan” membandingkan bentang lahan pada dua (atau lebih) titik waktu dan menonjolkan apa yang berubah. Fenomena berbeda meninggalkan sidik jari spektral atau radar yang berbeda, sehingga tiap skenario memakai indeks atau metode khusus:

ScenarioSkenario Method & formulaMetode & rumus Sensor BasisDasar
deforestation ΔNDVI < 0. NDVI = (NIR−Red)/(NIR+Red) = (B8−B4)/(B8+B4). Vegetation loss lowers NDVI.Kehilangan vegetasi menurunkan NDVI. Sentinel-2NDVI (Rouse 1973; Tucker 1979)
urbanization ΔNDBI > 0. NDBI = (SWIR−NIR)/(SWIR+NIR) = (B11−B8)/(B11+B8). Built-up reflects more SWIR.Permukaan terbangun memantulkan lebih banyak SWIR. Sentinel-2Zha et al. 2003
burn dNBR. NBR = (NIR−SWIR2)/(NIR+SWIR2) = (B8−B12)/(B8+B12). Burns lower NBR.Kebakaran menurunkan NBR. Sentinel-2Key & Benson 2006
water ΔNDWI. NDWI = (Green−NIR)/(Green+NIR) = (B3−B8)/(B3+B8). Sentinel-2McFeeters 1996
flood SAR water: smooth water gives low VV backscatter. Uses one pre scene and one post scene from the same relative orbit (the latest pass in each window) — the standard S1 method, not a window average. Flood = water in the post scene but not the pre scene. Permanent water & the ocean are masked (ESA WorldCover). The two acquisition dates are reported in the stats and on the map.Air SAR: air halus memberi backscatter VV rendah. Memakai satu citra pra dan satu citra pasca dari orbit relatif yang sama (lintasan terbaru di tiap jendela) — metode S1 baku, bukan rata-rata jendela. Banjir = air pada citra pasca tetapi tidak pada citra pra. Air permanen & laut di-mask (ESA WorldCover). Kedua tanggal akuisisi ditampilkan di statistik dan peta. Sentinel-1UN-SPIDER
disturbance Impact mapping where water detection fails — forested hills, flash floods, landslides. Vegetation/structure removed → VH backscatter drops. Uses one pre + one post scene (same relative orbit); flags where VH fell >3 dB (severe >6 dB). SRTM slope attributes steep drops as landslide-like vs flat drops as sediment/inundation-like. Use this when flood shows nothing over terrain.Pemetaan dampak saat deteksi air gagal — perbukitan berhutan, banjir bandang, longsor. Vegetasi/struktur hilang → backscatter VH turun. Memakai satu citra pra + satu pasca (orbit relatif sama); menandai VH turun >3 dB (berat >6 dB). Lereng SRTM mengklasifikasi penurunan di lereng curam sebagai mirip longsor vs di dataran sebagai mirip endapan/genangan. Pakai ini saat flood tak menampilkan apa pun di medan bergunung. Sentinel-1SAR VH change + slopePerubahan VH SAR + lereng
coastline Sea boundary, shoreline change & retreat rate. Extracts the sea↔land boundary as a raster and a vector polyline (GeoJSON). --pre/--post maps erosion (land→sea) vs accretion; --epochs builds a periodical time-series (shorelines-by-year map + trend) and casts cross-shore transects for the retreat rate in m/yr. Three sensors via --coast-method: sar (Sentinel-1, cloud-proof), optical (Sentinel-2 MNDWI sub-pixel, 2015+), landsat (MNDWI 30 m, back to 1984 — decadal change).Batas laut, perubahan garis pantai & laju surut. Mengekstrak batas laut↔darat sebagai raster dan polyline vektor (GeoJSON). --pre/--post memetakan abrasi (darat→laut) vs akresi; --epochs membangun deret waktu periodik (peta garis pantai per tahun + tren) dan membuat transek lintas-pantai untuk laju surut m/thn. Tiga sensor lewat --coast-method: sar (Sentinel-1, tembus awan), optical (MNDWI Sentinel-2 sub-piksel, sejak 2015), landsat (MNDWI 30 m, sejak 1984 — perubahan multi-dekade). S1 / S2 / LandsatMNDWI + Otsu + marching-squares (MIT; not CoastSat/GPL)MNDWI + Otsu + marching-squares (MIT; bukan CoastSat/GPL)
transit-access Share of population with access to public transport (SDG 11.2.1). Builds the pedestrian street network from OpenStreetMap, loads stops from your GeoJSON (--transit-file) or OSM, runs a multi-source Dijkstra so every node gets its walking distance along streets to the nearest stop, then weights by the WorldPop 100 m grid: a cell has access if its nearest street node is within the threshold (default 500 m; SDG 11.2.1 uses 500 m for buses, ~1 km for rail via --walk-dist 500,1000). Because it uses the real network, a river or limited-access road with no crossing correctly blocks access. Outputs the % served, a service-area polygon, and a map.Persentase populasi dengan akses ke transportasi publik (SDG 11.2.1). Membangun jaringan jalan pejalan kaki dari OpenStreetMap, memuat halte dari GeoJSON Anda (--transit-file) atau OSM, menjalankan multi-source Dijkstra agar tiap simpul memperoleh jarak jalan kaki menyusuri jalan ke halte terdekat, lalu dibobot grid WorldPop 100 m: sebuah sel dianggap punya akses bila simpul jalan terdekatnya ≤ ambang (default 500 m; SDG 11.2.1 memakai 500 m untuk bus, ~1 km untuk kereta lewat --walk-dist 500,1000). Karena memakai jaringan nyata, sungai atau jalan bebas-hambatan tanpa penyeberangan tetap memutus akses. Keluaran: % terlayani, poligon area terlayani, dan peta. WorldPop + OSMNetwork Dijkstra + population weightingDijkstra jaringan + pembobotan populasi
mining SIRAD: mean VH backscatter of 3 periods → an R/G/B image (new activity = blue). Plus ΔNDVI loss.SIRAD: rerata backscatter VH 3 periode → citra R/G/B (aktivitas baru = biru). Plus kehilangan ΔNDVI. Sentinel-1 + S2Multi-temporal SARSAR multi-temporal
urban-trend NDBI at 3 epochs → R/G/B timing composite. Blue = newest growth, white = built all epochs.NDBI di 3 epoch → komposit waktu R/G/B. Biru = pertumbuhan terbaru, putih = terbangun sepanjang waktu. Landsat 5, 8/9Multi-epoch NDBI (archive to 1984; L7 skipped — SLC-off)NDBI multi-epoch (arsip sejak 1984; L7 dilewati — SLC-off)
urban-history Built-up expansion & vegetation loss by decade since 1980. Combines GHSL GHS-BUILT-S (authoritative built-up surface, 1980-2025) with Landsat NDBI/NDVI. Produces a "first built-up decade" map (city sprawl ring by ring), a decadal panel, built-up & vegetation trend charts, a full-span vegetation-to-urban conversion map, and a one-page infographic poster. Landsat TM/OLI are kept separate (not comparable across the 2011→2013 break): TM epochs 1990/2000/2010 (mutually comparable) drive the vegetation-loss map; OLI epochs 2015/2020/2025 (mutually comparable) are reported separately. GHSL is GEE-only; on MPC it runs Landsat-only. It also overlays the current OSM road skeleton (motorway/toll/trunk/primary/secondary) as context — honest: OSM history records when a road was mapped, not built (data starts 2004; Indonesia only well-mapped after ~2015), so only today's network is shown, never a road-construction timeline.Perluasan terbangun & kehilangan vegetasi per dekade sejak 1980. Menggabungkan GHSL GHS-BUILT-S (permukaan terbangun otoritatif, 1980-2025) dengan NDBI/NDVI Landsat. Menghasilkan peta "dekade pertama terbangun" (kota meluas cincin demi cincin), panel per dekade, grafik tren terbangun & vegetasi, peta konversi vegetasi→urban rentang penuh, dan poster infografik satu halaman. TM/OLI Landsat dipisah (tak sebanding pada 2011→2013): epoch TM 1990/2000/2010 (sebanding satu sama lain) untuk peta vegetation-loss; epoch OLI 2015/2020/2025 (sebanding satu sama lain) dilaporkan terpisah. GHSL hanya di GEE; di MPC berjalan Landsat-saja. Juga menampilkan rangka jalan OSM terkini (tol/arteri) sebagai konteks — jujur: riwayat OSM mencatat kapan jalan dipetakan, bukan dibangun (data mulai 2004; Indonesia baru terpetakan baik setelah ~2015), jadi hanya jaringan masa kini yang ditampilkan, bukan lini masa pembangunan jalan. GHSL + LandsatEU JRC GHSL
Why radar for floods and mining? Optical sensors can't see through clouds — a serious problem during floods and in the wet tropics. Sentinel-1 radar penetrates cloud and works day or night, so it captures change when optical imagery is blind. VH polarisation is most sensitive to land-surface change; VV best separates smooth open water.
Mengapa radar untuk banjir dan tambang? Sensor optik tak bisa menembus awan — masalah serius saat banjir dan di tropis basah. Radar Sentinel-1 menembus awan dan bekerja siang-malam, sehingga menangkap perubahan ketika citra optik buta. Polarisasi VH paling peka pada perubahan permukaan; VV paling baik memisahkan air terbuka.

How clouds are handledCara menangani awan

Optical scenarios never rely on a single image. For each period the tool builds a cloud-masked median composite of many Sentinel-2 scenes: every scene is masked pixel-by-pixel using the Scene Classification (SCL) band, then the median is taken. The result is effectively cloud-free even in cloudy regions. The standalone Sentinel-2 downloader additionally prefers a single scene with ≤10% cloud, falling back to the composite when none exists.

Skenario optik tak pernah bergantung pada satu citra. Untuk tiap periode, alat menyusun median composite bebas awan dari banyak scene Sentinel-2: tiap scene di-mask per-piksel memakai band Scene Classification (SCL), lalu diambil mediannya. Hasilnya praktis bebas awan bahkan di wilayah berawan. Pengunduh Sentinel-2 mandiri juga mengutamakan satu scene dengan awan ≤10%, dan beralih ke composite bila tak ada.

3 · SetupPersiapan

You need Python 3.11+. The Earth Engine backend needs a free Google Earth Engine account (sign up); the Planetary Computer backend needs no account at all.

Anda memerlukan Python 3.11+. Backend Earth Engine butuh akun Google Earth Engine gratis (daftar); backend Planetary Computer tanpa akun sama sekali.

  1. Install the packagePasang paket
    pip install 'satchange[all]'          # everything
    # or leaner:  pip install 'satchange[gee]'  ·  pip install 'satchange[mpc,maps]'
    Gives you the satchange and satmap commands. To work from source instead: git clone https://github.com/firmanhadi21/rs-change-detection && cd rs-change-detection && pip install -e '.[all]' — then python3 detect.py … works identically. Memberi perintah satchange dan satmap. Untuk memakai dari sumber: git clone https://github.com/firmanhadi21/rs-change-detection && cd rs-change-detection && pip install -e '.[all]' — lalu python3 detect.py … berfungsi sama.
  2. Authenticate Earth Engine (GEE backend only)Autentikasi Earth Engine (backend GEE saja)
    earthengine authenticate
    Opens a browser to link your Google account. To use a service-account key from any directory, point satchange at it with --ee-key /path/to/ee-geodetic.json, or export SATCHANGE_EE_KEY=/path/to/ee-geodetic.json, or copy it to ~/.config/earthengine/ee-geodetic.json. (A key at ./scripts/config/ee-geodetic.json only works when you run from the repo root.) Skip this entirely with --backend mpc. Membuka browser untuk menautkan akun Google. Untuk memakai kunci service account dari direktori mana pun, arahkan satchange dengan --ee-key /path/ke/ee-geodetic.json, atau export SATCHANGE_EE_KEY=/path/ke/ee-geodetic.json, atau salin ke ~/.config/earthengine/ee-geodetic.json. (Kunci di ./scripts/config/ee-geodetic.json hanya berlaku saat dijalankan dari root repo.) Lewati dengan --backend mpc.
  3. Check it's workingCek berjalan
    satchange --list
    Should print the six available scenarios. Harusnya menampilkan enam skenario.
No Google Earth Engine account? Add --backend mpc to any command — Microsoft Planetary Computer streams Sentinel-1/2 & Landsat Cloud-Optimized GeoTIFFs and processes them locally (comes with satchange[mpc]). Outputs and maps are identical.
Very large area? Earth Engine's direct download has a size limit, so big AOIs get coarsened to fit. Add --drive (optionally --drive-folder NAME) to also start an async full-resolution export to your Google Drive. Note: Drive export uses your personal Google account — run earthengine authenticate first (a service-account key exports to the service account's own Drive, which you can't browse). Monitor the task in the EE Code Editor Tasks tab; the GeoTIFF appears in Drive/satchange/.
Area sangat luas? Unduhan langsung Earth Engine punya batas ukuran, jadi AOI besar diperkasar agar muat. Tambahkan --drive (opsional --drive-folder NAMA) untuk juga memulai ekspor resolusi penuh asinkron ke Google Drive Anda. Catatan: ekspor Drive memakai akun Google pribadi — jalankan earthengine authenticate dulu (kunci service account mengekspor ke Drive milik service account yang tak bisa Anda buka). Pantau task di tab Tasks EE Code Editor; GeoTIFF muncul di Drive/satchange/.
Tak punya akun Google Earth Engine? Tambahkan --backend mpc pada perintah apa pun — Microsoft Planetary Computer mengalirkan COG Sentinel-1/2 & Landsat dan memprosesnya lokal (termasuk di satchange[mpc]). Keluaran dan peta identik.

4 · Your first detectionDeteksi pertama Anda

Let's map vegetation loss around a coordinate. Pick any lat/lon (decimal degrees; south and west are negative).

Mari petakan kehilangan vegetasi di sekitar koordinat. Pilih lat/lon apa saja (derajat desimal; selatan dan barat negatif).

satchange -s deforestation --lat -3.333 --lon 122.25 --radius 6

The tool builds a square area of interest (half-side = radius km), makes a cloud-free Sentinel-2 NDVI composite for a baseline year and a recent year, subtracts them, and downloads the result. You'll see something like:

Alat membangun area minat persegi (setengah-sisi = radius km), membuat composite NDVI Sentinel-2 bebas awan untuk tahun dasar dan tahun terkini, menguranginya, lalu mengunduh hasilnya. Anda akan melihat kira-kira:

=== Change detection: deforestation ===
Location: -3.333, 122.25  radius 6.0 km  [m3p333_122p25]
Downloading dndvi PNG...    Saved: images/deforestation_dndvi_m3p333_122p25.png
Downloading dndvi GeoTIFF... Saved: data/deforestation_dndvi_m3p333_122p25.tif
=== Results ===
  "pct_affected": 6.3, "pct_severe": 2.7, "scenes_pre": 30, "scenes_post": 25 …
Negative-latitude tip. A leading - can confuse the shell if you use -l. Either use --lat/--lon (above) or attach the value with =: -l=-3.333,122.25.
Tips lintang negatif. Tanda - di depan bisa membingungkan shell bila memakai -l. Gunakan --lat/--lon (di atas) atau sambungkan nilai dengan =: -l=-3.333,122.25.
Prefer a place name? Use --city "Jakarta, Indonesia" instead of --lat/--lon — satchange geocodes it with free OpenStreetMap Nominatim and prints the resolved coordinates. Add a country for accuracy (e.g. --city "Bandung, Indonesia").
Lebih suka nama tempat? Pakai --city "Jakarta, Indonesia" alih-alih --lat/--lon — satchange menggeokode dengan OpenStreetMap Nominatim gratis dan mencetak koordinat hasilnya. Tambahkan negara agar akurat (mis. --city "Bandung, Indonesia").

5 · Reading the outputsMembaca keluaran

FileBerkas What it isIsinya Open withBuka dengan
…_<name>.pngColoured quick-lookPratinjau berwarnaAny image viewerPenampil gambar apa saja
…_<name>.tifFull-resolution, georeferenced GeoTIFF (EPSG:4326)GeoTIFF resolusi penuh tergeoreferensi (EPSG:4326)QGIS, ArcGIS, rasterio
…_map.pdf / .pngValue-added map (with --map)Peta value-added (dengan --map)PDF / image viewer
stats.jsonStatistics: % area affected, mean Δ, scene countsStatistik: % area terdampak, rerata Δ, jumlah sceneAny text editorEditor teks apa saja
Each run gets its own folder. Every run writes to a uniquely-named output/<timestamp>_<scenario>_<name>_<id>/ — PNG, GeoTIFF, stats, metadata (and maps) together, so runs never overwrite each other.
Tiap run punya folder sendiri. Setiap run menulis ke output/<timestamp>_<skenario>_<nama>_<id>/ yang unik — PNG, GeoTIFF, statistik, metadata (dan peta) jadi satu, sehingga antar-run tak saling menimpa.

For optical scenarios the change layer uses a diverging palette: red = loss, pale = little change, green = gain. The stats.json quantifies it — e.g. pct_affected is the share of the area whose change passed the threshold.

Untuk skenario optik, layer perubahan memakai palet divergen: merah = kehilangan, pucat = sedikit berubah, hijau = pertambahan. stats.json mengukurnya — mis. pct_affected adalah proporsi area yang perubahannya melewati ambang.

Thresholds are a starting point, not gospel. The default per-scenario thresholds (e.g. ΔNDVI < −0.15) are sensible defaults. As Key & Benson note for burn severity, cut-offs should ideally be calibrated with local ground truth. Treat the % figures as comparative, not absolute.
Ambang adalah titik awal, bukan patokan mutlak. Ambang default tiap skenario (mis. ΔNDVI < −0.15) adalah nilai wajar. Seperti dicatat Key & Benson untuk severity kebakaran, ambang idealnya dikalibrasi dengan data lapangan lokal. Anggap angka % sebagai perbandingan, bukan mutlak.

6 · Every scenarioSetiap skenario

List them any time with satchange --list. Commands:

Lihat daftarnya kapan saja dengan satchange --list. Perintah:

# Deforestation / vegetation loss (default baseline vs recent year)
satchange -s deforestation --lat -3.333 --lon 122.25

# Mining — radar temporal (SIRAD) + NDVI loss, two products
satchange -s mining --lat -3.333 --lon 122.25
#   set the 3 SIRAD periods (R/G/B) yourself with --epochs:
satchange -s mining --lat -3.333 --lon 122.25 \
    --epochs 2024-01-01:2024-06-30,2024-07-01:2024-12-31,2025-01-01:2025-06-30

# Urbanisation — built-up (NDBI) gain
satchange -s urbanization --lat -6.2 --lon 106.8 --radius 12

# Surface-water change (NDWI)
satchange -s water --lat -0.5 --lon 117.1

# Flood — needs an explicit baseline and event window
satchange -s flood --lat 27.2 --lon 68.3 \
    --pre 2022-07-01:2022-07-25 --post 2022-08-20:2022-09-10

# Disturbance — flood/landslide impact in terrain, when flood shows nothing
satchange -s disturbance --lat 1.9983 --lon 99.4235 --radius 15 \
    --pre 2025-11-01:2025-11-11 --post 2025-11-26:2025-11-29 --map

# Burn severity — needs pre-fire and post-fire windows
satchange -s burn --lat -7.5 --lon 110.4 \
    --pre 2025-08-01:2025-08-20 --post 2025-09-10:2025-09-30

# Urban growth timing across 3 epochs (Landsat; default 2010/2015/2020)
satchange -s urban-trend --lat -6.30 --lon 107.15 --radius 10 --map

# Urban history: built-up by decade since 1980 (GHSL + Landsat), whole metro
satchange -s urban-history --lat -6.2 --lon 106.85 --radius 45 -n jabodetabek

# Hybrid: auto-locate the most-changed hotspot + a PlanetScope ~3 m close-up
satchange -s urban-history --lat -6.2 --lon 106.85 --radius 45 --planet   # dry-run (free)

# Coastline: shoreline change (SAR) — erosion & accretion between two dates
satchange -s coastline --lat -6.95 --lon 110.45 --radius 8 \
    --pre 2016-01-01:2016-12-31 --post 2025-01-01:2025-12-31

# Coastline time-series + retreat-rate transects (Landsat, back to the 1990s)
satchange -s coastline --coast-method landsat --lat -6.95 --lon 110.45 --radius 10 \
    --epochs 1994-01-01:1996-12-31,2014-01-01:2016-12-31,2023-01-01:2025-12-31

# Custom cross-shore transects drawn in QGIS (clean m/yr in complex bays)
satchange -s coastline --coast-method landsat --city "Pekalongan" --radius 12 \
    --epochs 1994-01-01:1996-12-31,2014-01-01:2016-12-31,2023-01-01:2025-12-31 \
    --transects-file my_transects.geojson

# Public-transport access (SDG 11.2.1): % population within a walk of a stop
satchange -s transit-access --city "Semarang" --radius 8 --walk-dist 500,1000
# ...or supply your own stops/routes (e.g. a BRT corridor exported from QGIS)
satchange -s transit-access --city "Semarang" --radius 10 \
    --transit-file transjateng_stops.geojson
Transit access needs satchange[transit]. -s transit-access pulls the OSM walking network (Overpass) + WorldPop (GEE), so install the extra: pip install 'satchange[transit]'. Stops come from OSM by default; pass --transit-file stops.geojson (points or route lines, auto-sampled) for networks OSM hasn't mapped — Indonesian angkot routes often aren't. Tune the corridor width of the drawn service area with --access-buffer (map only; the % is network-distance based, not buffer based).
Transit access butuh satchange[transit]. -s transit-access mengambil jaringan jalan OSM (Overpass) + WorldPop (GEE), jadi pasang ekstra: pip install 'satchange[transit]'. Halte default dari OSM; berikan --transit-file stops.geojson (titik atau garis rute, dicuplik otomatis) untuk jaringan yang belum dipetakan OSM — rute angkot sering belum ada. Setel lebar koridor area terlayani dengan --access-buffer (hanya peta; persentasenya berbasis jarak jaringan, bukan buffer).
Custom transects (--transects-file). In complex bays the auto-transects can mis-orient. Draw your own cross-shore lines in QGIS, save them as a GeoJSON of LineStrings (first vertex = landward), and pass --transects-file my.geojson — each is intersected with every epoch's shoreline and regressed to a clean m/yr rate (CoastSat-style, MIT-native). On tides: shorelines here come from median composites, which are already tide-averaged (waterline ≈ mean tide). Per-scene tidal correction applies only to per-date imagery, not composites, so it's deliberately not faked.
Transek kustom (--transects-file). Di teluk kompleks, transek otomatis bisa salah arah. Gambar sendiri garis lintas-pantai di QGIS, simpan sebagai GeoJSON LineString (titik pertama = sisi darat), lalu berikan --transects-file my.geojson — tiap transek dipotong dengan garis pantai tiap epoch dan diregresi menjadi laju m/thn yang bersih (ala CoastSat, MIT-native). Soal pasang-surut: garis pantai di sini dari komposit median yang sudah ter-rata-rata terhadap pasang (waterline ≈ muka air rata-rata). Koreksi pasang per-scene hanya berlaku untuk citra per-tanggal, bukan komposit, jadi sengaja tidak dipalsukan.
Hybrid PlanetScope close-up (--planet). For urban-history, free wide-area GHSL locates the single most-changed grid cell, then a small, quota-cheap PlanetScope daily scene (4-band → NDVI works) zooms in at ~3 m. It's quota-safe: search is free, and ordering/downloading only happens with --planet-confirm. Set the change period with --hotspot-from/--hotspot-to and the imagery dates with --planet-pre/--planet-post. Key from $PLANET_API_KEY or ~/.planet.json/~/planet.conf. Without --planet, PlanetScope is never touched.
Close-up hibrida PlanetScope (--planet). Untuk urban-history, GHSL area-luas gratis menemukan sel paling banyak berubah, lalu satu scene harian PlanetScope (4-band → NDVI) meng-close-up pada ~3 m. Hemat kuota: pencarian gratis, unduhan hanya dengan --planet-confirm. Atur periode perubahan dengan --hotspot-from/--hotspot-to dan tanggal citra dengan --planet-pre/--planet-post. Kunci dari $PLANET_API_KEY atau ~/.planet.json/~/planet.conf. Tanpa --planet, PlanetScope tak pernah disentuh.
Flood and burn require dates. They compare a specific event to a baseline, so pass --pre START:END --post START:END (format YYYY-MM-DD:YYYY-MM-DD). Other scenarios have default windows you can still override. For flood, each window is a search range, not a composite — the tool picks a single Sentinel-1 scene from each (the most recent pass, same relative orbit). Keep windows tight (≈2–4 weeks) so each holds a pass but doesn't reach far from the event; place --post to end on/after the flood peak, since it takes the latest pass in the window.
Banjir dan kebakaran wajib tanggal. Keduanya membandingkan kejadian tertentu dengan baseline, jadi berikan --pre START:END --post START:END (format YYYY-MM-DD:YYYY-MM-DD). Skenario lain punya jendela default yang tetap bisa diubah. Untuk banjir, tiap jendela adalah rentang pencarian, bukan komposit — alat memilih satu citra Sentinel-1 dari masing-masing (lintasan terbaru, orbit relatif sama). Buat jendela sempit (≈2–4 minggu) agar tiap jendela berisi satu lintasan namun tidak jauh dari kejadian; atur --post berakhir pada/setelah puncak banjir, karena diambil lintasan terbaru dalam jendela.
Custom SIRAD periods (mining). -s mining builds an R/G/B radar composite from three time periods — blue = activity only in the newest. Set them with --epochs W1,W2,W3 (the same option urban-trend uses); omit it for the default recent years: satchange -s mining --lat -3.333 --lon 122.25 --epochs 2024-01-01:2024-12-31,2025-01-01:2025-12-31,2026-01-01:2026-06-30
Periode SIRAD kustom (mining). -s mining membangun komposit radar R/G/B dari tiga periode — biru = aktivitas hanya di periode terbaru. Aturlah dengan --epochs W1,W2,W3 (opsi yang sama dengan urban-trend); kosongkan untuk memakai tahun terkini default: satchange -s mining --lat -3.333 --lon 122.25 --epochs 2024-01-01:2024-12-31,2025-01-01:2025-12-31,2026-01-01:2026-06-30
Alternative methods (e.g. urbanisation). Optical scenarios aren't tied to one index — swap with --method. Sentinel-2 built-up: NDBI (default), UI, BU (=NDBI−NDVI), IBI. Thermal built-up NDISI, EBBI auto-switch to Landsat 8/9 (they need a thermal band Sentinel-2 lacks). Each method has its own default thresholds — tune with --thr/--severe.
Metode alternatif (mis. urbanisasi). Skenario optik tak terikat satu indeks — ganti dengan --method. Built-up Sentinel-2: NDBI (default), UI, BU (=NDBI−NDVI), IBI. Indeks termal NDISI, EBBI otomatis beralih ke Landsat 8/9 (butuh band termal yang tak ada di Sentinel-2). Tiap metode punya ambang default — setel dengan --thr/--severe.
Three (or more) dates / historical years. Sentinel-2 only starts ~2015 — it can't see 2010. For multi-year growth use -s urban-trend, which runs on Landsat (archive back to 1984) and maps NDBI at three epochs to R/G/B in one image (white = built the whole time, blue = newest growth): satchange -s urban-trend --lat -6.30 --lon 107.15 --epochs 2010-01-01:2010-12-31,2015-01-01:2015-12-31,2020-01-01:2020-12-31
Tiga (atau lebih) tanggal / tahun historis. Sentinel-2 baru ada sejak ~2015 — tak bisa melihat 2010. Untuk pertumbuhan multi-tahun pakai -s urban-trend yang berbasis Landsat (arsip sejak 1984) dan memetakan NDBI tiga epoch ke R/G/B dalam satu citra (putih = terbangun terus, biru = paling baru): satchange -s urban-trend --lat -6.30 --lon 107.15 --epochs 2010-01-01:2010-12-31,2015-01-01:2015-12-31,2020-01-01:2020-12-31
Example urban-trend map, Cikarang 2010/2015/2020
urban-trend over Cikarang/Bekasi (east Jakarta). Built-up grew 10% (2010) → 23% (2020), 15% newly built. White = built the whole time; blue/cyan = newer growth.urban-trend di Cikarang/Bekasi (timur Jakarta). Built-up tumbuh 10% (2010) → 23% (2020), 15% baru. Putih = terbangun terus; biru/cyan = pertumbuhan lebih baru.

7 · Make a map productMembuat produk peta

Add --map to any run to render an A4-landscape map sheet (PDF + PNG) per product: OpenStreetMap basemap, the change layer, title, legend, a statistics panel, a location inset, coordinate grid, scale bar and north arrow.

Tambahkan --map pada run apa pun untuk membuat lembar peta A4 landscape (PDF + PNG) per produk: basemap OpenStreetMap, layer perubahan, judul, legenda, panel statistik, inset lokasi, grid koordinat, skala, dan panah utara.

satchange -s mining --lat -3.333 --lon 122.25 --map
Example mining SIRAD map
A SIRAD radar-temporal map. Each colour is one period; blue = new activity only in the most recent period — a strong signal of fresh land clearing.Peta radar-temporal SIRAD. Tiap warna satu periode; biru = aktivitas baru hanya pada periode terkini — sinyal kuat pembukaan lahan baru.

Already have results and just want to re-render (no Earth Engine needed)? Every run drops a .meta.json sidecar, so:

Sudah punya hasil dan ingin render ulang (tanpa Earth Engine)? Tiap run menulis sidecar .meta.json, jadi:

satmap output/20260708-2226_deforestation_x_fac24e   # a run folder
satmap output/<run>/mining_sirad_x.tif --basemap gray  # or one .tif

Maps land in maps/. Tweak the layout in mapmaker.py and re-run instantly.

Peta tersimpan di maps/. Ubah tata letak di mapmaker.py lalu jalankan ulang seketika.

8 · Adapt it to your own areaMenyesuaikan untuk area Anda

Pick a locationPilih lokasi

Find coordinates by right-clicking a spot in Google Maps (it copies lat, lon), or from OpenStreetMap. Choose a --radius to match the feature: a few km for a single mine or town, 10–20 km for a flood plain or a fire.

Cari koordinat dengan klik kanan di Google Maps (menyalin lat, lon), atau dari OpenStreetMap. Pilih --radius sesuai objek: beberapa km untuk satu tambang atau kota, 10–20 km untuk dataran banjir atau kebakaran.

Save a named presetSimpan preset bernama

Add reusable places to sites.py and call them with --site:

Tambahkan lokasi yang sering dipakai ke sites.py dan panggil dengan --site:

SITES = {
  "my_area": {
    "label": "My study area",
    "lat": 1.234, "lon": 103.456, "radius_km": 8.0,
    "sentinel2_date": "2026-06-15",
    "sirad_periods": [("2024-01-01","2024-12-31"),
                      ("2025-01-01","2025-12-31"),
                      ("2026-01-01","2026-06-30")],
    "ndvi_pre":  ("2023-01-01","2023-12-31"),
    "ndvi_post": ("2026-01-01","2026-06-30"),
  },
}
satchange -s deforestation --site my_area --map

Add a brand-new scenarioTambah skenario baru

Most scenarios are one dictionary entry in scenarios.py. To add, say, a moisture-change scenario using NDWI on a custom threshold you only add a registry entry — the generic optical-change engine does the rest:

Kebanyakan skenario hanya satu entri dictionary di scenarios.py. Untuk menambah, misalnya, skenario perubahan kelembapan memakai NDWI dengan ambang khusus, cukup tambahkan satu entri — mesin perubahan optik generik menangani sisanya:

"moisture": {
    "label": "Moisture change (NDWI)",
    "run": _optical("NDWI", "gain", 0.08, 0.20),  # index, direction, thresholds
    "radius": 10.0, "needs": "pre_post",
    "pre":  ("2023-01-01","2023-12-31"),
    "post": ("2025-01-01","2025-12-31"),
    "interpretation": "Greener = wetter.",
},

New spectral indices go in indices.py (a one-line normalised difference). Radar or bespoke methods get a small run_* function in scenarios.py.

Indeks spektral baru masuk ke indices.py (satu baris normalized difference). Metode radar atau khusus memakai fungsi run_* kecil di scenarios.py.

9 · Reproducibility & dataReproduksibilitas & data

10 · TroubleshootingPemecahan masalah

SymptomGejalaFixSolusi
No Sentinel-2 scenes in pre/post windowWiden your --pre/--post dates or increase --radius. Very cloudy tropics may need a longer window.Perlebar tanggal --pre/--post atau perbesar --radius. Tropis sangat berawan mungkin butuh jendela lebih panjang.
No orbit covers all periodsEnlarge the radius or shift the period dates; Sentinel-1 coverage is sparse in some years/areas.Perbesar radius atau geser tanggal periode; cakupan Sentinel-1 jarang di sebagian tahun/area.
Earth Engine auth errorsGalat autentikasi Earth EngineUsually a wrong working directory: the key at scripts/config/ee-geodetic.json is only found when you run from the repo root. Pass --ee-key /path/to/ee-geodetic.json, set SATCHANGE_EE_KEY, copy the key to ~/.config/earthengine/ee-geodetic.json, or re-run earthengine authenticate.Biasanya direktori kerja salah: kunci di scripts/config/ee-geodetic.json hanya ditemukan saat dijalankan dari root repo. Berikan --ee-key /path/ke/ee-geodetic.json, set SATCHANGE_EE_KEY, salin kunci ke ~/.config/earthengine/ee-geodetic.json, atau jalankan ulang earthengine authenticate.
Basemap missing / Basemap hilang / EPSG code unknownA stale external PROJ database (e.g. from an OTB install) is shadowing PROJ. The tool clears PROJ_LIB; if it persists, unset PROJ_LIB.Basis data PROJ eksternal usang (mis. dari OTB) menutupi PROJ. Alat menghapus PROJ_LIB; bila masih, jalankan unset PROJ_LIB.
Direct GeoTIFF download refusedUnduhan GeoTIFF langsung ditolakVery large AOIs exceed Earth Engine's limit — reduce --radius, or use --drive to export to Google Drive.AOI sangat besar melewati batas Earth Engine — kecilkan --radius, atau pakai --drive untuk ekspor ke Google Drive.

11 · Further readingBacaan lanjut