Mengapa Memvisualisasikan Cuaca di Atas Peta
Angka mentah dari sebuah dataset cuaca jarang bicara langsung. Suhu 27,3 °C di Jakarta saja tidak banyak artinya tanpa konteks — apakah itu tinggi atau rendah dibanding Makassar, Medan, atau Jayapura? Pola spasial baru terlihat ketika kita meletakkan setiap nilai pada koordinat geografisnya, dan peta cuaca adalah cara paling natural untuk menjawab "di mana panas, di mana dingin".
Tutorial ini memandu workflow lengkap dari ujung ke ujung: ekstrak nilai suhu permukaan untuk enam kota besar Indonesia dari file ERA5 menggunakan xarray, susun sebagai DataFrame pandas, lalu visualisasikan sebagai scatter point di atas peta Indonesia dengan Cartopy. Fokusnya adalah workflow titik (point data) — pelengkap dari tutorial Cartopy gridded yang membahas plotting array dua dimensi penuh.
Pendekatan titik ini berguna ketika sumber datanya memang berupa pengamatan stasiun (data BMKG, METAR bandara, pelampung laut), atau ketika kita ingin overlay informasi lokasi spesifik di atas peta dasar.
Mempersiapkan Data Titik dari ERA5
Tutorial ini memerlukan file ERA5 berikut sudah ada di working directory: era5_t2m_indonesia_2024_6h.nc. Belum punya? Ikuti panduan Memulai cdsapi untuk Mengunduh ERA5 dari Nol untuk setup CDS + cdsapi + permintaan pertama.
Snippet pertama mendefinisikan koordinat enam kota — Medan, Jakarta, Surabaya, Denpasar, Makassar, Jayapura — dan mengekstrak rata-rata suhu permukaan tahun 2024 dari file ERA5 di titik-titik tersebut. xarray membuat ini ringkas: .sel(latitude=..., longitude=..., method="nearest") mencari grid-point terdekat dari koordinat yang kita minta.
import xarray as xr
import pandas as pd
import numpy as np
ds = xr.open_dataset("era5_t2m_indonesia_2024_6h.nc")
t2m = ds["t2m"] # K, dims (valid_time, latitude, longitude)
cities = pd.DataFrame({
"kota": ["Medan", "Jakarta", "Surabaya", "Denpasar", "Makassar", "Jayapura"],
"latitude": [ 3.60, -6.20, -7.30, -8.70, -5.10, -2.50],
"longitude": [98.70, 106.80, 112.70, 115.20, 119.40, 140.70],
})
records = []
for _, row in cities.iterrows():
point = t2m.sel(latitude=row["latitude"], longitude=row["longitude"], method="nearest")
mean_k = float(point.mean())
min_k = float(point.min())
max_k = float(point.max())
records.append({
"kota": row["kota"],
"lat": row["latitude"],
"lon": row["longitude"],
"mean_C": mean_k - 273.15,
"min_C": min_k - 273.15,
"max_C": max_k - 273.15,
"range_C": max_k - min_k,
})
df = pd.DataFrame(records)
print(df.to_string(index=False, formatters={
"lat": lambda x: f"{x:6.2f}",
"lon": lambda x: f"{x:7.2f}",
"mean_C": lambda x: f"{x:6.2f}",
"min_C": lambda x: f"{x:6.2f}",
"max_C": lambda x: f"{x:6.2f}",
"range_C": lambda x: f"{x:6.2f}",
}))
print(f"\nRata-rata seluruh kota: {df['mean_C'].mean():.2f} °C")
print(f"Range terlebar : {df.loc[df['range_C'].idxmax(), 'kota']} ({df['range_C'].max():.2f} °C)")
print(f"Range tersempit : {df.loc[df['range_C'].idxmin(), 'kota']} ({df['range_C'].min():.2f} °C)")
kota lat lon mean_C min_C max_C range_C
Medan 3.60 98.70 27.06 21.41 35.16 13.74
Jakarta -6.20 106.80 27.67 21.57 34.66 13.09
Surabaya -7.30 112.70 29.02 23.44 35.31 11.88
Denpasar -8.70 115.20 27.04 24.23 30.36 6.13
Makassar -5.10 119.40 27.59 24.77 31.04 6.27
Jayapura -2.50 140.70 27.51 24.50 30.71 6.21
Rata-rata seluruh kota: 27.65 °C
Range terlebar : Medan (13.74 °C)
Range tersempit : Denpasar (6.13 °C)
Hasilnya enam baris DataFrame berisi rata-rata, minimum, maksimum, dan rentang suhu setiap kota — ini "data mentah" yang akan kita petakan. Beberapa pola sudah terlihat tanpa peta: rentang suhu Medan dan Surabaya sekitar dua kali lebih lebar dibanding Denpasar, Makassar, atau Jayapura, mencerminkan posisi mereka di pulau besar (Sumatra, Jawa) yang punya massa daratan lebih luas sehingga efek kontinentalnya terasa. Sementara itu, Bali, Sulawesi Selatan, dan Papua utara dikelilingi laut hangat yang meredam fluktuasi harian — rentang sempit 6 °C khas zona maritim ekuatorial.
Surabaya muncul sebagai outlier dengan rata-rata 29 °C — sekitar 1,4 °C lebih hangat dibanding lima kota lainnya. Penyebabnya kombinasi dua faktor: lokasi pesisir utara Jawa Timur yang mengalami foehn dari pegunungan, dan urban heat dari kawasan industri Surabaya–Gresik yang sebagian ikut tertangkap oleh grid ERA5 0,25° meskipun resolusinya kasar.
Memahami Cartopy: CRS, GeoAxes, dan Fitur Peta
Cartopy adalah library Python untuk pemetaan geospasial yang menjadi standar di komunitas atmospheric science. Inti API-nya tiga konsep yang penting dipahami sebelum melompat ke kode:
Pipeline standar Cartopy untuk data titik: setiap point memiliki CRS sumber (biasanya PlateCarree untuk lat/lon biasa), diplot ke GeoAxes dengan projection target, lalu dihias dengan fitur geografis bawaan.
CRS (Coordinate Reference System) mendefinisikan bagaimana koordinat dua dimensi dipetakan ke permukaan bumi. Data lat/lon biasa hidup dalam ccrs.PlateCarree() — proyeksi paling sederhana yang menjadikan lintang dan bujur sebagai sumbu x-y. Saat kita memplot, Cartopy bisa mentransformasi point dari CRS sumber ke projection peta target secara otomatis lewat argumen transform=.
GeoAxes adalah subclass matplotlib Axes yang sadar CRS. Setiap subplot Cartopy adalah GeoAxes dengan projection tetap (PlateCarree, Mercator, LambertConformal, Robinson, dll). Mendekati Indonesia, PlateCarree adalah pilihan paling lugas — sumbu langsung lat/lon, tidak ada distorsi yang membingungkan.
Fitur peta bawaan (cfeature.COASTLINE, cfeature.BORDERS, cfeature.OCEAN, dll) berasal dari dataset Natural Earth yang sudah dibundel Cartopy. Tambahkan satu per satu sesuai kebutuhan; default sudah cukup untuk peta cuaca regional.
Memetakan Titik Suhu di Atas Peta Indonesia
Sekarang kita gabungkan DataFrame dari snippet sebelumnya dengan Cartopy untuk menghasilkan peta titik berwarna. Snippet di bawah membuat figure 12×7 inch, set extent ke bounding box Indonesia, menambahkan fitur peta dasar dari Natural Earth, dan memplot setiap kota sebagai scatter point berwarna sesuai rata-rata suhu tahunannya.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# Gunakan DataFrame `df` dari snippet sebelumnya (kolom: kota, lat, lon, mean_C)
fig = plt.figure(figsize=(12, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
# Bbox Indonesia: barat-timur 94-142°E, utara-selatan -11 sampai 7°N
ax.set_extent([94, 142, -11, 7], crs=ccrs.PlateCarree())
# Fitur dasar peta
ax.add_feature(cfeature.OCEAN.with_scale("50m"), facecolor="#e0eef7")
ax.add_feature(cfeature.LAND.with_scale("50m"), facecolor="#f5efe1")
ax.add_feature(cfeature.COASTLINE.with_scale("50m"), linewidth=0.6, edgecolor="#555")
ax.add_feature(cfeature.BORDERS.with_scale("50m"), linewidth=0.4, edgecolor="#777", linestyle=":")
# Gridline dengan label lat/lon
gl = ax.gridlines(draw_labels=True, linewidth=0.3, color="gray", alpha=0.5)
gl.top_labels = False
gl.right_labels = False
# Plot setiap kota sebagai scatter point berwarna sesuai mean_C
scatter = ax.scatter(
df["lon"], df["lat"],
c=df["mean_C"], cmap="RdYlBu_r",
s=180, edgecolor="black", linewidth=0.8,
transform=ccrs.PlateCarree(),
zorder=5,
)
# Label nama kota di sebelah point
for _, row in df.iterrows():
ax.text(row["lon"] + 0.5, row["lat"] + 0.3, row["kota"],
fontsize=10, weight="bold",
transform=ccrs.PlateCarree())
# Colorbar
cbar = plt.colorbar(scatter, ax=ax, orientation="horizontal",
pad=0.07, shrink=0.6, label="Rata-rata suhu 2 m, 2024 (°C)")
ax.set_title("Suhu Rata-rata 2 m di Enam Kota Besar Indonesia (ERA5, 2024)",
fontsize=13, weight="bold", pad=12)
plt.savefig("peta_suhu_indonesia.png", dpi=150, bbox_inches="tight")
Beberapa pilihan desain di atas yang sengaja dibuat eksplisit:
set_extentmembatasi peta pada bounding box Indonesia, agar kosmonya tidak melebar ke samudra Pasifik tengah atau benua Australia secara berlebihan.with_scale("50m")memilih versi medium-resolution dari dataset Natural Earth — cukup detail untuk skala regional tanpa membebani render. Untuk peta global pakai"110m"; untuk peta kota detail pakai"10m".zorder=5memaksa scatter point ke layer paling depan, di atas fitur peta dasar yang punya zorder lebih kecil. Tanpa ini, point bisa tertimpa coastline.transform=ccrs.PlateCarree()padascatter()adalah wajib — memberi tahu Cartopy bahwa datadf["lon"]dandf["lat"]ada di CRS PlateCarree, sehingga Cartopy bisa mereproyeksikannya ke projection axis (yang kebetulan juga PlateCarree di sini, tapi tetap eksplisit untuk safety).- Colormap
RdYlBu_r(Red-Yellow-Blue reversed) cocok untuk suhu — biru = dingin, merah = panas. Untuk variabel lain pertimbangkanviridis(sequential),RdBu_r(diverging dari nol), atauGreys(untuk dataset yang hanya butuh kontras).
Memetakan Field Suhu Penuh sebagai Gradien
Enam titik kota memberi konteks lokasi, tapi pola spasial sebenarnya jauh lebih kaya dari itu — ERA5 punya nilai suhu di setiap 0,25° lintang/bujur (sekitar 28 km), jadi domain Indonesia tertutup oleh sekitar 13.000 grid-point. Ketika kita memplot semua grid-point sebagai gradien warna kontinu, struktur monsun, kontras laut-darat, dan pegunungan yang lebih dingin langsung muncul.
Cartopy bisa memplot array dua dimensi langsung dengan pcolormesh di GeoAxes. Snippet di bawah menghitung rata-rata tahunan, lalu menggambarnya sebagai gradien di atas peta dengan titik enam kota tetap dipertahankan sebagai overlay agar pembaca punya jangkar lokasi.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# Rata-rata tahunan t2m di seluruh grid Indonesia
t2m_mean = (t2m.mean(dim="valid_time") - 273.15) # K → °C
fig = plt.figure(figsize=(13, 7))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([94, 142, -11, 7], crs=ccrs.PlateCarree())
# Gradien suhu sebagai field penuh
mesh = ax.pcolormesh(
t2m_mean.longitude, t2m_mean.latitude, t2m_mean.values,
cmap="RdYlBu_r", vmin=18, vmax=30,
shading="auto",
transform=ccrs.PlateCarree(),
)
# Garis pantai + perbatasan di atas gradien
ax.add_feature(cfeature.COASTLINE.with_scale("50m"), linewidth=0.6, edgecolor="#333")
ax.add_feature(cfeature.BORDERS.with_scale("50m"), linewidth=0.4, edgecolor="#555", linestyle=":")
# Titik enam kota sebagai overlay
ax.scatter(df["lon"], df["lat"],
c="white", s=70, edgecolor="black", linewidth=1.2,
transform=ccrs.PlateCarree(), zorder=5)
for _, row in df.iterrows():
ax.text(row["lon"] + 0.6, row["lat"] + 0.4, row["kota"],
fontsize=9.5, weight="bold", color="#111",
transform=ccrs.PlateCarree())
# Gridline dengan label
gl = ax.gridlines(draw_labels=True, linewidth=0.3, color="gray", alpha=0.5)
gl.top_labels = False
gl.right_labels = False
cbar = plt.colorbar(mesh, ax=ax, orientation="horizontal",
pad=0.07, shrink=0.7, label="Rata-rata suhu 2 m, 2024 (°C)")
ax.set_title("Gradien Suhu Permukaan 2 m di Indonesia (ERA5, rata-rata 2024)",
fontsize=13, weight="bold", pad=12)
plt.savefig("peta_gradien_suhu.png", dpi=150, bbox_inches="tight")
Pola yang muncul di gradien jauh lebih informatif dibanding enam titik saja. Pegunungan Bukit Barisan di Sumatra, Pegunungan Jayawijaya di Papua, dan dataran tinggi tengah Sulawesi muncul sebagai pita biru-sejuk — ketinggian topografi menurunkan suhu sekitar 6,5 °C per kilometer (lapse rate moist-adiabatic), dan ERA5 menangkap pola ini meskipun resolusi grid-nya 28 km. Sementara itu, kawasan pesisir utara Jawa dan Laut Jawa muncul sebagai zona merah-hangat — kombinasi laut hangat dan urban heat yang konsisten.
Perbedaan terpenting dari peta titik: gradien menjawab pertanyaan di mana saja yang panas, bukan hanya seberapa panas di enam lokasi. Untuk monitoring iklim regional, peta gradien adalah workhorse-nya; titik berguna untuk overlay informasi spesifik lokasi (stasiun BMKG, bandara, pelabuhan).
Mengembangkan ke Variabel dan Sumber Data Lain
Pola yang sama langsung berlaku untuk variabel cuaca lain — ganti t2m di snippet pertama dengan, misalnya, tp untuk total precipitation atau msl untuk mean sea level pressure. Cartopy bagian visualisasinya tidak berubah; yang berubah hanya cmap dan label colorbar.
Untuk data observasi nyata, BMKG mempublikasikan ringkasan klimatologi stasiun cuaca dalam format CSV yang bisa langsung dibaca pandas. Tinggal ganti DataFrame pada snippet pertama dengan pd.read_csv(...) dan pastikan kolomnya berisi latitude, longitude, dan variabel yang ingin diplot. Workflow di snippet kedua akan jalan tanpa modifikasi besar.
Cartopy juga punya integrasi langsung dengan xarray untuk plot gridded — tutorial Menggunakan Cartopy untuk Pemetaan Cuaca membahas pendekatan itu, termasuk reprojeksi array dua dimensi penuh ke berbagai projection (Mercator, LambertConformal). Kombinasi titik dan gridded dalam satu peta — misalnya kontur suhu ERA5 sebagai background dengan point stasiun BMKG sebagai overlay — adalah langkah natural berikutnya setelah menguasai keduanya.
Referensi
- Cartopy documentation — SciTools — Dokumentasi resmi Cartopy yang mencakup CRS, GeoAxes, projection list, dan contoh integrasi dengan matplotlib.
- ERA5 data documentation — Copernicus Knowledge Base — Dokumentasi teknis ERA5 termasuk daftar variabel single-level (t2m, msl, tp, u10, v10) yang bisa dipetakan.
- Hersbach et al. (2020): The ERA5 global reanalysis — Paper referensi sistem reanalisis ERA5 di Quarterly Journal of the Royal Meteorological Society.
- Natural Earth — Free vector and raster map data — Sumber dataset coastline, border, dan ocean yang dipakai Cartopy lewat
cfeature.*. - Project Pythia: Cartopy — Tutorial Cartopy yang dimaintain komunitas atmospheric science, mencakup pola-pola lanjutan untuk visualisasi data cuaca dan iklim.
Eksplorasi artikel meteorologi lainnya di meteo.my.id — temukan tutorial xarray, ERA5, dan teknik visualisasi atmosfer di https://meteo.my.id.
Tidak ada komentar:
Posting Komentar