@metweave/leaflet 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,7 @@ metweave 的 Leaflet 适配器:报文卡片上图——marker、tooltip 与卡
6
6
 
7
7
  ```bash
8
8
  npm install @metweave/leaflet leaflet
9
+ # TypeScript 用户另装类型:npm install -D @types/leaflet
9
10
  ```
10
11
 
11
12
  ### 最小示例
@@ -14,6 +15,8 @@ npm install @metweave/leaflet leaflet
14
15
  import { addMetarLayer } from "@metweave/leaflet";
15
16
  ```
16
17
 
18
+ `addMetarLayer` 是异步函数(返回 `Promise<LayerGroup>`):leaflet 由首次调用时动态装载,import 本包不会在 Node/SSR 模块图里触发 `window` 求值错误,上图动作需 `await`。
19
+
17
20
  底图瓦片仍是宿主侧一行 `L.tileLayer` 配置——本包不绑定任何底图。其他地图库(MapLibre 等)的适配器在路线图上。文档与端到端示例见[主仓库](https://github.com/yaojaro/metweave)。
18
21
 
19
22
  ## 许可
package/dist/index.cjs CHANGED
@@ -1,39 +1,15 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- //#endregion
24
- let leaflet = require("leaflet");
25
- leaflet = __toESM(leaflet, 1);
26
2
  let _metweave_core = require("@metweave/core");
27
3
  let _metweave_render = require("@metweave/render");
28
4
  //#region src/index.ts
29
5
  /**
30
- * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。
31
- * The Leaflet adapter: puts metweave report cards onto a Leaflet map.
32
- *
33
- * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。
34
- * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,
35
- * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。
6
+ * leaflet 惰性装载:import 本包时不再静态加载 leaflet——peer 库是浏览器专属实现,
7
+ * 顶层静态 import 会让 Node/SSR 的模块图在求值期即崩(window is not defined,
8
+ * 2026-09-16 五方实测评测实锤:ESM/CJS 双格式 import 均炸)。改为首次 addMetarLayer
9
+ * 调用时动态 import 并缓存;类型面仍由顶层 import type 提供,宿主类型检查零影响。
36
10
  */
11
+ let leafletModule;
12
+ const loadLeaflet = () => leafletModule ??= import("leaflet");
37
13
  /** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */
38
14
  const ADD_METAR_LAYER_OPTION_KEYS = /* @__PURE__ */ new Set([
39
15
  "locale",
@@ -100,10 +76,10 @@ const toMps = (value, unit) => unit === "kt" ? value * .514444 : unit === "kmh"
100
76
  * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。
101
77
  * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:
102
78
  * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)
103
- * - poor(红)= 能见度 < 1500 m,或云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)
104
- * 或现象含 GR/VA,或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭
105
- * - caution(琥珀)= 能见度 1500–4800 m,或云底 1000–3000 ft,或任何降水族(RA/SN 等),
106
- * 或 FZ 族(结冰),或阵风 15–25 m/s
79
+ * - poor(红)= 能见度 < 1500 m,或 BKN/OVC 云层(含垂直能见度)云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)
80
+ * 或现象含 GR/VA,或冻降水(FZ 描述符族,冻雨/冻毛毛雨),或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭
81
+ * - caution(琥珀)= 能见度 1500–5000 m(能见度分档取国内通行 1500/5000 m 口径),或 BKN/OVC 云底 1000–3000 ft,或任何降水族(RA/SN 等),
82
+ * 或 FZ 描述符以外的结冰现象,或阵风 15–25 m/s
107
83
  * - good(绿)= 其余(含 CAVOK)
108
84
  * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。
109
85
  */
@@ -126,22 +102,19 @@ function conditionOf(report) {
126
102
  for (const g of v.weather ?? []) {
127
103
  const thunderstorm = g.descriptor === "TS";
128
104
  const hailOrAsh = g.phenomena.includes("GR") || g.phenomena.includes("VA");
105
+ const freezing = g.descriptor === "FZ";
129
106
  const heavyPrecip = g.intensity === "+" && (g.descriptor === "SH" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));
130
- if (thunderstorm || hailOrAsh || heavyPrecip) return "poor";
107
+ if (thunderstorm || hailOrAsh || freezing || heavyPrecip) return "poor";
131
108
  }
132
109
  const gust = v.wind?.gust;
133
110
  if (gust !== void 0 && toMps(gust.value, gust.unit) >= 25) return "poor";
134
111
  if (elements.some((e) => e.kind === "layer" && e.convective !== void 0)) return "poor";
135
112
  if (v.runwayStates.some((st) => st.closed === true)) return "poor";
136
113
  if (vis !== void 0) {
137
- if ((vis.unit === "m" ? vis.value : vis.value * 1609.344) < 4800) return "caution";
114
+ if ((vis.unit === "m" ? vis.value : vis.value * 1609.344) < 5e3) return "caution";
138
115
  }
139
116
  if (ceiling < 3e3) return "caution";
140
- for (const g of v.weather ?? []) {
141
- const freezing = g.descriptor === "FZ";
142
- const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));
143
- if (freezing || precip) return "caution";
144
- }
117
+ for (const g of v.weather ?? []) if (g.phenomena.some((p) => PRECIP_PHENOMENA.has(p))) return "caution";
145
118
  if (gust !== void 0 && toMps(gust.value, gust.unit) >= 15) return "caution";
146
119
  return "good";
147
120
  }
@@ -202,25 +175,29 @@ const escapeBoundMaps = /* @__PURE__ */ new WeakSet();
202
175
  * @param map - A Leaflet map instance. Leaflet 地图实例。
203
176
  * @param items - Stations to plot (report + position + title). 待上图的站点列表。
204
177
  * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。
178
+ *
179
+ * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR
180
+ * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。
205
181
  */
206
- function addMetarLayer(map, items, options = {}) {
182
+ async function addMetarLayer(map, items, options = {}) {
207
183
  for (const key of Object.keys(options)) if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) throw new Error(`addMetarLayer 收到未知选项 "${key}"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`);
208
184
  const locale = options.card?.locale ?? options.locale ?? "zh";
209
185
  if (locale !== "zh" && locale !== "en") throw new Error(`addMetarLayer 的 locale 选项值 "${locale}" 不受支持(可用:"zh" | "en")`);
210
- const group = leaflet.layerGroup();
186
+ const L = await loadLeaflet();
187
+ const group = L.layerGroup();
211
188
  for (const item of items) {
212
189
  const name = item.title ?? item.report.station;
213
190
  let marker;
214
191
  if (options.conditionColors === true) {
215
192
  const tier = conditionOf(item.report);
216
193
  const label = `${name} · ${TIER_WORDS[locale][tier]}`;
217
- marker = leaflet.marker(item.position, { icon: leaflet.divIcon({
194
+ marker = L.marker(item.position, { icon: L.divIcon({
218
195
  className: "mw-cond-icon",
219
196
  html: `<span role="img" aria-label="${escapeHtml(label)}" class="mw-dot mw-dot-${tier}" style="display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)"></span>`,
220
197
  iconSize: [14, 14],
221
198
  iconAnchor: [7, 7]
222
199
  }) });
223
- } else marker = leaflet.marker(item.position, { alt: name });
200
+ } else marker = L.marker(item.position, { alt: name });
224
201
  marker.bindTooltip(tooltipContent(item, options.conditionColors === true, locale));
225
202
  if (options.popup ?? true) {
226
203
  const cardOpts = { ...options.card };
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["toValues","bad","L","renderCard"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。\n * The Leaflet adapter: puts metweave report cards onto a Leaflet map.\n *\n * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。\n * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,\n * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。\n */\nimport * as L from \"leaflet\";\nimport type { CloudElement, MetarReport, WeatherGroup } from \"@metweave/core\";\nimport { toValues } from \"@metweave/core\";\nimport { renderCard } from \"@metweave/render\";\n\n/**\n * One station on the map: parsed report IR + WGS-84 position + optional tooltip title.\n * 地图上的一个站点:已解析报文 IR + WGS-84 坐标 + 可选悬浮标题。\n */\nexport interface MetarLayerItem {\n /** 已解析的报文 IR */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon];用 GCJ-02 底图时的偏移由宿主决定是否校正) */\n position: [number, number];\n /** 站点提示(tooltip 文案,缺省用 IR 站名)。按纯文本处理——不解析 HTML,宿主自传任意串无注入面 */\n title?: string;\n}\n\n/**\n * Options for addMetarLayer: popup behavior, pass-through renderCard options, and the condition-color dot mode.\n * addMetarLayer 的选项:弹窗行为、透传 renderCard 的选项、条件色圆点模式。\n */\nexport interface AddMetarLayerOptions {\n /** 显示语言速记:等价 `card.locale`,同时决定 tooltip 摘要/档位词与弹窗卡片语言;\n * card.locale 显式传入时以其为准。未知选项运行时抛错(拼写错误不静默) */\n locale?: \"zh\" | \"en\";\n /** 点击站点时以弹窗展示报文卡片(缺省开启) */\n popup?: boolean;\n /** 传给 renderCard 的选项 */\n card?: Parameters<typeof renderCard>[1];\n /** marker 按四档气象条件着色(unknown/poor/caution/good 圆点替代默认图钉;缺省 false 保持现状)。\n * 判据为本库自拟的扫视启发式(初稿待审,见 conditionOf 注释)——不对应任何官方飞行天气分类,\n * 不得用作运行判据;\n * 开启时 tooltip 同时追加一行要素摘要(能见度/天气/最差云;NIL 与关键组全缺测站显示「缺报/数据缺测」),\n * 圆点 aria-label 追加档位词(zh「天气好/差…」/ en「Weather …」,语言随 card.locale) */\n conditionColors?: boolean;\n}\n\n/** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */\nconst ADD_METAR_LAYER_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"locale\",\n \"popup\",\n \"card\",\n \"conditionColors\",\n]);\n\n/**\n * tooltip 纯文本承载:Leaflet 对 string 内容走 innerHTML——宿主自传的 title 是任意串,\n * 经由 textContent 装载(非 HTML 路径)杜绝标记注入;文案渲染不受影响。\n */\nfunction textCarrier(text: string): HTMLElement {\n const el = document.createElement(\"span\");\n el.textContent = text;\n return el;\n}\n\n/** HTML 属性上下文转义(divIcon 的 html 字符串构造用——title/站名不可信面) */\nconst escapeHtml = (text: string): string =>\n text.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n\n/** 四档条件色(与卡片 mw-danger/mw-caution 同族色相:灰=不明、红=差、琥珀=注意、绿=好) */\nconst TIER_COLORS: Record<ConditionTier, string> = {\n unknown: \"#8a94a0\",\n poor: \"#d05656\",\n caution: \"#e0a13c\",\n good: \"#3aa657\",\n};\n\ntype ConditionTier = \"unknown\" | \"poor\" | \"caution\" | \"good\";\n\n/** 档位可读名(aria-label 追加词——a11y 1.4.1:档位信息不只靠颜色传达) */\nconst TIER_WORDS: Record<\"zh\" | \"en\", Record<ConditionTier, string>> = {\n zh: { unknown: \"天气不明\", poor: \"天气差\", caution: \"天气注意\", good: \"天气好\" },\n en: {\n unknown: \"Weather unknown\",\n poor: \"Weather poor\",\n caution: \"Weather caution\",\n good: \"Weather good\",\n },\n};\n\n/** 降水类现象(caution 判据用,与 WMO 4678 降水族对应——RA/SN/SG/PL/GS/IC/DZ/UP) */\nconst PRECIP_PHENOMENA: ReadonlySet<string> = new Set([\n \"RA\",\n \"SN\",\n \"SG\",\n \"PL\",\n \"GS\",\n \"IC\",\n \"DZ\",\n \"UP\",\n]);\n\n/** 风速折米/秒(阵风判据统一单位:kt ×0.514444、kmh ÷3.6、mps ×1) */\nconst toMps = (value: number, unit: \"kt\" | \"mps\" | \"kmh\"): number =>\n unit === \"kt\" ? value * 0.514444 : unit === \"kmh\" ? value / 3.6 : value;\n\n/**\n * 四档气象条件分级(判据本库自拟、初稿待审——显示层扫视启发式):\n * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。\n * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:\n * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)\n * - poor(红)= 能见度 < 1500 m,或云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)\n * 或现象含 GR/VA,或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭\n * - caution(琥珀)= 能见度 1500–4800 m,或云底 1000–3000 ft,或任何降水族(RA/SN 等),\n * 或 FZ 族(结冰),或阵风 15–25 m/s\n * - good(绿)= 其余(含 CAVOK)\n * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。\n */\nfunction conditionOf(report: MetarReport): ConditionTier {\n if (report.nil === true) return \"unknown\";\n const v = toValues(report);\n // 关键组全缺测:能见度与云均缺测(云组每个体均为全缺测形态)且天气缺测/无——判读无从下手,灰而非绿\n const visMissing = report.visibility?.kind === \"missing\";\n const elements = v.clouds?.elements ?? [];\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone) return \"unknown\";\n\n // —— poor 判据(任一命中即红)\n const vis = v.visibility;\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 1500) return \"poor\";\n }\n const ceilings = elements\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .map((e) => e.heightFt.value ?? Number.POSITIVE_INFINITY);\n for (const e of elements) {\n if (e.kind === \"vertical-visibility\")\n ceilings.push(e.heightFt.value ?? Number.POSITIVE_INFINITY);\n }\n const ceiling = ceilings.length > 0 ? Math.min(...ceilings) : Number.POSITIVE_INFINITY;\n if (ceiling < 1000) return \"poor\";\n for (const g of v.weather ?? []) {\n const thunderstorm = g.descriptor === \"TS\"; // TS 族:任何雷暴(含 VCTS 邻近雷暴)\n const hailOrAsh = g.phenomena.includes(\"GR\") || g.phenomena.includes(\"VA\");\n const heavyPrecip =\n g.intensity === \"+\" &&\n (g.descriptor === \"SH\" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));\n if (thunderstorm || hailOrAsh || heavyPrecip) return \"poor\";\n }\n const gust = v.wind?.gust;\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 25) return \"poor\";\n const convective = elements.some(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n if (convective) return \"poor\";\n if (v.runwayStates.some((st) => st.closed === true)) return \"poor\";\n\n // —— caution 判据(任一命中即琥珀)\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 4800) return \"caution\";\n }\n if (ceiling < 3000) return \"caution\";\n for (const g of v.weather ?? []) {\n const freezing = g.descriptor === \"FZ\";\n const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));\n if (freezing || precip) return \"caution\";\n }\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 15) return \"caution\";\n return \"good\";\n}\n\n/** 天气组显示码:span 在位取原码,缺席由 IR 重建(摘要行的要素原样口径) */\nfunction weatherCode(report: MetarReport, g: WeatherGroup): string {\n return g.span === undefined\n ? `${g.proximity ? \"VC\" : \"\"}${g.intensity ?? \"\"}${g.descriptor ?? \"\"}${g.phenomena.join(\"\")}`\n : report.raw.slice(g.span.start, g.span.end);\n}\n\n/** 云层显示码:span 在位取原码,缺席由 IR 重建(缺测位还原为 ///) */\nfunction cloudCode(report: MetarReport, layer: CloudElement): string {\n if (layer.span === undefined) {\n if (layer.kind === \"vertical-visibility\") {\n return `VV${layer.heightFt.value === null ? \"///\" : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\")}`;\n }\n const height =\n layer.heightFt.value === null\n ? \"///\"\n : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\");\n return `${layer.amount ?? \"///\"}${height}${layer.convective ?? \"\"}`;\n }\n return report.raw.slice(layer.span.start, layer.span.end);\n}\n\n/**\n * tooltip 第二行要素摘要(扫视初筛):`2500m +TSRA BKN030CB` 式——\n * 能见度 / 最显著天气(优先 TS/GR/+ 强度族)/ 最差云(对流云优先,否则最低 BKN/OVC,VV 兜底)。\n * NIL 站显示「缺报(NIL)」、关键组全缺测站显示「数据缺测」(与 conditionOf 的 unknown 判据同款口径)。\n */\nfunction summarizeReport(report: MetarReport, locale: \"zh\" | \"en\"): string {\n if (report.nil === true) return locale === \"en\" ? \"No report (NIL)\" : \"缺报(NIL)\";\n if (report.cavok) return \"CAVOK\";\n const v = toValues(report);\n // 关键组全缺测(能见度与云均缺测且天气缺测/无):要素摘要无从拼起,显示缺测占位而非空行\n const elements = v.clouds?.elements ?? [];\n const visMissing = report.visibility?.kind === \"missing\";\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone)\n return locale === \"en\" ? \"Data missing\" : \"数据缺测\";\n const parts: string[] = [];\n const vis = v.visibility;\n if (vis !== undefined) {\n parts.push(\n vis.unit === \"m\"\n ? vis.exact\n ? `${vis.value} m`\n : \"≥10 km\"\n : vis.beyond === \"below\"\n ? `<${vis.value} SM`\n : vis.beyond === \"above\"\n ? `>${vis.value} SM`\n : `${vis.value} SM`,\n );\n }\n const weather = v.weather ?? [];\n const significant =\n weather.find(\n (g) => g.descriptor === \"TS\" || g.phenomena.includes(\"GR\") || g.intensity === \"+\",\n ) ?? weather[0];\n if (significant !== undefined) parts.push(weatherCode(report, significant));\n const layers = v.clouds?.elements ?? [];\n const convective = layers.find(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n const ceilingLayer = layers\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .reduce<Extract<CloudElement, { kind: \"layer\" }> | undefined>(\n (lowest, e) =>\n lowest === undefined ||\n (e.heightFt.value ?? Number.POSITIVE_INFINITY) <\n (lowest.heightFt.value ?? Number.POSITIVE_INFINITY)\n ? e\n : lowest,\n undefined,\n );\n const vertical = layers.find((e) => e.kind === \"vertical-visibility\");\n const worstCloud = convective ?? ceilingLayer ?? vertical;\n if (worstCloud !== undefined) parts.push(cloudCode(report, worstCloud));\n else if (v.clouds?.clear !== undefined) parts.push(v.clouds.clear.code);\n return parts.join(\" \");\n}\n\n/** tooltip 内容节点:标题行 + 要素摘要行(两行皆纯文本装载;locale 决定摘要占位词语言) */\nfunction tooltipContent(\n item: MetarLayerItem,\n withSummary: boolean,\n locale: \"zh\" | \"en\",\n): HTMLElement {\n const container = document.createElement(\"span\");\n container.append(textCarrier(item.title ?? item.report.station));\n if (withSummary) {\n container.append(\n document.createElement(\"br\"),\n textCarrier(summarizeReport(item.report, locale)),\n );\n }\n return container;\n}\n\n/** Escape 关闭已开弹窗的监听只挂一次/地图(多图层叠加不重复绑定) */\nconst escapeBoundMaps = new WeakSet<L.Map>();\n\n/**\n * Put a set of report stations onto a Leaflet map: markers + tooltips + card popups. Returns a removable layer group.\n * 把一组报文站点挂上地图:标记 + tooltip + 卡片弹窗。返回可整体移除的图层组。\n * @param map - A Leaflet map instance. Leaflet 地图实例。\n * @param items - Stations to plot (report + position + title). 待上图的站点列表。\n * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。\n */\n\nexport function addMetarLayer(\n map: L.Map,\n items: readonly MetarLayerItem[],\n options: AddMetarLayerOptions = {},\n): L.LayerGroup {\n // 未知选项运行时抛错:拼写错误的选项被静默忽略 = 显示语言/行为悄悄不符预期(2026-09-15\n // 五角色评测实测:顶层 locale 此前被静默忽略,popup 整卡仍中文)。中文提示 = v0.1 message 语言契约。\n for (const key of Object.keys(options)) {\n if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) {\n throw new Error(\n `addMetarLayer 收到未知选项 \"${key}\"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`,\n );\n }\n }\n // 语言解析单一出口:card.locale 显式传入 > 顶层 locale 速记 > zh;非法值清晰报错不裸崩\n const locale = options.card?.locale ?? options.locale ?? \"zh\";\n if (locale !== \"zh\" && locale !== \"en\") {\n // never 收窄后的宽化中转:模板表达式不接受 never 字面量类型\n const bad: string = locale;\n throw new Error(`addMetarLayer 的 locale 选项值 \"${bad}\" 不受支持(可用:\"zh\" | \"en\")`);\n }\n const group = L.layerGroup();\n for (const item of items) {\n const name = item.title ?? item.report.station;\n // alt 写入图标 img 的 alt 属性:marker 在读屏下 role=button,可访问名称 = 站名(WCAG 4.1.2)\n let marker: L.Marker;\n if (options.conditionColors === true) {\n // 圆点 divIcon 无 img——alt 失效,改以 role=img + aria-label 保住可访问名称(内容经 HTML 转义);\n // aria-label 追加档位词(a11y 1.4.1:档位不只靠颜色传达;语言随 card.locale,缺省中文)\n const tier = conditionOf(item.report);\n const label = `${name} · ${TIER_WORDS[locale][tier]}`;\n marker = L.marker(item.position, {\n icon: L.divIcon({\n className: \"mw-cond-icon\",\n html: `<span role=\"img\" aria-label=\"${escapeHtml(label)}\" class=\"mw-dot mw-dot-${tier}\" style=\"display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)\"></span>`,\n iconSize: [14, 14],\n iconAnchor: [7, 7],\n }),\n });\n } else {\n marker = L.marker(item.position, { alt: name });\n }\n marker.bindTooltip(tooltipContent(item, options.conditionColors === true, locale));\n if (options.popup ?? true) {\n // 弹窗内容是 renderCard 的 DOM 元素(createElement/textContent 构建),本就走非 HTML 路径;\n // maxWidth 420 = 卡片设计宽(Leaflet 缺省 300 会压窄)\n // 元数据联表得到的站名随弹窗进卡片站名行(title = \"ICAO 站名\",剥掉 ICAO 前缀);调用方显式传入时以其为准\n const cardOpts: Parameters<typeof renderCard>[1] = { ...options.card };\n if (cardOpts.locale === undefined && options.locale !== undefined) {\n cardOpts.locale = options.locale;\n }\n const stationName =\n item.title !== undefined && item.title.startsWith(`${item.report.station} `)\n ? item.title.slice(item.report.station.length + 1)\n : undefined;\n if (cardOpts.stationTitle === undefined && stationName !== undefined) {\n cardOpts.stationTitle = stationName;\n }\n marker.bindPopup(renderCard(item.report, cardOpts), { maxWidth: 420 });\n marker.on(\"popupopen\", () => {\n // 触屏双浮层消除:弹窗打开即收起 tooltip\n marker.closeTooltip();\n // 焦点移入弹窗首个可聚焦元素(Leaflet 关闭按钮)——键盘与读屏可直达\n const focusTarget = marker\n .getPopup()\n ?.getElement()\n ?.querySelector<HTMLElement>(\"a.leaflet-popup-close-button, button, [href]\");\n focusTarget?.focus();\n });\n }\n marker.addTo(group);\n }\n if (!escapeBoundMaps.has(map)) {\n escapeBoundMaps.add(map);\n // 地图容器键盘路径:Escape 关闭已开弹窗(closePopup 对未开弹窗是空操作)\n map.getContainer().addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") map.closePopup();\n });\n }\n group.addTo(map);\n return group;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["toValues","bad","renderCard"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。\n * The Leaflet adapter: puts metweave report cards onto a Leaflet map.\n *\n * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。\n * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,\n * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。\n */\nimport type * as Leaflet from \"leaflet\";\nimport type { CloudElement, MetarReport, WeatherGroup } from \"@metweave/core\";\nimport { toValues } from \"@metweave/core\";\nimport { renderCard } from \"@metweave/render\";\n\n/**\n * leaflet 惰性装载:import 本包时不再静态加载 leaflet——peer 库是浏览器专属实现,\n * 顶层静态 import 会让 Node/SSR 的模块图在求值期即崩(window is not defined,\n * 2026-09-16 五方实测评测实锤:ESM/CJS 双格式 import 均炸)。改为首次 addMetarLayer\n * 调用时动态 import 并缓存;类型面仍由顶层 import type 提供,宿主类型检查零影响。\n */\nlet leafletModule: Promise<typeof import(\"leaflet\")> | undefined;\nconst loadLeaflet = (): Promise<typeof import(\"leaflet\")> => (leafletModule ??= import(\"leaflet\"));\n\n/**\n * One station on the map: parsed report IR + WGS-84 position + optional tooltip title.\n * 地图上的一个站点:已解析报文 IR + WGS-84 坐标 + 可选悬浮标题。\n */\nexport interface MetarLayerItem {\n /** 已解析的报文 IR */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon];用 GCJ-02 底图时的偏移由宿主决定是否校正) */\n position: [number, number];\n /** 站点提示(tooltip 文案,缺省用 IR 站名)。按纯文本处理——不解析 HTML,宿主自传任意串无注入面 */\n title?: string;\n}\n\n/**\n * Options for addMetarLayer: popup behavior, pass-through renderCard options, and the condition-color dot mode.\n * addMetarLayer 的选项:弹窗行为、透传 renderCard 的选项、条件色圆点模式。\n */\nexport interface AddMetarLayerOptions {\n /** 显示语言速记:等价 `card.locale`,同时决定 tooltip 摘要/档位词与弹窗卡片语言;\n * card.locale 显式传入时以其为准。未知选项运行时抛错(拼写错误不静默) */\n locale?: \"zh\" | \"en\";\n /** 点击站点时以弹窗展示报文卡片(缺省开启) */\n popup?: boolean;\n /** 传给 renderCard 的选项 */\n card?: Parameters<typeof renderCard>[1];\n /** marker 按四档气象条件着色(unknown/poor/caution/good 圆点替代默认图钉;缺省 false 保持现状)。\n * 判据为本库自拟的扫视启发式(初稿待审,见 conditionOf 注释)——不对应任何官方飞行天气分类,\n * 不得用作运行判据;\n * 开启时 tooltip 同时追加一行要素摘要(能见度/天气/最差云;NIL 与关键组全缺测站显示「缺报/数据缺测」),\n * 圆点 aria-label 追加档位词(zh「天气好/差…」/ en「Weather …」,语言随 card.locale) */\n conditionColors?: boolean;\n}\n\n/** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */\nconst ADD_METAR_LAYER_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"locale\",\n \"popup\",\n \"card\",\n \"conditionColors\",\n]);\n\n/**\n * tooltip 纯文本承载:Leaflet 对 string 内容走 innerHTML——宿主自传的 title 是任意串,\n * 经由 textContent 装载(非 HTML 路径)杜绝标记注入;文案渲染不受影响。\n */\nfunction textCarrier(text: string): HTMLElement {\n const el = document.createElement(\"span\");\n el.textContent = text;\n return el;\n}\n\n/** HTML 属性上下文转义(divIcon 的 html 字符串构造用——title/站名不可信面) */\nconst escapeHtml = (text: string): string =>\n text.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n\n/** 四档条件色(与卡片 mw-danger/mw-caution 同族色相:灰=不明、红=差、琥珀=注意、绿=好) */\nconst TIER_COLORS: Record<ConditionTier, string> = {\n unknown: \"#8a94a0\",\n poor: \"#d05656\",\n caution: \"#e0a13c\",\n good: \"#3aa657\",\n};\n\ntype ConditionTier = \"unknown\" | \"poor\" | \"caution\" | \"good\";\n\n/** 档位可读名(aria-label 追加词——a11y 1.4.1:档位信息不只靠颜色传达) */\nconst TIER_WORDS: Record<\"zh\" | \"en\", Record<ConditionTier, string>> = {\n zh: { unknown: \"天气不明\", poor: \"天气差\", caution: \"天气注意\", good: \"天气好\" },\n en: {\n unknown: \"Weather unknown\",\n poor: \"Weather poor\",\n caution: \"Weather caution\",\n good: \"Weather good\",\n },\n};\n\n/** 降水类现象(caution 判据用,与 WMO 4678 降水族对应——RA/SN/SG/PL/GS/IC/DZ/UP) */\nconst PRECIP_PHENOMENA: ReadonlySet<string> = new Set([\n \"RA\",\n \"SN\",\n \"SG\",\n \"PL\",\n \"GS\",\n \"IC\",\n \"DZ\",\n \"UP\",\n]);\n\n/** 风速折米/秒(阵风判据统一单位:kt ×0.514444、kmh ÷3.6、mps ×1) */\nconst toMps = (value: number, unit: \"kt\" | \"mps\" | \"kmh\"): number =>\n unit === \"kt\" ? value * 0.514444 : unit === \"kmh\" ? value / 3.6 : value;\n\n/**\n * 四档气象条件分级(判据本库自拟、初稿待审——显示层扫视启发式):\n * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。\n * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:\n * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)\n * - poor(红)= 能见度 < 1500 m,或 BKN/OVC 云层(含垂直能见度)云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)\n * 或现象含 GR/VA,或冻降水(FZ 描述符族,冻雨/冻毛毛雨),或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭\n * - caution(琥珀)= 能见度 1500–5000 m(能见度分档取国内通行 1500/5000 m 口径),或 BKN/OVC 云底 1000–3000 ft,或任何降水族(RA/SN 等),\n * 或 FZ 描述符以外的结冰现象,或阵风 15–25 m/s\n * - good(绿)= 其余(含 CAVOK)\n * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。\n */\nfunction conditionOf(report: MetarReport): ConditionTier {\n if (report.nil === true) return \"unknown\";\n const v = toValues(report);\n // 关键组全缺测:能见度与云均缺测(云组每个体均为全缺测形态)且天气缺测/无——判读无从下手,灰而非绿\n const visMissing = report.visibility?.kind === \"missing\";\n const elements = v.clouds?.elements ?? [];\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone) return \"unknown\";\n\n // —— poor 判据(任一命中即红)\n const vis = v.visibility;\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 1500) return \"poor\";\n }\n const ceilings = elements\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .map((e) => e.heightFt.value ?? Number.POSITIVE_INFINITY);\n for (const e of elements) {\n if (e.kind === \"vertical-visibility\")\n ceilings.push(e.heightFt.value ?? Number.POSITIVE_INFINITY);\n }\n const ceiling = ceilings.length > 0 ? Math.min(...ceilings) : Number.POSITIVE_INFINITY;\n if (ceiling < 1000) return \"poor\";\n for (const g of v.weather ?? []) {\n const thunderstorm = g.descriptor === \"TS\"; // TS 族:任何雷暴(含 VCTS 邻近雷暴)\n const hailOrAsh = g.phenomena.includes(\"GR\") || g.phenomena.includes(\"VA\");\n const freezing = g.descriptor === \"FZ\"; // 冻降水族(FZRA/FZDZ 等)——危害与雷暴同级,2026-09-22 运行视角评审升红\n const heavyPrecip =\n g.intensity === \"+\" &&\n (g.descriptor === \"SH\" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));\n if (thunderstorm || hailOrAsh || freezing || heavyPrecip) return \"poor\";\n }\n const gust = v.wind?.gust;\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 25) return \"poor\";\n const convective = elements.some(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n if (convective) return \"poor\";\n if (v.runwayStates.some((st) => st.closed === true)) return \"poor\";\n\n // —— caution 判据(任一命中即琥珀)\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 5000) return \"caution\";\n }\n if (ceiling < 3000) return \"caution\";\n for (const g of v.weather ?? []) {\n const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));\n if (precip) return \"caution\";\n }\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 15) return \"caution\";\n return \"good\";\n}\n\n/** 天气组显示码:span 在位取原码,缺席由 IR 重建(摘要行的要素原样口径) */\nfunction weatherCode(report: MetarReport, g: WeatherGroup): string {\n return g.span === undefined\n ? `${g.proximity ? \"VC\" : \"\"}${g.intensity ?? \"\"}${g.descriptor ?? \"\"}${g.phenomena.join(\"\")}`\n : report.raw.slice(g.span.start, g.span.end);\n}\n\n/** 云层显示码:span 在位取原码,缺席由 IR 重建(缺测位还原为 ///) */\nfunction cloudCode(report: MetarReport, layer: CloudElement): string {\n if (layer.span === undefined) {\n if (layer.kind === \"vertical-visibility\") {\n return `VV${layer.heightFt.value === null ? \"///\" : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\")}`;\n }\n const height =\n layer.heightFt.value === null\n ? \"///\"\n : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\");\n return `${layer.amount ?? \"///\"}${height}${layer.convective ?? \"\"}`;\n }\n return report.raw.slice(layer.span.start, layer.span.end);\n}\n\n/**\n * tooltip 第二行要素摘要(扫视初筛):`2500m +TSRA BKN030CB` 式——\n * 能见度 / 最显著天气(优先 TS/GR/+ 强度族)/ 最差云(对流云优先,否则最低 BKN/OVC,VV 兜底)。\n * NIL 站显示「缺报(NIL)」、关键组全缺测站显示「数据缺测」(与 conditionOf 的 unknown 判据同款口径)。\n */\nfunction summarizeReport(report: MetarReport, locale: \"zh\" | \"en\"): string {\n if (report.nil === true) return locale === \"en\" ? \"No report (NIL)\" : \"缺报(NIL)\";\n if (report.cavok) return \"CAVOK\";\n const v = toValues(report);\n // 关键组全缺测(能见度与云均缺测且天气缺测/无):要素摘要无从拼起,显示缺测占位而非空行\n const elements = v.clouds?.elements ?? [];\n const visMissing = report.visibility?.kind === \"missing\";\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone)\n return locale === \"en\" ? \"Data missing\" : \"数据缺测\";\n const parts: string[] = [];\n const vis = v.visibility;\n if (vis !== undefined) {\n parts.push(\n vis.unit === \"m\"\n ? vis.exact\n ? `${vis.value} m`\n : \"≥10 km\"\n : vis.beyond === \"below\"\n ? `<${vis.value} SM`\n : vis.beyond === \"above\"\n ? `>${vis.value} SM`\n : `${vis.value} SM`,\n );\n }\n const weather = v.weather ?? [];\n const significant =\n weather.find(\n (g) => g.descriptor === \"TS\" || g.phenomena.includes(\"GR\") || g.intensity === \"+\",\n ) ?? weather[0];\n if (significant !== undefined) parts.push(weatherCode(report, significant));\n const layers = v.clouds?.elements ?? [];\n const convective = layers.find(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n const ceilingLayer = layers\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .reduce<Extract<CloudElement, { kind: \"layer\" }> | undefined>(\n (lowest, e) =>\n lowest === undefined ||\n (e.heightFt.value ?? Number.POSITIVE_INFINITY) <\n (lowest.heightFt.value ?? Number.POSITIVE_INFINITY)\n ? e\n : lowest,\n undefined,\n );\n const vertical = layers.find((e) => e.kind === \"vertical-visibility\");\n const worstCloud = convective ?? ceilingLayer ?? vertical;\n if (worstCloud !== undefined) parts.push(cloudCode(report, worstCloud));\n else if (v.clouds?.clear !== undefined) parts.push(v.clouds.clear.code);\n return parts.join(\" \");\n}\n\n/** tooltip 内容节点:标题行 + 要素摘要行(两行皆纯文本装载;locale 决定摘要占位词语言) */\nfunction tooltipContent(\n item: MetarLayerItem,\n withSummary: boolean,\n locale: \"zh\" | \"en\",\n): HTMLElement {\n const container = document.createElement(\"span\");\n container.append(textCarrier(item.title ?? item.report.station));\n if (withSummary) {\n container.append(\n document.createElement(\"br\"),\n textCarrier(summarizeReport(item.report, locale)),\n );\n }\n return container;\n}\n\n/** Escape 关闭已开弹窗的监听只挂一次/地图(多图层叠加不重复绑定) */\nconst escapeBoundMaps = new WeakSet<Leaflet.Map>();\n\n/**\n * Put a set of report stations onto a Leaflet map: markers + tooltips + card popups. Returns a removable layer group.\n * 把一组报文站点挂上地图:标记 + tooltip + 卡片弹窗。返回可整体移除的图层组。\n * @param map - A Leaflet map instance. Leaflet 地图实例。\n * @param items - Stations to plot (report + position + title). 待上图的站点列表。\n * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。\n *\n * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR\n * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。\n */\n\nexport async function addMetarLayer(\n map: Leaflet.Map,\n items: readonly MetarLayerItem[],\n options: AddMetarLayerOptions = {},\n): Promise<Leaflet.LayerGroup> {\n // 未知选项运行时抛错:拼写错误的选项被静默忽略 = 显示语言/行为悄悄不符预期(2026-09-15\n // 五角色评测实测:顶层 locale 此前被静默忽略,popup 整卡仍中文)。中文提示 = v0.1 message 语言契约。\n for (const key of Object.keys(options)) {\n if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) {\n throw new Error(\n `addMetarLayer 收到未知选项 \"${key}\"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`,\n );\n }\n }\n // 语言解析单一出口:card.locale 显式传入 > 顶层 locale 速记 > zh;非法值清晰报错不裸崩\n const locale = options.card?.locale ?? options.locale ?? \"zh\";\n if (locale !== \"zh\" && locale !== \"en\") {\n // never 收窄后的宽化中转:模板表达式不接受 never 字面量类型\n const bad: string = locale;\n throw new Error(`addMetarLayer 的 locale 选项值 \"${bad}\" 不受支持(可用:\"zh\" | \"en\")`);\n }\n const L = await loadLeaflet();\n const group = L.layerGroup();\n for (const item of items) {\n const name = item.title ?? item.report.station;\n // alt 写入图标 img 的 alt 属性:marker 在读屏下 role=button,可访问名称 = 站名(WCAG 4.1.2)\n let marker: Leaflet.Marker;\n if (options.conditionColors === true) {\n // 圆点 divIcon 无 img——alt 失效,改以 role=img + aria-label 保住可访问名称(内容经 HTML 转义);\n // aria-label 追加档位词(a11y 1.4.1:档位不只靠颜色传达;语言随 card.locale,缺省中文)\n const tier = conditionOf(item.report);\n const label = `${name} · ${TIER_WORDS[locale][tier]}`;\n marker = L.marker(item.position, {\n icon: L.divIcon({\n className: \"mw-cond-icon\",\n html: `<span role=\"img\" aria-label=\"${escapeHtml(label)}\" class=\"mw-dot mw-dot-${tier}\" style=\"display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)\"></span>`,\n iconSize: [14, 14],\n iconAnchor: [7, 7],\n }),\n });\n } else {\n marker = L.marker(item.position, { alt: name });\n }\n marker.bindTooltip(tooltipContent(item, options.conditionColors === true, locale));\n if (options.popup ?? true) {\n // 弹窗内容是 renderCard 的 DOM 元素(createElement/textContent 构建),本就走非 HTML 路径;\n // maxWidth 420 = 卡片设计宽(Leaflet 缺省 300 会压窄)\n // 元数据联表得到的站名随弹窗进卡片站名行(title = \"ICAO 站名\",剥掉 ICAO 前缀);调用方显式传入时以其为准\n const cardOpts: Parameters<typeof renderCard>[1] = { ...options.card };\n if (cardOpts.locale === undefined && options.locale !== undefined) {\n cardOpts.locale = options.locale;\n }\n const stationName =\n item.title !== undefined && item.title.startsWith(`${item.report.station} `)\n ? item.title.slice(item.report.station.length + 1)\n : undefined;\n if (cardOpts.stationTitle === undefined && stationName !== undefined) {\n cardOpts.stationTitle = stationName;\n }\n marker.bindPopup(renderCard(item.report, cardOpts), { maxWidth: 420 });\n marker.on(\"popupopen\", () => {\n // 触屏双浮层消除:弹窗打开即收起 tooltip\n marker.closeTooltip();\n // 焦点移入弹窗首个可聚焦元素(Leaflet 关闭按钮)——键盘与读屏可直达\n const focusTarget = marker\n .getPopup()\n ?.getElement()\n ?.querySelector<HTMLElement>(\"a.leaflet-popup-close-button, button, [href]\");\n focusTarget?.focus();\n });\n }\n marker.addTo(group);\n }\n if (!escapeBoundMaps.has(map)) {\n escapeBoundMaps.add(map);\n // 地图容器键盘路径:Escape 关闭已开弹窗(closePopup 对未开弹窗是空操作)\n map.getContainer().addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") map.closePopup();\n });\n }\n group.addTo(map);\n return group;\n}\n"],"mappings":";;;;;;;;;;AAmBA,IAAI;AACJ,MAAM,oBAAwD,kBAAkB,OAAO;;AAoCvF,MAAM,8CAAmD,IAAI,IAAI;CAC/D;CACA;CACA;CACA;AACF,CAAC;;;;;AAMD,SAAS,YAAY,MAA2B;CAC9C,MAAM,KAAK,SAAS,cAAc,MAAM;CACxC,GAAG,cAAc;CACjB,OAAO;AACT;;AAGA,MAAM,cAAc,SAClB,KAAK,QAAQ,aAAa,OAAO;CAC/B,QAAQ,IAAR;EACE,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,SACE,OAAO;CACX;AACF,CAAC;;AAGH,MAAM,cAA6C;CACjD,SAAS;CACT,MAAM;CACN,SAAS;CACT,MAAM;AACR;;AAKA,MAAM,aAAiE;CACrE,IAAI;EAAE,SAAS;EAAQ,MAAM;EAAO,SAAS;EAAQ,MAAM;CAAM;CACjE,IAAI;EACF,SAAS;EACT,MAAM;EACN,SAAS;EACT,MAAM;CACR;AACF;;AAGA,MAAM,mCAAwC,IAAI,IAAI;CACpD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAM,SAAS,OAAe,SAC5B,SAAS,OAAO,QAAQ,UAAW,SAAS,QAAQ,QAAQ,MAAM;;;;;;;;;;;;;AAcpE,SAAS,YAAY,QAAoC;CACvD,IAAI,OAAO,QAAQ,MAAM,OAAO;CAChC,MAAM,KAAA,GAAIA,eAAAA,SAAAA,CAAS,MAAM;CAEzB,MAAM,aAAa,OAAO,YAAY,SAAS;CAC/C,MAAM,WAAW,EAAE,QAAQ,YAAY,CAAC;CACxC,MAAM,mBACJ,CAAC,OAAO,SACR,SAAS,OACN,MAAM,EAAE,SAAS,UAAU,SAAS,EAAE,SAAS,yBAAyB,EAAE,WAAW,KACxF;CACF,MAAM,uBACJ,OAAO,YAAY,KAAA,KACnB,OAAO,QAAQ,SAAS,aACvB,OAAO,QAAQ,SAAS,WAAW,OAAO,QAAQ,MAAM,WAAW;CACtE,IAAI,cAAc,oBAAoB,sBAAsB,OAAO;CAGnE,MAAM,MAAM,EAAE;CACd,IAAI,QAAQ,KAAA,GACQ;OAAA,IAAI,SAAS,MAAM,IAAI,QAAQ,IAAI,QAAQ,YAC7C,MAAM,OAAO;CAAA;CAE/B,MAAM,WAAW,SACd,QAAQ,MAAqD,EAAE,SAAS,OAAO,CAAC,CAChF,QAAQ,MAAM,EAAE,WAAW,SAAS,EAAE,WAAW,KAAK,CAAC,CACvD,KAAK,MAAM,EAAE,SAAS,SAAS,OAAO,iBAAiB;CAC1D,KAAK,MAAM,KAAK,UACd,IAAI,EAAE,SAAS,uBACb,SAAS,KAAK,EAAE,SAAS,SAAS,OAAO,iBAAiB;CAE9D,MAAM,UAAU,SAAS,SAAS,IAAI,KAAK,IAAI,GAAG,QAAQ,IAAI,OAAO;CACrE,IAAI,UAAU,KAAM,OAAO;CAC3B,KAAK,MAAM,KAAK,EAAE,WAAW,CAAC,GAAG;EAC/B,MAAM,eAAe,EAAE,eAAe;EACtC,MAAM,YAAY,EAAE,UAAU,SAAS,IAAI,KAAK,EAAE,UAAU,SAAS,IAAI;EACzE,MAAM,WAAW,EAAE,eAAe;EAClC,MAAM,cACJ,EAAE,cAAc,QACf,EAAE,eAAe,QAAQ,EAAE,UAAU,MAAM,MAAM,iBAAiB,IAAI,CAAC,CAAC;EAC3E,IAAI,gBAAgB,aAAa,YAAY,aAAa,OAAO;CACnE;CACA,MAAM,OAAO,EAAE,MAAM;CACrB,IAAI,SAAS,KAAA,KAAa,MAAM,KAAK,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO;CAKrE,IAJmB,SAAS,MACzB,MACC,EAAE,SAAS,WAAW,EAAE,eAAe,KAAA,CAE9B,GAAG,OAAO;CACvB,IAAI,EAAE,aAAa,MAAM,OAAO,GAAG,WAAW,IAAI,GAAG,OAAO;CAG5D,IAAI,QAAQ,KAAA,GACQ;OAAA,IAAI,SAAS,MAAM,IAAI,QAAQ,IAAI,QAAQ,YAC7C,KAAM,OAAO;CAAA;CAE/B,IAAI,UAAU,KAAM,OAAO;CAC3B,KAAK,MAAM,KAAK,EAAE,WAAW,CAAC,GAE5B,IADe,EAAE,UAAU,MAAM,MAAM,iBAAiB,IAAI,CAAC,CACpD,GAAG,OAAO;CAErB,IAAI,SAAS,KAAA,KAAa,MAAM,KAAK,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO;CACrE,OAAO;AACT;;AAGA,SAAS,YAAY,QAAqB,GAAyB;CACjE,OAAO,EAAE,SAAS,KAAA,IACd,GAAG,EAAE,YAAY,OAAO,KAAK,EAAE,aAAa,KAAK,EAAE,cAAc,KAAK,EAAE,UAAU,KAAK,EAAE,MACzF,OAAO,IAAI,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,GAAG;AAC/C;;AAGA,SAAS,UAAU,QAAqB,OAA6B;CACnE,IAAI,MAAM,SAAS,KAAA,GAAW;EAC5B,IAAI,MAAM,SAAS,uBACjB,OAAO,KAAK,MAAM,SAAS,UAAU,OAAO,QAAQ,OAAO,KAAK,MAAM,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EAEpH,MAAM,SACJ,MAAM,SAAS,UAAU,OACrB,QACA,OAAO,KAAK,MAAM,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EACpE,OAAO,GAAG,MAAM,UAAU,QAAQ,SAAS,MAAM,cAAc;CACjE;CACA,OAAO,OAAO,IAAI,MAAM,MAAM,KAAK,OAAO,MAAM,KAAK,GAAG;AAC1D;;;;;;AAOA,SAAS,gBAAgB,QAAqB,QAA6B;CACzE,IAAI,OAAO,QAAQ,MAAM,OAAO,WAAW,OAAO,oBAAoB;CACtE,IAAI,OAAO,OAAO,OAAO;CACzB,MAAM,KAAA,GAAIA,eAAAA,SAAAA,CAAS,MAAM;CAEzB,MAAM,WAAW,EAAE,QAAQ,YAAY,CAAC;CACxC,MAAM,aAAa,OAAO,YAAY,SAAS;CAC/C,MAAM,mBACJ,CAAC,OAAO,SACR,SAAS,OACN,MAAM,EAAE,SAAS,UAAU,SAAS,EAAE,SAAS,yBAAyB,EAAE,WAAW,KACxF;CACF,MAAM,uBACJ,OAAO,YAAY,KAAA,KACnB,OAAO,QAAQ,SAAS,aACvB,OAAO,QAAQ,SAAS,WAAW,OAAO,QAAQ,MAAM,WAAW;CACtE,IAAI,cAAc,oBAAoB,sBACpC,OAAO,WAAW,OAAO,iBAAiB;CAC5C,MAAM,QAAkB,CAAC;CACzB,MAAM,MAAM,EAAE;CACd,IAAI,QAAQ,KAAA,GACV,MAAM,KACJ,IAAI,SAAS,MACT,IAAI,QACF,GAAG,IAAI,MAAM,MACb,WACF,IAAI,WAAW,UACb,IAAI,IAAI,MAAM,OACd,IAAI,WAAW,UACb,IAAI,IAAI,MAAM,OACd,GAAG,IAAI,MAAM,IACvB;CAEF,MAAM,UAAU,EAAE,WAAW,CAAC;CAC9B,MAAM,cACJ,QAAQ,MACL,MAAM,EAAE,eAAe,QAAQ,EAAE,UAAU,SAAS,IAAI,KAAK,EAAE,cAAc,GAChF,KAAK,QAAQ;CACf,IAAI,gBAAgB,KAAA,GAAW,MAAM,KAAK,YAAY,QAAQ,WAAW,CAAC;CAC1E,MAAM,SAAS,EAAE,QAAQ,YAAY,CAAC;CACtC,MAAM,aAAa,OAAO,MACvB,MACC,EAAE,SAAS,WAAW,EAAE,eAAe,KAAA,CAC3C;CACA,MAAM,eAAe,OAClB,QAAQ,MAAqD,EAAE,SAAS,OAAO,CAAC,CAChF,QAAQ,MAAM,EAAE,WAAW,SAAS,EAAE,WAAW,KAAK,CAAC,CACvD,QACE,QAAQ,MACP,WAAW,KAAA,MACV,EAAE,SAAS,SAAS,OAAO,sBACzB,OAAO,SAAS,SAAS,OAAO,qBAC/B,IACA,QACN,KAAA,CACF;CACF,MAAM,WAAW,OAAO,MAAM,MAAM,EAAE,SAAS,qBAAqB;CACpE,MAAM,aAAa,cAAc,gBAAgB;CACjD,IAAI,eAAe,KAAA,GAAW,MAAM,KAAK,UAAU,QAAQ,UAAU,CAAC;MACjE,IAAI,EAAE,QAAQ,UAAU,KAAA,GAAW,MAAM,KAAK,EAAE,OAAO,MAAM,IAAI;CACtE,OAAO,MAAM,KAAK,GAAG;AACvB;;AAGA,SAAS,eACP,MACA,aACA,QACa;CACb,MAAM,YAAY,SAAS,cAAc,MAAM;CAC/C,UAAU,OAAO,YAAY,KAAK,SAAS,KAAK,OAAO,OAAO,CAAC;CAC/D,IAAI,aACF,UAAU,OACR,SAAS,cAAc,IAAI,GAC3B,YAAY,gBAAgB,KAAK,QAAQ,MAAM,CAAC,CAClD;CAEF,OAAO;AACT;;AAGA,MAAM,kCAAkB,IAAI,QAAqB;;;;;;;;;;;AAajD,eAAsB,cACpB,KACA,OACA,UAAgC,CAAC,GACJ;CAG7B,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,GACnC,IAAI,CAAC,4BAA4B,IAAI,GAAG,GACtC,MAAM,IAAI,MACR,yBAAyB,IAAI,qEAC/B;CAIJ,MAAM,SAAS,QAAQ,MAAM,UAAU,QAAQ,UAAU;CACzD,IAAI,WAAW,QAAQ,WAAW,MAGhC,MAAM,IAAI,MAAM,+BAA+BC,OAAI,uBAAuB;CAE5E,MAAM,IAAI,MAAM,YAAY;CAC5B,MAAM,QAAQ,EAAE,WAAW;CAC3B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,KAAK,SAAS,KAAK,OAAO;EAEvC,IAAI;EACJ,IAAI,QAAQ,oBAAoB,MAAM;GAGpC,MAAM,OAAO,YAAY,KAAK,MAAM;GACpC,MAAM,QAAQ,GAAG,KAAK,KAAK,WAAW,OAAO,CAAC;GAC9C,SAAS,EAAE,OAAO,KAAK,UAAU,EAC/B,MAAM,EAAE,QAAQ;IACd,WAAW;IACX,MAAM,gCAAgC,WAAW,KAAK,EAAE,yBAAyB,KAAK,oFAAoF,YAAY,MAAM;IAC5L,UAAU,CAAC,IAAI,EAAE;IACjB,YAAY,CAAC,GAAG,CAAC;GACnB,CAAC,EACH,CAAC;EACH,OACE,SAAS,EAAE,OAAO,KAAK,UAAU,EAAE,KAAK,KAAK,CAAC;EAEhD,OAAO,YAAY,eAAe,MAAM,QAAQ,oBAAoB,MAAM,MAAM,CAAC;EACjF,IAAI,QAAQ,SAAS,MAAM;GAIzB,MAAM,WAA6C,EAAE,GAAG,QAAQ,KAAK;GACrE,IAAI,SAAS,WAAW,KAAA,KAAa,QAAQ,WAAW,KAAA,GACtD,SAAS,SAAS,QAAQ;GAE5B,MAAM,cACJ,KAAK,UAAU,KAAA,KAAa,KAAK,MAAM,WAAW,GAAG,KAAK,OAAO,QAAQ,EAAE,IACvE,KAAK,MAAM,MAAM,KAAK,OAAO,QAAQ,SAAS,CAAC,IAC/C,KAAA;GACN,IAAI,SAAS,iBAAiB,KAAA,KAAa,gBAAgB,KAAA,GACzD,SAAS,eAAe;GAE1B,OAAO,WAAA,GAAUC,iBAAAA,WAAAA,CAAW,KAAK,QAAQ,QAAQ,GAAG,EAAE,UAAU,IAAI,CAAC;GACrE,OAAO,GAAG,mBAAmB;IAE3B,OAAO,aAAa;IAMpB,CAJoB,OACjB,SAAS,CAAC,EACT,WAAW,CAAC,EACZ,cAA2B,8CAA8C,EAAA,EAChE,MAAM;GACrB,CAAC;EACH;EACA,OAAO,MAAM,KAAK;CACpB;CACA,IAAI,CAAC,gBAAgB,IAAI,GAAG,GAAG;EAC7B,gBAAgB,IAAI,GAAG;EAEvB,IAAI,aAAa,CAAC,CAAC,iBAAiB,YAAY,UAAU;GACxD,IAAI,MAAM,QAAQ,UAAU,IAAI,WAAW;EAC7C,CAAC;CACH;CACA,MAAM,MAAM,GAAG;CACf,OAAO;AACT"}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import * as L from "leaflet";
1
+ import * as Leaflet from "leaflet";
2
2
  import { MetarReport } from "@metweave/core";
3
3
  import { renderCard } from "@metweave/render";
4
4
  //#region src/index.d.ts
@@ -39,7 +39,10 @@ export interface AddMetarLayerOptions {
39
39
  * @param map - A Leaflet map instance. Leaflet 地图实例。
40
40
  * @param items - Stations to plot (report + position + title). 待上图的站点列表。
41
41
  * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。
42
+ *
43
+ * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR
44
+ * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。
42
45
  */
43
- export declare function addMetarLayer(map: L.Map, items: readonly MetarLayerItem[], options?: AddMetarLayerOptions): L.LayerGroup;
46
+ export declare function addMetarLayer(map: Leaflet.Map, items: readonly MetarLayerItem[], options?: AddMetarLayerOptions): Promise<Leaflet.LayerGroup>;
44
47
  //#endregion
45
48
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import * as L from "leaflet";
2
1
  import { MetarReport } from "@metweave/core";
3
2
  import { renderCard } from "@metweave/render";
3
+ import * as Leaflet from "leaflet";
4
4
  //#region src/index.d.ts
5
5
  /**
6
6
  * One station on the map: parsed report IR + WGS-84 position + optional tooltip title.
@@ -39,7 +39,10 @@ export interface AddMetarLayerOptions {
39
39
  * @param map - A Leaflet map instance. Leaflet 地图实例。
40
40
  * @param items - Stations to plot (report + position + title). 待上图的站点列表。
41
41
  * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。
42
+ *
43
+ * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR
44
+ * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。
42
45
  */
43
- export declare function addMetarLayer(map: L.Map, items: readonly MetarLayerItem[], options?: AddMetarLayerOptions): L.LayerGroup;
46
+ export declare function addMetarLayer(map: Leaflet.Map, items: readonly MetarLayerItem[], options?: AddMetarLayerOptions): Promise<Leaflet.LayerGroup>;
44
47
  //#endregion
45
48
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,15 +1,14 @@
1
- import * as L from "leaflet";
2
1
  import { toValues } from "@metweave/core";
3
2
  import { renderCard } from "@metweave/render";
4
3
  //#region src/index.ts
5
4
  /**
6
- * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。
7
- * The Leaflet adapter: puts metweave report cards onto a Leaflet map.
8
- *
9
- * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。
10
- * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,
11
- * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。
5
+ * leaflet 惰性装载:import 本包时不再静态加载 leaflet——peer 库是浏览器专属实现,
6
+ * 顶层静态 import 会让 Node/SSR 的模块图在求值期即崩(window is not defined,
7
+ * 2026-09-16 五方实测评测实锤:ESM/CJS 双格式 import 均炸)。改为首次 addMetarLayer
8
+ * 调用时动态 import 并缓存;类型面仍由顶层 import type 提供,宿主类型检查零影响。
12
9
  */
10
+ let leafletModule;
11
+ const loadLeaflet = () => leafletModule ??= import("leaflet");
13
12
  /** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */
14
13
  const ADD_METAR_LAYER_OPTION_KEYS = /* @__PURE__ */ new Set([
15
14
  "locale",
@@ -76,10 +75,10 @@ const toMps = (value, unit) => unit === "kt" ? value * .514444 : unit === "kmh"
76
75
  * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。
77
76
  * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:
78
77
  * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)
79
- * - poor(红)= 能见度 < 1500 m,或云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)
80
- * 或现象含 GR/VA,或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭
81
- * - caution(琥珀)= 能见度 1500–4800 m,或云底 1000–3000 ft,或任何降水族(RA/SN 等),
82
- * 或 FZ 族(结冰),或阵风 15–25 m/s
78
+ * - poor(红)= 能见度 < 1500 m,或 BKN/OVC 云层(含垂直能见度)云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)
79
+ * 或现象含 GR/VA,或冻降水(FZ 描述符族,冻雨/冻毛毛雨),或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭
80
+ * - caution(琥珀)= 能见度 1500–5000 m(能见度分档取国内通行 1500/5000 m 口径),或 BKN/OVC 云底 1000–3000 ft,或任何降水族(RA/SN 等),
81
+ * 或 FZ 描述符以外的结冰现象,或阵风 15–25 m/s
83
82
  * - good(绿)= 其余(含 CAVOK)
84
83
  * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。
85
84
  */
@@ -102,22 +101,19 @@ function conditionOf(report) {
102
101
  for (const g of v.weather ?? []) {
103
102
  const thunderstorm = g.descriptor === "TS";
104
103
  const hailOrAsh = g.phenomena.includes("GR") || g.phenomena.includes("VA");
104
+ const freezing = g.descriptor === "FZ";
105
105
  const heavyPrecip = g.intensity === "+" && (g.descriptor === "SH" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));
106
- if (thunderstorm || hailOrAsh || heavyPrecip) return "poor";
106
+ if (thunderstorm || hailOrAsh || freezing || heavyPrecip) return "poor";
107
107
  }
108
108
  const gust = v.wind?.gust;
109
109
  if (gust !== void 0 && toMps(gust.value, gust.unit) >= 25) return "poor";
110
110
  if (elements.some((e) => e.kind === "layer" && e.convective !== void 0)) return "poor";
111
111
  if (v.runwayStates.some((st) => st.closed === true)) return "poor";
112
112
  if (vis !== void 0) {
113
- if ((vis.unit === "m" ? vis.value : vis.value * 1609.344) < 4800) return "caution";
113
+ if ((vis.unit === "m" ? vis.value : vis.value * 1609.344) < 5e3) return "caution";
114
114
  }
115
115
  if (ceiling < 3e3) return "caution";
116
- for (const g of v.weather ?? []) {
117
- const freezing = g.descriptor === "FZ";
118
- const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));
119
- if (freezing || precip) return "caution";
120
- }
116
+ for (const g of v.weather ?? []) if (g.phenomena.some((p) => PRECIP_PHENOMENA.has(p))) return "caution";
121
117
  if (gust !== void 0 && toMps(gust.value, gust.unit) >= 15) return "caution";
122
118
  return "good";
123
119
  }
@@ -178,11 +174,15 @@ const escapeBoundMaps = /* @__PURE__ */ new WeakSet();
178
174
  * @param map - A Leaflet map instance. Leaflet 地图实例。
179
175
  * @param items - Stations to plot (report + position + title). 待上图的站点列表。
180
176
  * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。
177
+ *
178
+ * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR
179
+ * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。
181
180
  */
182
- function addMetarLayer(map, items, options = {}) {
181
+ async function addMetarLayer(map, items, options = {}) {
183
182
  for (const key of Object.keys(options)) if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) throw new Error(`addMetarLayer 收到未知选项 "${key}"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`);
184
183
  const locale = options.card?.locale ?? options.locale ?? "zh";
185
184
  if (locale !== "zh" && locale !== "en") throw new Error(`addMetarLayer 的 locale 选项值 "${locale}" 不受支持(可用:"zh" | "en")`);
185
+ const L = await loadLeaflet();
186
186
  const group = L.layerGroup();
187
187
  for (const item of items) {
188
188
  const name = item.title ?? item.report.station;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["bad"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。\n * The Leaflet adapter: puts metweave report cards onto a Leaflet map.\n *\n * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。\n * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,\n * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。\n */\nimport * as L from \"leaflet\";\nimport type { CloudElement, MetarReport, WeatherGroup } from \"@metweave/core\";\nimport { toValues } from \"@metweave/core\";\nimport { renderCard } from \"@metweave/render\";\n\n/**\n * One station on the map: parsed report IR + WGS-84 position + optional tooltip title.\n * 地图上的一个站点:已解析报文 IR + WGS-84 坐标 + 可选悬浮标题。\n */\nexport interface MetarLayerItem {\n /** 已解析的报文 IR */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon];用 GCJ-02 底图时的偏移由宿主决定是否校正) */\n position: [number, number];\n /** 站点提示(tooltip 文案,缺省用 IR 站名)。按纯文本处理——不解析 HTML,宿主自传任意串无注入面 */\n title?: string;\n}\n\n/**\n * Options for addMetarLayer: popup behavior, pass-through renderCard options, and the condition-color dot mode.\n * addMetarLayer 的选项:弹窗行为、透传 renderCard 的选项、条件色圆点模式。\n */\nexport interface AddMetarLayerOptions {\n /** 显示语言速记:等价 `card.locale`,同时决定 tooltip 摘要/档位词与弹窗卡片语言;\n * card.locale 显式传入时以其为准。未知选项运行时抛错(拼写错误不静默) */\n locale?: \"zh\" | \"en\";\n /** 点击站点时以弹窗展示报文卡片(缺省开启) */\n popup?: boolean;\n /** 传给 renderCard 的选项 */\n card?: Parameters<typeof renderCard>[1];\n /** marker 按四档气象条件着色(unknown/poor/caution/good 圆点替代默认图钉;缺省 false 保持现状)。\n * 判据为本库自拟的扫视启发式(初稿待审,见 conditionOf 注释)——不对应任何官方飞行天气分类,\n * 不得用作运行判据;\n * 开启时 tooltip 同时追加一行要素摘要(能见度/天气/最差云;NIL 与关键组全缺测站显示「缺报/数据缺测」),\n * 圆点 aria-label 追加档位词(zh「天气好/差…」/ en「Weather …」,语言随 card.locale) */\n conditionColors?: boolean;\n}\n\n/** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */\nconst ADD_METAR_LAYER_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"locale\",\n \"popup\",\n \"card\",\n \"conditionColors\",\n]);\n\n/**\n * tooltip 纯文本承载:Leaflet 对 string 内容走 innerHTML——宿主自传的 title 是任意串,\n * 经由 textContent 装载(非 HTML 路径)杜绝标记注入;文案渲染不受影响。\n */\nfunction textCarrier(text: string): HTMLElement {\n const el = document.createElement(\"span\");\n el.textContent = text;\n return el;\n}\n\n/** HTML 属性上下文转义(divIcon 的 html 字符串构造用——title/站名不可信面) */\nconst escapeHtml = (text: string): string =>\n text.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n\n/** 四档条件色(与卡片 mw-danger/mw-caution 同族色相:灰=不明、红=差、琥珀=注意、绿=好) */\nconst TIER_COLORS: Record<ConditionTier, string> = {\n unknown: \"#8a94a0\",\n poor: \"#d05656\",\n caution: \"#e0a13c\",\n good: \"#3aa657\",\n};\n\ntype ConditionTier = \"unknown\" | \"poor\" | \"caution\" | \"good\";\n\n/** 档位可读名(aria-label 追加词——a11y 1.4.1:档位信息不只靠颜色传达) */\nconst TIER_WORDS: Record<\"zh\" | \"en\", Record<ConditionTier, string>> = {\n zh: { unknown: \"天气不明\", poor: \"天气差\", caution: \"天气注意\", good: \"天气好\" },\n en: {\n unknown: \"Weather unknown\",\n poor: \"Weather poor\",\n caution: \"Weather caution\",\n good: \"Weather good\",\n },\n};\n\n/** 降水类现象(caution 判据用,与 WMO 4678 降水族对应——RA/SN/SG/PL/GS/IC/DZ/UP) */\nconst PRECIP_PHENOMENA: ReadonlySet<string> = new Set([\n \"RA\",\n \"SN\",\n \"SG\",\n \"PL\",\n \"GS\",\n \"IC\",\n \"DZ\",\n \"UP\",\n]);\n\n/** 风速折米/秒(阵风判据统一单位:kt ×0.514444、kmh ÷3.6、mps ×1) */\nconst toMps = (value: number, unit: \"kt\" | \"mps\" | \"kmh\"): number =>\n unit === \"kt\" ? value * 0.514444 : unit === \"kmh\" ? value / 3.6 : value;\n\n/**\n * 四档气象条件分级(判据本库自拟、初稿待审——显示层扫视启发式):\n * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。\n * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:\n * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)\n * - poor(红)= 能见度 < 1500 m,或云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)\n * 或现象含 GR/VA,或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭\n * - caution(琥珀)= 能见度 1500–4800 m,或云底 1000–3000 ft,或任何降水族(RA/SN 等),\n * 或 FZ 族(结冰),或阵风 15–25 m/s\n * - good(绿)= 其余(含 CAVOK)\n * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。\n */\nfunction conditionOf(report: MetarReport): ConditionTier {\n if (report.nil === true) return \"unknown\";\n const v = toValues(report);\n // 关键组全缺测:能见度与云均缺测(云组每个体均为全缺测形态)且天气缺测/无——判读无从下手,灰而非绿\n const visMissing = report.visibility?.kind === \"missing\";\n const elements = v.clouds?.elements ?? [];\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone) return \"unknown\";\n\n // —— poor 判据(任一命中即红)\n const vis = v.visibility;\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 1500) return \"poor\";\n }\n const ceilings = elements\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .map((e) => e.heightFt.value ?? Number.POSITIVE_INFINITY);\n for (const e of elements) {\n if (e.kind === \"vertical-visibility\")\n ceilings.push(e.heightFt.value ?? Number.POSITIVE_INFINITY);\n }\n const ceiling = ceilings.length > 0 ? Math.min(...ceilings) : Number.POSITIVE_INFINITY;\n if (ceiling < 1000) return \"poor\";\n for (const g of v.weather ?? []) {\n const thunderstorm = g.descriptor === \"TS\"; // TS 族:任何雷暴(含 VCTS 邻近雷暴)\n const hailOrAsh = g.phenomena.includes(\"GR\") || g.phenomena.includes(\"VA\");\n const heavyPrecip =\n g.intensity === \"+\" &&\n (g.descriptor === \"SH\" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));\n if (thunderstorm || hailOrAsh || heavyPrecip) return \"poor\";\n }\n const gust = v.wind?.gust;\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 25) return \"poor\";\n const convective = elements.some(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n if (convective) return \"poor\";\n if (v.runwayStates.some((st) => st.closed === true)) return \"poor\";\n\n // —— caution 判据(任一命中即琥珀)\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 4800) return \"caution\";\n }\n if (ceiling < 3000) return \"caution\";\n for (const g of v.weather ?? []) {\n const freezing = g.descriptor === \"FZ\";\n const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));\n if (freezing || precip) return \"caution\";\n }\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 15) return \"caution\";\n return \"good\";\n}\n\n/** 天气组显示码:span 在位取原码,缺席由 IR 重建(摘要行的要素原样口径) */\nfunction weatherCode(report: MetarReport, g: WeatherGroup): string {\n return g.span === undefined\n ? `${g.proximity ? \"VC\" : \"\"}${g.intensity ?? \"\"}${g.descriptor ?? \"\"}${g.phenomena.join(\"\")}`\n : report.raw.slice(g.span.start, g.span.end);\n}\n\n/** 云层显示码:span 在位取原码,缺席由 IR 重建(缺测位还原为 ///) */\nfunction cloudCode(report: MetarReport, layer: CloudElement): string {\n if (layer.span === undefined) {\n if (layer.kind === \"vertical-visibility\") {\n return `VV${layer.heightFt.value === null ? \"///\" : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\")}`;\n }\n const height =\n layer.heightFt.value === null\n ? \"///\"\n : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\");\n return `${layer.amount ?? \"///\"}${height}${layer.convective ?? \"\"}`;\n }\n return report.raw.slice(layer.span.start, layer.span.end);\n}\n\n/**\n * tooltip 第二行要素摘要(扫视初筛):`2500m +TSRA BKN030CB` 式——\n * 能见度 / 最显著天气(优先 TS/GR/+ 强度族)/ 最差云(对流云优先,否则最低 BKN/OVC,VV 兜底)。\n * NIL 站显示「缺报(NIL)」、关键组全缺测站显示「数据缺测」(与 conditionOf 的 unknown 判据同款口径)。\n */\nfunction summarizeReport(report: MetarReport, locale: \"zh\" | \"en\"): string {\n if (report.nil === true) return locale === \"en\" ? \"No report (NIL)\" : \"缺报(NIL)\";\n if (report.cavok) return \"CAVOK\";\n const v = toValues(report);\n // 关键组全缺测(能见度与云均缺测且天气缺测/无):要素摘要无从拼起,显示缺测占位而非空行\n const elements = v.clouds?.elements ?? [];\n const visMissing = report.visibility?.kind === \"missing\";\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone)\n return locale === \"en\" ? \"Data missing\" : \"数据缺测\";\n const parts: string[] = [];\n const vis = v.visibility;\n if (vis !== undefined) {\n parts.push(\n vis.unit === \"m\"\n ? vis.exact\n ? `${vis.value} m`\n : \"≥10 km\"\n : vis.beyond === \"below\"\n ? `<${vis.value} SM`\n : vis.beyond === \"above\"\n ? `>${vis.value} SM`\n : `${vis.value} SM`,\n );\n }\n const weather = v.weather ?? [];\n const significant =\n weather.find(\n (g) => g.descriptor === \"TS\" || g.phenomena.includes(\"GR\") || g.intensity === \"+\",\n ) ?? weather[0];\n if (significant !== undefined) parts.push(weatherCode(report, significant));\n const layers = v.clouds?.elements ?? [];\n const convective = layers.find(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n const ceilingLayer = layers\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .reduce<Extract<CloudElement, { kind: \"layer\" }> | undefined>(\n (lowest, e) =>\n lowest === undefined ||\n (e.heightFt.value ?? Number.POSITIVE_INFINITY) <\n (lowest.heightFt.value ?? Number.POSITIVE_INFINITY)\n ? e\n : lowest,\n undefined,\n );\n const vertical = layers.find((e) => e.kind === \"vertical-visibility\");\n const worstCloud = convective ?? ceilingLayer ?? vertical;\n if (worstCloud !== undefined) parts.push(cloudCode(report, worstCloud));\n else if (v.clouds?.clear !== undefined) parts.push(v.clouds.clear.code);\n return parts.join(\" \");\n}\n\n/** tooltip 内容节点:标题行 + 要素摘要行(两行皆纯文本装载;locale 决定摘要占位词语言) */\nfunction tooltipContent(\n item: MetarLayerItem,\n withSummary: boolean,\n locale: \"zh\" | \"en\",\n): HTMLElement {\n const container = document.createElement(\"span\");\n container.append(textCarrier(item.title ?? item.report.station));\n if (withSummary) {\n container.append(\n document.createElement(\"br\"),\n textCarrier(summarizeReport(item.report, locale)),\n );\n }\n return container;\n}\n\n/** Escape 关闭已开弹窗的监听只挂一次/地图(多图层叠加不重复绑定) */\nconst escapeBoundMaps = new WeakSet<L.Map>();\n\n/**\n * Put a set of report stations onto a Leaflet map: markers + tooltips + card popups. Returns a removable layer group.\n * 把一组报文站点挂上地图:标记 + tooltip + 卡片弹窗。返回可整体移除的图层组。\n * @param map - A Leaflet map instance. Leaflet 地图实例。\n * @param items - Stations to plot (report + position + title). 待上图的站点列表。\n * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。\n */\n\nexport function addMetarLayer(\n map: L.Map,\n items: readonly MetarLayerItem[],\n options: AddMetarLayerOptions = {},\n): L.LayerGroup {\n // 未知选项运行时抛错:拼写错误的选项被静默忽略 = 显示语言/行为悄悄不符预期(2026-09-15\n // 五角色评测实测:顶层 locale 此前被静默忽略,popup 整卡仍中文)。中文提示 = v0.1 message 语言契约。\n for (const key of Object.keys(options)) {\n if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) {\n throw new Error(\n `addMetarLayer 收到未知选项 \"${key}\"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`,\n );\n }\n }\n // 语言解析单一出口:card.locale 显式传入 > 顶层 locale 速记 > zh;非法值清晰报错不裸崩\n const locale = options.card?.locale ?? options.locale ?? \"zh\";\n if (locale !== \"zh\" && locale !== \"en\") {\n // never 收窄后的宽化中转:模板表达式不接受 never 字面量类型\n const bad: string = locale;\n throw new Error(`addMetarLayer 的 locale 选项值 \"${bad}\" 不受支持(可用:\"zh\" | \"en\")`);\n }\n const group = L.layerGroup();\n for (const item of items) {\n const name = item.title ?? item.report.station;\n // alt 写入图标 img 的 alt 属性:marker 在读屏下 role=button,可访问名称 = 站名(WCAG 4.1.2)\n let marker: L.Marker;\n if (options.conditionColors === true) {\n // 圆点 divIcon 无 img——alt 失效,改以 role=img + aria-label 保住可访问名称(内容经 HTML 转义);\n // aria-label 追加档位词(a11y 1.4.1:档位不只靠颜色传达;语言随 card.locale,缺省中文)\n const tier = conditionOf(item.report);\n const label = `${name} · ${TIER_WORDS[locale][tier]}`;\n marker = L.marker(item.position, {\n icon: L.divIcon({\n className: \"mw-cond-icon\",\n html: `<span role=\"img\" aria-label=\"${escapeHtml(label)}\" class=\"mw-dot mw-dot-${tier}\" style=\"display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)\"></span>`,\n iconSize: [14, 14],\n iconAnchor: [7, 7],\n }),\n });\n } else {\n marker = L.marker(item.position, { alt: name });\n }\n marker.bindTooltip(tooltipContent(item, options.conditionColors === true, locale));\n if (options.popup ?? true) {\n // 弹窗内容是 renderCard 的 DOM 元素(createElement/textContent 构建),本就走非 HTML 路径;\n // maxWidth 420 = 卡片设计宽(Leaflet 缺省 300 会压窄)\n // 元数据联表得到的站名随弹窗进卡片站名行(title = \"ICAO 站名\",剥掉 ICAO 前缀);调用方显式传入时以其为准\n const cardOpts: Parameters<typeof renderCard>[1] = { ...options.card };\n if (cardOpts.locale === undefined && options.locale !== undefined) {\n cardOpts.locale = options.locale;\n }\n const stationName =\n item.title !== undefined && item.title.startsWith(`${item.report.station} `)\n ? item.title.slice(item.report.station.length + 1)\n : undefined;\n if (cardOpts.stationTitle === undefined && stationName !== undefined) {\n cardOpts.stationTitle = stationName;\n }\n marker.bindPopup(renderCard(item.report, cardOpts), { maxWidth: 420 });\n marker.on(\"popupopen\", () => {\n // 触屏双浮层消除:弹窗打开即收起 tooltip\n marker.closeTooltip();\n // 焦点移入弹窗首个可聚焦元素(Leaflet 关闭按钮)——键盘与读屏可直达\n const focusTarget = marker\n .getPopup()\n ?.getElement()\n ?.querySelector<HTMLElement>(\"a.leaflet-popup-close-button, button, [href]\");\n focusTarget?.focus();\n });\n }\n marker.addTo(group);\n }\n if (!escapeBoundMaps.has(map)) {\n escapeBoundMaps.add(map);\n // 地图容器键盘路径:Escape 关闭已开弹窗(closePopup 对未开弹窗是空操作)\n map.getContainer().addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") map.closePopup();\n });\n }\n group.addTo(map);\n return group;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["bad"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。\n * The Leaflet adapter: puts metweave report cards onto a Leaflet map.\n *\n * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。\n * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,\n * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。\n */\nimport type * as Leaflet from \"leaflet\";\nimport type { CloudElement, MetarReport, WeatherGroup } from \"@metweave/core\";\nimport { toValues } from \"@metweave/core\";\nimport { renderCard } from \"@metweave/render\";\n\n/**\n * leaflet 惰性装载:import 本包时不再静态加载 leaflet——peer 库是浏览器专属实现,\n * 顶层静态 import 会让 Node/SSR 的模块图在求值期即崩(window is not defined,\n * 2026-09-16 五方实测评测实锤:ESM/CJS 双格式 import 均炸)。改为首次 addMetarLayer\n * 调用时动态 import 并缓存;类型面仍由顶层 import type 提供,宿主类型检查零影响。\n */\nlet leafletModule: Promise<typeof import(\"leaflet\")> | undefined;\nconst loadLeaflet = (): Promise<typeof import(\"leaflet\")> => (leafletModule ??= import(\"leaflet\"));\n\n/**\n * One station on the map: parsed report IR + WGS-84 position + optional tooltip title.\n * 地图上的一个站点:已解析报文 IR + WGS-84 坐标 + 可选悬浮标题。\n */\nexport interface MetarLayerItem {\n /** 已解析的报文 IR */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon];用 GCJ-02 底图时的偏移由宿主决定是否校正) */\n position: [number, number];\n /** 站点提示(tooltip 文案,缺省用 IR 站名)。按纯文本处理——不解析 HTML,宿主自传任意串无注入面 */\n title?: string;\n}\n\n/**\n * Options for addMetarLayer: popup behavior, pass-through renderCard options, and the condition-color dot mode.\n * addMetarLayer 的选项:弹窗行为、透传 renderCard 的选项、条件色圆点模式。\n */\nexport interface AddMetarLayerOptions {\n /** 显示语言速记:等价 `card.locale`,同时决定 tooltip 摘要/档位词与弹窗卡片语言;\n * card.locale 显式传入时以其为准。未知选项运行时抛错(拼写错误不静默) */\n locale?: \"zh\" | \"en\";\n /** 点击站点时以弹窗展示报文卡片(缺省开启) */\n popup?: boolean;\n /** 传给 renderCard 的选项 */\n card?: Parameters<typeof renderCard>[1];\n /** marker 按四档气象条件着色(unknown/poor/caution/good 圆点替代默认图钉;缺省 false 保持现状)。\n * 判据为本库自拟的扫视启发式(初稿待审,见 conditionOf 注释)——不对应任何官方飞行天气分类,\n * 不得用作运行判据;\n * 开启时 tooltip 同时追加一行要素摘要(能见度/天气/最差云;NIL 与关键组全缺测站显示「缺报/数据缺测」),\n * 圆点 aria-label 追加档位词(zh「天气好/差…」/ en「Weather …」,语言随 card.locale) */\n conditionColors?: boolean;\n}\n\n/** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */\nconst ADD_METAR_LAYER_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"locale\",\n \"popup\",\n \"card\",\n \"conditionColors\",\n]);\n\n/**\n * tooltip 纯文本承载:Leaflet 对 string 内容走 innerHTML——宿主自传的 title 是任意串,\n * 经由 textContent 装载(非 HTML 路径)杜绝标记注入;文案渲染不受影响。\n */\nfunction textCarrier(text: string): HTMLElement {\n const el = document.createElement(\"span\");\n el.textContent = text;\n return el;\n}\n\n/** HTML 属性上下文转义(divIcon 的 html 字符串构造用——title/站名不可信面) */\nconst escapeHtml = (text: string): string =>\n text.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n\n/** 四档条件色(与卡片 mw-danger/mw-caution 同族色相:灰=不明、红=差、琥珀=注意、绿=好) */\nconst TIER_COLORS: Record<ConditionTier, string> = {\n unknown: \"#8a94a0\",\n poor: \"#d05656\",\n caution: \"#e0a13c\",\n good: \"#3aa657\",\n};\n\ntype ConditionTier = \"unknown\" | \"poor\" | \"caution\" | \"good\";\n\n/** 档位可读名(aria-label 追加词——a11y 1.4.1:档位信息不只靠颜色传达) */\nconst TIER_WORDS: Record<\"zh\" | \"en\", Record<ConditionTier, string>> = {\n zh: { unknown: \"天气不明\", poor: \"天气差\", caution: \"天气注意\", good: \"天气好\" },\n en: {\n unknown: \"Weather unknown\",\n poor: \"Weather poor\",\n caution: \"Weather caution\",\n good: \"Weather good\",\n },\n};\n\n/** 降水类现象(caution 判据用,与 WMO 4678 降水族对应——RA/SN/SG/PL/GS/IC/DZ/UP) */\nconst PRECIP_PHENOMENA: ReadonlySet<string> = new Set([\n \"RA\",\n \"SN\",\n \"SG\",\n \"PL\",\n \"GS\",\n \"IC\",\n \"DZ\",\n \"UP\",\n]);\n\n/** 风速折米/秒(阵风判据统一单位:kt ×0.514444、kmh ÷3.6、mps ×1) */\nconst toMps = (value: number, unit: \"kt\" | \"mps\" | \"kmh\"): number =>\n unit === \"kt\" ? value * 0.514444 : unit === \"kmh\" ? value / 3.6 : value;\n\n/**\n * 四档气象条件分级(判据本库自拟、初稿待审——显示层扫视启发式):\n * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。\n * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:\n * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)\n * - poor(红)= 能见度 < 1500 m,或 BKN/OVC 云层(含垂直能见度)云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)\n * 或现象含 GR/VA,或冻降水(FZ 描述符族,冻雨/冻毛毛雨),或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭\n * - caution(琥珀)= 能见度 1500–5000 m(能见度分档取国内通行 1500/5000 m 口径),或 BKN/OVC 云底 1000–3000 ft,或任何降水族(RA/SN 等),\n * 或 FZ 描述符以外的结冰现象,或阵风 15–25 m/s\n * - good(绿)= 其余(含 CAVOK)\n * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。\n */\nfunction conditionOf(report: MetarReport): ConditionTier {\n if (report.nil === true) return \"unknown\";\n const v = toValues(report);\n // 关键组全缺测:能见度与云均缺测(云组每个体均为全缺测形态)且天气缺测/无——判读无从下手,灰而非绿\n const visMissing = report.visibility?.kind === \"missing\";\n const elements = v.clouds?.elements ?? [];\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone) return \"unknown\";\n\n // —— poor 判据(任一命中即红)\n const vis = v.visibility;\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 1500) return \"poor\";\n }\n const ceilings = elements\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .map((e) => e.heightFt.value ?? Number.POSITIVE_INFINITY);\n for (const e of elements) {\n if (e.kind === \"vertical-visibility\")\n ceilings.push(e.heightFt.value ?? Number.POSITIVE_INFINITY);\n }\n const ceiling = ceilings.length > 0 ? Math.min(...ceilings) : Number.POSITIVE_INFINITY;\n if (ceiling < 1000) return \"poor\";\n for (const g of v.weather ?? []) {\n const thunderstorm = g.descriptor === \"TS\"; // TS 族:任何雷暴(含 VCTS 邻近雷暴)\n const hailOrAsh = g.phenomena.includes(\"GR\") || g.phenomena.includes(\"VA\");\n const freezing = g.descriptor === \"FZ\"; // 冻降水族(FZRA/FZDZ 等)——危害与雷暴同级,2026-09-22 运行视角评审升红\n const heavyPrecip =\n g.intensity === \"+\" &&\n (g.descriptor === \"SH\" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));\n if (thunderstorm || hailOrAsh || freezing || heavyPrecip) return \"poor\";\n }\n const gust = v.wind?.gust;\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 25) return \"poor\";\n const convective = elements.some(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n if (convective) return \"poor\";\n if (v.runwayStates.some((st) => st.closed === true)) return \"poor\";\n\n // —— caution 判据(任一命中即琥珀)\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 5000) return \"caution\";\n }\n if (ceiling < 3000) return \"caution\";\n for (const g of v.weather ?? []) {\n const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));\n if (precip) return \"caution\";\n }\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 15) return \"caution\";\n return \"good\";\n}\n\n/** 天气组显示码:span 在位取原码,缺席由 IR 重建(摘要行的要素原样口径) */\nfunction weatherCode(report: MetarReport, g: WeatherGroup): string {\n return g.span === undefined\n ? `${g.proximity ? \"VC\" : \"\"}${g.intensity ?? \"\"}${g.descriptor ?? \"\"}${g.phenomena.join(\"\")}`\n : report.raw.slice(g.span.start, g.span.end);\n}\n\n/** 云层显示码:span 在位取原码,缺席由 IR 重建(缺测位还原为 ///) */\nfunction cloudCode(report: MetarReport, layer: CloudElement): string {\n if (layer.span === undefined) {\n if (layer.kind === \"vertical-visibility\") {\n return `VV${layer.heightFt.value === null ? \"///\" : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\")}`;\n }\n const height =\n layer.heightFt.value === null\n ? \"///\"\n : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\");\n return `${layer.amount ?? \"///\"}${height}${layer.convective ?? \"\"}`;\n }\n return report.raw.slice(layer.span.start, layer.span.end);\n}\n\n/**\n * tooltip 第二行要素摘要(扫视初筛):`2500m +TSRA BKN030CB` 式——\n * 能见度 / 最显著天气(优先 TS/GR/+ 强度族)/ 最差云(对流云优先,否则最低 BKN/OVC,VV 兜底)。\n * NIL 站显示「缺报(NIL)」、关键组全缺测站显示「数据缺测」(与 conditionOf 的 unknown 判据同款口径)。\n */\nfunction summarizeReport(report: MetarReport, locale: \"zh\" | \"en\"): string {\n if (report.nil === true) return locale === \"en\" ? \"No report (NIL)\" : \"缺报(NIL)\";\n if (report.cavok) return \"CAVOK\";\n const v = toValues(report);\n // 关键组全缺测(能见度与云均缺测且天气缺测/无):要素摘要无从拼起,显示缺测占位而非空行\n const elements = v.clouds?.elements ?? [];\n const visMissing = report.visibility?.kind === \"missing\";\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone)\n return locale === \"en\" ? \"Data missing\" : \"数据缺测\";\n const parts: string[] = [];\n const vis = v.visibility;\n if (vis !== undefined) {\n parts.push(\n vis.unit === \"m\"\n ? vis.exact\n ? `${vis.value} m`\n : \"≥10 km\"\n : vis.beyond === \"below\"\n ? `<${vis.value} SM`\n : vis.beyond === \"above\"\n ? `>${vis.value} SM`\n : `${vis.value} SM`,\n );\n }\n const weather = v.weather ?? [];\n const significant =\n weather.find(\n (g) => g.descriptor === \"TS\" || g.phenomena.includes(\"GR\") || g.intensity === \"+\",\n ) ?? weather[0];\n if (significant !== undefined) parts.push(weatherCode(report, significant));\n const layers = v.clouds?.elements ?? [];\n const convective = layers.find(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n const ceilingLayer = layers\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .reduce<Extract<CloudElement, { kind: \"layer\" }> | undefined>(\n (lowest, e) =>\n lowest === undefined ||\n (e.heightFt.value ?? Number.POSITIVE_INFINITY) <\n (lowest.heightFt.value ?? Number.POSITIVE_INFINITY)\n ? e\n : lowest,\n undefined,\n );\n const vertical = layers.find((e) => e.kind === \"vertical-visibility\");\n const worstCloud = convective ?? ceilingLayer ?? vertical;\n if (worstCloud !== undefined) parts.push(cloudCode(report, worstCloud));\n else if (v.clouds?.clear !== undefined) parts.push(v.clouds.clear.code);\n return parts.join(\" \");\n}\n\n/** tooltip 内容节点:标题行 + 要素摘要行(两行皆纯文本装载;locale 决定摘要占位词语言) */\nfunction tooltipContent(\n item: MetarLayerItem,\n withSummary: boolean,\n locale: \"zh\" | \"en\",\n): HTMLElement {\n const container = document.createElement(\"span\");\n container.append(textCarrier(item.title ?? item.report.station));\n if (withSummary) {\n container.append(\n document.createElement(\"br\"),\n textCarrier(summarizeReport(item.report, locale)),\n );\n }\n return container;\n}\n\n/** Escape 关闭已开弹窗的监听只挂一次/地图(多图层叠加不重复绑定) */\nconst escapeBoundMaps = new WeakSet<Leaflet.Map>();\n\n/**\n * Put a set of report stations onto a Leaflet map: markers + tooltips + card popups. Returns a removable layer group.\n * 把一组报文站点挂上地图:标记 + tooltip + 卡片弹窗。返回可整体移除的图层组。\n * @param map - A Leaflet map instance. Leaflet 地图实例。\n * @param items - Stations to plot (report + position + title). 待上图的站点列表。\n * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。\n *\n * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR\n * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。\n */\n\nexport async function addMetarLayer(\n map: Leaflet.Map,\n items: readonly MetarLayerItem[],\n options: AddMetarLayerOptions = {},\n): Promise<Leaflet.LayerGroup> {\n // 未知选项运行时抛错:拼写错误的选项被静默忽略 = 显示语言/行为悄悄不符预期(2026-09-15\n // 五角色评测实测:顶层 locale 此前被静默忽略,popup 整卡仍中文)。中文提示 = v0.1 message 语言契约。\n for (const key of Object.keys(options)) {\n if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) {\n throw new Error(\n `addMetarLayer 收到未知选项 \"${key}\"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`,\n );\n }\n }\n // 语言解析单一出口:card.locale 显式传入 > 顶层 locale 速记 > zh;非法值清晰报错不裸崩\n const locale = options.card?.locale ?? options.locale ?? \"zh\";\n if (locale !== \"zh\" && locale !== \"en\") {\n // never 收窄后的宽化中转:模板表达式不接受 never 字面量类型\n const bad: string = locale;\n throw new Error(`addMetarLayer 的 locale 选项值 \"${bad}\" 不受支持(可用:\"zh\" | \"en\")`);\n }\n const L = await loadLeaflet();\n const group = L.layerGroup();\n for (const item of items) {\n const name = item.title ?? item.report.station;\n // alt 写入图标 img 的 alt 属性:marker 在读屏下 role=button,可访问名称 = 站名(WCAG 4.1.2)\n let marker: Leaflet.Marker;\n if (options.conditionColors === true) {\n // 圆点 divIcon 无 img——alt 失效,改以 role=img + aria-label 保住可访问名称(内容经 HTML 转义);\n // aria-label 追加档位词(a11y 1.4.1:档位不只靠颜色传达;语言随 card.locale,缺省中文)\n const tier = conditionOf(item.report);\n const label = `${name} · ${TIER_WORDS[locale][tier]}`;\n marker = L.marker(item.position, {\n icon: L.divIcon({\n className: \"mw-cond-icon\",\n html: `<span role=\"img\" aria-label=\"${escapeHtml(label)}\" class=\"mw-dot mw-dot-${tier}\" style=\"display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)\"></span>`,\n iconSize: [14, 14],\n iconAnchor: [7, 7],\n }),\n });\n } else {\n marker = L.marker(item.position, { alt: name });\n }\n marker.bindTooltip(tooltipContent(item, options.conditionColors === true, locale));\n if (options.popup ?? true) {\n // 弹窗内容是 renderCard 的 DOM 元素(createElement/textContent 构建),本就走非 HTML 路径;\n // maxWidth 420 = 卡片设计宽(Leaflet 缺省 300 会压窄)\n // 元数据联表得到的站名随弹窗进卡片站名行(title = \"ICAO 站名\",剥掉 ICAO 前缀);调用方显式传入时以其为准\n const cardOpts: Parameters<typeof renderCard>[1] = { ...options.card };\n if (cardOpts.locale === undefined && options.locale !== undefined) {\n cardOpts.locale = options.locale;\n }\n const stationName =\n item.title !== undefined && item.title.startsWith(`${item.report.station} `)\n ? item.title.slice(item.report.station.length + 1)\n : undefined;\n if (cardOpts.stationTitle === undefined && stationName !== undefined) {\n cardOpts.stationTitle = stationName;\n }\n marker.bindPopup(renderCard(item.report, cardOpts), { maxWidth: 420 });\n marker.on(\"popupopen\", () => {\n // 触屏双浮层消除:弹窗打开即收起 tooltip\n marker.closeTooltip();\n // 焦点移入弹窗首个可聚焦元素(Leaflet 关闭按钮)——键盘与读屏可直达\n const focusTarget = marker\n .getPopup()\n ?.getElement()\n ?.querySelector<HTMLElement>(\"a.leaflet-popup-close-button, button, [href]\");\n focusTarget?.focus();\n });\n }\n marker.addTo(group);\n }\n if (!escapeBoundMaps.has(map)) {\n escapeBoundMaps.add(map);\n // 地图容器键盘路径:Escape 关闭已开弹窗(closePopup 对未开弹窗是空操作)\n map.getContainer().addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") map.closePopup();\n });\n }\n group.addTo(map);\n return group;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metweave/leaflet",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Put parsed METAR/SPECI reports on a Leaflet map: markers, tooltips and report-card popups in one call — bring your own tiles.",
5
5
  "keywords": [
6
6
  "leaflet",
@@ -10,6 +10,10 @@
10
10
  "popup",
11
11
  "weather"
12
12
  ],
13
+ "homepage": "https://github.com/yaojaro/metweave#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/yaojaro/metweave/issues"
16
+ },
13
17
  "license": "MIT",
14
18
  "author": "YaoJaro <yaojaro@metweave.com>",
15
19
  "repository": {
@@ -43,13 +47,13 @@
43
47
  "access": "public"
44
48
  },
45
49
  "dependencies": {
46
- "@metweave/core": "0.1.0",
47
- "@metweave/render": "0.1.0"
50
+ "@metweave/core": "0.1.2",
51
+ "@metweave/render": "0.1.2"
48
52
  },
49
53
  "devDependencies": {
50
54
  "@types/leaflet": "^1.9.22",
51
55
  "leaflet": "^1.9.4",
52
- "@metweave/parser": "0.1.0"
56
+ "@metweave/parser": "0.1.2"
53
57
  },
54
58
  "peerDependencies": {
55
59
  "leaflet": "^1.9.0"