TANGERANG SELATAN WEATHER

Sabtu, 13 Juni 2026

Membaca METAR dan Dekoding Observasi Bandara

Gradient background for an article on decoding METAR aviation weather reports

Mengapa Pilot Membaca METAR

Setiap pilot yang akan takeoff atau landing membutuhkan satu pertanyaan terjawab: seperti apa kondisi cuaca di bandara tujuan sekarang? Jawaban standarnya ada di satu baris teks yang tampak acak — METAR.

METAR (Meteorological Aerodrome Report) adalah format laporan cuaca observasi rutin di bandara yang ditetapkan WMO sebagai standar FM 15. Spesifikasi lengkapnya ada di WMO-No. 306 Manual on Codes, Volume I.1, yang mendefinisikan setiap field group dan code table yang digunakan. Laporan ini diterbitkan setiap jam (H+00) dan kadang setengah jam (H+30) sebagai SPECI saat terjadi perubahan kondisi signifikan. Isinya mencakup angin, visibility, weather phenomena, sky condition, temperature/dewpoint, dan QNH — semua dalam satu baris teks ringkas yang bisa dibaca mesin maupun manusia.

Di Indonesia, BMKG (Badan Meteorologi, Klimatologi, dan Geofisika) adalah otoritas meteorologi yang ditunjuk ICAO untuk menerbitkan METAR, SPECI, dan TAF di seluruh bandara sipil Indonesia. Bandara Soekarno-Hatta (WIII), Ngurah Rai Denpasar (WADD), Juanda Surabaya (WARR), dan Sultan Hasanuddin Makassar (WAAA) semuanya terpantau di portal BMKG Aviation.

Artikel ini mengajak kita mendekode METAR secara bertahap — mulai dari parsing manual field per field, memanfaatkan library metar di Python, hingga fetch data live dari API publik NOAA Aviation Weather Center.

Anatomi Sebuah Laporan METAR

Ambil contoh nyata dari BMKG untuk Soekarno-Hatta:

METAR WIII 100200Z 27009KT 6000 -RA BKN020 25/23 Q1010 NOSIG=

Setiap token di sini punya makna spesifik. Diagram berikut menunjukkan urutan field group dalam sebuah METAR:

Diagram diagram-1

Urutan field group dalam laporan METAR internasional (WMO FM 15). Field bersifat wajib kecuali weather phenomena yang hanya muncul jika ada.

Snippet berikut memecah string METAR tersebut secara manual, mencetak setiap field beserta labelnya:

raw = "METAR WIII 100200Z 27009KT 6000 -RA BKN020 25/23 Q1010 NOSIG="

tokens = raw.rstrip("=").split()

labels = {
    0: "Report type",
    1: "Station ID (ICAO)",
    2: "Date/Time UTC (DDHHmmZ)",
    3: "Wind (direction°true + speed + unit)",
    4: "Visibility (metres)",
    5: "Present weather (intensity + phenomenon)",
    6: "Sky condition (coverage + height × 100 ft AGL)",
    7: "Temperature / Dewpoint (°C)",
    8: "Altimeter QNH (Q + hPa)",
    9: "Trend / NOSIG",
}

print(f"{'Token':<12}  {'Field'}")
print("-" * 55)
for i, token in enumerate(tokens):
    label = labels.get(i, f"Field {i}")
    print(f"{token:<12}  {label}")
Token         Field
-------------------------------------------------------
METAR         Report type
WIII          Station ID (ICAO)
100200Z       Date/Time UTC (DDHHmmZ)
27009KT       Wind (direction°true + speed + unit)
6000          Visibility (metres)
-RA           Present weather (intensity + phenomenon)
BKN020        Sky condition (coverage + height × 100 ft AGL)
25/23         Temperature / Dewpoint (°C)
Q1010         Altimeter QNH (Q + hPa)
NOSIG         Trend / NOSIG

Dari output di atas, setiap token menjadi jelas:

  • WIII — kode ICAO empat huruf Soekarno-Hatta; seluruh bandara Indonesia diawali huruf W.
  • 100200Z — hari ke-10 pukul 02:00 UTC.
  • 27009KT — angin dari arah 270° (barat) berkecepatan 9 knot.
  • 6000 — visibility 6.000 meter; format internasional memakai meter, bukan statute miles seperti METAR Amerika.
  • Q1010 — QNH 1.010 hPa (tekanan permukaan laut ter-reduksi).
  • NOSIG — tidak ada perubahan signifikan yang diprakirakan dalam dua jam ke depan.

Mengurai METAR dengan python-metar

Parsing manual cocok untuk memahami struktur, tetapi untuk workflow nyata kita butuh library yang mengekstrak setiap field ke atribut terstruktur. Library metar (pip install metar) melakukan persis itu — ia mengurai string METAR sesuai spesifikasi WMO dan US, lalu mengekspos field sebagai atribut bertipe (temperature, wind, visibility, pressure, sky conditions).

Cara pakainya: import kelas Metar dari modul metar.Metar, lalu instansiasi dengan raw string-nya. Parameter strict=False membuat parser tidak melempar exception untuk grup yang tidak dikenali — lebih robust untuk string dari sumber berbeda.

from metar import Metar

raw = "METAR WIII 100200Z 27009KT 6000 -RA BKN020 25/23 Q1010 NOSIG="

obs = Metar.Metar(raw, strict=False)

print(f"Station ID    : {obs.station_id}")
print(f"Time (UTC)    : {obs.time}")
print(f"Wind dir      : {obs.wind_dir.value():.0f} degrees")
print(f"Wind speed    : {obs.wind_speed.value('KT'):.0f} knots")
print(f"Visibility    : {obs.visibility()}")
print(f"Temperature   : {obs.temp.value('C'):.1f} °C")
print(f"Dewpoint      : {obs.dewpt.value('C'):.1f} °C")
print(f"QNH           : {obs.press.value('HPA'):.0f} hPa")
print(f"Sky conditions: {obs.sky_conditions()}")
print(f"Weather       : {obs.weather}")
Station ID    : WIII
Time (UTC)    : 2026-06-10 02:00:00
Wind dir      : 270 degrees
Wind speed    : 9 knots
Visibility    : 6000 meters
Temperature   : 25.0 °C
Dewpoint      : 23.0 °C
QNH           : 1010 hPa
Sky conditions: broken clouds at 2000 feet
Weather       : [('-', None, 'RA', None, None)]

Output di atas memperlihatkan bahwa library mengembalikan setiap field sebagai nilai yang sudah dikonversi — temperature dan dewpoint dalam °C, visibility dalam unit yang ditentukan string (meter untuk format internasional), dan sky_conditions sebagai list tuple berisi coverage code, ketinggian awan, dan cloud type. Ini jauh lebih mudah diproses dibandingkan parsing string manual.

Mendekode Weather Phenomena dan Sky Condition

Dua field yang paling kaya informasi dalam METAR adalah present weather dan sky condition. Keduanya menggunakan kode singkat yang perlu dipahami.

Present weather dikodekan sebagai kombinasi awalan intensitas + descriptor (opsional) + fenomena:

  • Intensitas: - (ringan), tidak ada awalan (sedang), + (berat)
  • Descriptor: TS (thunderstorm), SH (shower), FZ (freezing), BL (blowing), DR (drifting), VC (vicinity)
  • Fenomena: RA (rain), SN (snow), DZ (drizzle), GR (hail besar), GS (hail kecil), FG (fog), BR (mist), HZ (haze)

Sky condition melaporkan tutupan awan dalam okta (1/8 bagian langit):

  • SKC / CLR — clear (0/8)
  • FEW — 1–2/8
  • SCT — 3–4/8
  • BKN — 5–7/8 (broken)
  • OVC — 8/8 (overcast)

Angka tiga digit setelah kode coverage adalah ketinggian cloud base dalam ratusan kaki di atas permukaan bandara (AGL). Jadi BKN020 berarti broken clouds di 2.000 ft AGL. Qualifier CB (cumulonimbus) atau TCU (towering cumulus) ditambahkan setelah ketinggian bila terdeteksi — misalnya BKN040CB.

Kondisi CAVOK (Ceiling And Visibility OK) menggantikan field visibility, weather, dan sky condition sekaligus bila ketiga syarat terpenuhi: visibility ≥ 10 km, tidak ada awan di bawah 5.000 ft, tidak ada CB atau TCU, dan tidak ada significant weather.

Snippet berikut mendekode empat METAR dengan kombinasi cuaca berbeda:

from metar import Metar

samples = [
    ("WIII hujan ringan + broken", "METAR WIII 100200Z 27009KT 6000 -RA BKN020 25/23 Q1010 NOSIG="),
    ("WADD kabut + visibility rendah", "METAR WADD 100000Z 36003KT 0800 FG VV002 24/24 Q1012 NOSIG="),
    ("WARR badai petir + CB",        "METAR WARR 100600Z 18015G25KT 3000 +TSRA BKN015CB 27/25 Q1008 NOSIG="),
    ("WAAA cerah CAVOK",             "METAR WAAA 100300Z 09008KT CAVOK 28/22 Q1013 NOSIG="),
]

for label, raw in samples:
    obs = Metar.Metar(raw, strict=False)
    print(f"\n=== {label} ===")
    print(f"  Raw         : {raw}")
    print(f"  Weather     : {obs.weather}")
    print(f"  Sky cond    : {obs.sky_conditions()}")
    print(f"  Visibility  : {obs.visibility()}")
    print(f"  Temp/Dewpt  : {obs.temp.value('C'):.1f}°C / {obs.dewpt.value('C'):.1f}°C")

=== WIII hujan ringan + broken ===
  Raw         : METAR WIII 100200Z 27009KT 6000 -RA BKN020 25/23 Q1010 NOSIG=
  Weather     : [('-', None, 'RA', None, None)]
  Sky cond    : broken clouds at 2000 feet
  Visibility  : 6000 meters
  Temp/Dewpt  : 25.0°C / 23.0°C

=== WADD kabut + visibility rendah ===
  Raw         : METAR WADD 100000Z 36003KT 0800 FG VV002 24/24 Q1012 NOSIG=
  Weather     : [('', None, '', 'FG', None)]
  Sky cond    : indefinite ceiling, vertical visibility to 200 feet
  Visibility  : 800 meters
  Temp/Dewpt  : 24.0°C / 24.0°C

=== WARR badai petir + CB ===
  Raw         : METAR WARR 100600Z 18015G25KT 3000 +TSRA BKN015CB 27/25 Q1008 NOSIG=
  Weather     : [('+', 'TS', 'RA', None, None)]
  Sky cond    : broken cumulonimbus at 1500 feet
  Visibility  : 3000 meters
  Temp/Dewpt  : 27.0°C / 25.0°C

=== WAAA cerah CAVOK ===
  Raw         : METAR WAAA 100300Z 09008KT CAVOK 28/22 Q1013 NOSIG=
  Weather     : []
  Sky cond    : 
  Visibility  : 10000 meters
  Temp/Dewpt  : 28.0°C / 22.0°C

Perhatikan perbedaan output antara kasus kabut (visibility 800 m, VV002 = vertical visibility 200 ft) dan kasus CAVOK (langit bersih, visibility optimal). Kode +TSRA pada WARR menunjukkan hujan lebat disertai thunderstorm — sinyal flight hazard yang perlu diwaspadai dispatcher dan pilot.

Mengambil METAR Live dari Bandara Indonesia

Selain parsing string statis, kita bisa fetch METAR real-time dari NOAA Aviation Weather Center menggunakan endpoint publik di aviationweather.gov. API ini tidak memerlukan autentikasi — cukup kirim GET request dengan parameter ids (kode ICAO) dan format.

Snippet berikut fetch METAR untuk empat bandara utama Indonesia, parse hasilnya dengan python-metar, lalu buat bar chart matplotlib yang menunjukkan nilai okta sky condition masing-masing stasiun. Kalau koneksi ke API gagal (jaringan diblokir atau respons kosong), snippet otomatis fallback ke data hardcoded representatif:

import os
import requests
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from metar import Metar

STATIONS = ["WIII", "WADD", "WARR", "WAAA"]
URL = "https://aviationweather.gov/api/data/metar"

# --- Fallback hardcoded METAR strings ---
FALLBACK = {
    "WIII": "METAR WIII 100200Z 27009KT 6000 -RA BKN020 25/23 Q1010 NOSIG=",
    "WADD": "METAR WADD 100200Z 36005KT 9000 SCT018 28/24 Q1012 NOSIG=",
    "WARR": "METAR WARR 100200Z 18010KT 8000 FEW015 SCT025 29/25 Q1009 NOSIG=",
    "WAAA": "METAR WAAA 100200Z 09008KT CAVOK 30/22 Q1013 NOSIG=",
}

raw_strings = {}

try:
    resp = requests.get(
        URL,
        params={"ids": ",".join(STATIONS), "format": "raw", "hours": 1},
        timeout=15,
    )
    if resp.status_code == 200 and resp.text.strip():
        for line in resp.text.strip().splitlines():
            line = line.strip()
            if not line:
                continue
            for sid in STATIONS:
                if sid in line:
                    raw_strings[sid] = line
                    break
        print(f"[INFO] Fetched {len(raw_strings)} METAR dari aviationweather.gov")
    else:
        print(f"[WARN] API status {resp.status_code} — menggunakan fallback data")
except Exception as e:
    print(f"[WARN] Fetch gagal ({e}) — menggunakan fallback data")

# Fill any missing stations from fallback
for sid in STATIONS:
    if sid not in raw_strings:
        raw_strings[sid] = FALLBACK[sid]

# --- Parse & extract cloud cover okta ---
OKTA_MAP = {"SKC": 0, "CLR": 0, "NSC": 0, "NCD": 0,
            "FEW": 2, "SCT": 4, "BKN": 6, "OVC": 8, "VV": 8}

results = {}
for sid in STATIONS:
    raw = raw_strings.get(sid, FALLBACK[sid])
    try:
        obs = Metar.Metar(raw, strict=False)
        if obs.sky:
            # obs.sky is a list of tuples: (coverage_code, height_obj, cloud_type)
            max_okta = max(OKTA_MAP.get(layer[0], 0) for layer in obs.sky)
        else:
            max_okta = 0  # CAVOK or no sky info
        results[sid] = max_okta
        sky_codes = [layer[0] for layer in obs.sky] if obs.sky else ["CAVOK/clear"]
        print(f"{sid}: sky layers={sky_codes}  => max okta={max_okta}")
    except Exception as ex:
        print(f"{sid}: parse error ({ex}), defaulting to 0")
        results[sid] = 0

# --- Bar chart ---
stations = list(results.keys())
oktas = [results[s] for s in stations]

colors = []
for o in oktas:
    if o <= 2:
        colors.append("#4CAF50")    # green = clear / FEW
    elif o <= 4:
        colors.append("#FFC107")    # amber = scattered
    elif o <= 6:
        colors.append("#FF9800")    # orange = broken
    else:
        colors.append("#F44336")    # red = overcast / obscuration

fig, ax = plt.subplots(figsize=(11, 5))
bars = ax.bar(stations, oktas, color=colors, edgecolor="white", linewidth=0.8)

ax.set_ylim(0, 9)
ax.set_yticks(range(0, 9))
ax.set_yticklabels(["0\n(SKC)", "1", "2\n(FEW)", "3", "4\n(SCT)", "5", "6\n(BKN)", "7", "8\n(OVC)"])
ax.set_xlabel("Kode ICAO Bandara", fontsize=12)
ax.set_ylabel("Sky Cover (okta, 1/8 langit)", fontsize=12)
ax.set_title("Sky Condition (Okta) di Bandara Utama Indonesia\nBerdasarkan METAR terkini", fontsize=13, fontweight="bold")

for bar, val in zip(bars, oktas):
    ax.text(bar.get_x() + bar.get_width() / 2, val + 0.15, f"{val:.1f}", ha="center", va="bottom", fontsize=11)

legend_patches = [
    plt.Rectangle((0, 0), 1, 1, fc="#4CAF50", label="Clear / FEW (0–2 okta)"),
    plt.Rectangle((0, 0), 1, 1, fc="#FFC107", label="Scattered / SCT (3–4 okta)"),
    plt.Rectangle((0, 0), 1, 1, fc="#FF9800", label="Broken / BKN (5–6 okta)"),
    plt.Rectangle((0, 0), 1, 1, fc="#F44336", label="Overcast / Obscuration (7–8 okta)"),
]
ax.legend(handles=legend_patches, loc="upper right", fontsize=9)

plt.tight_layout()
plt.savefig("metar_clouds.png", dpi=150)
plt.close()
print("[OK] Figure tersimpan sebagai metar_clouds.png")
print(f"\nRingkasan sky cover: {', '.join(f'{s}={results[s]:.1f}' for s in stations)} okta")

snippet-4

Bar chart memperlihatkan nilai okta sky cover untuk setiap stasiun, diwarnai sesuai kategori: hijau (0–2 okta, clear/FEW), kuning-amber (3–4 okta, SCT), oranye (5–6 okta, BKN), dan merah (7–8 okta, OVC atau obscuration seperti VV). Stasiun dengan CAVOK tidak melaporkan sky condition eksplisit sehingga defaultnya 0 okta. Threshold BKN dan OVC menjadi pertimbangan utama dispatcher saat menentukan apakah ceiling memenuhi minima pendaratan suatu prosedur instrument approach.

Langkah Selanjutnya

Kita sudah menempuh jalur lengkap: dari membaca METAR string secara manual field per field, memahami kode weather phenomena dan sky condition, memakai python-metar untuk parsing terstruktur, hingga fetch data live dari aviationweather.gov.

Workflow ini bisa diperluas ke beberapa arah. Pertama, bandingkan METAR dengan TAF (Terminal Aerodrome Forecast) untuk melihat seberapa akurat prakiraan terhadap observasi aktual — keduanya bisa diambil dari API yang sama. Kedua, bangun monitoring sederhana yang alert jika visibility atau ceiling di suatu bandara turun di bawah threshold tertentu. Ketiga, integrasikan parsing METAR ke pipeline data yang lebih besar bersama data NWP untuk analisis performa model di area terminal.

Untuk data METAR real-time bandara Indonesia, portal BMKG Aviation di web-aviation.bmkg.go.id menyediakan METAR, SPECI, dan SIGMET untuk lebih dari 100 bandara. Data ini juga bisa diakses lewat NOAA AWC API dengan kode ICAO yang dimulai huruf W.

Eksplorasi artikel meteorologi lainnya di meteo.my.id (kunjungi situs), mulai dari analisis data ERA5, teknik NWP, hingga topik meteorologi sinoptik dan maritim yang relevan untuk wilayah Indonesia.

Referensi

Tidak ada komentar:

Posting Komentar