Add Warsaw official nearby station data

This commit is contained in:
2569718930@qq.com
2026-03-21 21:32:54 +08:00
parent 33385c3dda
commit 2ba680954c
6 changed files with 122 additions and 3 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "PolyWeather Side Panel",
"description": "PolyWeather 右侧城市卡片(浏览器侧边栏)",
"version": "0.1.1",
"version": "0.1.2",
"icons": {
"16": "icon-16.png",
"32": "icon-32.png",
+1
View File
@@ -267,6 +267,7 @@ export interface CityDetail {
current: CurrentConditions;
mgm?: MgmData;
mgm_nearby?: NearbyStation[];
nearby_source?: string;
forecast?: ForecastData;
multi_model?: Record<string, number | null>;
deb?: DebForecast;
+6 -1
View File
@@ -1132,11 +1132,16 @@ export function getShortTermNowcastLines(
? detail.metar_recent_obs.slice(-4)
: [];
const nearby = Array.isArray(detail.mgm_nearby) ? detail.mgm_nearby : [];
const nearbySource = String(detail.nearby_source || "").toLowerCase();
const sourceLabel =
detail.name === "ankara"
nearbySource === "mgm" || detail.name === "ankara"
? isEnglish(locale)
? "MGM nearby stations"
: "MGM 周边站"
: nearbySource === "official_cluster"
? isEnglish(locale)
? "Official nearby stations"
: "官方周边站"
: isEnglish(locale)
? "METAR nearby stations"
: "METAR 周边站";
+81
View File
@@ -9,6 +9,9 @@ from loguru import logger
class SettlementSourceMixin:
IMGW_METEO_API_BASE = "https://meteo.imgw.pl/api/v1"
IMGW_METEO_API_TOKEN = "p4DXKjsYadfBV21TYrDk"
def _get_settlement_cache(self, key: str) -> Optional[Dict[str, Any]]:
now_ts = time.time()
with self._settlement_cache_lock:
@@ -260,6 +263,84 @@ class SettlementSourceMixin:
logger.warning(f"CWA Forecast request failed: {exc}")
return None
def _imgw_api_get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
try:
query = {"token": self.IMGW_METEO_API_TOKEN}
if isinstance(params, dict):
query.update(params)
response = self.session.get(
f"{self.IMGW_METEO_API_BASE}/{path}",
params=query,
timeout=self.timeout,
)
response.raise_for_status()
return response.json() if response.content else None
except Exception as exc:
logger.warning(f"IMGW API request failed path={path}: {exc}")
return None
def fetch_imgw_synoptic_station_current(
self,
localization: str,
*,
display_name: Optional[str] = None,
use_fahrenheit: bool = False,
) -> Optional[Dict[str, Any]]:
cache_key = f"imgw:synoptic:{str(localization or '').strip().lower()}"
cached = self._get_settlement_cache(cache_key)
if cached:
return cached
location_payload = self._imgw_api_get("geo/search", {"name": localization})
location_rows = (location_payload or {}).get("data") or []
if not isinstance(location_rows, list) or not location_rows:
return None
location_row = location_rows[0] if isinstance(location_rows[0], dict) else None
if not isinstance(location_row, dict):
return None
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
synoptic_payload = self._imgw_api_get(
"forecast/synoptic",
{"date": date_str, "localization": localization},
)
data = (synoptic_payload or {}).get("data") or {}
if not isinstance(data, dict) or not data:
return None
latest_row = None
for key in sorted(data.keys()):
row = data.get(key)
if isinstance(row, dict) and row.get("temp") is not None:
latest_row = row
if not isinstance(latest_row, dict):
return None
temp_kelvin = self._safe_float(latest_row.get("temp"))
if temp_kelvin is None:
return None
temp_c = temp_kelvin - 273.15
temp_value = (temp_c * 9 / 5) + 32 if use_fahrenheit else temp_c
wind_speed_ms = self._safe_float(latest_row.get("ws"))
payload = {
"name": display_name or str(location_row.get("name") or localization),
"lat": self._safe_float(location_row.get("lat")),
"lon": self._safe_float(location_row.get("lon")),
"temp": round(temp_value, 1),
"icao": "IMGW",
"istNo": "IMGW",
"wind_dir": self._safe_float(latest_row.get("wd")),
"wind_speed": wind_speed_ms,
"wind_speed_kt": round(float(wind_speed_ms) * 1.943844, 1) if wind_speed_ms is not None else None,
"source": "imgw_synoptic",
"raw_time": latest_row.get("fd"),
}
if payload["lat"] is None or payload["lon"] is None:
return None
self._set_settlement_cache(cache_key, payload)
return payload
def fetch_settlement_current(self, city: str) -> Optional[Dict[str, Any]]:
normalized = str(city or "").strip().lower()
if normalized == "hong kong":
+30
View File
@@ -585,6 +585,32 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
)
if cluster_data:
results["mgm_nearby"] = cluster_data
results["nearby_source"] = "metar_cluster"
def _attach_warsaw_official_nearby(
self, results: Dict, use_fahrenheit: bool
) -> None:
if "mgm_nearby" in results:
return
official_rows = []
epwa_rows = self.fetch_metar_nearby_cluster(["EPWA"], use_fahrenheit=use_fahrenheit)
if epwa_rows:
epwa = dict(epwa_rows[0])
epwa["name"] = "Warszawa-Okęcie (EPWA)"
official_rows.append(epwa)
imgw_row = self.fetch_imgw_synoptic_station_current(
"Warszawa",
display_name="Warszawa (IMGW synoptic)",
use_fahrenheit=use_fahrenheit,
)
if imgw_row:
official_rows.append(imgw_row)
if official_rows:
results["mgm_nearby"] = official_rows
results["nearby_source"] = "official_cluster"
def _attach_nws_and_models(
self,
@@ -649,6 +675,8 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
results["metar"] = metar_data
self._attach_turkish_mgm_data(results, city_lower)
if city_lower == "warsaw":
self._attach_warsaw_official_nearby(results, use_fahrenheit)
self._attach_global_nearby_cluster(
results, city_lower, use_fahrenheit
)
@@ -668,6 +696,8 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
results["metar"] = metar_data
self._attach_turkish_mgm_data(results, city_lower)
if city_lower == "warsaw":
self._attach_warsaw_official_nearby(results, use_fahrenheit)
self._attach_global_nearby_cluster(
results, city_lower, use_fahrenheit
)
+3 -1
View File
@@ -635,6 +635,7 @@ def _analyze(city: str, force_refresh: bool = False) -> Dict[str, Any]:
},
"mgm": mgm_data,
"mgm_nearby": raw.get("mgm_nearby", []),
"nearby_source": raw.get("nearby_source") or ("mgm" if city.lower() == "ankara" else "metar_cluster"),
"forecast": {
"today_high": om_today,
"daily": forecast_daily,
@@ -837,7 +838,7 @@ def _build_city_detail_payload(
"weather_gov": {},
"mgm": data.get("mgm") or {},
"mgm_nearby": data.get("mgm_nearby") or [],
"nearby_source": "mgm" if data.get("name") == "ankara" else "metar_cluster",
"nearby_source": data.get("nearby_source") or ("mgm" if data.get("name") == "ankara" else "metar_cluster"),
},
"timeseries": {
"metar_recent_obs": data.get("metar_recent_obs") or [],
@@ -856,6 +857,7 @@ def _build_city_detail_payload(
"dynamic_commentary": data.get("dynamic_commentary") or {"summary": "", "notes": []},
"market_scan": market_scan,
"risk": data.get("risk"),
"nearby_source": data.get("nearby_source") or ("mgm" if data.get("name") == "ankara" else "metar_cluster"),
"ai_analysis": data.get("ai_analysis") or "",
"errors": {},
}