适配 NOAA MADIS HFMETAR netCDF 新格式:stationId 替代 icaoId,开尔文转摄氏度,气压 Pa 转 hPa

This commit is contained in:
2569718930@qq.com
2026-05-18 17:48:56 +08:00
parent c6321253fe
commit abdce3cd3b
+47 -29
View File
@@ -57,7 +57,11 @@ class MadisSourceMixin:
def _madis_parse_hfmetar(
self, nc_bytes: bytes, fname: str
) -> List[Dict[str, Any]]:
"""Parse a MADIS HFMETAR NetCDF file and return per-station observations."""
"""Parse a MADIS HFMETAR NetCDF file and return per-station observations.
NOAA restructured the netCDF layout (2026-05): stationId replaces icaoId,
temperatures are in Kelvin, altimeter in Pascal.
"""
try:
from netCDF4 import Dataset
nc = Dataset(fname, memory=nc_bytes)
@@ -70,71 +74,85 @@ class MadisSourceMixin:
results: List[Dict[str, Any]] = []
try:
# MADIS HFMETAR uses these variable names
icaos = [str(s).strip() for s in nc.variables.get("icaoId", [])[:]]
temps = nc.variables.get("temperature", None) # in Celsius
dewpts = nc.variables.get("dewpoint", None)
winds = nc.variables.get("windSpeed", None)
pressures = nc.variables.get("seaLevelPress", None)
obs_times = nc.variables.get("observationTime", None)
station_ids = nc.variables.get("stationId")
temps = nc.variables.get("temperature") # Kelvin
dewpts = nc.variables.get("dewpoint") # Kelvin
winds = nc.variables.get("windSpeed") # m/s
pressures = nc.variables.get("altimeter") # Pa
obs_times = nc.variables.get("observationTime") # epoch seconds
n = len(icaos)
if station_ids is None:
logger.warning("MADIS: stationId variable not found in netCDF")
return []
n = station_ids.shape[0]
for i in range(n):
icao = str(icaos[i]).strip().upper()
if not icao or icao == "0":
# Decode stationId (char array per row)
try:
import numpy as np
row = station_ids[i]
if isinstance(row, np.ndarray):
sid_bytes = b"".join(row.tobytes().split(b"\x00")[:1])
icao = sid_bytes.decode("ascii", errors="replace").strip()
else:
icao = str(row).strip()
except Exception:
icao = ""
icao = icao.upper()
if not icao or len(icao) != 4:
continue
# Temperature (Kelvin → Celsius)
temp_c = None
if temps is not None:
try:
v = float(temps[i])
if -90 < v < 60:
temp_c = round(v, 1)
if 180 < v < 340: # valid Kelvin range (-93C to +67C)
temp_c = round(v - 273.15, 1)
except (ValueError, IndexError):
pass
if temp_c is None:
continue # skip stations without temperature
continue
# Dewpoint (Kelvin → Celsius)
dewp_c = None
if dewpts is not None:
try:
v = float(dewpts[i])
if -90 < v < 60:
dewp_c = round(v, 1)
if 180 < v < 340:
dewp_c = round(v - 273.15, 1)
except (ValueError, IndexError):
pass
# Wind (m/s → kt)
wind_kt = None
if winds is not None:
try:
w = float(winds[i])
if w >= 0:
if 0 <= w < 200:
wind_kt = round(w * 1.94384, 1)
except (ValueError, IndexError):
pass
# Pressure (Pa → hPa)
pressure_hpa = None
if pressures is not None:
try:
p = float(pressures[i])
if 800 < p < 1100:
pressure_hpa = round(p, 1)
if 50000 < p < 120000: # valid Pa range
pressure_hpa = round(p / 100.0, 1)
except (ValueError, IndexError):
pass
# Observation time (epoch seconds)
obs_time = ""
if obs_times is not None:
try:
ts = str(obs_times[i]).strip()
# Try to parse as epoch or ISO
if ts and ts != "0":
try:
epoch = int(float(ts))
dt = datetime.fromtimestamp(epoch, tz=timezone.utc)
obs_time = dt.isoformat()
except (ValueError, OverflowError):
obs_time = ts
except (ValueError, IndexError):
ts = obs_times[i]
dt = datetime.fromtimestamp(float(ts), tz=timezone.utc)
obs_time = dt.isoformat()
except Exception:
pass
results.append({