适配 NOAA MADIS HFMETAR netCDF 新格式:stationId 替代 icaoId,开尔文转摄氏度,气压 Pa 转 hPa
This commit is contained in:
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user