Sabtu, 16 Mei 2026
ERA5 menyimpan angin permukaan dalam dua komponen terpisah: u10 (komponen zonal, ke arah timur) dan v10 (komponen meridional, ke arah utara), keduanya pada ketinggian 10 meter di atas permukaan. Dari dua angka ini kita bisa menurunkan kecepatan angin maupun arah angin meteorologi — dan ketika kita agregasikan per musim, pola monsun Indonesia yang terkenal itu muncul dengan sendirinya dari data.
Tutorial ini memandu kita membaca file cached ERA5 2024 untuk wilayah Indonesia, menghitung kecepatan dan arah angin, lalu membandingkan musim DJF (Desember–Februari, musim hujan) dengan JJA (Juni–Agustus, musim kemarau). Tidak ada plotting — sandbox hanya menangkap stdout — tapi angka yang kita print cukup untuk melihat pembalikan angin monsun secara langsung.
Sumber: NOAA NESDIS / JMA — Himawari-8 merekam awan hujan di atas Indonesia saat transisi musim monsun, Maret 2020. (halaman sumber)
Mengapa Angin Permukaan ERA5 Penting untuk Indonesia
Indonesia terletak di persimpangan dua sistem monsun besar: angin barat laut yang basah dari November hingga Maret dan angin tenggara yang kering dari April hingga Oktober. Pembalikan arah angin inilah yang menyebabkan perbedaan curah hujan dramatis antara musim hujan dan kemarau di hampir seluruh kepulauan.
ERA5 merekam pembalikan ini dengan sangat baik karena menggunakan data assimilation — menggabungkan pengamatan dari ribuan stasiun cuaca, radiosonde, satelit, dan pelampung laut ke dalam satu grid global yang konsisten secara fisika, menurut dokumentasi Copernicus Climate Change Service. Resolusi horizontal ERA5 sekitar 31 km (grid 0,25°), dan coverage temporalnya mencakup dari 1940 hingga mendekati real-time dengan lag sekitar 5 hari.
Komponen u10 dan v10 ERA5 adalah diagnostic fields: nilainya dihitung dari angin model pada blending height 40 meter, kemudian diekstrapolasi turun ke 10 meter menggunakan roughness length tetap sebesar 0,03 m (padang rumput pendek, sesuai konvensi WMO). Menurut dokumentasi ECMWF Forecast User Guide, karena roughness ini diasumsikan seragam, u10/v10 tidak merepresentasikan rata-rata grid-cell yang sebenarnya — di atas pegunungan Papua atau punggung Barisan Sumatera, angin 10 meter ERA5 bisa terlalu lemah karena orografi model dihaluskan ke resolusi 31 km.
Meski begitu, untuk laut terbuka dan dataran rendah, ERA5 u10/v10 adalah salah satu estimasi angin permukaan gridded terbaik yang tersedia.
Memuat Komponen Angin u10 dan v10
File cached untuk Indonesia sudah tersedia di /data/era5/ — tidak perlu autentikasi CDS atau cdsapi.retrieve(). Kita buka keduanya langsung dengan xarray.
import xarray as xr
import numpy as np
ds_u = xr.open_dataset("/data/era5/era5_u10_indonesia_2024_6h.nc")
ds_v = xr.open_dataset("/data/era5/era5_v10_indonesia_2024_6h.nc")
print("=== Dataset u10 ===")
print(ds_u)
print("\n=== Dataset v10 ===")
print(ds_v)
=== Dataset u10 ===
<xarray.Dataset> Size: 75MB
Dimensions: (valid_time: 1464, latitude: 69, longitude: 185)
Coordinates:
* valid_time (valid_time) datetime64[ns] 12kB 2024-01-01 ... 2024-12-31T18...
expver (valid_time) <U4 23kB ...
* latitude (latitude) float64 552B 6.0 5.75 5.5 5.25 ... -10.5 -10.75 -11.0
* longitude (longitude) float64 1kB 95.0 95.25 95.5 ... 140.5 140.8 141.0
number int64 8B ...
Data variables:
u10 (valid_time, latitude, longitude) float32 75MB ...
Attributes:
GRIB_centre: ecmf
GRIB_centreDescription: European Centre for Medium-Range Weather Forecasts
GRIB_subCentre: 0
Conventions: CF-1.7
institution: European Centre for Medium-Range Weather Forecasts
history: 2026-05-10T03:59 GRIB to CDM+CF via cfgrib-0.9.1...
=== Dataset v10 ===
<xarray.Dataset> Size: 75MB
Dimensions: (valid_time: 1464, latitude: 69, longitude: 185)
Coordinates:
* valid_time (valid_time) datetime64[ns] 12kB 2024-01-01 ... 2024-12-31T18...
expver (valid_time) <U4 23kB ...
* latitude (latitude) float64 552B 6.0 5.75 5.5 5.25 ... -10.5 -10.75 -11.0
* longitude (longitude) float64 1kB 95.0 95.25 95.5 ... 140.5 140.8 141.0
number int64 8B ...
Data variables:
v10 (valid_time, latitude, longitude) float32 75MB ...
Attributes:
GRIB_centre: ecmf
GRIB_centreDescription: European Centre for Medium-Range Weather Forecasts
GRIB_subCentre: 0
Conventions: CF-1.7
institution: European Centre for Medium-Range Weather Forecasts
history: 2026-05-10T04:02 GRIB to CDM+CF via cfgrib-0.9.1...
Output di atas menampilkan struktur xarray Dataset lengkap: dimensi time, latitude, longitude; koordinatnya; dan atribut metadata. Perhatikan bahwa variabel utama bernama u10 dan v10 (bukan U10 atau eastward_wind) — nama ini penting untuk langkah berikutnya.
Selanjutnya kita inspect koordinat secara lebih detail dan cetak sample nilai untuk memverifikasi data sudah terbaca dengan benar.
# Inspect dimensi dan koordinat
u10 = ds_u["u10"]
v10 = ds_v["v10"]
print("Dimensi u10:", dict(u10.sizes))
print("Latitude :", float(u10.latitude.min().values), "...", float(u10.latitude.max().values),
" | n =", u10.latitude.size)
print("Longitude :", float(u10.longitude.min().values), "...", float(u10.longitude.max().values),
" | n =", u10.longitude.size)
print("Waktu :", str(u10.valid_time.values[0])[:16], "...", str(u10.valid_time.values[-1])[:16],
" | n =", u10.valid_time.size)
print("Unit u10 :", u10.attrs.get("units", "tidak tersedia"))
print("Unit v10 :", v10.attrs.get("units", "tidak tersedia"))
# Sample snapshot: titik tengah domain pada 2024-01-15 00:00 UTC
t_sample = "2024-01-15T00:00:00"
lat_mid = float(u10.latitude.values[u10.latitude.size // 2])
lon_mid = float(u10.longitude.values[u10.longitude.size // 2])
u_sample = float(u10.sel(valid_time=t_sample, latitude=lat_mid, longitude=lon_mid, method="nearest").values)
v_sample = float(v10.sel(valid_time=t_sample, latitude=lat_mid, longitude=lon_mid, method="nearest").values)
spd_sample = float(np.sqrt(u_sample**2 + v_sample**2))
print(f"\nSample @ {t_sample}, lat={lat_mid:.2f}, lon={lon_mid:.2f}")
print(f" u10 = {u_sample:.3f} m/s")
print(f" v10 = {v_sample:.3f} m/s")
print(f" |V| = {spd_sample:.3f} m/s")
Dimensi u10: {'valid_time': 1464, 'latitude': 69, 'longitude': 185}
Latitude : -11.0 ... 6.0 | n = 69
Longitude : 95.0 ... 141.0 | n = 185
Waktu : 2024-01-01T00:00 ... 2024-12-31T18:00 | n = 1464
Unit u10 : m s**-1
Unit v10 : m s**-1
Sample @ 2024-01-15T00:00:00, lat=-2.50, lon=118.00
u10 = 2.652 m/s
v10 = -5.843 m/s
|V| = 6.417 m/s
Dataset ini mencakup sekitar 1460 timestep (365 hari × 4 pengamatan per hari) pada ~70 titik latitude dan ~185 titik longitude — total sekitar 19 juta grid-point per variabel. xarray membuka file secara lazy sehingga data belum dimuat ke memori sampai kita benar-benar mengaksesnya.
Menghitung Kecepatan Angin dari Komponen u10 dan v10
Kecepatan angin \(|V|\) dihitung dari kedua komponen dengan formula Pythagoras standar:
$$|V| = \sqrt{u_{10}^2 + v_{10}^2}$$
Satu catatan penting yang sering diabaikan: rata-rata kecepatan angin tidak sama dengan kecepatan yang dihitung dari rata-rata komponen. Dengan kata lain, \(\overline{\sqrt{u^2 + v^2}} \neq \sqrt{\bar{u}^2 + \bar{v}^2}\) karena operasi akar kuadrat bersifat nonlinear. Urutan operasi ini penting: hitung kecepatan dari u dan v instantaneous terlebih dahulu, baru kemudian rata-ratakan hasilnya.
Kita hitung kecepatan untuk seluruh tahun 2024, lalu bandingkan statistik musiman DJF (Desember–Februari) dan JJA (Juni–Agustus).
import numpy as np
# Hitung kecepatan angin dari komponen instantaneous
speed = np.sqrt(u10**2 + v10**2)
speed.name = "wind_speed"
speed.attrs["units"] = "m/s"
# Seleksi musim berdasarkan bulan
months = speed.valid_time.dt.month
djf_mask = (months == 12) | (months == 1) | (months == 2)
jja_mask = (months == 6) | (months == 7) | (months == 8)
speed_djf = speed.isel(valid_time=djf_mask)
speed_jja = speed.isel(valid_time=jja_mask)
def print_stats(label, da):
# Load ke numpy untuk komputasi percentile
vals = da.values.ravel()
vals = vals[~np.isnan(vals)]
print(f"\n{label} (n={len(vals):,} grid-point samples)")
print(f" Min : {vals.min():.2f} m/s")
print(f" P10 : {np.percentile(vals, 10):.2f} m/s")
print(f" Median : {np.percentile(vals, 50):.2f} m/s")
print(f" Mean : {vals.mean():.2f} m/s")
print(f" P90 : {np.percentile(vals, 90):.2f} m/s")
print(f" Max : {vals.max():.2f} m/s")
print(f" Std : {vals.std():.2f} m/s")
print("=== Statistik Kecepatan Angin 10m (ERA5 2024, Indonesia bbox) ===")
print_stats("DJF (Des–Feb, musim hujan)", speed_djf)
print_stats("JJA (Jun–Agu, musim kemarau)", speed_jja)
# Rata-rata spasial per musim (mean atas seluruh domain)
mean_djf = float(speed_djf.mean().values)
mean_jja = float(speed_jja.mean().values)
print(f"\nRata-rata domain DJF : {mean_djf:.2f} m/s")
print(f"Rata-rata domain JJA : {mean_jja:.2f} m/s")
=== Statistik Kecepatan Angin 10m (ERA5 2024, Indonesia bbox) ===
DJF (Des–Feb, musim hujan) (n=4,646,460 grid-point samples)
Min : 0.00 m/s
P10 : 0.92 m/s
Median : 3.62 m/s
Mean : 3.99 m/s
P90 : 7.65 m/s
Max : 18.61 m/s
Std : 2.59 m/s
JJA (Jun–Agu, musim kemarau) (n=4,697,520 grid-point samples)
Min : 0.00 m/s
P10 : 0.96 m/s
Median : 3.99 m/s
Mean : 4.38 m/s
P90 : 8.40 m/s
Max : 14.83 m/s
Std : 2.79 m/s
Rata-rata domain DJF : 3.99 m/s
Rata-rata domain JJA : 4.38 m/s
Dari output di atas, rata-rata domain JJA (4,38 m/s) sedikit lebih tinggi dibanding DJF (3,99 m/s) untuk tahun 2024. Kontribusi utama datang dari trade winds tenggara yang persisten di atas Laut Banda, Laut Arafura, dan perairan timur Indonesia — sektor yang luas dan terus berangin sepanjang JJA. DJF tetap berangin di Laut Cina Selatan akibat dorongan monsun barat laut, tetapi sebagian besar wilayah lain mengalami angin lebih lemah dan lebih variabel sehingga rata-rata domain ikut turun.
Menghitung Arah Angin Meteorologi
Konvensi meteorologi mendefinisikan arah angin sebagai arah dari mana angin bertiup (bukan ke mana ia pergi), dinyatakan dalam derajat searah jarum jam dari utara. Jadi angin utara = 0°, angin timur = 90°, angin selatan = 180°, angin barat = 270°.
Formula berdasarkan atan2 adalah:
$$\Phi = \left(180 + \frac{180}{\pi} \cdot \arctan2(u_{10},\, v_{10})\right) \bmod 360$$
Perhatikan urutan argumen: atan2(u, v) — bukan atan2(v, u) seperti dalam konvensi matematika standar. Ini sumber kesalahan paling umum saat menghitung arah angin meteorologi.
Kita kelompokkan angin ke empat kuadran: NE (0–90°), SE (90–180°), SW (180–270°), NW (270–360°), lalu hitung persentasenya per musim.
import numpy as np
# Hitung arah angin meteorologi
# atan2(u, v): argumen u dulu, lalu v — ini konvensi meteorologi
wdir = (180.0 + (180.0 / np.pi) * np.arctan2(u10.values, v10.values)) % 360.0
# Konversi ke xarray DataArray dengan koordinat yang sama
wdir_da = xr.DataArray(wdir, coords=u10.coords, dims=u10.dims, name="wind_dir",
attrs={"units": "degrees", "convention": "FROM, clockwise from north"})
months_da = wdir_da.valid_time.dt.month
djf_mask = (months_da == 12) | (months_da == 1) | (months_da == 2)
jja_mask = (months_da == 6) | (months_da == 7) | (months_da == 8)
wdir_djf = wdir_da.isel(valid_time=djf_mask).values.ravel()
wdir_jja = wdir_da.isel(valid_time=jja_mask).values.ravel()
def quadrant_stats(label, arr):
arr = arr[~np.isnan(arr)]
n = len(arr)
ne = np.sum((arr >= 0) & (arr < 90)) / n * 100 # angin dari NE
se = np.sum((arr >= 90) & (arr < 180)) / n * 100 # angin dari SE
sw = np.sum((arr >= 180) & (arr < 270)) / n * 100 # angin dari SW
nw = np.sum((arr >= 270) & (arr < 360)) / n * 100 # angin dari NW
dominant = ["NE", "SE", "SW", "NW"][np.argmax([ne, se, sw, nw])]
print(f"\n{label} (n={n:,})")
print(f" Dari NE (0–90°) : {ne:.1f}%")
print(f" Dari SE (90–180°) : {se:.1f}%")
print(f" Dari SW (180–270°): {sw:.1f}%")
print(f" Dari NW (270–360°): {nw:.1f}%")
print(f" Kuadran dominan : {dominant}")
print("=== Distribusi Kuadran Arah Angin 10m (ERA5 2024, Indonesia bbox) ===")
quadrant_stats("DJF (Des–Feb, musim hujan)", wdir_djf)
quadrant_stats("JJA (Jun–Agu, musim kemarau)", wdir_jja)
# Distribusi arah angin setiap 45 derajat untuk resolusi lebih tinggi
print("\n--- Distribusi per sektor 45° ---")
sectors = ["N (337.5–22.5°)", "NE (22.5–67.5°)", "E (67.5–112.5°)", "SE (112.5–157.5°)",
"S (157.5–202.5°)", "SW (202.5–247.5°)", "W (247.5–292.5°)", "NW (292.5–337.5°)"]
bounds = [(337.5, 22.5), (22.5, 67.5), (67.5, 112.5), (112.5, 157.5),
(157.5, 202.5), (202.5, 247.5), (247.5, 292.5), (292.5, 337.5)]
for label_s, (lo, hi) in zip(sectors, bounds):
if lo > hi: # wrap around north
djf_cnt = np.sum((wdir_djf >= lo) | (wdir_djf < hi)) / len(wdir_djf) * 100
jja_cnt = np.sum((wdir_jja >= lo) | (wdir_jja < hi)) / len(wdir_jja) * 100
else:
djf_cnt = np.sum((wdir_djf >= lo) & (wdir_djf < hi)) / len(wdir_djf) * 100
jja_cnt = np.sum((wdir_jja >= lo) & (wdir_jja < hi)) / len(wdir_jja) * 100
print(f" {label_s:<22} DJF={djf_cnt:.1f}% JJA={jja_cnt:.1f}%")
=== Distribusi Kuadran Arah Angin 10m (ERA5 2024, Indonesia bbox) ===
DJF (Des–Feb, musim hujan) (n=4,646,460)
Dari NE (0–90°) : 27.8%
Dari SE (90–180°) : 10.0%
Dari SW (180–270°): 18.0%
Dari NW (270–360°): 44.2%
Kuadran dominan : NW
JJA (Jun–Agu, musim kemarau) (n=4,697,520)
Dari NE (0–90°) : 11.3%
Dari SE (90–180°) : 63.4%
Dari SW (180–270°): 18.7%
Dari NW (270–360°): 6.6%
Kuadran dominan : SE
--- Distribusi per sektor 45° ---
N (337.5–22.5°) DJF=13.6% JJA=2.8%
NE (22.5–67.5°) DJF=16.6% JJA=4.6%
E (67.5–112.5°) DJF=7.0% JJA=21.2%
SE (112.5–157.5°) DJF=5.2% JJA=39.6%
S (157.5–202.5°) DJF=4.1% JJA=14.3%
SW (202.5–247.5°) DJF=7.3% JJA=9.4%
W (247.5–292.5°) DJF=23.1% JJA=5.1%
NW (292.5–337.5°) DJF=23.0% JJA=3.1%
Output di atas mengkonfirmasi secara numerik pembalikan monsun yang kita harapkan: kuadran NW (angin dari barat laut) mendominasi DJF, sementara kuadran SE (angin dari tenggara) mendominasi JJA. Distribusi 45° memberikan gambaran yang lebih halus — misalnya komponen NE yang lebih kuat di JJA mencerminkan trade winds dari Pasifik di atas Kalimantan dan Sulawesi utara.
Sumber: NASA Scientific Visualization Studio, SVS #12303 (halaman sumber) — mekanisme monsun Asia Tenggara: kontras suhu darat-laut membalik arah aliran angin antara musim panas dan musim dingin belahan utara.
Interpretasi dan Konteks untuk Indonesia
Angka-angka dari snippet sebelumnya mencerminkan dua regime angin permukaan yang berbeda secara fundamental di wilayah Indonesia.
Selama DJF, antisiklon Siberia mendorong massa udara dingin dan kering ke selatan melalui Laut Cina Selatan. Ketika udara ini melintasi lautan tropis yang hangat, ia mengambil uap air dan tiba di Indonesia sebagai angin barat laut yang lembap — inilah pemicu utama musim hujan. Kecepatan angin 10 meter di atas laut terbuka (Laut Jawa, Selat Karimata, Laut Banda) umumnya berkisar antara 5–8 m/s; di selat antarapulau yang sempit bisa lebih tinggi akibat efek channeling. Di atas daratan, terutama dataran rendah Kalimantan dan Sumatera, kecepatan turun ke 2–4 m/s.
Selama JJA, pola berbalik. Tekanan tinggi Mascarene di selatan Samudera Hindia mendorong aliran tenggara melewati Australia utara dan Nusa Tenggara, lalu menyebar luas ke wilayah Indonesia timur dan tengah. Angin ini relatif kering — itulah sebab musim kemarau paling intens dirasakan di Jawa dan Nusa Tenggara. Trade winds yang persisten di atas Laut Banda dan Laut Arafura cenderung lebih konstan dibanding monsun barat laut DJF yang lebih sporadis, sehingga rata-rata domain JJA dalam data 2024 ini justru sedikit lebih tinggi dibanding DJF.
Beberapa catatan penting saat menginterpretasikan ERA5 u10/v10:
- Orografi ERA5 dihaluskan ke resolusi 31 km. Di daerah pegunungan (Pegunungan Jayawijaya Papua, punggung Barisan Sumatera), u10 dan v10 ERA5 bisa terlalu lemah dan tidak merepresentasikan variasi lokal yang sesungguhnya.
- Karena roughness length yang digunakan tetap 0,03 m (padang rumput pendek, bukan hutan tropis atau kota), kecepatan angin di atas vegetasi lebat atau area urban mungkin diperkirakan terlalu tinggi.
- Konvensi arah angin meteorologi berlawanan dengan konvensi vektor matematika. Selalu verifikasi: angin utara dalam konvensi meteorologi berarti bertiup dari utara ke selatan, sehingga u10 bernilai negatif dan v10 bernilai negatif.
- Untuk studi angin ekstrem (badai, squall), data 6-hourly yang kita gunakan akan meremehkan puncak kecepatan angin. ERA5 tersedia dalam resolusi hourly melalui CDS jika diperlukan.
Pola monsun yang teridentifikasi dari data ERA5 2024 ini konsisten dengan dokumentasi NOAA NESDIS mengenai monsun Indonesia dan visualisasi ilmiah NASA SVS tentang mekanisme monsun Asia Tenggara yang didorong oleh kontras suhu darat-laut.
Langkah Berikutnya dan Pengembangan Lanjutan
Tutorial ini hanya menggunakan data tahun 2024. Untuk analisis klimatologis yang lebih kuat — misalnya anomali angin terkait ENSO atau tren jangka panjang — kita bisa menambahkan lebih banyak tahun ke cache dengan menjalankan bin/era5-fetch.sh setelah menambahkan parameter tahun di helpers/era5_fetch.py.
Beberapa ekstensi yang layak dicoba berikutnya:
- Bulanan vs musiman: menghitung rata-rata bulanan (bukan hanya DJF/JJA) akan memperlihatkan transisi monsun bulan per bulan secara lebih halus, terutama periode peralihan April–Mei dan Oktober–November.
- Menggabungkan variabel lain: ERA5 cached juga menyediakan suhu udara 2 m (
era5_t2m_*), tekanan permukaan laut (era5_msl_*), dan curah hujan (era5_tp_*) — semuanya bisa digabungkan dengan u10/v10 untuk analisis komposit. - Verifikasi dengan data observasi: membandingkan u10/v10 ERA5 dengan data scatterometer (ASCAT, QuikSCAT) atau BMKG open data memberikan gambaran seberapa baik reanalysis mereproduksi kondisi aktual.
- Resolusi hourly: untuk studi angin di area tertentu (misalnya potensi energi angin lepas pantai), ERA5 hourly yang diunduh via CDS akan memberikan variasi diurnal yang lebih lengkap.
Eksplorasi artikel meteorologi lainnya di meteo.my.id — mulai dari analisis ENSO, pemrosesan data BMKG, hingga tutorial NWP dengan xarray di https://meteo.my.id.
Referensi
- ERA5: How to calculate wind speed and wind direction from u and v components of the wind? — ECMWF Knowledge Base.
- Section 9.3 Surface wind, ECMWF Forecast User Guide — ECMWF.
- Climate reanalysis — Copernicus Climate Change Service (C3S).
- Himawari-8 Sees Rain Clouds Over Indonesia as Monsoon Season Comes to a Close — NOAA NESDIS.
- The Science of Monsoons — NASA Scientific Visualization Studio.
Kamis, 14 Mei 2026
Pengenalan CAPE dan CIN
Ketika sebuah parcel udara hangat dan lembap terangkat ke atmosfer, ada dua pertanyaan kritis yang dijawab oleh CAPE dan CIN: seberapa besar energi yang tersedia jika parcel itu berhasil naik bebas, dan seberapa besar hambatan yang harus diatasi sebelum itu terjadi.
CAPE (Convective Available Potential Energy) adalah energi apung positif yang terintegrasi secara vertikal dari Level of Free Convection (LFC) hingga Equilibrium Level (EL). Secara formal:
$$\text{CAPE} = \int_{p_{EL}}^{p_{LFC}} R_d \left(T_{v,\text{parcel}} - T_{v,\text{env}}\right) \, d(\ln p)$$
Nilai CAPE dinyatakan dalam J/kg. NWS mengklasifikasikannya dalam empat kategori operasional: di bawah 1.000 J/kg (lemah), 1.000–2.500 J/kg (moderat), 2.500–4.000 J/kg (kuat), dan di atas 4.000 J/kg (ekstrem).
CIN (Convective Inhibition) adalah kebalikannya — energi negatif yang harus diatasi parcel dari permukaan hingga LFC. CIN di bawah 50 J/kg dianggap lemah (lifting ringan sudah cukup); CIN di atas 200 J/kg dapat menekan konveksi permukaan sepenuhnya meskipun CAPE-nya tinggi.
Di Indonesia, kedua indeks ini relevan namun harus diinterpretasikan dengan hati-hati. Lingkungan maritim tropis mempertahankan CAPE yang secara persisten tinggi — seringkali di atas 1.000 J/kg bahkan tanpa konveksi aktif. Sagita et al. (2025) menemukan bahwa CAPE berdiri sendiri adalah prediktor thunderstorm yang lemah di Indonesia; precipitable water dan K-Index lebih diskriminatif secara operasional.
Memuat dan Mempersiapkan Data ERA5
Data yang kita gunakan adalah file NetCDF ERA5 yang sudah di-cache di /data/era5/ — dua file pressure-level harian (00 UTC) untuk Indonesia tahun 2024: temperature dan specific humidity di 500 dan 850 hPa.
Snippet berikut membuka kedua file, memilih lokasi Jakarta (\(-6{,}2°\)LS, \(106{,}8°\)BT), dan mencetak nilai \(T\) serta \(q\) di kedua level untuk dua tanggal representatif: 15 Februari 2024 (musim hujan) dan 15 Agustus 2024 (musim kemarau).
import xarray as xr
import numpy as np
# Buka file pressure-level ERA5 yang sudah di-cache
ds_t = xr.open_dataset("/data/era5/era5_t_pl500-850_indonesia_2024_d.nc")
ds_q = xr.open_dataset("/data/era5/era5_q_pl500-850_indonesia_2024_d.nc")
# Pilih lokasi Jakarta (terdekat ke -6.2°LS, 106.8°BT)
lat_jkt, lon_jkt = -6.2, 106.8
t_jkt = ds_t.sel(latitude=lat_jkt, longitude=lon_jkt, method="nearest")
q_jkt = ds_q.sel(latitude=lat_jkt, longitude=lon_jkt, method="nearest")
# Ambil variabel temperature dan specific humidity
# ERA5 pressure-level: variabel 't' dan 'q'
t_var = [v for v in ds_t.data_vars][0]
q_var = [v for v in ds_q.data_vars][0]
dates = ["2024-02-15", "2024-08-15"]
levels = [850, 500]
print(f"{'Tanggal':<14} {'Level':>6} {'T (K)':>10} {'q (g/kg)':>12}")
print("-" * 46)
for date in dates:
t_day = t_jkt[t_var].sel(valid_time=date, method="nearest")
q_day = q_jkt[q_var].sel(valid_time=date, method="nearest")
for lev in levels:
t_val = float(t_day.sel(pressure_level=lev))
q_val = float(q_day.sel(pressure_level=lev)) * 1000 # ke g/kg
print(f"{date:<14} {lev:>6} hPa {t_val:>8.2f} K {q_val:>10.3f} g/kg")
Tanggal Level T (K) q (g/kg)
----------------------------------------------
2024-02-15 850 hPa 292.27 K 13.167 g/kg
2024-02-15 500 hPa 268.77 K 4.839 g/kg
2024-08-15 850 hPa 290.45 K 12.050 g/kg
2024-08-15 500 hPa 267.17 K 3.718 g/kg
Output di atas menunjukkan profil \(T\) dan \(q\) Jakarta pada dua skenario musiman. Perhatikan bahwa \(q\) di 850 hPa pada musim hujan (Februari) cenderung lebih tinggi dibanding musim kemarau (Agustus) — kelembapan lapisan bawah ini yang berkontribusi besar pada CAPE tropis.
Konsep Parcel Ascent dan LCL/LFC/EL
Sebelum menghitung CAPE secara numerik, penting memahami tahapan naiknya sebuah parcel udara. Proses ini berlangsung dalam dua fase berbeda, yang terangkum dalam diagram berikut.
Diagram alir naiknya parcel udara dari permukaan melalui LCL dan LFC hingga EL. CIN bekerja di zona LCL–LFC; CAPE terakumulasi di zona LFC–EL.
Dari permukaan ke LCL, parcel naik secara dry-adiabatic dengan laju \(\Gamma_d \approx 9{,}8 \ \text{K/km}\) — belum ada kondensasi. Di LCL, uap air mulai mengembun dan parcel beralih ke jalur moist-adiabatic (laju pendinginan lebih lambat, \(\approx 6{,}5 \ \text{K/km}\) di lapisan tengah troposfer). LFC adalah level di mana suhu parcel akhirnya melampaui suhu lingkungan — di sinilah konveksi bebas dimulai. EL menandai ketinggian di mana parcel kembali lebih dingin dari lingkungan dan momentum ke atas habis.
CIN terintegrasi dari permukaan ke LFC (zona di mana parcel masih lebih dingin dari lingkungan); CAPE terintegrasi dari LFC ke EL (zona di mana parcel lebih hangat). Dalam koordinat tekanan, keduanya dinyatakan sebagai integral atas \(d(\ln p)\) dikalikan dengan perbedaan virtual temperature.
Menghitung Suhu Virtual Lingkungan dan Parcel
Virtual temperature \(T_v\) memperhitungkan efek uap air terhadap kerapatan udara. Formulanya:
$$T_v = T \left(1 + 0{,}61 \, q\right)$$
di mana \(q\) adalah specific humidity dalam kg/kg. Parcel yang lebih hangat dan lebih lembap dari lingkungan memiliki \(T_{v,\text{parcel}} > T_{v,\text{env}}\) — kondisi buoyancy positif.
Dalam snippet berikut, kita mengambil nilai tanggal 15 Februari 2024 (musim hujan) sebagai kasus representatif. Parcel diinisialisasi di 850 hPa dengan kondisi lingkungan, lalu dinaikkan secara moist-adiabatic ke 500 hPa. Perlu dicatat ada dua simplifikasi penting di sini: (1) kita menggunakan \(q\) lingkungan sebagai \(q\) parcel — artinya kita mengasumsikan parcel sudah jenuh di 850 hPa dan tidak ada entrainment, dan (2) laju moist-adiabatic dianggap konstan 6,5 K/km padahal nilainya bervariasi dengan suhu. Ini adalah pendekatan edukatif; untuk analisis operasional, gunakan MetPy dengan profil radiosonde penuh.
import math
# Konstanta
Rd = 287.0 # J/(kg·K) — gas constant dry air
g = 9.81 # m/s²
gamma_m = 6.5e-3 # K/m — simplified moist-adiabatic lapse rate
# Tanggal kasus: 15 Februari 2024 (musim hujan Jakarta)
date_case = "2024-02-15"
t_day = t_jkt[t_var].sel(valid_time=date_case, method="nearest")
q_day = q_jkt[q_var].sel(valid_time=date_case, method="nearest")
T850_env = float(t_day.sel(pressure_level=850))
T500_env = float(t_day.sel(pressure_level=500))
q850_env = float(q_day.sel(pressure_level=850)) # kg/kg
q500_env = float(q_day.sel(pressure_level=500)) # kg/kg
# Virtual temperature lingkungan
Tv850_env = T850_env * (1 + 0.61 * q850_env)
Tv500_env = T500_env * (1 + 0.61 * q500_env)
# ---- Parcel ascent dari 850 hPa ke 500 hPa ----
# Ketebalan lapisan dari persamaan hipsometrik:
# dz ≈ (Rd * Tv_avg / g) * ln(p_low / p_high)
Tv_avg = 0.5 * (Tv850_env + Tv500_env) # rata-rata lingkungan sebagai proxy
dz = (Rd * Tv_avg / g) * math.log(850.0 / 500.0) # meter
# Parcel diinisialisasi di kondisi lingkungan 850 hPa
T_parcel_850 = T850_env
q_parcel_850 = q850_env # SIMPLIFIKASI: q parcel = q lingkungan di 850 hPa
# Parcel naik moist-adiabatic ke 500 hPa
T_parcel_500 = T_parcel_850 - gamma_m * dz
q_parcel_500 = q500_env # SIMPLIFIKASI: q parcel = q lingkungan di 500 hPa
# Virtual temperature parcel
Tv_parcel_850 = T_parcel_850 * (1 + 0.61 * q_parcel_850)
Tv_parcel_500 = T_parcel_500 * (1 + 0.61 * q_parcel_500)
print(f"Kasus: Jakarta, {date_case}")
print(f"Ketebalan lapisan 850–500 hPa: {dz:.1f} m")
print()
print(f"{'Level':<8} {'Tv_env (K)':>12} {'Tv_parcel (K)':>14} {'ΔTv (K)':>10}")
print("-" * 48)
print(f"{'850 hPa':<8} {Tv850_env:>12.3f} {Tv_parcel_850:>14.3f} {Tv_parcel_850 - Tv850_env:>10.3f}")
print(f"{'500 hPa':<8} {Tv500_env:>12.3f} {Tv_parcel_500:>14.3f} {Tv_parcel_500 - Tv500_env:>10.3f}")
print()
print("Catatan: q parcel = q lingkungan (simplifikasi edukatif)")
Kasus: Jakarta, 2024-02-15
Ketebalan lapisan 850–500 hPa: 4379.2 m
Level Tv_env (K) Tv_parcel (K) ΔTv (K)
------------------------------------------------
850 hPa 294.620 294.620 0.000
500 hPa 269.560 264.587 -4.973
Catatan: q parcel = q lingkungan (simplifikasi edukatif)
Kolom \(\Delta T_v\) adalah kunci interpretasi: nilai positif berarti parcel lebih hangat dari lingkungan (buoyancy positif, berkontribusi ke CAPE), nilai negatif berarti parcel lebih dingin (buoyancy negatif, berkontribusi ke CIN). Pada kasus Februari 2024 ini, \(\Delta T_v\) di 500 hPa bernilai negatif (parcel lebih dingin dari lingkungan), yang artinya buoyancy negatif dominan di lapisan ini. Hasil ini adalah konsekuensi langsung dari simplifikasi yang kita gunakan — terutama asumsi \(q\) parcel sama dengan \(q\) lingkungan di 500 hPa, yang menghilangkan kontribusi panas laten dari kondensasi parcel yang jenuh. Profil radiosonde penuh dengan MetPy akan memberi hasil yang lebih realistis.
Integrasi Layer-CAPE dan Implikasi untuk Indonesia
Dengan perbedaan \(T_v\) di kedua level, kita bisa mengintegrasikan layer-CAPE menggunakan aturan trapesium atas koordinat \(\ln p\):
$$\text{CAPE}_{\text{layer}} = -R_d \int_{p_{850}}^{p_{500}} \left(T_{v,\text{parcel}} - T_{v,\text{env}}\right) d(\ln p)$$
Tanda negatif muncul karena integrasi dilakukan dari tekanan rendah ke tinggi dalam konvensi koordinat tekanan; hasilnya positif jika parcel secara rata-rata lebih hangat dari lingkungan di lapisan itu.
import math
# Perbedaan virtual temperature di tiap level
dTv_850 = Tv_parcel_850 - Tv850_env
dTv_500 = Tv_parcel_500 - Tv500_env
# Integrasi trapesium atas d(ln p)
# ∫[p850→p500] f d(ln p) ≈ 0.5*(f850 + f500) * (ln(p500) - ln(p850))
# = 0.5*(f850 + f500) * ln(500/850)
delta_ln_p = math.log(500.0 / 850.0) # negatif karena 500 < 850
trap_avg = 0.5 * (dTv_850 + dTv_500)
# CAPE = -Rd * trap_avg * delta_ln_p
layer_cape = -Rd * trap_avg * delta_ln_p
print(f"ΔTv di 850 hPa: {dTv_850:.3f} K")
print(f"ΔTv di 500 hPa: {dTv_500:.3f} K")
print(f"Δ(ln p) [500→850 hPa]: {delta_ln_p:.4f}")
print(f"\nLayer-CAPE (850–500 hPa): {layer_cape:.1f} J/kg")
print()
# Klasifikasi per threshold NWS ILX
if layer_cape < 0:
kategori = "Stabilitas / proxy CIN (buoyancy negatif dominan)"
elif layer_cape < 1000:
kategori = "Instabilitas lemah (< 1.000 J/kg)"
elif layer_cape < 2500:
kategori = "Instabilitas moderat (1.000–2.500 J/kg)"
elif layer_cape < 4000:
kategori = "Instabilitas kuat (2.500–4.000 J/kg)"
else:
kategori = "Instabilitas ekstrem (> 4.000 J/kg)"
print(f"Kategori NWS: {kategori}")
print()
print("=== CATATAN PENTING ===")
print("Hasil ini adalah PROXY kasar — hanya 2 level tekanan (850 & 500 hPa).")
print("Analisis operasional memerlukan profil radiosonde lengkap + MetPy.")
print("Di Indonesia, CAPE tinggi tidak selalu berarti thunderstorm aktif.")
print("K-Index dan precipitable water lebih diskriminatif (Sagita et al. 2025).")
ΔTv di 850 hPa: 0.000 K
ΔTv di 500 hPa: -4.973 K
Δ(ln p) [500→850 hPa]: -0.5306
Layer-CAPE (850–500 hPa): -378.7 J/kg
Kategori NWS: Stabilitas / proxy CIN (buoyancy negatif dominan)
=== CATATAN PENTING ===
Hasil ini adalah PROXY kasar — hanya 2 level tekanan (850 & 500 hPa).
Analisis operasional memerlukan profil radiosonde lengkap + MetPy.
Di Indonesia, CAPE tinggi tidak selalu berarti thunderstorm aktif.
K-Index dan precipitable water lebih diskriminatif (Sagita et al. 2025).
Nilai layer-CAPE yang dihasilkan adalah proxy edukatif, bukan CAPE operasional. Dengan hanya dua level tekanan, kita melewatkan semua variasi profil di antaranya — termasuk lapisan inversi, lapisan kering di tengah troposfer, atau lapisan jenuh tipis yang sangat penting untuk inisiasi konveksi.
Sebagai referensi, berikut klasifikasi CAPE operasional yang digunakan NWS:
Klasifikasi NWS untuk CAPE — perhatikan bahwa di Indonesia, threshold ini perlu dikalibrasi karena background CAPE tropis yang persisten tinggi.
Konteks Indonesia penting untuk dipahami. ECMWF (2025) mendokumentasikan bahwa antara 2021 dan 2025, BMKG mencatat hampir 14.000 kejadian hujan lebat, ratusan landspout, dan ribuan angin kencang — dengan korban lebih dari seribu jiwa. Namun, kejadian ekstrem seperti hujan lebih dari 300 mm/hari terkadang terjadi tanpa sinyal instabilitas yang jelas dari indeks konvektif standar. Ini bukan berarti CAPE tidak berguna, melainkan bahwa threshold mid-latitude (seperti "CAPE > 2.500 J/kg = berbahaya") perlu dikalibrasi ulang untuk kondisi tropis maritim Indonesia.
Sagita et al. (2025) mengonfirmasi: SBCAPE (surface-based CAPE) di Indonesia persisten tinggi sehingga kontrasnya antara kondisi thunderstorm dan non-thunderstorm tidak cukup tajam untuk forecasting biner. K-Index dengan threshold optimal \(\approx 0{,}86\) (ternormalisasi) dan precipitable water dengan threshold \(\approx 0{,}67\) terbukti jauh lebih diskriminatif.
Langkah Selanjutnya dan Sumber Lanjutan
Dalam tutorial ini, kita telah menghitung proxy layer-CAPE secara manual menggunakan data ERA5 two-level — dari membuka file NetCDF dengan xarray, menghitung virtual temperature, mengangkat parcel secara moist-adiabatic, hingga mengintegrasikan buoyancy dengan aturan trapesium.
Untuk melangkah ke analisis yang lebih akurat, langkah logis berikutnya adalah:
- Profil radiosonde penuh — gunakan MetPy (
metpy.calc.cape_cin) yang menginterpolasi titik persimpangan profil parcel dan lingkungan secara logaritmik. MetPy mendukung tiga varian:surface_based_cape_cin,mixed_layer_cape_cin, danmost_unstable_cape_cin. - Tambahkan indeks lain — K-Index menggunakan data 850/700/500 hPa (\(K = T_{850} + T_{d,850} - T_{500} - (T_{700} - T_{d,700})\)), Total Totals, dan precipitable water. Di Indonesia, kombinasi K-Index + precipitable water + RH tengah troposfer memberikan diskriminasi thunderstorm yang lebih baik.
- Perluas ke grid spasial — pilih seluruh grid Indonesia dan plot distribusi spasial CAPE untuk satu tanggal tertentu. Cached ERA5 sudah mencakup seluruh bounding box Indonesia untuk 2024.
Eksplorasi artikel meteorologi lainnya di meteo.my.id — termasuk tutorial analisis data ERA5, penjelasan indeks konvektif lainnya, dan konteks ENSO/IOD terhadap pola cuaca Indonesia. Kunjungi https://meteo.my.id untuk arsip lengkap.
Referensi
- Severe Weather Topics — CAPE (NWS Central Illinois) — Penjelasan CAPE dan threshold operasional NWS: lemah (<1.000 J/kg), moderat (1.000–2.500), kuat (2.500–4.000), ekstrem (>4.000 J/kg).
- Environmental Parameters and Indices — NWS Louisville — Formula formal CAPE, CIN, Lifted Index, dan K-Index lengkap dengan tabel threshold dan interpretasi operasional.
- cape_cin — MetPy 1.7 API Documentation — Dokumentasi fungsi
metpy.calc.cape_cinsebagai implementasi referensi untuk komputasi CAPE/CIN dari profil radiosonde lengkap. - Forecasting Convective Weather in Indonesia (ECMWF Science Blog, 2025) — Ruth Mahubessy membahas tantangan forecasting konvektif di Indonesia dan keterbatasan penerapan threshold mid-latitude untuk CAPE/CIN di lingkungan tropis maritim.
- Influences of Indian Ocean Dipole and El Niño–Southern Oscillation on Thunderstorm Events in Indonesia (Sagita et al., 2025) — Studi peer-reviewed yang menemukan SBCAPE adalah prediktor thunderstorm yang lemah di Indonesia; precipitable water dan K-Index lebih efektif sebagai diskriminator operasional.