feat: introduce multi-source weather data collection module supporting OpenWeatherMap, Visual Crossing, and NOAA METAR.
This commit is contained in:
+9
-8
@@ -187,20 +187,21 @@ def start_bot():
|
|||||||
city_today_str = city_now.strftime("%Y-%m-%d")
|
city_today_str = city_now.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
msg_lines.append(f"\n📊 <b>Open-Meteo 7天预测</b>")
|
msg_lines.append(f"\n📊 <b>Open-Meteo 7天预测</b>")
|
||||||
model_split = daily.get("model_split")
|
nws = weather_data.get("nws", {})
|
||||||
|
nws_high = nws.get("today_high")
|
||||||
|
|
||||||
for i, (d, t) in enumerate(zip(dates[:7], max_temps[:7])):
|
for i, (d, t) in enumerate(zip(dates[:7], max_temps[:7])):
|
||||||
day_label = "今天" if d == city_today_str else d[5:]
|
day_label = "今天" if d == city_today_str else d[5:]
|
||||||
indicator = "👉 " if d == city_today_str else " "
|
indicator = "👉 " if d == city_today_str else " "
|
||||||
|
|
||||||
# 如果是今天且存在模型分歧,则特别标注
|
# 如果是今天且有 NWS 数据,显示模型对比
|
||||||
if d == city_today_str and model_split:
|
if d == city_today_str and nws_high is not None:
|
||||||
ecmwf = model_split.get("ecmwf")
|
diff = abs(t - nws_high)
|
||||||
hrrr = model_split.get("hrrr")
|
if diff > 1:
|
||||||
if ecmwf and hrrr and abs(ecmwf - hrrr) > 0.5:
|
|
||||||
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol} ⚠️")
|
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol} ⚠️")
|
||||||
msg_lines.append(f" (模型分歧: ECMWF {ecmwf} | HRRR {hrrr})")
|
msg_lines.append(f" (NWS官方预报: {nws_high}{temp_symbol},差异 {diff:.1f}°)")
|
||||||
else:
|
else:
|
||||||
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}")
|
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol} (NWS: {nws_high})")
|
||||||
else:
|
else:
|
||||||
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}")
|
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}")
|
||||||
|
|
||||||
|
|||||||
@@ -314,6 +314,55 @@ class WeatherDataCollector:
|
|||||||
logger.error(f"METAR 数据解析失败 ({icao}): {e}")
|
logger.error(f"METAR 数据解析失败 ({icao}): {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def fetch_nws(self, lat: float, lon: float) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
从 NWS (美国国家气象局) 获取高精度预报
|
||||||
|
仅适用于美国城市,全球 VPS 均可访问
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 1. 获取网格点
|
||||||
|
points_url = f"https://api.weather.gov/points/{lat},{lon}"
|
||||||
|
headers = {"User-Agent": "PolyWeather/1.0 (weather-bot)"}
|
||||||
|
|
||||||
|
points_resp = self.session.get(points_url, headers=headers, timeout=self.timeout)
|
||||||
|
points_resp.raise_for_status()
|
||||||
|
points_data = points_resp.json()
|
||||||
|
|
||||||
|
forecast_url = points_data.get("properties", {}).get("forecast")
|
||||||
|
if not forecast_url:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 2. 获取预报
|
||||||
|
forecast_resp = self.session.get(forecast_url, headers=headers, timeout=self.timeout)
|
||||||
|
forecast_resp.raise_for_status()
|
||||||
|
forecast_data = forecast_resp.json()
|
||||||
|
|
||||||
|
periods = forecast_data.get("properties", {}).get("periods", [])
|
||||||
|
if not periods:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 3. 提取今日最高温(找 isDaytime=True 的第一个)
|
||||||
|
today_high = None
|
||||||
|
for p in periods:
|
||||||
|
if p.get("isDaytime") and "High" in p.get("name", ""):
|
||||||
|
today_high = p.get("temperature")
|
||||||
|
break
|
||||||
|
# 如果没有明确的 High,取第一个 daytime 的温度
|
||||||
|
if today_high is None:
|
||||||
|
for p in periods:
|
||||||
|
if p.get("isDaytime"):
|
||||||
|
today_high = p.get("temperature")
|
||||||
|
break
|
||||||
|
|
||||||
|
return {
|
||||||
|
"source": "nws",
|
||||||
|
"today_high": today_high,
|
||||||
|
"unit": "fahrenheit",
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"NWS 请求失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def fetch_from_open_meteo(
|
def fetch_from_open_meteo(
|
||||||
self,
|
self,
|
||||||
lat: float,
|
lat: float,
|
||||||
@@ -343,10 +392,9 @@ class WeatherDataCollector:
|
|||||||
"_t": int(time.time()), # 禁用缓存,强制刷新
|
"_t": int(time.time()), # 禁用缓存,强制刷新
|
||||||
}
|
}
|
||||||
|
|
||||||
# 对于美国市场,使用华氏度并请求更多的模型共识
|
# 对于美国市场,使用华氏度
|
||||||
if use_fahrenheit:
|
if use_fahrenheit:
|
||||||
params["temperature_unit"] = "fahrenheit"
|
params["temperature_unit"] = "fahrenheit"
|
||||||
params["models"] = "ecmwf_ifs,hrrr_conus"
|
|
||||||
|
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
url,
|
url,
|
||||||
@@ -588,6 +636,12 @@ class WeatherDataCollector:
|
|||||||
metar_data = self.fetch_metar(city, use_fahrenheit=use_fahrenheit, utc_offset=utc_offset)
|
metar_data = self.fetch_metar(city, use_fahrenheit=use_fahrenheit, utc_offset=utc_offset)
|
||||||
if metar_data:
|
if metar_data:
|
||||||
results["metar"] = metar_data
|
results["metar"] = metar_data
|
||||||
|
|
||||||
|
# 对美国城市,额外获取 NWS 高精预报
|
||||||
|
if use_fahrenheit:
|
||||||
|
nws_data = self.fetch_nws(lat, lon)
|
||||||
|
if nws_data:
|
||||||
|
results["nws"] = nws_data
|
||||||
else:
|
else:
|
||||||
# 降级方案
|
# 降级方案
|
||||||
metar_data = self.fetch_metar(city, use_fahrenheit=use_fahrenheit)
|
metar_data = self.fetch_metar(city, use_fahrenheit=use_fahrenheit)
|
||||||
|
|||||||
Reference in New Issue
Block a user