diff --git a/docs/CITY_DATA_SOURCES.md b/docs/CITY_DATA_SOURCES.md index 9a1226d5..5df9e66a 100644 --- a/docs/CITY_DATA_SOURCES.md +++ b/docs/CITY_DATA_SOURCES.md @@ -48,7 +48,7 @@ | amsterdam | KNMI 数据平台 | 10 min | 荷兰 | | taipei | CWA 开放数据 (466920) | ~10 min | 台湾 | | tel aviv | IMS Lod (225) | 实时 | 以色列 | -| paris | AEROWEB 实况 / AROME HD 15min | 实时/15min | 法国 | +| paris | AEROWEB 实况 / AROME HD | 实时/15min | 法国 (AROME是15分钟临近预报) | ### Tier 4 — 仅 METAR(10 分钟缓存) @@ -107,3 +107,30 @@ ankara, istanbul, helsinki, amsterdam, paris - **Tier 2 城市**(5 分钟级):修正效果良好,MADIS 更新稳定 - **Tier 3 城市**(10-15 分钟级):修正可用但滞后较大 - **Tier 4 城市**(仅 METAR):修正效果有限,不建议依赖 + + +## 关于网站终端图表的数据曲线展示逻辑 + +### 1. 实测数据(默认全开,突出核心) + +- **跑道全量展示**:北京、上海、广州、成都、重庆、武汉、首尔等城市的跑道实测数据,默认全量开启,无需手动勾选。 +- **结算跑道高亮**:系统内置了各大机场的官方结算跑道映射。命中的跑道将被**重点强调**(加粗的青色实线 #009688,线宽 2.8),并标记为“[跑道号] 结算跑道”。具体的跑道映射如下: + - 北京:19/01 + - 上海:17L/35R + - 广州:02L/20R + - 成都:02L/20R + - 重庆:20R/02L + - 武汉:04/22 + - 首尔:15R/33L +- **辅助跑道弱化**:同一机场下的其他非结算跑道,也会同时展示,但采用较细的虚线(线宽 1.2)以作陪衬区分。 +- **其他实测展示**:所有城市的 METAR 报文曲线、官方气象站实测(如 Hong Kong / Lau Fau Shan 的香港天文台曲线)均默认展示。 + +### 2. 核心预测数据(默认展示) + +- **DEB 模型融合**:作为平台核心的高精度智能融合预测曲线,默认始终展示给用户。 + +### 3. 多模型原始数据(默认隐藏,按需自选) + +- **保持整洁**:为了防止图表线缆过于杂乱,各大原始模型(ECMWF, GFS, ICON, GEM 等)的数据曲线在初次加载时**默认隐藏**。 +- **特例**:仅针对巴黎(Paris),由于其 AROME HD 是高精度的 15 分钟级临近预报,极具参考价值,因此默认开启。 +- **自由交互**:用户可通过图表底部的图例交互按钮,随时自由勾选、叠加或隐藏任意所需的数据曲线。 diff --git a/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts index 8007c638..7da92124 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts @@ -13,6 +13,10 @@ function seriesByKey(series: Array<{ key: string }>, key: string) { return series.find((item) => item.key === key); } +function runwayKey(rwy: string) { + return `runway_${rwy.split("/").map((part) => part.trim().toUpperCase()).join("_")}`; +} + export function runTests() { const guangzhou = { city: "guangzhou", @@ -95,10 +99,62 @@ export function runTests() { ), "users should still be able to enable a hidden multi-model curve from the legend", ); + assert( + defaultVisibleSeries.some((item) => item.key === "hourly_forecast"), + "DEB fusion forecast should be visible by default", + ); assert( __isTemperatureSeriesVisibleByDefaultForTest("paris", "model_curve_AROME HD"), "Paris AROME HD should be the only default-visible model curve exception", ); + assert( + __getVisibleTemperatureSeriesForTest( + "paris", + [{ key: "model_curve_AROME HD" }, { key: "model_curve_ECMWF" }] as any, + {}, + ).some((item) => item.key === "model_curve_AROME HD"), + "Paris AROME HD should be active in the default visible series", + ); + + const settlementRunwayCases = [ + ["beijing", "19/01"], + ["shanghai", "17L/35R"], + ["guangzhou", "02L/20R"], + ["chengdu", "02L/20R"], + ["chongqing", "20R/02L"], + ["wuhan", "04/22"], + ["seoul", "15R/33L"], + ] as const; + settlementRunwayCases.forEach(([city, settlementRwy]) => { + const chart = __buildTemperatureChartDataForTest( + { + city, + local_date: "2026-05-25", + local_time: "10:00", + tz_offset_seconds: 8 * 60 * 60, + runway_plate_history: { + [settlementRwy]: [ + { time: "00:05", temp: 25.1 }, + { time: "00:35", temp: 25.3 }, + ], + "99/00": [ + { time: "00:05", temp: 24.1 }, + { time: "00:35", temp: 24.3 }, + ], + }, + } as any, + { localTime: "10:00", times: ["00:00", "00:30"], temps: [25, 26] } as any, + "1D", + ); + const highlighted = seriesByKey(chart.series, runwayKey(settlementRwy)) as any; + assert(highlighted, `${city} settlement runway should be present`); + assert(highlighted.label.includes("结算跑道"), `${city} settlement runway should be labeled`); + assert(highlighted.color === "#009688", `${city} settlement runway should use highlight cyan`); + assert(highlighted.featured === true, `${city} settlement runway should be featured`); + assert(!highlighted.dashed, `${city} settlement runway should be solid`); + const auxiliary = seriesByKey(chart.series, "runway_99_00") as any; + assert(auxiliary?.dashed === true, `${city} auxiliary runway should be dashed`); + }); const shenzhen = __buildTemperatureChartDataForTest( {