适配 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( def _madis_parse_hfmetar(
self, nc_bytes: bytes, fname: str self, nc_bytes: bytes, fname: str
) -> List[Dict[str, Any]]: ) -> 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: try:
from netCDF4 import Dataset from netCDF4 import Dataset
nc = Dataset(fname, memory=nc_bytes) nc = Dataset(fname, memory=nc_bytes)
@@ -70,71 +74,85 @@ class MadisSourceMixin:
results: List[Dict[str, Any]] = [] results: List[Dict[str, Any]] = []
try: try:
# MADIS HFMETAR uses these variable names station_ids = nc.variables.get("stationId")
icaos = [str(s).strip() for s in nc.variables.get("icaoId", [])[:]] temps = nc.variables.get("temperature") # Kelvin
temps = nc.variables.get("temperature", None) # in Celsius dewpts = nc.variables.get("dewpoint") # Kelvin
dewpts = nc.variables.get("dewpoint", None) winds = nc.variables.get("windSpeed") # m/s
winds = nc.variables.get("windSpeed", None) pressures = nc.variables.get("altimeter") # Pa
pressures = nc.variables.get("seaLevelPress", None) obs_times = nc.variables.get("observationTime") # epoch seconds
obs_times = nc.variables.get("observationTime", None)
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): for i in range(n):
icao = str(icaos[i]).strip().upper() # Decode stationId (char array per row)
if not icao or icao == "0": 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 continue
# Temperature (Kelvin → Celsius)
temp_c = None temp_c = None
if temps is not None: if temps is not None:
try: try:
v = float(temps[i]) v = float(temps[i])
if -90 < v < 60: if 180 < v < 340: # valid Kelvin range (-93C to +67C)
temp_c = round(v, 1) temp_c = round(v - 273.15, 1)
except (ValueError, IndexError): except (ValueError, IndexError):
pass pass
if temp_c is None: if temp_c is None:
continue # skip stations without temperature continue
# Dewpoint (Kelvin → Celsius)
dewp_c = None dewp_c = None
if dewpts is not None: if dewpts is not None:
try: try:
v = float(dewpts[i]) v = float(dewpts[i])
if -90 < v < 60: if 180 < v < 340:
dewp_c = round(v, 1) dewp_c = round(v - 273.15, 1)
except (ValueError, IndexError): except (ValueError, IndexError):
pass pass
# Wind (m/s → kt)
wind_kt = None wind_kt = None
if winds is not None: if winds is not None:
try: try:
w = float(winds[i]) w = float(winds[i])
if w >= 0: if 0 <= w < 200:
wind_kt = round(w * 1.94384, 1) wind_kt = round(w * 1.94384, 1)
except (ValueError, IndexError): except (ValueError, IndexError):
pass pass
# Pressure (Pa → hPa)
pressure_hpa = None pressure_hpa = None
if pressures is not None: if pressures is not None:
try: try:
p = float(pressures[i]) p = float(pressures[i])
if 800 < p < 1100: if 50000 < p < 120000: # valid Pa range
pressure_hpa = round(p, 1) pressure_hpa = round(p / 100.0, 1)
except (ValueError, IndexError): except (ValueError, IndexError):
pass pass
# Observation time (epoch seconds)
obs_time = "" obs_time = ""
if obs_times is not None: if obs_times is not None:
try: try:
ts = str(obs_times[i]).strip() ts = obs_times[i]
# Try to parse as epoch or ISO dt = datetime.fromtimestamp(float(ts), tz=timezone.utc)
if ts and ts != "0": obs_time = dt.isoformat()
try: except Exception:
epoch = int(float(ts))
dt = datetime.fromtimestamp(epoch, tz=timezone.utc)
obs_time = dt.isoformat()
except (ValueError, OverflowError):
obs_time = ts
except (ValueError, IndexError):
pass pass
results.append({ results.append({