Kamis, 09 Juli 2026
Mengapa Presipitasi dari Radar Penting
BMKG mengoperasikan lebih dari 41 stasiun radar cuaca yang tersebar di seluruh kepulauan Indonesia — jaringan pengamatan yang menjadi tulang punggung peringatan dini hujan lebat, pemantauan konveksi tropis skala meso, dan prakiraan banjir bandang. Setiap radar memancarkan pulsa gelombang mikro dan merekam sinyal pantul dari butiran air di atmosfer. Nilai yang tersimpan dalam produk CAPPI BMKG adalah reflektivitas — bukan intensitas curah hujan secara langsung.
Di sinilah tantangan dimulai: radar mengukur hamburan elektromagnetik dari populasi butiran hujan, bukan volume air yang jatuh. Mengubah data reflektivitas (dBZ) menjadi perkiraan curah hujan kuantitatif disebut quantitative precipitation estimation (QPE). Prosesnya memerlukan hubungan empiris antara reflektivitas \(Z\) (dalam \(\text{mm}^6/\text{m}^3\)) dan rain rate \(R\) (dalam mm/h), yang dikenal sebagai Z-R relationship:
$$Z = a \cdot R^b$$
Konstanta \(a\) dan \(b\) bergantung pada drop-size distribution yang berbeda-beda antara hujan stratiform, konvektif dalam, dan konveksi tropis maritim.
Tutorial ini membangun synthetic 2D radar reflectivity field dengan NumPy — berdiri sebagai proxy data CAPPI — lalu menerapkan tiga Z-R relationship operasional, membandingkan hasilnya dengan xarray, dan memvisualisasikan peta reflektivitas dan rain rate dengan matplotlib. Seluruh workflow berjalan sepenuhnya offline tanpa membutuhkan data eksternal apa pun, sehingga dapat langsung dicoba dan direproduksi.
Memahami Reflektivitas dan Skala dBZ
Reflektivitas radar \(Z\) didefinisikan sebagai jumlah pangkat enam dari diameter semua butiran hujan dalam volume sampling yang diradar:
$$Z = \sum_{i} D_i^6 \quad [\text{mm}^6/\text{m}^3]$$
Nilai \(Z\) mencakup rentang yang sangat ekstrem — dari \(\approx 10^{-3}\) untuk gerimis ringan hingga \(\approx 3{,}6 \times 10^{7}\) untuk hujan es besar. Karena rentang ini terlalu lebar untuk ditampilkan secara linear, radar menggunakan skala logaritmik:
$$\text{dBZ} = 10 \cdot \log_{10}(Z)$$
Invers yang kita butuhkan untuk konversi di Python adalah:
$$Z = 10^{\,\text{dBZ}/10}$$
Tabel berikut merangkum interpretasi operasional skala dBZ:
| dBZ | Kategori | Catatan |
|---|---|---|
| < 10 | Noise / non-meteorologis | Mask wajib sebelum konversi Z-R |
| 10–20 | Gerimis sangat ringan | Ambang batas deteksi hujan NWS |
| 20–40 | Stratiform / hujan ringan–sedang | Target dominan QPE |
| 40–55 | Konvektif / hujan lebat | Presipitasi intensitas tinggi |
| > 55 | Hujan sangat lebat / hail | WSR-88D men-truncate di 103,9 mm/h |
Snippet berikut membangun synthetic reflectivity field 128×128 yang mencakup domain Indonesia (95°–141°E, 11°S–6°N), dengan tiga storm cell Gaussian di atas background stratiform, lalu mengonversi dBZ ke \(Z\) menggunakan formula invers.
import numpy as np
# --- Synthetic 2D radar reflectivity field (128 x 128) ---
# Domain: Indonesia, 95°E–141°E, 11°S–6°N
ny, nx = 128, 128
lon_arr = np.linspace(95, 141, nx)
lat_arr = np.linspace(-11, 6, ny)
LON_2D, LAT_2D = np.meshgrid(lon_arr, lat_arr)
rng = np.random.default_rng(seed=42)
# Background: weak stratiform field (15–22 dBZ)
dbz = 18 + 5 * np.sin(np.deg2rad(LON_2D * 2)) * np.cos(np.deg2rad(LAT_2D * 3))
# Storm cell 1: deep convective, ~52 dBZ peak, central Java
cell1 = 52 * np.exp(-(((LON_2D - 108)**2 + (LAT_2D + 7)**2) / (3.0**2)))
# Storm cell 2: convective, ~48 dBZ peak, southern Sumatra
cell2 = 48 * np.exp(-(((LON_2D - 104)**2 + (LAT_2D + 2)**2) / (2.5**2)))
# Storm cell 3: moderate, ~38 dBZ peak, central Kalimantan
cell3 = 38 * np.exp(-(((LON_2D - 115)**2 + (LAT_2D - 0)**2) / (4.0**2)))
dbz = dbz + cell1 + cell2 + cell3
# Add realistic noise (±2 dBZ)
dbz += rng.normal(0, 2, dbz.shape)
# Clip to operational radar range
dbz = np.clip(dbz, 0, 65)
# Convert dBZ → Z (mm^6/m^3)
Z = 10 ** (dbz / 10)
print("=== Synthetic Reflectivity Field ===")
print(f"Grid size : {ny} x {nx} (lat x lon)")
print(f"Domain : {lat_arr[0]:.0f}°–{lat_arr[-1]:.0f}° lat, "
f"{lon_arr[0]:.0f}°–{lon_arr[-1]:.0f}° lon")
print()
print("dBZ statistics:")
print(f" Min : {dbz.min():.2f} dBZ")
print(f" Max : {dbz.max():.2f} dBZ")
print(f" Mean : {dbz.mean():.2f} dBZ")
print()
print("Z (mm^6/m^3) statistics:")
print(f" Min : {Z.min():.3e}")
print(f" Max : {Z.max():.3e}")
print(f" Ratio Max/Min : {Z.max()/Z.min():.2e} (orders of magnitude span)")
print()
print("dBZ class breakdown (fraction of grid cells):")
categories = [
("< 10 (noise)", dbz < 10),
("10–20 (drizzle)", (dbz >= 10) & (dbz < 20)),
("20–40 (stratiform)", (dbz >= 20) & (dbz < 40)),
("40–55 (convective)", (dbz >= 40) & (dbz < 55)),
(">= 55 (very heavy/hail)", dbz >= 55),
]
for label, m in categories:
pct = m.sum() / dbz.size * 100
print(f" {label:<30}: {pct:5.1f}%")
=== Synthetic Reflectivity Field ===
Grid size : 128 x 128 (lat x lon)
Domain : -11°–6° lat, 95°–141° lon
dBZ statistics:
Min : 4.47 dBZ
Max : 65.00 dBZ
Mean : 19.78 dBZ
Z (mm^6/m^3) statistics:
Min : 2.799e+00
Max : 3.162e+06
Ratio Max/Min : 1.13e+06 (orders of magnitude span)
dBZ class breakdown (fraction of grid cells):
< 10 (noise) : 2.2%
10–20 (drizzle) : 70.4%
20–40 (stratiform) : 20.2%
40–55 (convective) : 5.7%
>= 55 (very heavy/hail) : 1.6%
Output di atas menampilkan distribusi kelas dBZ dan rasio Max/Min dari \(Z\) yang mencakup beberapa orde besaran — persis alasan skala logaritmik dBZ diperlukan. Perhatikan bahwa sebagian besar pixel berada di kisaran stratiform, sementara sel konvektif hanya menempati fraksi kecil domain namun mendominasi intensitas puncak.
Hubungan Z-R dan Tiga Jenis Presipitasi
Tiga Z-R relationship yang digunakan dalam meteorologi operasional global:
Marshall-Palmer (1948) — presipitasi stratiform:
$$Z = 200 \cdot R^{1{,}6} \quad \Rightarrow \quad R = \left(\frac{Z}{200}\right)^{0{,}625}$$
Dikembangkan dari pengukuran disdrometer di iklim mid-latitude, ini adalah default BMKG untuk seluruh jaringan radar nasional.
WSR-88D default — konveksi dalam (deep convection):
$$Z = 300 \cdot R^{1{,}4} \quad \Rightarrow \quad R = \left(\frac{Z}{300}\right)^{0{,}7143}$$
Digunakan oleh NWS Amerika Serikat untuk presipitasi konvektif musim panas. Sistem WSR-88D Precipitation Processing System juga menerapkan hail truncation di batas 103,9 mm/h untuk mencegah overestimation ekstrem dari echo hail.
Tropical / Rosenfeld — konveksi tropis:
$$Z = 250 \cdot R^{1{,}2} \quad \Rightarrow \quad R = \left(\frac{Z}{250}\right)^{0{,}8333}$$
Konveksi tropis di Indonesia dan Asia Tenggara cenderung menghasilkan drop-size distribution yang lebih kecil dibanding mid-latitude — reflektivitas lebih rendah untuk rain rate yang sama. Akibatnya, tropical Z-R seringkali menghasilkan estimasi yang lebih realistis untuk sistem konvektif maritim di wilayah ini. Studi regional di Surabaya bahkan menemukan koefisien lokal \(Z = 110 \cdot R^{1{,}6}\) lebih akurat dari Marshall-Palmer standar.
Satu insight penting: pilihan Z-R relationship memengaruhi hasil, tapi tidak sebesar yang sering diasumsikan. Akurasi pengukuran \(Z\) itu sendiri — termasuk kalibrasi radar antar stasiun — memiliki dampak yang lebih besar terhadap kualitas QPE. Perbedaan kalibrasi antara radar yang berdekatan dalam jaringan WSR-88D bisa melebihi 17%.
Diagram berikut merangkum alur pemilihan Z-R relationship berdasarkan regime presipitasi dan rentang dBZ:
Alur pemilihan Z-R relationship berdasarkan regime presipitasi dan rentang dBZ operasional.
Snippet berikut menerapkan ketiga formula ke synthetic field, melakukan noise masking, membungkus hasilnya dalam xarray DataArray dengan koordinat lat/lon, dan menghitung domain-mean rain rate untuk perbandingan kuantitatif.
import xarray as xr
# Noise mask: dBZ < 10 dianggap echo non-meteorologis
NOISE_THRESHOLD = 10.0
mask = dbz >= NOISE_THRESHOLD
# --- Tiga Z-R relationships ---
# Marshall-Palmer: Z = 200 * R^1.6
R_mp = np.where(mask, (Z / 200.0) ** (1.0 / 1.6), 0.0)
# WSR-88D default: Z = 300 * R^1.4
R_wsr = np.where(mask, (Z / 300.0) ** (1.0 / 1.4), 0.0)
# Tropical Rosenfeld: Z = 250 * R^1.2
R_trop = np.where(mask, (Z / 250.0) ** (1.0 / 1.2), 0.0)
# Hail truncation (WSR-88D Precipitation Processing System cap)
HAIL_CAP = 103.9 # mm/h
R_wsr = np.minimum(R_wsr, HAIL_CAP)
# Wrap dalam xarray DataArray dengan koordinat lat/lon
coords = {"lat": lat_arr, "lon": lon_arr}
da_dbz = xr.DataArray(dbz, dims=["lat", "lon"], coords=coords,
attrs={"units": "dBZ", "long_name": "Reflectivity factor"})
da_mp = xr.DataArray(R_mp, dims=["lat", "lon"], coords=coords,
attrs={"units": "mm/h", "long_name": "Rain rate Marshall-Palmer"})
da_wsr = xr.DataArray(R_wsr, dims=["lat", "lon"], coords=coords,
attrs={"units": "mm/h", "long_name": "Rain rate WSR-88D default"})
da_trop = xr.DataArray(R_trop, dims=["lat", "lon"], coords=coords,
attrs={"units": "mm/h", "long_name": "Rain rate Tropical Z-R"})
# Domain-mean hanya untuk pixel yang presipitasi (R > 0)
mean_mp = float(da_mp.where(da_mp > 0).mean())
mean_wsr = float(da_wsr.where(da_wsr > 0).mean())
mean_trop = float(da_trop.where(da_trop > 0).mean())
print("=== Z-R Comparison (precipitating pixels only) ===")
print(f"Noise-masked cells : {(~mask).sum()} / {mask.size} ({(~mask).sum()/mask.size*100:.1f}%)")
print(f"Meteorological cells : {mask.sum()} / {mask.size} ({mask.sum()/mask.size*100:.1f}%)")
print()
print("Domain-mean rain rate for precipitating pixels:")
print(f" Marshall-Palmer Z=200*R^1.6 : {mean_mp:.3f} mm/h")
print(f" WSR-88D default Z=300*R^1.4 : {mean_wsr:.3f} mm/h")
print(f" Tropical Z=250*R^1.2 : {mean_trop:.3f} mm/h")
print()
print(f"Tropical vs Marshall-Palmer : {(mean_trop/mean_mp - 1)*100:+.1f}%")
print(f"WSR-88D vs Marshall-Palmer : {(mean_wsr/mean_mp - 1)*100:+.1f}%")
print()
# Perbandingan di pixel puncak (convective peak)
peak_idx = np.unravel_index(np.argmax(dbz), dbz.shape)
print(f"Convective peak dBZ={dbz[peak_idx]:.1f} "
f"lat={lat_arr[peak_idx[0]]:.2f} lon={lon_arr[peak_idx[1]]:.2f}:")
print(f" Marshall-Palmer : {R_mp[peak_idx]:.2f} mm/h")
print(f" WSR-88D default : {R_wsr[peak_idx]:.2f} mm/h")
print(f" Tropical Z-R : {R_trop[peak_idx]:.2f} mm/h")
print()
# Contoh xarray area-subset (domain Jawa)
R_java = da_trop.sel(lat=slice(-9, -5), lon=slice(105, 115))
print(f"Subset domain Jawa (xarray .sel): shape={dict(R_java.sizes)}")
print(f" Mean rain rate (Tropical Z-R) : {float(R_java.mean()):.3f} mm/h")
=== Z-R Comparison (precipitating pixels only) ===
Noise-masked cells : 353 / 16384 (2.2%)
Meteorological cells : 16031 / 16384 (97.8%)
Domain-mean rain rate for precipitating pixels:
Marshall-Palmer Z=200*R^1.6 : 6.624 mm/h
WSR-88D default Z=300*R^1.4 : 4.804 mm/h
Tropical Z=250*R^1.2 : 27.221 mm/h
Tropical vs Marshall-Palmer : +310.9%
WSR-88D vs Marshall-Palmer : -27.5%
Convective peak dBZ=65.0 lat=-7.92 lon=107.68:
Marshall-Palmer : 421.07 mm/h
WSR-88D default : 103.90 mm/h
Tropical Z-R : 2620.49 mm/h
Subset domain Jawa (xarray .sel): shape={'lat': 30, 'lon': 28}
Mean rain rate (Tropical Z-R) : 308.269 mm/h
Output di atas menunjukkan perbedaan kuantitatif antara ketiga formula pada dataset yang identik — tropical Z-R konsisten menghasilkan rain rate lebih tinggi untuk reflektivitas yang sama, mencerminkan asumsi drop-size distribution yang lebih kecil. Di pixel puncak konvektif (dBZ = 65,0), perbedaan antar formula mencapai ratusan hingga ribuan mm/h — Marshall-Palmer menghasilkan 421,07 mm/h, WSR-88D terpangkas di 103,90 mm/h akibat hail truncation, sementara tropical Z-R mencapai 2.620,49 mm/h. Rentang inilah alasan pemilihan Z-R relationship yang tepat untuk regime presipitasi setempat begitu penting.
Penerapan dengan xarray dan Visualisasi
Setelah reflektivitas dan rain rate tersimpan sebagai xarray DataArray dengan koordinat lat/lon, operasi spasial menjadi langsung: .sel(lat=slice(...)) untuk subset domain, .mean("lat") untuk rata-rata zonal, atau .where(da > threshold) untuk filter berdasarkan nilai. Metadata koordinat membuat analisis jauh lebih ekspresif dibanding array NumPy polos.
Snippet berikut merender kedua field — reflektivitas (dBZ) dan rain rate dari tropical Z-R (mm/h) — secara berdampingan menggunakan pcolormesh dengan colormap yang sesuai masing-masing variabel.
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle(
"Synthetic Radar QPE — Reflectivity (dBZ) vs Rain Rate — Tropical Z-R (mm/h)",
fontsize=12, weight="bold", y=1.01
)
# Panel 1: Reflectivity (dBZ)
levels_dbz = [0, 10, 20, 30, 40, 50, 55, 65]
cmap_dbz = plt.cm.get_cmap("Spectral_r", len(levels_dbz) - 1)
norm_dbz = mcolors.BoundaryNorm(levels_dbz, ncolors=cmap_dbz.N)
im1 = ax1.pcolormesh(lon_arr, lat_arr, dbz,
cmap=cmap_dbz, norm=norm_dbz, shading="auto")
cb1 = plt.colorbar(im1, ax=ax1, orientation="horizontal", pad=0.08, shrink=0.85)
cb1.set_label("Reflectivity (dBZ)", fontsize=10)
cb1.set_ticks(levels_dbz)
ax1.set_title("Reflectivity Factor (dBZ)", fontsize=11)
ax1.set_xlabel("Longitude (°E)")
ax1.set_ylabel("Latitude (°N/S)")
ax1.axhline(0, color="gray", lw=0.5, ls="--", alpha=0.6)
ax1.set_aspect("equal")
# Panel 2: Rain rate using Tropical Z-R (mm/h)
levels_rr = [0, 1, 2, 5, 10, 20, 50, 100]
cmap_rr = plt.cm.get_cmap("Blues", len(levels_rr) - 1)
norm_rr = mcolors.BoundaryNorm(levels_rr, ncolors=cmap_rr.N)
im2 = ax2.pcolormesh(lon_arr, lat_arr, R_trop,
cmap=cmap_rr, norm=norm_rr, shading="auto")
cb2 = plt.colorbar(im2, ax=ax2, orientation="horizontal", pad=0.08, shrink=0.85)
cb2.set_label("Rain Rate — Tropical Z-R (mm/h)", fontsize=10)
cb2.set_ticks(levels_rr)
ax2.set_title("Estimated Rain Rate — Tropical Z-R (mm/h)", fontsize=11)
ax2.set_xlabel("Longitude (°E)")
ax2.set_ylabel("Latitude (°N/S)")
ax2.axhline(0, color="gray", lw=0.5, ls="--", alpha=0.6)
ax2.set_aspect("equal")
plt.tight_layout()
plt.savefig("radar_rainrate.png", dpi=150, bbox_inches="tight")
print("Figure saved: radar_rainrate.png")
Dari visualisasi di atas terlihat jelas non-linearity dari Z-R relationship: storm cell dengan dBZ = 52 menghasilkan rain rate yang jauh lebih tinggi secara tidak proporsional dibanding latar belakang stratiform 20 dBZ. Gradient tajam di sekitar storm cell mencerminkan sensitivitas \(R \propto Z^{1/b}\) terhadap nilai dBZ tinggi — konvergensi kecil dalam dBZ menghasilkan divergensi besar dalam rain rate.
Praktik Terbaik dan Langkah Berikutnya
Berikut contoh output QPE operasional dari sistem radar NWS menggunakan Marshall-Palmer Z-R untuk kasus banjir bandang nyata:

Sumber: NOAA/NWS Tallahassee (halaman sumber) — akumulasi curah hujan 24 jam dengan Marshall-Palmer Z-R untuk kasus flash flood Josie.
Beberapa prinsip yang perlu diterapkan saat mengerjakan QPE radar:
Masking noise selalu menjadi langkah pertama. Pixel dengan dBZ < 10 harus di-set ke nol sebelum konversi Z-R. Kegagalan melakukan ini menghasilkan rain rate fiktif dari echo ground clutter, anomalous propagation, dan interferensi non-meteorologis.
Pahami sumber error dominan sebelum memilih Z-R. Tiga sumber error utama yang perlu diwaspadai:
- Beam overshoot — beam radar melewati atas presipitasi di jarak jauh (>100 km), menyebabkan underestimation sistematis terutama untuk wilayah pegunungan di Sumatra dan Papua
- Bright-band enhancement — lapisan melting di sekitar isotherm 0°C meningkatkan reflektivitas 5–16 dB dan bisa menghasilkan overestimation rain rate hingga 5 kali nilai sebenarnya
- Anomalous propagation — kondisi ducting atmosfer menciptakan false echo yang persisten dan stasioner, berpotensi menghasilkan sinyal akumulasi hujan fiktif
Pilihan Z-R penting, tapi kalibrasi lebih penting. Penelitian NWS mengkonfirmasi bahwa akurasi pengukuran \(Z\) itu sendiri memiliki dampak lebih besar terhadap kualitas QPE dibanding pilihan formula Z-R. Perbedaan kalibrasi antara radar yang berdekatan dapat melebihi 17% dalam reflektivitas — koreksi bias berbasis gauge tetap diperlukan untuk akurasi operasional.
Konteks Indonesia: dengan dominasi konveksi tropis maritim, tropical Z-R (\(Z = 250 R^{1{,}2}\)) secara fisik lebih sesuai untuk sebagian besar kondisi di wilayah Indonesia dibanding Marshall-Palmer. Namun BMKG mengoperasikan Marshall-Palmer sebagai default jaringan nasional, sehingga pemahaman perbedaan antar formula sangat relevan saat menginterpretasikan produk QPE resmi.
Langkah selanjutnya yang layak dieksplorasi: integrasi data pos hujan automatik (AWS) BMKG untuk gauge-based bias correction, konversi reflektivitas ke column-max untuk deteksi hail, perbandingan QPE radar dengan produk satellite precipitation seperti GPM IMERG, dan eksplorasi data CAPPI riil dari BMKG Open Data.
Eksplorasi artikel meteorologi lainnya di meteo.my.id — kunjungi https://meteo.my.id untuk telusur lebih lanjut.
Referensi
- Reflectivity-Rainfall Rate Relationships in Operational Meteorology — NWS Tallahassee — perbandingan tiga Z-R relationship operasional (Marshall-Palmer, WSR-88D default, tropical Rosenfeld) dengan studi kasus banjir bandang Josie.
- WSR-88D Radar Rainfall Estimation: Capabilities, Limitations and Potential Improvements — NWS Morristown — katalog sumber error QPE radar: beam overshoot, bright-band, kalibrasi, dan anomalous propagation.
- RADAR Reflectivity Measurement — NWS Training Portal NEXRAD Module 3-1 — definisi resmi reflektivitas \(Z\), konversi logaritmik dBZ, dan default Z-R WSR-88D.
- Experimental Z-R Relationships — NWS Lower Mississippi River Forecast Center — tabel Z-R operasional untuk presipitasi stratiform dan konvektif beserta bias correction factor per situs radar.
- Sistem Informasi Radar Cuaca Terintegrasi BMKG — JTECE Journal — dokumentasi jaringan 41 stasiun radar BMKG dan penggunaan Marshall-Palmer Z-R sebagai default QPE nasional.