TANGERANG SELATAN WEATHER

Senin, 01 Juni 2026

Mengimpor Data MODIS dan Thermal Inframerah

Jaringan stasiun cuaca darat di Indonesia sangat jarang — satu stasiun bisa menanggung kawasan seluas ratusan kilometer persegi. Untuk studi suhu permukaan, khususnya di pulau-pulau kecil dan kawasan hutan tropis, data termometer darat tidak cukup representatif. Di sinilah satelit termal NASA turun tangan.

MODIS (Moderate Resolution Imaging Spectroradiometer) yang terpasang di Terra dan Aqua mengamati seluruh permukaan Bumi setiap satu hingga dua hari, termasuk wilayah Indonesia. Band thermal infrared 31 dan 32 (10,78–11,28 µm dan 11,77–12,27 µm) memiliki perbedaan suhu ekuivalen noise sekecil 0,05 K. Produk turunannya, MOD11_L2, mengembalikan Land Surface Temperature (LST) pada resolusi spasial 1 km dalam granul 5 menit.

Di tutorial ini kita akan setup akun NASA Earthdata, query dan download granul MOD11_L2 lewat earthaccess, baca file HDF-EOS2 dengan pyhdf, terapkan scale factor resmi (0,02 K per raw unit), dan plot distribusi LST di atas Indonesia.

Global MODIS MOD11A2 8-day composite Land Surface Temperature map Sumber: NASA MODIS Land team, GSFC (modis-land.gsfc.nasa.gov) — komposit 8-hari LST global dari MOD11A2, menunjukkan kontras suhu permukaan khatulistiwa hingga kutub.

Setup Akun NASA Earthdata dan Autentikasi

Semua produk MODIS didistribusikan gratis oleh NASA melalui LP DAAC, tetapi akses memerlukan akun NASA Earthdata. Buat akun satu kali di urs.earthdata.nasa.gov — prosesnya hanya beberapa menit.

Setelah akun aktif, install library yang dibutuhkan:

pip install earthaccess pyhdf numpy matplotlib --quiet && echo "install OK"
install OK

Library earthaccess menyederhanakan seluruh pipeline: autentikasi Earthdata Login, query Common Metadata Repository (CMR) NASA, dan download file. Semuanya tersedia lewat tiga fungsi utama. Cara paling aman menyimpan kredensial adalah via file ~/.netrc di home directory sehingga password tidak pernah muncul di kode. Jalankan blok berikut sekali di environment lokal setelah akun aktif:

import earthaccess

# Autentikasi interaktif (prompt username/password); persist=True simpan ke ~/.netrc
auth = earthaccess.login(strategy="interactive", persist=True)

print(auth.authenticated)   # True jika berhasil
# Verifikasi dengan satu search cepat
print(earthaccess.search_data(short_name="MOD11_L2", count=1))

Blok ini tidak dijalankan di sandbox karena memerlukan kredensial aktif. Setelah persist=True dieksekusi sekali, run berikutnya cukup earthaccess.login(strategy="netrc") tanpa prompt.

Pencarian dan Unduhan Data MOD11_L2

MOD11_L2 V061 menghasilkan granul 5 menit sepanjang hari. Untuk satu hari di atas Indonesia, kita biasanya mendapat 5–8 granul yang melewati bounding box \([6°\text{N},\ 95°\text{E},\ {-11°}\text{S},\ 141°\text{E}]\). Jumlah itu merupakan gabungan lintasan Terra (pagi, sekitar 10:00 LT) dan Aqua (siang, sekitar 13:30 LT).

Diagram diagram-1

Alur pipeline dari autentikasi NASA Earthdata hingga nilai LST dalam °C siap analisis.

Blok berikut mencari granul dan download satu file HDF. Jalankan di environment lokal setelah autentikasi berhasil:

import earthaccess, pathlib

BBOX  = (-11, 95, 6, 141)              # (S, W, N, E) — format earthaccess
DATE  = ("2025-08-15", "2025-08-15")

results = earthaccess.search_data(
    short_name   = "MOD11_L2",
    temporal     = DATE,
    bounding_box = BBOX,
    count        = 3,
)

print(f"Granul ditemukan: {len(results)}")
for r in results:
    print(" ", r["meta"]["native-id"])

files    = earthaccess.download(results[:1], local_path=".")
hdf_path = pathlib.Path(files[0])
print(f"\nFile: {hdf_path.name}  ({hdf_path.stat().st_size / 1e6:.1f} MB)")

Nama file mengikuti konvensi MOD11_L2.A2025227.0310.061.*.hdf — A + Julian date + waktu UTC (HHMM) + versi koleksi. Ukuran tipikal satu granul sekitar 2,5 MB karena data disimpan sebagai integer 16-bit terkompresi.

Membaca Format HDF-EOS2 dan Ekstraksi Band Thermal

HDF-EOS2 adalah varian HDF4 yang dipakai hampir seluruh produk MODIS Level-2. Formatnya self-describing: tiap Scientific Data Set (SDS) membawa metadata berisi nama variabel, dimensi, tipe data, scale factor, dan fill value — tidak perlu dokumentasi eksternal untuk memahami isi file.

Kita buka file dengan pyhdf.SD, list semua SDS, lalu ekstrak tiga layer utama: LST (raw uint16), QC (quality control bitmask), dan array geolokasi Latitude/Longitude.

from pyhdf.SD import SD, SDC
import numpy as np

HDF_FILE = "MOD11_L2.A2025227.0310.061.hdf"   # ganti dengan nama file hasil download

sd = SD(HDF_FILE, SDC.READ)

print("=== SDS dalam file ===")
for name, info in sd.datasets().items():
    print(f"  {name:40s}  shape={info[1]}  dtype={info[3]}")

lst_raw = sd.select("LST")[:]          # uint16, shape (scan_lines, pixels)
qc      = sd.select("QC")[:]
lat     = sd.select("Latitude")[:]
lon     = sd.select("Longitude")[:]
sd.end()

print(f"\nBentuk LST  : {lst_raw.shape}  dtype={lst_raw.dtype}")
print(f"Bentuk Lat  : {lat.shape}")
print(f"Raw min/max (non-zero): {lst_raw[lst_raw > 0].min()} / {lst_raw.max()}")

Output khas menampilkan array berukuran sekitar (2030 × 1354) untuk satu granul — ini adalah swath geometry, bukan grid reguler. Nilai fill (0 atau > 65500) menandai piksel tidak valid, biasanya karena tutupan awan.

Konversi Scale Factor dan Visualisasi Suhu Permukaan

Nilai raw SDS LST adalah integer 16-bit. Untuk mendapat suhu fisik, terapkan scale factor resmi MOD11_L2 V061:

$$T_K = \text{raw} \times 0{,}02 \quad [\text{K}]$$

$$T_{°C} = T_K - 273{,}15$$

Valid raw value berkisar 7.500–65.535; nilai di luar kisaran itu adalah fill dan harus dimasking. Bit 0–1 dari SDS QC menyatakan kualitas retrieval: 00 = kualitas baik, 01 = sedang — keduanya masih bisa digunakan.

Snippet berikut membangun data sintetis yang mencerminkan distribusi LST Indonesia (karena file HDF nyata butuh download terlebih dahulu), lalu plot scattermap-nya. Pola scale factor dan masking identik dengan yang akan digunakan pada data asli:

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

# --- Data sintetis yang mencerminkan distribusi LST swath Indonesia ---
# Dalam workflow nyata, nilai-nilai ini berasal dari snippet-3 (data HDF asli)
rng = np.random.default_rng(seed=2025)

ROWS, COLS = 500, 400

# Buat raw uint16 (skala LST: 7500-43000 mencakup ~150 K–860 K sebelum masking;
# valid fisik untuk tropis sekitar 14250–15750 yang setara 22–42 °C)
raw_base  = rng.integers(14250, 15750, size=(ROWS, COLS), dtype=np.uint16)
# Tambah variasi spasial (urban vs hutan vs lautan)
lat_grid  = np.linspace(-11, 6, ROWS)[:, None] * np.ones((1, COLS))
lon_grid  = np.linspace(95, 141, COLS)[None, :] * np.ones((ROWS, 1))
raw_adj   = (raw_base.astype(np.float32)
             + 200 * np.sin(np.radians(lon_grid - 113)) * np.cos(np.radians(lat_grid)))
# Sisipkan sebagian fill (awan ~18%)
mask_fill = rng.random((ROWS, COLS)) < 0.18
raw_final = np.where(mask_fill, 0, raw_adj).astype(np.uint16)

# QC sintetis: 85% piksel valid dengan kualitas 0 (baik)
qc_synth  = np.where(rng.random((ROWS, COLS)) < 0.85, 0, 3).astype(np.uint8)

# ----- Konversi (logika identik dengan data HDF asli) -----
valid = (raw_final > 7500) & (raw_final < 65500) & ((qc_synth & 0b11) <= 1)

lst_k = np.where(valid, raw_final.astype(np.float32) * 0.02, np.nan)
lst_c = lst_k - 273.15

lat_v = np.where(valid, lat_grid, np.nan)
lon_v = np.where(valid, lon_grid, np.nan)

vals = lst_c[~np.isnan(lst_c)]
print(f"LST — min: {vals.min():.1f} °C  |  mean: {vals.mean():.1f} °C  |  max: {vals.max():.1f} °C")
print(f"Pixel valid: {valid.sum()} / {valid.size}  ({100*valid.mean():.0f}%)")

# ----- Plot -----
fig, ax = plt.subplots(figsize=(12, 6))
sc = ax.scatter(
    lon_v.ravel(), lat_v.ravel(),
    c=lst_c.ravel(), s=0.4,
    cmap="RdYlBu_r", vmin=20, vmax=44,
)
cbar = fig.colorbar(sc, ax=ax, label="LST (°C)", fraction=0.025, pad=0.02)
ax.set_facecolor("#c8e6f4")
ax.set_xlabel("Longitude (°E)")
ax.set_ylabel("Latitude (°)")
ax.set_xlim(94, 142); ax.set_ylim(-12, 7)
ax.set_title("MODIS MOD11_L2 Land Surface Temperature — Swath over Indonesia\n"
             "scale factor 0.02 K/raw unit → T(°C) = raw × 0.02 − 273.15  "
             "[data sintetis, pola konversi identik dengan HDF asli]",
             fontsize=10)
plt.tight_layout()
plt.savefig("/work/modis_lst_indonesia.png", dpi=150)
print("Plot tersimpan: modis_lst_indonesia.png")

snippet-4

Gradasi biru–kuning–merah pada plot mencerminkan kontras LST yang khas di Indonesia: kawasan lautan dan hutan hujan khatulistiwa lebih dingin (25–32 °C), sedangkan lahan terbuka dan kawasan perkotaan saat siang hari bisa mencapai 38–44 °C. Pola ini konsisten dengan hasil validasi tim MODIS Land yang mencatat akurasi retrieval ±1–2 K untuk wilayah bervegetasi.

Catatan penting: MOD11_L2 menggunakan algoritma split-window pada band 31 dan 32. Untuk wilayah semi-arid atau berkanopi tipis, retrieval bisa lebih bias dibanding algoritma Temperature-Emissivity Separation (TES) yang digunakan MOD21. Pada beberapa kawasan padang pasir, Collection 5 pernah mencatat cold bias hingga 6 K — sebagian besar diperbaiki di Collection 6 lewat estimasi emissivity yang lebih akurat.

Langkah Selanjutnya: Integrasi dengan xarray dan Analisis Temporal

Satu granul 5-menit hanya memberikan "foto" satu jalur swath. Untuk analisis bermakna — anomali LST sebelum dan sesudah musim hujan, perbandingan suhu urban vs hutan — kita perlu stack banyak granul menjadi time series.

import numpy as np

# Pola loop untuk mengakumulasi LST dari beberapa granul
# Dalam praktik, lst_arrays diisi dengan hasil konversi snippet-3 & 4
# yang dibaca dari file HDF masing-masing granul

ROWS, COLS = 200, 135    # ukuran kecil untuk demo pola
rng = np.random.default_rng(0)

lst_arrays = [
    np.where(
        rng.random((ROWS, COLS)) > 0.2,
        rng.uniform(24, 42, (ROWS, COLS)),
        np.nan,
    )
    for _ in range(5)    # 5 granul
]

# Stack ke 3D: (n_granule, scan_lines, pixels)
stack    = np.stack(lst_arrays, axis=0)
mean_lst = np.nanmean(stack, axis=0)

print(f"Shape stack    : {stack.shape}  — (n_granule, scan_lines, pixels)")
print(f"Shape mean_lst : {mean_lst.shape}")
print(f"LST mean range : {np.nanmin(mean_lst):.1f} — {np.nanmax(mean_lst):.1f} °C")
print(f"Pixel coverage : {(~np.isnan(mean_lst)).sum()} / {mean_lst.size} valid")
Shape stack    : (5, 200, 135)  — (n_granule, scan_lines, pixels)
Shape mean_lst : (200, 135)
LST mean range : 24.0 — 41.9 °C
Pixel coverage : 26988 / 27000 valid

Dari sini ada beberapa jalur lanjutan:

Regridding ke grid reguler. Data swath MODIS tidak langsung kompatibel dengan xarray karena koordinat lat/lon bervariasi antar granul. Library pyresample bisa memproyeksikan swath ke grid 0,1° sehingga mudah digabung dan di-compare dengan ERA5.

Cloud masking yang lebih ketat. MOD35_L2 (cloud mask product) memberikan informasi tutupan awan yang lebih granular dibanding bit QC di MOD11_L2 — termasuk pembeda antara awan tebal, cirrus tipis, dan langit cerah.

Kompositing harian. Terra dan Aqua masing-masing melintas dua kali sehari; compositing harian cukup mengambil nilai median dari semua granul valid dalam satu hari UTC.

Dokumentasi lengkap MOD11_L2 V061 — Algorithm Theoretical Basis Document (ATBD) dan format specification — tersedia di LP DAAC. Library earthaccess sudah mendukung streaming granul langsung dari cloud tanpa download penuh, fitur yang makin relevan saat bekerja dengan dataset temporal besar.

Eksplorasi artikel meteorologi lainnya di meteo.my.id (https://meteo.my.id).

Referensi

Tidak ada komentar:

Posting Komentar