Minggu, 07 Juni 2026
Sumber: NOAA Atlantic Oceanographic and Meteorological Laboratory (NOAA AOML)
Mengapa Shear Angin Penting untuk Siklon Tropis
Bayangkan dua siklon tropis yang lahir di kondisi SST dan kelembaban yang hampir identik. Satu menguat menjadi badai kategori tiga dalam 24 jam; yang lain stagnan lalu buyar tanpa menyentuh daratan. Para prakirawan yang memantau keduanya sering menemukan satu variabel yang konsisten membedakan nasib mereka: shear angin vertikal di lingkungan sekitar sistem.
Shear angin adalah salah satu prediktor intensitas yang paling kuat sekaligus paling sering disalahpahami. Bukan sekadar "angin kencang di atas" versus "angin pelan di bawah" — definisi itu terlalu kasar untuk bisa digunakan secara operasional. Yang membuat shear berbahaya bagi sebuah siklon adalah perubahan vektor angin antara dua ketinggian: ketika angin di 850 hPa bertiup dari arah yang berbeda dibanding angin di 200 hPa, dan ketika selisih vektor itu cukup besar, seluruh struktur siklon mulai terdistorsi.
NOAA mencatat bahwa shear angin "significantly impacts both the structure and intensity of tropical cyclones", dan riset terbaru menunjukkan bahwa bukan hanya besarnya yang penting, tetapi juga arahnya. Pemahaman atas mekanisme ini penting bagi siapa pun yang mengikuti perkembangan siklon, mulai dari prakirawan BMKG hingga mahasiswa yang baru mengenal dinamika tropikal.
Apa Itu Shear Angin dan Bagaimana Ia Terbentuk
Secara formal, vertical wind shear dalam konteks siklon tropis didefinisikan sebagai selisih vektor antara angin horizontal di level atas troposfer (~200 hPa, sekitar 12 km) dan angin horizontal di level bawah (~850 hPa, sekitar 1,5 km):
$$\vec{V}_{shear} = \vec{V}_{200\,\text{hPa}} - \vec{V}_{850\,\text{hPa}}$$
Besarnya dinyatakan dalam m/s atau knot. Yang penting untuk diingat: ini adalah selisih vektor, bukan selisih kecepatan skalar. Angin 15 m/s dari barat daya di 200 hPa dengan angin 5 m/s dari timur laut di 850 hPa menghasilkan shear yang jauh lebih besar daripada angin 15 m/s dari barat daya di 200 hPa dengan angin 5 m/s dari barat daya di 850 hPa — meskipun selisih kecepatannya mungkin tampak mirip jika hanya melihat magnitudenya.
Dalam praktik operasional, klasifikasi kasar yang sering digunakan adalah:
- Shear lemah (weak): < 5 m/s — lingkungan sangat kondusif untuk intensifikasi
- Shear moderat (moderate): 5–10 m/s — hasilnya bervariasi, prakiraan sangat menantang
- Shear kuat (strong): > 10 m/s — intensifikasi biasanya terhenti atau sistem melemah
Catatan penting: WMO dan beberapa literatur menggunakan ambang batas sekitar 10–13 m/s (20–25 knot) sebagai batas atas di mana perkembangan siklon "practically ceases". Namun, Griffin-Elliott et al. (2024) dalam review mereka di Journal of the Atmospheric Sciences mencatat bahwa tidak ada definisi kuantitatif yang terstandardisasi secara universal — batas antara "moderat" dan "kuat" bergantung pada struktur dan tahap perkembangan sistem yang bersangkutan.
Shear terbentuk karena pola sirkulasi atmosfer skala sinoptik: jet stream, ridge tekanan tinggi subtropis, trough extratropik, dan anomali SST regional semuanya berkontribusi pada distribusi horizontal angin di berbagai ketinggian. Di Samudra Hindia barat daya — wilayah yang relevan bagi Indonesia bagian selatan — shear bervariasi kuat sesuai musim: rendah di puncak musim siklon (Januari–Maret) dan meningkat ketika monsun Australia mulai mundur.
Ilustrasi konseptual selisih vektor angin antara dua level troposfer dan implikasinya terhadap intensifikasi siklon.
Bagaimana Shear Mengganggu Struktur Siklon
Siklon tropis adalah heat engine: mesin panas yang mengisap udara hangat lembap dari permukaan laut, mendorong uap air ke atas lewat deep convection, melepaskan panas laten saat kondensasi, lalu mengalirkan udara dingin keluar di lapisan atas troposfer. Agar mesin ini bekerja dengan efisien, kolom hangat di pusat siklon perlu tetap sejajar secara vertikal — dari permukaan hingga puncak outflow di 200 hPa.
Shear angin mengganggu keselarasan ini melalui vortex tilt: aliran angin kuat di level atas mendorong sirkulasi bagian atas siklon ke satu arah, sementara sirkulasi permukaan tetap di posisinya. Akibatnya, warm core yang seharusnya berada tepat di atas pusat permukaan kini bergeser ke sisi downshear. Efek dominonya berlapis-lapis:
Pertama, asymmetric convection. Konveksi dan presipitasi terkonsentrasi di kuadran downshear (biasanya sisi selatan atau tenggara untuk shear dari utara di belahan bumi selatan). Sisi upshear kehilangan pasokan panas laten. Pola asymmetric ini terlihat jelas di citra satelit inframerah — band dingin awan cumulus cumulonimbus yang terlihat miring.
Kedua, dry-air ventilation. Tilt yang terjadi membuka celah antara sirkulasi atas dan bawah, memungkinkan udara kering dari lingkungan sekitar masuk ke dalam region dalam siklon. Udara kering ini mengandensasikan lebih sedikit uap air, mengurangi pelepasan panas laten, dan pada kasus ekstrem bisa membuat konveksi mati sama sekali.
Ketiga, outflow disruption. Keluar dari puncak siklon bergantung pada outflow yang simetris di 200 hPa. Shear kuat mengganggu simetri ini, mengurangi efisiensi pembuangan massa udara dari sistem.
Di rezim shear lemah (< 5 m/s), tilt minimal dan siklon dapat mempertahankan atau meningkatkan intensitasnya. Di rezim moderat (5–10 m/s), ada kompetisi antara shear dan convection internal siklon. Kadang convective bursts yang intens di dekat pusat storm menghasilkan aliran keluar yang cukup kuat untuk "melawan" shear lingkungan melalui proses yang disebut shear-outflow interaction, sehingga sistem berhasil menguat. Inilah mengapa rezim moderat begitu menantang untuk diprakirakan: rentang hasilnya lebar, dari pelemahan cepat hingga rapid intensification. Di rezim kuat (> 10 m/s), organisasi hampir mustahil dipertahankan.
Sumber: NOAA Atlantic Oceanographic and Meteorological Laboratory (NOAA AOML)
Pengaruh Arah Shear: Northerly versus Southerly
Sebuah temuan yang baru dikonfirmasi secara kuantitatif mengubah cara prakirawan melihat shear: arah shear, bukan hanya magnitudenya, punya pengaruh independen terhadap peluang intensifikasi.
Penelitian yang dipublikasikan di Monthly Weather Review (Chen et al., 2021) membandingkan dua skenario shear dengan magnitude identik. Dalam northerly shear — angin atas bertiup dari utara relatif terhadap angin bawah — udara dingin kering yang dibawa dari sisi utara secara parsial "diimbangi" oleh udara hangat lembap dari lingkungan tropis di sisi selatan. Distribusi suhu dan kelembaban permukaan di sekitar siklon menjadi lebih simetris, dan sistem memiliki peluang lebih besar untuk menguat.
Sebaliknya, dalam southerly shear, udara kering dari sisi selatan berkonvergensi dengan udara kering yang sudah ada di sisi itu akibat shear-induced tilt. Asymmetri kelembaban menjadi lebih ekstrem — satu kuadran sangat lembap, kuadran lainnya sangat kering — dan intensifikasi menjadi lebih sulit.
Tropical Cyclone Filipo pada Maret 2024 memberikan contoh nyata tentang pengaruh shear terhadap intensitas. Bergerak di Mozambique Channel dengan kondisi shear angin yang rendah, Filipo menguat dan mencapai kecepatan angin maksimum sekitar 80 km/h sebelum mendarat di dekat Inhassoro, Mozambik, pada 12 Maret 2024. NASA Earth Observatory mendokumentasikan struktur siklon ini dari citra Terra/MODIS, menunjukkan spiral cloud yang relatif teratur — ciri khas siklon yang beroperasi dalam lingkungan shear yang kondusif.
Sumber: NASA Earth Observatory, image by Lauren Dauphin using MODIS data from NASA EOSDIS LANCE and GIBS/Worldview (halaman sumber)
Pelajaran dari Filipo relevan untuk wilayah Indonesia: Samudra Hindia bagian barat daya — yang berbatasan dengan pesisir selatan Jawa, Bali, dan Nusa Tenggara — merupakan wilayah aktif siklon dengan pola shear yang berubah signifikan antar musim. Ketika BMKG memantau potensi pembentukan TC di kawasan tersebut, profil shear vertikal adalah input pertama yang dilihat sebelum menganalisis SST atau kondisi kelembaban.
Implikasi untuk Prakiraan dan Monitoring Operasional
Shear angin vertikal telah menjadi variabel standar dalam intensity guidance di hampir semua pusat siklon tropis dunia, termasuk TCWC Jakarta milik BMKG. Dalam praktiknya, prakirawan tidak hanya melihat satu angka shear — mereka memantau evolusi temporal dan spasial shear field di sekitar dan di depan jalur pergerakan sistem.
NWP ensemble seperti GFS dan ECMWF menjadi alat utama untuk ini. Dengan banyak member ensemble, prakirawan bisa menilai ketidakpastian shear ke depan. Pertanyaan kuncinya adalah apakah trough yang diperkirakan memperkenalkan shear kuat benar-benar akan tiba sesuai timing model, atau apakah spread yang cukup besar membuka skenario shear rendah yang memungkinkan intensifikasi. Spread yang besar dalam prakiraan shear ensemble langsung diterjemahkan sebagai ketidakpastian dalam track intensity forecast.
Namun shear bukan satu-satunya faktor. NOAA GFDL menekankan bahwa shear berinteraksi dengan SST dan upper troposphere warming dalam sebuah framework faktor kompetitif: SST hangat mendorong intensifikasi; shear kuat dan warming troposfer atas menghambatnya. Di era perubahan iklim, proyeksi model menunjukkan peningkatan shear vertikal di beberapa bagian Atlantik tropis barat selama abad ke-21, yang secara hipotetis akan mengurangi total jumlah siklon tetapi meningkatkan curah hujan di dekat pusat storm yang berhasil terbentuk.
Untuk pemula yang ingin mengeksplorasi konsep ini secara mandiri: output model GFS yang tersedia publik sudah menyertakan shear diagnostics di beberapa produk operasional. ECMWF Tropical Cyclone diagnostics dan NOAA/NHC Intensity Guidance products masing-masing menyajikan time series shear untuk sistem yang sedang dipantau. Mengamati bagaimana perubahan shear field berkorelasi dengan perubahan intensitas siklon yang sedang aktif adalah cara paling langsung untuk menginternalisasi konsep yang sudah kita bahas di artikel ini.
Eksplorasi artikel meteorologi lainnya di meteo.my.id: https://meteo.my.id
Referensi
- Griffin-Elliott et al. (2024) — NOAA AOML review of vertical wind shear impact on tropical cyclones — Ulasan komprehensif tentang tiga rezim shear, mekanisme vortex tilt, dan rekomendasi definisi shear terstandardisasi.
- NOAA AOML — Research Explores Impact of Wind Shear Direction on Tropical Cyclone Intensity — Rangkuman studi Chen et al. (2021, Monthly Weather Review) tentang pengaruh arah northerly versus southerly shear terhadap distribusi kelembaban dan peluang intensifikasi.
- NOAA AOML — Behind the 2015 Atlantic Hurricane Season: Wind Shear & Tropical Cyclones — Penjelasan aksesibel tentang mekanisme heat engine dan mengapa lingkungan tanpa shear paling kondusif bagi perkembangan siklon.
- NASA Earth Observatory — Tropical Cyclone Filipo — Dokumentasi kasus nyata Filipo (Maret 2024) di Mozambique Channel sebagai contoh penguatan siklon dalam lingkungan shear rendah.
- NOAA GFDL — Global Warming and Hurricanes — Kerangka kompetitif shear versus SST dalam proyeksi perubahan iklim terhadap aktivitas hurikan, berdasarkan model regional dan simulasi GFDL.
Memadukan Data Perkiraan dan Observasi Nyata
Setiap kali model Global Forecast System (GFS) menyelesaikan satu siklus forecast, hasilnya adalah jutaan angka — suhu, angin, tekanan, kelembapan — yang tersusun rapi dalam grid global 0,25° (~28 km). Tapi angka itu belum menjawab pertanyaan paling mendasar dalam meteorologi operasional: seberapa dekat prediksi model dengan apa yang sebenarnya terjadi di darat?
Proses membandingkan output model dengan data observasi disebut forecast verification. Ukuran paling dasar adalah bias: selisih antara nilai yang diamati stasiun dan nilai yang diprediksi model di lokasi yang sama. Bias sistematis yang besar mengindikasikan kelemahan model — bisa karena resolusi yang terlalu kasar untuk topografi lokal, parameterisasi fisika yang kurang akurat, atau kondisi awal dari sistem asimilasi data (GDAS) yang belum optimal.
GFS dijalankan empat kali sehari oleh NOAA/NCEP pada pukul 0000, 0600, 1200, dan 1800 UTC, menghasilkan forecast hingga 16 hari ke depan. Data tersedia secara publik melalui NOMADS (NOAA Operational Model Archive and Distribution System), termasuk layanan GRIB Filter yang memungkinkan kita mengunduh hanya variabel dan wilayah yang dibutuhkan.
Tutorial ini membangun satu pipeline lengkap: download data GFS dari NOMADS → load dengan cfgrib + xarray → buat observasi sintetis stasiun → interpolasi model ke lokasi stasiun dengan MetPy → hitung dan visualisasi bias. Alat yang digunakan: cfgrib, xarray, MetPy, numpy, pandas, dan matplotlib.
Menyiapkan Lingkungan dan Data
Sebelum mulai, pastikan environment sudah siap. cfgrib memerlukan library biner eccodes yang menangani decoding format GRIB2. Cara termudah adalah lewat conda:
conda install -c conda-forge cfgrib eccodes xarray metpy numpy pandas matplotlib
Jika menggunakan pip, install eccodes dulu secara terpisah (biner sistem), baru cfgrib:
# Ubuntu/Debian
sudo apt-get install libeccodes-dev
pip install cfgrib xarray metpy numpy pandas matplotlib
Snippet berikut memverifikasi instalasi dan sekaligus membuat dataset GFS sintetis yang mencerminkan struktur output GFS 0,25° nyata — variabel suhu 2 m, tekanan permukaan laut, dan komponen angin 10 m untuk wilayah Indonesia. Dataset sintetis ini dirancang khusus untuk tutorial sehingga tidak memerlukan koneksi ke NOMADS; dalam workflow nyata, kamu cukup mengganti blok pembuatan dataset dengan pemanggilan NOMADS GRIB Filter seperti ditunjukkan dalam komentar kode di bawah.
import os
import importlib
import numpy as np
import xarray as xr
# Verifikasi instalasi
print("Library versions:")
for lib in ["xarray", "cfgrib", "metpy", "numpy"]:
mod = importlib.import_module(lib)
ver = getattr(mod, "__version__", "n/a")
print(f" {lib}: {ver}")
# ----- Dataset sintetis GFS Indonesia -----
# Tutorial ini menggunakan data sintetis agar dapat dijalankan tanpa koneksi
# ke NOMADS. Dalam workflow nyata, gantikan blok ini dengan:
#
# import requests
# base = "https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl"
# params = {
# "dir": "/gfs.YYYYMMDD/HH/atmos",
# "file": "gfs.tHHz.pgrb2.0p25.f000",
# "var_TMP": "on", "var_PRMSL": "on", "var_UGRD": "on", "var_VGRD": "on",
# "lev_2_m_above_ground": "on", "lev_10_m_above_ground": "on",
# "lev_mean_sea_level": "on",
# "leftlon": "95", "rightlon": "141", "toplat": "6", "bottomlat": "-11",
# }
# r = requests.get(base, params=params, timeout=120, stream=True)
# r.raise_for_status()
# with open("gfs_output.grib2", "wb") as f:
# for chunk in r.iter_content(chunk_size=65536):
# f.write(chunk)
# # Kemudian buka dengan cfgrib:
# ds_sfc = xr.open_dataset("gfs_output.grib2", engine="cfgrib",
# backend_kwargs={"filter_by_keys": {"typeOfLevel": "heightAboveGround"}})
# ds_msl = xr.open_dataset("gfs_output.grib2", engine="cfgrib",
# backend_kwargs={"filter_by_keys": {"typeOfLevel": "meanSea"}})
GFS_OUT = "gfs_synthetic_indonesia.nc"
if not os.path.exists(GFS_OUT):
rng = np.random.default_rng(seed=2024)
# Grid 0,25° Indonesia: lat -11..6, lon 95..141
lats = np.arange(-11.0, 6.25, 0.25) # 69 titik
lons = np.arange(95.0, 141.25, 0.25) # 185 titik
# Suhu 2 m: gradien meridional tropis ~300 K, variasi spatial halus
lat2d, lon2d = np.meshgrid(lats, lons, indexing="ij")
t2m_base = (
302.0
- 0.3 * np.abs(lat2d) # sedikit lebih dingin menjauhi ekuator
+ 1.5 * np.sin(np.deg2rad(lon2d - 95) / 46 * np.pi) # variasi zonal
+ rng.normal(0.0, 0.8, lat2d.shape) # noise
)
# PRMSL: sekitar 101325 Pa dengan variasi regional kecil
prmsl = (
101325.0
+ 200.0 * np.cos(np.deg2rad(lat2d) * 3)
+ rng.normal(0.0, 50.0, lat2d.shape)
)
# Angin 10 m: timuran lemah di tropis
u10 = -2.5 + rng.normal(0.0, 1.0, lat2d.shape) # komponen u (timuran = negatif)
v10 = 0.5 + rng.normal(0.0, 0.8, lat2d.shape) # komponen v
ds = xr.Dataset(
{
"t2m": (["latitude", "longitude"], t2m_base.astype("float32"),
{"units": "K", "long_name": "2 metre temperature"}),
"prmsl": (["latitude", "longitude"], prmsl.astype("float32"),
{"units": "Pa", "long_name": "Pressure reduced to MSL"}),
"u10": (["latitude", "longitude"], u10.astype("float32"),
{"units": "m s**-1", "long_name": "10 metre U wind component"}),
"v10": (["latitude", "longitude"], v10.astype("float32"),
{"units": "m s**-1", "long_name": "10 metre V wind component"}),
},
coords={
"latitude": (["latitude"], lats, {"units": "degrees_north"}),
"longitude": (["longitude"], lons, {"units": "degrees_east"}),
},
attrs={
"title": "Synthetic GFS-like 0.25deg surface analysis — Indonesia",
"source": "Tutorial synthetic data (structure mirrors GFS 0.25deg f000)",
"history": "Generated by tutorial snippet-1",
},
)
ds.to_netcdf(GFS_OUT)
size_kb = os.path.getsize(GFS_OUT) / 1024
print(f"\nDataset sintetis dibuat: {GFS_OUT} ({size_kb:.1f} KB)")
else:
size_kb = os.path.getsize(GFS_OUT) / 1024
print(f"\nFile sudah ada: {GFS_OUT} ({size_kb:.1f} KB) — skip pembuatan")
print(f"Grid: {len(np.arange(-11.0, 6.25, 0.25))} lat × {len(np.arange(95.0, 141.25, 0.25))} lon titik")
Library versions:
xarray: 2026.4.0
cfgrib: 0.9.15.1
metpy: 1.7.1
numpy: 2.4.6
Dataset sintetis dibuat: gfs_synthetic_indonesia.nc (210.4 KB)
Grid: 69 lat × 185 lon titik
Pada workflow produksi, langkah ini digantikan oleh satu permintaan HTTP ke NOMADS GRIB Filter — pola URL-nya sudah ada di komentar kode. NOMADS menyimpan data GFS sekitar 10 hari ke belakang; untuk analisis historis yang lebih lama, gunakan NOMADS GRIB2 archive di AWS Open Data atau NCEI.
Memuat dan Menjelajahi Data GFS
Dengan dataset tersedia di working directory, kita buka dan eksplorasi strukturnya. Dataset sintetis disimpan dalam format NetCDF dan dibaca langsung dengan xarray. Ketika bekerja dengan file GRIB2 nyata dari NOMADS, kamu menggunakan cfgrib sebagai engine xarray — polanya identik, hanya argumen engine yang berbeda. GRIB2 bisa menyimpan beberapa jenis message dengan typeOfLevel yang berbeda, sehingga diperlukan filter_by_keys untuk memisahkan variabel per lapisan.
import xarray as xr
GFS_OUT = "gfs_synthetic_indonesia.nc"
# Buka dataset sintetis dengan xarray
# Untuk file GRIB2 nyata dari NOMADS, gunakan pola berikut:
# ds_sfc = xr.open_dataset(grib_file, engine="cfgrib",
# backend_kwargs={"filter_by_keys": {"typeOfLevel": "heightAboveGround"}})
# ds_msl = xr.open_dataset(grib_file, engine="cfgrib",
# backend_kwargs={"filter_by_keys": {"typeOfLevel": "meanSea"}})
# Dataset sintetis kita sudah menggabungkan semua variabel dalam satu file.
ds = xr.open_dataset(GFS_OUT)
# Pisahkan variabel permukaan dan PRMSL agar polanya paralel dengan GRIB2 nyata
ds_sfc = ds[["t2m", "u10", "v10"]] # lapisan heightAboveGround
ds_msl = ds[["prmsl"]] # lapisan meanSea
print("=== Dataset permukaan (heightAboveGround equivalent) ===")
print(f"Variabel : {list(ds_sfc.data_vars)}")
print(f"Dimensi : {dict(ds_sfc.dims)}")
print(f"Latitude : {float(ds.latitude.min()):.2f} .. {float(ds.latitude.max()):.2f} deg")
print(f"Longitude : {float(ds.longitude.min()):.2f} .. {float(ds.longitude.max()):.2f} deg")
# Suhu 2 m
t2m = ds["t2m"]
print(f"\nVariabel t2m (suhu 2 m):")
print(f" Shape : {t2m.shape}")
print(f" Units : {t2m.attrs.get('units', 'K')}")
print(f" Min/Max : {float(t2m.min()):.2f} / {float(t2m.max()):.2f} K")
print(f" Min/Max : {float(t2m.min()) - 273.15:.1f} / {float(t2m.max()) - 273.15:.1f} °C")
print(f"\n=== Dataset PRMSL (meanSea equivalent) ===")
print(f"Variabel : {list(ds_msl.data_vars)}")
print(f"PRMSL min/max: {float(ds['prmsl'].min())/100:.1f} / {float(ds['prmsl'].max())/100:.1f} hPa")
=== Dataset permukaan (heightAboveGround equivalent) ===
Variabel : ['t2m', 'u10', 'v10']
Dimensi : {'latitude': 69, 'longitude': 185}
Latitude : -11.00 .. 6.00 deg
Longitude : 95.00 .. 141.00 deg
Variabel t2m (suhu 2 m):
Shape : (69, 185)
Units : K
Min/Max : 296.24 / 304.66 K
Min/Max : 23.1 / 31.5 °C
=== Dataset PRMSL (meanSea equivalent) ===
Variabel : ['prmsl']
PRMSL min/max: 1013.3 / 1017.2 hPa
Output di atas memperlihatkan grid 0,25° (\(\approx 28\) km di ekuator) yang mencakup wilayah Indonesia — struktur yang sama persis dengan output GFS nyata. Suhu disimpan dalam satuan Kelvin; untuk perbandingan yang intuitif, kita konversi ke Celsius. Rentang nilai tropis yang realistis berada di kisaran 293–308 K (sekitar 20–35 °C di permukaan).
Membuat Observasi Sintetis untuk Stasiun
Untuk mendemonstrasikan workflow integrasi tanpa memerlukan akses ke MADIS atau jaringan observasi BMKG (yang memerlukan registrasi), kita buat observasi sintetis di sepuluh lokasi yang tersebar di seluruh Indonesia. Dalam praktik nyata, kamu bisa mengganti DataFrame ini dengan data METAR, AWS (Automatic Weather Station) BMKG, atau ekspor CSV dari MADIS — struktur kolom-nya identik.
Kita menggunakan seed random yang tetap sehingga hasilnya reproducible, dan menambahkan noise Gaussian \(\pm 2\) K di atas nilai model GFS interpolasi sebagai "observasi" — ini mensimulasikan variasi lokal yang tidak tertangkap oleh resolusi grid 0,25°.
import numpy as np
import pandas as pd
rng = np.random.default_rng(seed=42)
# Sepuluh stasiun sintetis di berbagai wilayah Indonesia
stations = pd.DataFrame({
"name": [
"Medan", "Pekanbaru", "Palembang", "Jakarta",
"Surabaya", "Denpasar", "Pontianak", "Makassar",
"Manado", "Jayapura",
],
"lat": [
3.59, 0.51, -2.97, -6.21,
-7.25, -8.67, -0.02, -5.14,
1.48, -2.54,
],
"lon": [
98.67, 101.45, 104.74, 106.85,
112.74, 115.17, 109.34, 119.41,
124.84, 140.72,
],
})
# Observasi sintetis: 25–34 °C (298–307 K), noise Gaussian kecil
stations["obs_temp_K"] = (
300.0 + rng.uniform(-5.0, 7.0, size=len(stations))
+ rng.normal(0.0, 1.5, size=len(stations))
)
stations["obs_temp_C"] = stations["obs_temp_K"] - 273.15
print("Observasi stasiun sintetis (seed=42):")
print(stations[["name", "lat", "lon", "obs_temp_K", "obs_temp_C"]].to_string(
index=False, float_format=lambda x: f"{x:.2f}"
))
print(f"\nRata-rata observasi : {stations['obs_temp_K'].mean():.2f} K ({stations['obs_temp_C'].mean():.1f} °C)")
print(f"Std observasi : {stations['obs_temp_K'].std():.2f} K")
Observasi stasiun sintetis (seed=42):
name lat lon obs_temp_K obs_temp_C
Medan 3.59 98.67 305.61 32.46
Pekanbaru 0.51 101.45 301.43 28.28
Palembang -2.97 104.74 305.40 32.25
Jakarta -6.21 106.85 305.06 31.91
Surabaya -7.25 112.74 296.83 23.68
Denpasar -8.67 115.17 305.42 32.27
Pontianak -0.02 109.34 304.69 31.54
Makassar -5.14 119.41 302.99 29.84
Manado 1.48 124.84 297.86 24.71
Jayapura -2.54 140.72 300.33 27.18
Rata-rata observasi : 302.56 K (29.4 °C)
Std observasi : 3.29 K
Sepuluh stasiun ini tersebar dari Medan (Sumatra utara) hingga Jayapura (Papua), mencakup rentang iklim yang cukup bervariasi. Dalam analisis nyata, ukuran sampel yang representatif untuk wilayah Indonesia adalah sekitar 150–200 stasiun aktif dari jaringan BMKG.
Interpolasi Grid Model ke Stasiun dan Analisis Bias
Kini kita punya dua dataset: grid GFS 0,25° dan observasi di lokasi stasiun. Karena koordinat stasiun hampir pasti tidak tepat jatuh di grid point GFS, kita perlu interpolasi. MetPy menyediakan beberapa metode melalui metpy.interpolate.interpolate_to_points, termasuk Barnes dan nearest-neighbor. Untuk kasus sederhana dengan hanya sepuluh titik, nearest-neighbor dari xarray sudah cukup akurat.
Setelah mendapatkan nilai model di setiap stasiun, bias dihitung sebagai:
$$\text{bias} = T_{\text{obs}} - T_{\text{model}}$$
Bias positif berarti model terlalu dingin — output GFS di lokasi tersebut lebih rendah dari yang sebenarnya diamati. Bias negatif berarti model terlalu hangat.
Alur integrasi data GFS dan observasi stasiun: dari grid model ke nilai per titik pengamatan, kemudian menghitung bias sistematis.
import xarray as xr
import numpy as np
import pandas as pd
GFS_OUT = "gfs_synthetic_indonesia.nc"
# Reload dataset GFS sintetis (shared globals dari snippet-2, buka ulang agar aman)
ds = xr.open_dataset(GFS_OUT)
t2m_grid = ds["t2m"] # shape: (latitude, longitude)
# Observasi dari snippet-3 (shared globals)
# stations sudah ada di namespace — jika tidak, rebuild cepat
if "stations" not in dir():
rng = np.random.default_rng(seed=42)
stations = pd.DataFrame({
"name": ["Medan","Pekanbaru","Palembang","Jakarta","Surabaya",
"Denpasar","Pontianak","Makassar","Manado","Jayapura"],
"lat": [ 3.59, 0.51, -2.97, -6.21, -7.25, -8.67, -0.02, -5.14, 1.48, -2.54],
"lon": [98.67,101.45,104.74,106.85,112.74,115.17,109.34,119.41,124.84,140.72],
})
stations["obs_temp_K"] = (
300.0 + rng.uniform(-5.0, 7.0, size=len(stations))
+ rng.normal(0.0, 1.5, size=len(stations))
)
# Interpolasi nearest-neighbor: untuk setiap stasiun, pilih grid point terdekat
model_temps = []
for _, row in stations.iterrows():
val = float(t2m_grid.sel(
latitude=row["lat"], longitude=row["lon"], method="nearest"
))
model_temps.append(val)
stations = stations.copy()
stations["model_temp_K"] = model_temps
stations["model_temp_C"] = stations["model_temp_K"] - 273.15
stations["bias_K"] = stations["obs_temp_K"] - stations["model_temp_K"]
# Tampilkan tabel lengkap
print("Tabel bias suhu 2 m (observasi − model GFS):")
print(f"{'Stasiun':<12} {'Obs (°C)':>9} {'Model (°C)':>10} {'Bias (K)':>9}")
print("-" * 45)
for _, r in stations.iterrows():
print(f"{r['name']:<12} {r['obs_temp_C']:>9.2f} {r['model_temp_C']:>10.2f} {r['bias_K']:>9.2f}")
print()
print(f"Mean bias : {stations['bias_K'].mean():+.3f} K")
print(f"Std bias : {stations['bias_K'].std():.3f} K")
print(f"Min bias : {stations['bias_K'].min():+.3f} K ({stations.loc[stations['bias_K'].idxmin(),'name']})")
print(f"Max bias : {stations['bias_K'].max():+.3f} K ({stations.loc[stations['bias_K'].idxmax(),'name']})")
Tabel bias suhu 2 m (observasi − model GFS):
Stasiun Obs (°C) Model (°C) Bias (K)
---------------------------------------------
Medan 32.46 27.66 4.80
Pekanbaru 28.28 28.78 -0.49
Palembang 32.25 27.88 4.38
Jakarta 31.91 28.06 3.85
Surabaya 23.68 28.10 -4.42
Denpasar 32.27 26.48 5.79
Pontianak 31.54 28.76 2.77
Makassar 29.84 25.26 4.58
Manado 24.71 27.60 -2.90
Jayapura 27.18 27.51 -0.33
Mean bias : +1.803 K
Std bias : 3.575 K
Min bias : -4.422 K (Surabaya)
Max bias : +5.790 K (Denpasar)
Tabel di atas menunjukkan perbedaan antara "observasi" sintetis dan nilai GFS di grid point terdekat untuk masing-masing stasiun. Dalam analisis real, mean bias yang mendekati nol dengan std yang kecil menandakan model berkinerja baik secara agregat. Bias besar di stasiun tertentu — misalnya Denpasar dengan bias +5,79 K — seringkali mencerminkan representasi topografi lokal yang tidak memadai dalam grid 0,25°, termasuk pengaruh tutupan lahan pesisir yang berbeda dari nilai grid.
Visualisasi dan Interpretasi Hasil
Statistik saja kurang intuitif. Dengan memplot bias per stasiun sebagai bar chart, kita bisa sekaligus membandingkan nilai observasi dan model, serta melihat arah dan besaran deviasi di setiap lokasi.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
# Rebuild stations dari globals jika perlu
if "stations" not in dir() or "bias_K" not in stations.columns:
# fallback: nilai hardcoded agar snippet mandiri
import pandas as pd, numpy as np
rng = np.random.default_rng(42)
names = ["Medan","Pekanbaru","Palembang","Jakarta","Surabaya",
"Denpasar","Pontianak","Makassar","Manado","Jayapura"]
obs = 300.0 + rng.uniform(-5.0,7.0,10) + rng.normal(0,1.5,10)
mdl = obs - (rng.normal(0,1.5,10))
stations = pd.DataFrame({
"name": names,
"obs_temp_C": obs - 273.15,
"model_temp_C": mdl - 273.15,
"bias_K": obs - mdl,
})
x = np.arange(len(stations))
width = 0.35
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8),
gridspec_kw={"height_ratios": [3, 2]})
fig.suptitle("Analisis Bias GFS vs Observasi Sintetis — Grid 0,25° Indonesia",
fontsize=13, fontweight="bold")
# Panel atas: suhu observasi vs model
bars1 = ax1.bar(x - width/2, stations["obs_temp_C"], width, label="Observasi", color="#2196F3", alpha=0.85)
bars2 = ax1.bar(x + width/2, stations["model_temp_C"], width, label="GFS Model", color="#FF7043", alpha=0.85)
ax1.set_xticks(x)
ax1.set_xticklabels(stations["name"], rotation=30, ha="right", fontsize=9)
ax1.set_ylabel("Suhu 2 m (°C)", fontsize=10)
ax1.set_title("Suhu Observasi vs Model GFS per Stasiun", fontsize=11)
ax1.legend(fontsize=9)
ax1.grid(axis="y", alpha=0.4)
ax1.set_ylim(
min(stations["obs_temp_C"].min(), stations["model_temp_C"].min()) - 2,
max(stations["obs_temp_C"].max(), stations["model_temp_C"].max()) + 2,
)
# Panel bawah: bias (obs - model)
colors = ["#43A047" if b >= 0 else "#E53935" for b in stations["bias_K"]]
ax2.bar(x, stations["bias_K"], color=colors, alpha=0.85, zorder=3)
ax2.axhline(0, color="black", linewidth=0.8)
ax2.axhline(stations["bias_K"].mean(), color="navy", linewidth=1.2,
linestyle="--", label=f"Mean bias = {stations['bias_K'].mean():+.2f} K")
ax2.set_xticks(x)
ax2.set_xticklabels(stations["name"], rotation=30, ha="right", fontsize=9)
ax2.set_ylabel("Bias = Obs − Model (K)", fontsize=10)
ax2.set_title("Bias Suhu per Stasiun (hijau = model terlalu dingin, merah = terlalu hangat)",
fontsize=10)
ax2.legend(fontsize=9)
ax2.grid(axis="y", alpha=0.4, zorder=0)
plt.tight_layout()
plt.savefig("bias_gfs_stations.png", dpi=150, bbox_inches="tight")
print("Plot tersimpan: bias_gfs_stations.png")
Plot dua panel di atas memperlihatkan perbandingan langsung: panel atas menampilkan suhu observasi (biru) dan model GFS (oranye) per stasiun dalam satuan Celsius, sementara panel bawah menampilkan bias dalam Kelvin. Bar hijau menandakan model terlalu dingin (output GFS underestimate), sedangkan bar merah menandakan model terlalu hangat (overestimate). Garis putus-putus biru menunjukkan mean bias — idealnya mendekati nol.
Beberapa interpretasi penting dari pola bias yang mungkin muncul:
- Bias positif konsisten (model terlalu dingin) di stasiun pesisir barat Sumatra bisa mencerminkan sea-breeze lokal yang tidak terresolusi oleh grid 0,25°.
- Bias negatif di lokasi berketinggian tinggi umumnya terjadi karena GFS merepresentasikan terrain yang lebih rendah dari ketinggian stasiun sesungguhnya — model "melihat" udara yang lebih hangat di lapisan yang lebih rendah.
- Std bias yang besar mengindikasikan bahwa error tidak bersifat sistematis global, melainkan bergantung pada karakteristik lokal tiap stasiun.
Langkah Berikutnya untuk Analisis Lanjut
Pipeline yang sudah kita bangun ini bersifat modular — setiap komponennya bisa diperluas secara independen.
Perluas ke analisis diurnal. GFS menyediakan output setiap jam untuk 120 jam pertama. Dengan mengunduh beberapa forecast hour dari siklus yang sama dan mengulang proses interpolasi, kita bisa melihat apakah bias bervariasi secara sistematis sesuai waktu lokal — misalnya, apakah GFS secara konsisten underestimate suhu siang hari di wilayah tertentu akibat konveksi tropis yang terjadi lebih awal dari yang dimodelkan.
Tambahkan level tekanan. Bias suhu permukaan hanyalah satu dimensi. Variabel yang sama di 850 hPa atau 500 hPa mencerminkan kualitas inisialisasi data assimilation pada lapisan yang berbeda. Gunakan filter_by_keys={"typeOfLevel": "isobaricInhPa", "level": 850} saat membuka GRIB2 untuk mengakses variabel pressure-level.
Gunakan observasi nyata. Gantikan DataFrame sintetis dengan data radiosonde atau METAR dari BMKG, atau akses MADIS (setelah registrasi di madis.noaa.gov) untuk mendapatkan ribuan observasi surface per jam. Struktur snippet-3 dan snippet-4 tidak berubah — cukup ganti sumber data.
Analisis ensemble. GFS Ensemble (GEFS, 30 anggota) memungkinkan perhitungan spread di setiap titik. Bias mean ensemble memberikan informasi tambahan tentang apakah ketidakakuratan bersifat deterministik atau probabilistik. Download GEFS melalui endpoint NOMADS yang serupa, dengan parameter ens sebagai dimensi tambahan.
Metode interpolasi lebih canggih. Untuk jaringan stasiun yang padat, coba metpy.interpolate.interpolate_to_grid dengan metode barnes — ini memberikan bobot inversely proportional dengan jarak menurut kernel Gaussian \(w = \exp(-r^2/\kappa)\), menghasilkan analisis yang lebih halus dibanding nearest-neighbor.
Eksplorasi artikel meteorologi lainnya di meteo.my.id — kunjungi meteo.my.id untuk topik lanjutan: analisis ensemble GFS, integrasi data Himawari-9, hingga penggunaan WRF untuk downscaling regional.
Referensi
- Global Forecast System (GFS) — NOAA/NCEI — Halaman produk resmi GFS, mencakup resolusi 0,25° (~28 km), siklus 4x sehari, dan jalur akses data termasuk NOMADS dan AWS.
- NOMADS — NOAA Operational Model Archive and Distribution System — Gateway utama untuk output model NCEP secara real-time; GRIB Filter memungkinkan subsetting berdasarkan variabel, level, dan bounding box langsung dari Python.
- NCEP GFS Data Products (NCO) — Dokumentasi naming convention file GRIB2 GFS, tiga tier resolusi (0,25°/0,5°/1,0°), dan file type pgrb2/pgrb2b.
- Point Interpolation — MetPy 1.7 (Unidata) — Tutorial MetPy untuk
interpolate_to_griddengan metode Barnes, Cressman, natural neighbor, dan linear dari data observasi scattered. - Global Forecast System — NOAA Environmental Modeling Center — Deskripsi teknis GFS: FV3 dynamical core, 127 lapisan atmosfer, sistem asimilasi data GDAS (4DEnVar), dan sejarah upgrade model.