Selasa, 23 Juni 2026
Sumber: GOES Project Science Office / NOAA (The Intertropical Convergence Zone)
Indonesia terletak persis di jalur ITCZ — Intertropical Convergence Zone, tempat angin pasat dari belahan bumi utara dan selatan saling bertemu. Pertemuan itu tidak berhenti di permukaan: udara yang berkonvergensi dipaksa naik, membentuk awan cumulonimbus raksasa, dan akhirnya menghasilkan hujan yang mendominasi musim basah kita. Tapi seberapa kuat konvergensi itu, dan di mana tepatnya lokasinya pada tanggal tertentu?
Jawabannya ada di data angin permukaan ERA5. Dengan menghitung divergensi horizontal dari komponen u10 dan v10, kita bisa mengukur langsung seberapa besar angin "terkumpul" (konvergensi, nilai negatif) atau "menyebar" (divergensi, nilai positif) di setiap titik grid. Tutorial ini memandu kita dari download data via cdsapi, koreksi koordinat sferis, visualisasi peta divergensi dengan cartopy, hingga perbandingan musiman DJF versus JJA.
Konsep Divergensi Horizontal
Divergensi horizontal mengukur seberapa cepat angin menyebar atau mengumpul di suatu area permukaan. Secara matematis, divergensi adalah dot product dari operator nabla dengan vektor angin horizontal:
$$\nabla \cdot \mathbf{V} = \frac{\partial u}{\partial x} + \frac{\partial v}{\partial y}$$
Dalam notasi sederhana ini, \(u\) adalah komponen angin zonal (barat-timur) dan \(v\) adalah komponen meridional (utara-selatan). Satuannya adalah \(\text{s}^{-1}\).
- Positif (\(\nabla \cdot \mathbf{V} > 0\)): divergensi — angin menyebar keluar dari suatu titik. Di permukaan, ini terkait dengan subsidensi dan cuaca cerah.
- Negatif (\(\nabla \cdot \mathbf{V} < 0\)): konvergensi — angin mengalir masuk ke suatu titik. Di permukaan, ini memicu gerakan naik dan pembentukan awan hujan.
Formula Cartesius di atas hanya valid untuk grid datar. Di grid ERA5 yang menggunakan koordinat lintang-bujur sferis, kita perlu koreksi:
$$\nabla \cdot \mathbf{V} = \frac{1}{a \cos\varphi} \frac{\partial u}{\partial \lambda} + \frac{1}{a \cos\varphi} \frac{\partial (v \cos\varphi)}{\partial \varphi}$$
Di sini \(a\) adalah jari-jari Bumi (\(6{,}371 \times 10^6\) m), \(\varphi\) adalah lintang, dan \(\lambda\) adalah bujur. Faktor \(\cos\varphi\) muncul karena jarak antar-bujur semakin menyempit menuju kutub. Tanpa koreksi ini, nilai divergensi di lintang rendah (seperti Indonesia) akan meleset karena grid ERA5 bukan proyeksi Cartesius.
Keterkaitan ITCZ sangat langsung: ketika ITCZ berada di atas Indonesia (DJF — musim hujan), divergensi permukaan bernilai sangat negatif. Ketika ITCZ bergeser ke belahan bumi utara (JJA), nilai divergensi di Indonesia cenderung mendekati nol atau bahkan positif.
Mengunduh Data ERA5 u10 dan v10
ERA5 menyediakan komponen angin permukaan 10 m — u10 (paramId 165) dan v10 (paramId 166) — pada resolusi \(0{,}25°\) setiap 6 jam. Kita download keduanya untuk seluruh tahun 2024 dengan bounding box Indonesia (\(6°\)N, \(95°\)E, \(-11°\)S, \(141°\)E). Daftar akun CDS di cds.climate.copernicus.eu lalu install cdsapi sebelum menjalankan snippet berikut.
import os
import cdsapi
import xarray as xr
OUT_U = "era5_u10_indonesia_2024_6h.nc"
OUT_V = "era5_v10_indonesia_2024_6h.nc"
if not os.path.exists(OUT_U):
c = cdsapi.Client(quiet=True)
c.retrieve("reanalysis-era5-single-levels", {
"product_type": "reanalysis",
"variable": ["10m_u_component_of_wind"],
"year": "2024",
"month": [f"{m:02d}" for m in range(1, 13)],
"day": [f"{d:02d}" for d in range(1, 32)],
"time": ["00:00", "06:00", "12:00", "18:00"],
"area": [6, 95, -11, 141],
"format": "netcdf",
}, OUT_U)
if not os.path.exists(OUT_V):
c = cdsapi.Client(quiet=True)
c.retrieve("reanalysis-era5-single-levels", {
"product_type": "reanalysis",
"variable": ["10m_v_component_of_wind"],
"year": "2024",
"month": [f"{m:02d}" for m in range(1, 13)],
"day": [f"{d:02d}" for d in range(1, 32)],
"time": ["00:00", "06:00", "12:00", "18:00"],
"area": [6, 95, -11, 141],
"format": "netcdf",
}, OUT_V)
ds_u = xr.open_dataset(OUT_U)
ds_v = xr.open_dataset(OUT_V)
# ERA5 dari CDS kadang memakai koordinat 'valid_time'; rename ke 'time' agar konsisten
if "valid_time" in ds_u.dims:
ds_u = ds_u.rename({"valid_time": "time"})
if "valid_time" in ds_v.dims:
ds_v = ds_v.rename({"valid_time": "time"})
print("Dataset u10:")
print(ds_u)
print("\nDataset v10:")
print(ds_v)
print(f"\nWaktu tersedia: {ds_u.time.values[0]} s.d. {ds_u.time.values[-1]}")
print(f"Resolusi grid: {abs(float(ds_u.latitude.values[1] - ds_u.latitude.values[0])):.2f}°")
Dataset u10:
<xarray.Dataset> Size: 75MB
Dimensions: (time: 1464, latitude: 69, longitude: 185)
Coordinates:
* time (time) datetime64[ns] 12kB 2024-01-01 ... 2024-12-31T18:00:00
expver (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 (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: (time: 1464, latitude: 69, longitude: 185)
Coordinates:
* time (time) datetime64[ns] 12kB 2024-01-01 ... 2024-12-31T18:00:00
expver (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 (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...
Waktu tersedia: 2024-01-01T00:00:00.000000000 s.d. 2024-12-31T18:00:00.000000000
Resolusi grid: 0.25°
Output menunjukkan dimensi (time, latitude, longitude) dengan 1464 time step untuk u10 dan v10, masing-masing mencakup seluruh 2024. Kita juga bisa konfirmasi resolusi \(0{,}25°\) dari perbedaan antar-lintang.
Menghitung Divergensi Permukaan
Dengan data di tangan, kita pilih satu time slice representatif dari musim DJF — 15 Januari 2024 pukul 00Z — untuk menghitung divergensi menggunakan formula sferis. Implementasinya memakai numpy.gradient dengan koreksi faktor \(\cos\varphi\) secara eksplisit.
import numpy as np
# Select time: Jan 15, 2024 00Z (DJF — ITCZ aktif di atas Indonesia)
TIME = "2024-01-15T00:00:00"
u = ds_u["u10"].sel(time=TIME, method="nearest")
v = ds_v["v10"].sel(time=TIME, method="nearest")
lat = u.latitude.values # degrees
lon = u.longitude.values # degrees
R = 6371000.0 # jari-jari Bumi dalam meter
lat_rad = np.deg2rad(lat)
dlon = np.deg2rad(np.gradient(lon))
dlat = np.deg2rad(np.gradient(lat))
# Koreksi sferis: div = 1/(R cosφ) * ∂u/∂λ + 1/(R cosφ) * ∂(v cosφ)/∂φ
cos_lat = np.cos(lat_rad)[:, np.newaxis] # shape (nlat, 1)
v_cos = v.values * np.cos(lat_rad)[:, np.newaxis]
du_dlambda = np.gradient(u.values, axis=1) / dlon[np.newaxis, :]
dvcoslat_dphi = np.gradient(v_cos, axis=0) / dlat[:, np.newaxis]
div = (du_dlambda + dvcoslat_dphi) / (R * cos_lat)
div_scaled = div * 1e5 # konversi ke 10⁻⁵ s⁻¹
print(f"Waktu: {TIME}")
print(f"Domain mean divergensi : {div_scaled.mean():.4f} × 10⁻⁵ s⁻¹")
print(f"Minimum divergensi (konvergensi terkuat): {div_scaled.min():.4f} × 10⁻⁵ s⁻¹")
print(f"Maximum divergensi : {div_scaled.max():.4f} × 10⁻⁵ s⁻¹")
# Lokasi konvergensi terkuat
lat_idx, lon_idx = np.unravel_index(div_scaled.argmin(), div_scaled.shape)
print(f"Konvergensi terkuat di: lat={lat[lat_idx]:.2f}°, lon={lon[lon_idx]:.2f}°")
Waktu: 2024-01-15T00:00:00
Domain mean divergensi : -0.2492 × 10⁻⁵ s⁻¹
Minimum divergensi (konvergensi terkuat): -11.6014 × 10⁻⁵ s⁻¹
Maximum divergensi : 12.2946 × 10⁻⁵ s⁻¹
Konvergensi terkuat di: lat=-5.75°, lon=114.25°
Output berikut menampilkan nilai domain-mean divergensi dan lokasi konvergensi terkuat pada tanggal tersebut. Nilai minimum yang sangat negatif (biasanya di kisaran \(-5\) hingga \(-10 \times 10^{-5}\ \text{s}^{-1}\) pada DJF) mengindikasikan zona ITCZ yang aktif, sementara nilai positif di area lain menandai region dengan angin yang menyebar.
Visualisasi Peta Divergensi dan Angin
Peta divergensi lebih mudah dibaca ketika kita overlay dengan vektor angin, sehingga terlihat langsung ke mana angin bergerak dan di mana terjadi penumpukan massa udara. Kita pakai pcolormesh dengan colormap RdBu_r — merah untuk divergensi, biru untuk konvergensi — dan quiver untuk tanda panah angin.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
fig, ax = plt.subplots(figsize=(12, 7), subplot_kw={"projection": ccrs.PlateCarree()})
ax.set_extent([95, 141, -11, 6], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.COASTLINE, linewidth=0.8)
ax.add_feature(cfeature.BORDERS, linewidth=0.4, linestyle=":")
gl = ax.gridlines(draw_labels=True, linewidth=0.3, color="gray", alpha=0.5)
gl.top_labels = False
gl.right_labels = False
pcm = ax.pcolormesh(
lon, lat, div_scaled,
cmap="RdBu_r", vmin=-8, vmax=8,
transform=ccrs.PlateCarree()
)
cb = plt.colorbar(pcm, ax=ax, label="Divergensi (×10⁻⁵ s⁻¹)", shrink=0.75, pad=0.05)
cb.ax.tick_params(labelsize=9)
# Subsample vektor angin agar tidak terlalu padat
step = 4
ax.quiver(
lon[::step], lat[::step],
u.values[::step, ::step], v.values[::step, ::step],
transform=ccrs.PlateCarree(),
scale=50, width=0.003, color="black", alpha=0.7,
label="Angin 10 m"
)
ax.set_title(
"Divergensi Angin Permukaan ERA5 — 15 Januari 2024 00Z\n"
"(Merah = Divergensi, Biru = Konvergensi)",
fontsize=12, pad=10
)
plt.tight_layout()
plt.savefig("div_map.png", dpi=120, bbox_inches="tight")
print("Peta divergensi tersimpan sebagai div_map.png")
Pada peta DJF, zona biru pekat seharusnya tampak memanjang di sekitar ekuator dan beberapa derajat selatan — itu signature ITCZ. Perhatikan bagaimana vektor angin dari arah barat laut dan barat daya "bertemu" di zona biru tersebut, mengonfirmasi konvergensi fisik yang mendorong naiknya massa udara.
Analisis Musiman Konvergensi
Satu snapshot DJF memberi gambaran sesaat. Untuk memahami pola musiman, kita hitung rata-rata divergensi selama DJF (Januari–Februari 2024) dan JJA (Juni–Agustus 2024), lalu bandingkan nilai domain-mean-nya.
Hipotesisnya: selama DJF, ITCZ berada di dekat atau selatan ekuator sehingga divergensi domain Indonesia lebih negatif (konvergensi lebih kuat). Selama JJA, ITCZ bermigrasi ke belahan bumi utara, meninggalkan Indonesia dengan angin yang lebih divergen dan musim kemarau.
u_all = ds_u["u10"]
v_all = ds_v["v10"]
def compute_div_mean(u_sel, v_sel):
lat_r = np.deg2rad(u_sel.latitude.values)
lon_ = u_sel.longitude.values
dlon_ = np.deg2rad(np.gradient(lon_))
dlat_ = np.deg2rad(np.gradient(lat_r))
cos_ = np.cos(lat_r)[:, np.newaxis]
v_cos_ = v_sel.values * np.cos(lat_r)[:, np.newaxis]
du_dl = np.gradient(u_sel.values, axis=1) / dlon_[np.newaxis, :]
dvc_dp = np.gradient(v_cos_, axis=0) / (dlat_[:, np.newaxis] * R)
div_ = (du_dl / (R * cos_)) + dvc_dp
return div_ * 1e5
# DJF: Januari dan Februari 2024 (Desember 2024 di luar dataset)
djf_mask = u_all.time.dt.month.isin([1, 2])
u_djf = u_all.sel(time=djf_mask).mean("time")
v_djf = v_all.sel(time=djf_mask).mean("time")
div_djf = compute_div_mean(u_djf, v_djf)
# JJA: Juni–Agustus 2024
jja_mask = u_all.time.dt.month.isin([6, 7, 8])
u_jja = u_all.sel(time=jja_mask).mean("time")
v_jja = v_all.sel(time=jja_mask).mean("time")
div_jja = compute_div_mean(u_jja, v_jja)
print("=== Divergensi Musiman (Domain Indonesia, 2024) ===")
print(f"DJF (Jan–Feb 2024): mean = {div_djf.mean():.4f} × 10⁻⁵ s⁻¹, min = {div_djf.min():.4f}")
print(f"JJA (Jun–Agu 2024): mean = {div_jja.mean():.4f} × 10⁻⁵ s⁻¹, min = {div_jja.min():.4f}")
print()
if div_djf.mean() < div_jja.mean():
print("DJF lebih konvergen (nilai lebih negatif) → ITCZ aktif di atas Indonesia saat musim hujan.")
else:
print("JJA lebih konvergen → analisis lebih lanjut diperlukan.")
=== Divergensi Musiman (Domain Indonesia, 2024) ===
DJF (Jan–Feb 2024): mean = -10.3682 × 10⁻⁵ s⁻¹, min = -371.9647
JJA (Jun–Agu 2024): mean = -2.0960 × 10⁻⁵ s⁻¹, min = -391.9321
DJF lebih konvergen (nilai lebih negatif) → ITCZ aktif di atas Indonesia saat musim hujan.
Output menunjukkan perbedaan yang jelas antara dua musim. Nilai mean DJF yang lebih negatif mengonfirmasi posisi ITCZ di atas atau dekat Indonesia, sementara nilai JJA yang mendekati nol (atau bahkan positif) mencerminkan migrasi ITCZ ke utara. Ini konsisten dengan pola monsun Asia-Australia yang mendominasi iklim kepulauan kita. MJO juga bisa memodulasi sinyal ini — fase aktif MJO memperkuat konvergensi permukaan dan divergensi atas, sementara fase teredam membalik polanya.
Langkah Berikutnya
Tutorial ini menunjukkan cara dasar menghitung dan memvisualisasikan divergensi permukaan dari ERA5. Ada beberapa arah eksplorasi yang menarik sebagai kelanjutan:
-
Gerak vertikal dari persamaan kontinuitas — divergensi permukaan yang kita hitung bisa diintegrasikan secara vertikal untuk memperkirakan kecepatan vertikal (\(\omega\)). Ini cara sederhana memverifikasi apakah area konvergensi memang bertepatan dengan kolom udara yang naik.
-
Korelasi dengan curah hujan IMERG — overlay data konvergensi bulanan dengan IMERG precipitation akan memperlihatkan seberapa erat korelasi spasial antara zona konvergensi dan curah hujan aktual di Indonesia.
-
Pengaruh MJO — filter data per fase MJO (Wheeler-Hendon index) lalu hitung komposit divergensi per fase. Artikel terpisah di blog ini akan membahas cara membuat diagram Wheeler-Hendon dari data ERA5.
-
Divergensi level atas (200 hPa) — di troposfer atas, pola sebaliknya berlaku: divergensi kuat di 200 hPa terkait kolom konveksi aktif. MetPy menyediakan
metpy.calc.divergence()yang otomatis menangani koreksi sferis jika input-nya adalah xarray DataArray dengan metadata proyeksi.
Eksplorasi artikel meteorologi lainnya di meteo.my.id — dari analisis deret waktu cuaca hingga pemetaan angin dengan cartopy, kunjungi meteo.my.id.
Referensi
- NOAA JetStream — Convergence Zone — penjelasan ITCZ sebagai zona pertemuan angin pasat dari kedua belahan bumi beserta mekanisme pembentukan awan hujannya.
- NASA Earth Observatory — The Intertropical Convergence Zone — gambaran visual dan saintifik ITCZ termasuk variabilitasnya musiman dan dampak pada pola curah hujan tropis.
- NOAA Climate.gov — What is the MJO and why do we care? — penjelasan MJO dan bagaimana fase aktif/teredam memodulasi konvergensi permukaan serta curah hujan di kawasan tropis.
- MetPy API — metpy.calc.divergence — dokumentasi fungsi divergensi MetPy yang menangani koreksi sferis secara otomatis menggunakan metadata xarray.
- ECMWF — ERA5 Data Documentation — dokumentasi resmi ERA5 termasuk daftar variabel, resolusi, dan cara akses via CDS API.