metweave 0.1.0 → 0.1.1
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 +1 -1
- package/dist/sources.cjs +2 -2
- package/dist/sources.cjs.map +1 -1
- package/dist/sources.d.cts +5 -0
- package/dist/sources.d.ts +5 -0
- package/dist/sources.js +2 -2
- package/dist/sources.js.map +1 -1
- package/dist/stations-cn.cjs +283 -0
- package/dist/stations-cn.cjs.map +1 -0
- package/dist/stations-cn.d.cts +11 -0
- package/dist/stations-cn.d.ts +11 -0
- package/dist/stations-cn.js +282 -0
- package/dist/stations-cn.js.map +1 -0
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ L.tileLayer(
|
|
|
25
25
|
`https://t{s}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TDT_KEY}`,
|
|
26
26
|
{ subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"], attribution: "底图 © 天地图" },
|
|
27
27
|
).addTo(map);
|
|
28
|
-
addMetarLayer(map, await getMetarReports(), { conditionColors: true });
|
|
28
|
+
await addMetarLayer(map, await getMetarReports(), { conditionColors: true });
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
本包再导出 `@metweave/core` / `parser` / `render`(整条管道一个入口,含 `toValues` 与机读错误类);`metweave/sources` 提供取数 helper。文档与完整示例见[主仓库](https://github.com/yaojaro/metweave)。
|
package/dist/sources.cjs
CHANGED
|
@@ -75,7 +75,7 @@ async function getMetars(network = "CN__ASOS", options = {}) {
|
|
|
75
75
|
* 传 options.onUnparseable 则逐行容错——失败行跳过并逐条回调(可观测),好行照常返回。
|
|
76
76
|
*/
|
|
77
77
|
async function getMetarReports(network = "CN__ASOS", options = {}) {
|
|
78
|
-
const { stations, onUnparseable, ...fetchOptions } = options;
|
|
78
|
+
const { stations, onUnparseable, spans, ...fetchOptions } = options;
|
|
79
79
|
const table = new Map((stations ?? []).map((s) => [s.icao, s]));
|
|
80
80
|
const observations = await getMetars(network, fetchOptions);
|
|
81
81
|
const items = [];
|
|
@@ -87,7 +87,7 @@ async function getMetarReports(network = "CN__ASOS", options = {}) {
|
|
|
87
87
|
if (lat === null || lon === null) continue;
|
|
88
88
|
try {
|
|
89
89
|
items.push({
|
|
90
|
-
report: (0, _metweave_parser.parse)(obs.raw),
|
|
90
|
+
report: (0, _metweave_parser.parse)(obs.raw, spans === false ? { spans: false } : void 0),
|
|
91
91
|
position: [lat, lon],
|
|
92
92
|
title: `${obs.station}${station?.name ? ` ${station.name}` : ""}`
|
|
93
93
|
});
|
package/dist/sources.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sources.cjs","names":["MetarSourceError","parse","MetarParseError"],"sources":["../src/sources.ts"],"sourcesContent":["/**\n * metweave/sources — 取数 helper(伞包子路径)。\n *\n * 纪律:只做端点模板 + fetch + 取数侧整理(解析、定位联表)——缓存/质控/归档等\n * 数据管理逻辑永不进入开源包;核心包(core/parser/render)零网络代码。\n * 不静默:HTTP 失败、响应 schema 不符、报文解析失败一律 throw,绝不吞错返回空数组。\n * 错误面机读化:五路失败一律抛 MetarSourceError(code 稳定契约 + network 网络名字段;\n * message 权威文案为中文;英文用 @metweave/core 导出的 EN_MESSAGES[code] 查表,\n * 或渲染层 locale:\"en\" 整卡切换(card locale 已内置 code→英文映射))。\n */\nimport type { MetarReport } from \"@metweave/core\";\nimport type { MetarParseErrorCode } from \"@metweave/core\";\nimport { MetarParseError, MetarSourceError } from \"@metweave/core\";\nimport { parse } from \"@metweave/parser\";\n\n// 错误类复出(API 完整性):消费方从 sources 子路径 import 即可 catch 两类错误,\n// 不必再从伞包主入口双入口引入(类实体与 core 同一,instanceof 双向成立)。\nexport { MetarParseError, MetarSourceError } from \"@metweave/core\";\n\n/**\n * One fetched observation row: station, verbatim raw report, and source-provided coordinates (city-level for IEM China rows).\n * 单条取数观测行:站名、原样报文、源站坐标(IEM 中国行为城市级粗坐标)。\n */\nexport interface MetarObservation {\n /** 站名(ICAO) */\n station: string;\n /** 报文原文(IEM 通路无类型词,IEM 自带坐标为城市级粗坐标——定位请使用站点元数据) */\n raw: string;\n lat: number | null;\n lon: number | null;\n}\n\n/**\n * Options for getMetars: external abort signal and request timeout (either firing aborts).\n * getMetars 的选项:外部取消信号与请求超时(任一触发即中止)。\n */\nexport interface GetMetarsOptions {\n /** 外部取消信号(与超时组合,任一触发即中止) */\n signal?: AbortSignal;\n /** 请求超时毫秒数(触发即中止并抛出超时错误) */\n timeoutMs?: number;\n}\n\n/**\n * Station metadata for lookup joins: authoritative coordinates overriding the source's city-level ones.\n * 站点元数据(联表用):坐标为权威定位源,覆盖源站的城市级粗坐标。\n */\nexport interface StationRef {\n /** 站名(ICAO) */\n icao: string;\n /** 纬度(WGS-84) */\n lat: number;\n /** 经度(WGS-84) */\n lon: number;\n /** 站点名称(进 tooltip 标题) */\n name?: string;\n}\n\n/**\n * Per-row failure detail (the onUnparseable callback argument): one observation row failed to parse wholesale (missing station/time etc.).\n * 逐行容错的失败详情(onUnparseable 回调入参):一行观测报文解析整体失败(站名/时组缺失等)。\n * `code` is the machine-readable failure code (MetarParseError's stable contract); `error` is the narrative message.\n * code 是机读失败码(MetarParseError 的稳定契约),error 是中文 message。\n */\nexport interface UnparseableReport {\n /** 源站站名(IEM 行的 station 字段) */\n station: string;\n /** 报文原文 */\n raw: string;\n /** 机读失败码(missing-station / missing-time / invalid-time) */\n code: MetarParseErrorCode;\n /** 解析失败原因(Error.message,中文) */\n error: string;\n}\n\n/**\n * Options for getMetarReports: fetch options plus station metadata and the per-row tolerance callback.\n * getMetarReports 的选项:取数选项 + 站点联表元数据 + 逐行容错回调。\n */\nexport interface GetMetarReportsOptions extends GetMetarsOptions {\n /** 按 icao 联表的站点元数据:命中则用其精确坐标与名称,未命中回落源站坐标 */\n stations?: readonly StationRef[];\n /**\n * 逐行容错(缺省关闭):传入时解析失败的行跳过并逐条回调——失败可观测,不静默;\n * 不传时任一失败仍聚合抛出(默认行为不变,全有或全无)。\n */\n onUnparseable?: (failure: UnparseableReport) => void;\n}\n\n/**\n * A map-ready report item (structurally compatible with MetarLayerItem): the { report, position, title } triple —\n * report is the parsed IR (raw text preserved verbatim on report.raw; field values via toValues(report)),\n * position is WGS-84 [lat, lon], title is the hover title.\n * 可直接喂给地图适配器(如 addMetarLayer)的报文项(与 MetarLayerItem 结构兼容):\n * `{ report, position, title }` 三件套——report 是已解析的 IR(报文原文在 report.raw 原样保真,\n * 需要字段值走 toValues(report)),position 是 WGS-84 [lat, lon],title 是悬浮提示标题。\n */\nexport interface MetarReportItem {\n /** 已解析的报文 IR(原文经 report.raw 原样保真回溯) */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon]) */\n position: [number, number];\n /** 站点提示(「ICAO 站名」;适配器缺省回落 IR 站名) */\n title: string;\n}\n\nconst IEM_BASE = \"https://mesonet.agron.iastate.edu\";\n\n/**\n * The IEM whole-network currents endpoint (CORS-open, direct browser access). Defaults to the China ASOS network (39 stations).\n * IEM 整网实况端点(CORS 全开,浏览器可直连)。缺省中国 ASOS 网(39 站)。\n * @param network - IEM network name (e.g. CN__ASOS, RU__ASOS, IA_ASOS). IEM 网络名。\n */\nexport function iemCurrentsUrl(network = \"CN__ASOS\"): string {\n return `${IEM_BASE}/api/1/currents.json?network=${encodeURIComponent(network)}`;\n}\n\n/** 观测记录守卫:station/raw 均须为字符串(只查键不查类型是旧洞)。 */\nfunction isObservation(v: unknown): v is MetarObservation {\n if (typeof v !== \"object\" || v === null) return false;\n const rec = v as { station?: unknown; raw?: unknown };\n return typeof rec.station === \"string\" && typeof rec.raw === \"string\";\n}\n\n/**\n * Fetch IEM whole-network current observations (station/raw/coordinates passed through).\n * 拉取 IEM 整网实况观测(station/raw/坐标透传)。\n * No-silent-failure discipline on the fetch side / 不静默纪律的取数面:\n * - 空数组是错误网络名的典型形态(IEM 对未知 network 返回 200 + 空 data,合法网络不会返回空)——显式 throw;\n * - fetch 网络层失败(断网/DNS)包装为中文可读错误(原始 message 保留在文末与 cause);\n * - 超时/外部 AbortSignal 触发的拒绝不包装、原样透传(保持取消语义)。\n */\nexport async function getMetars(\n network = \"CN__ASOS\",\n options: GetMetarsOptions = {},\n): Promise<MetarObservation[]> {\n // 手工组合超时与外部信号(不依赖 AbortSignal.any:兼容 Node 20 全系;setTimeout 可被测试假时钟接管)\n const controller = new AbortController();\n const external = options.signal;\n const forward = (): void => {\n controller.abort(external?.reason);\n };\n if (external !== undefined) {\n if (external.aborted) forward();\n else external.addEventListener(\"abort\", forward, { once: true });\n }\n const timer =\n options.timeoutMs === undefined\n ? undefined\n : setTimeout(\n () =>\n controller.abort(\n // 超时中止原因即最终抛出的错误:code \"timeout\"(外部 signal 取消不在此列,reason 原样透传)\n new MetarSourceError(\n \"timeout\",\n network,\n `IEM 请求超时(>${options.timeoutMs}ms,network=${network})`,\n ),\n ),\n options.timeoutMs,\n );\n try {\n let res: Response;\n try {\n res = await fetch(iemCurrentsUrl(network), { signal: controller.signal });\n } catch (err) {\n // 取消/超时引发的拒绝(controller 已中止)原样透传——保持 AbortSignal 语义\n //(超时场景透传的即上方 MetarSourceError \"timeout\");\n // 其余(断网/DNS 的 TypeError 等)包装为 MetarSourceError \"network\",原始 message 保留在文末与 cause\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"network\",\n network,\n `网络请求失败(源:IEM,network=${network}):请检查网络连通性后重试(${reason})`,\n { cause: err },\n );\n }\n if (!res.ok) {\n throw new MetarSourceError(\n \"http-error\",\n network,\n `IEM HTTP ${res.status}(network=${network})`,\n );\n }\n let body: unknown;\n try {\n body = await res.json();\n } catch (err) {\n // HTTP 200 但响应体不是 JSON(企业代理/防火墙拦截页的典型形态)——裸 SyntaxError 打穿属错误面缺口;\n // 归入 bad-schema 机读码,人话提示指向代理环境。中止引发的拒绝不在此列(保持取消语义透传)\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应不是合法 JSON(可能被代理/防火墙拦截,network=${network}):${reason}`,\n { cause: err },\n );\n }\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"data\" in body) ||\n !Array.isArray(body.data)\n ) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:缺少 data 数组(network=${network},schema 不符)`,\n );\n }\n const rows = body.data;\n if (!rows.every(isObservation)) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:data 存在 station/raw 非字符串的记录(network=${network},schema 不符)`,\n );\n }\n if (rows.length === 0) {\n // 合法网络不会返回空(整网实况端点)——空 data 几乎必然是网络名写错(IEM 对未知网络名返回 200 + 空数组)\n throw new MetarSourceError(\n \"empty-data\",\n network,\n `IEM 返回空数据(network=${network})——请核对 IEM 网络名(如 CN__ASOS/RU__ASOS,参考 https://mesonet.agron.iastate.edu/sites/networks.php)`,\n );\n }\n return rows;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", forward);\n }\n}\n\n/**\n * Fetch → parse → locate in one step: returns map-ready report items.\n * 取数 → 解析 → 定位一步到位:返回可直接喂给地图适配器的报文项数组。\n *\n * 定位规则:stations 命中的站用元数据精确坐标(并带站名),未命中的回落源站坐标\n * (IEM 中国站为城市级粗坐标,严肃场景请传站点元数据——见 examples 的 stations.json 用法);\n * 源站坐标为 null 且无元数据的行跳过。\n * 失败语义(不静默,两种模式):缺省任一报文解析整体失败(站名/时组缺失)即聚合抛出,绝不静默丢行;\n * 传 options.onUnparseable 则逐行容错——失败行跳过并逐条回调(可观测),好行照常返回。\n */\nexport async function getMetarReports(\n network = \"CN__ASOS\",\n options: GetMetarReportsOptions = {},\n): Promise<MetarReportItem[]> {\n const { stations, onUnparseable, ...fetchOptions } = options;\n const table = new Map((stations ?? []).map((s) => [s.icao, s] as const));\n const observations = await getMetars(network, fetchOptions);\n const items: MetarReportItem[] = [];\n const failures: string[] = [];\n for (const obs of observations) {\n const station = table.get(obs.station);\n const lat = station?.lat ?? obs.lat;\n const lon = station?.lon ?? obs.lon;\n if (lat === null || lon === null) continue;\n try {\n items.push({\n report: parse(obs.raw),\n position: [lat, lon],\n title: `${obs.station}${station?.name ? ` ${station.name}` : \"\"}`,\n });\n } catch (err) {\n // parse 对字符串输入只抛 MetarParseError(code 稳定契约);其他异常属编程错误,直接上抛不降级\n if (!(err instanceof MetarParseError)) throw err;\n if (onUnparseable !== undefined) {\n onUnparseable({ station: obs.station, raw: obs.raw, code: err.code, error: err.message });\n continue;\n }\n failures.push(`${obs.station}: ${err.message}`);\n }\n }\n if (failures.length > 0) {\n // 聚合错误机读化(原裸 Error):code 'batch-parse-failed' 稳定契约;raw 字段在此承载\n // 汇总信息(网络名/条数/逐条站名与原因)而非单条报文原文——语义调整见 core/errors.ts 注释\n const summary = `network=${network}; failures=${failures.length}; ${failures.join(\"; \")}`;\n throw new MetarParseError(\n \"batch-parse-failed\",\n summary,\n `报文解析失败 ${failures.length} 条(network=${network})——${failures.slice(0, 3).join(\";\")}${failures.length > 3 ? \"……\" : \"\"}`,\n );\n }\n return items;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sources.cjs","names":["MetarSourceError","parse","MetarParseError"],"sources":["../src/sources.ts"],"sourcesContent":["/**\n * metweave/sources — 取数 helper(伞包子路径)。\n *\n * 纪律:只做端点模板 + fetch + 取数侧整理(解析、定位联表)——缓存/质控/归档等\n * 数据管理逻辑永不进入开源包;核心包(core/parser/render)零网络代码。\n * 不静默:HTTP 失败、响应 schema 不符、报文解析失败一律 throw,绝不吞错返回空数组。\n * 错误面机读化:五路失败一律抛 MetarSourceError(code 稳定契约 + network 网络名字段;\n * message 权威文案为中文;英文用 @metweave/core 导出的 EN_MESSAGES[code] 查表,\n * 或渲染层 locale:\"en\" 整卡切换(card locale 已内置 code→英文映射))。\n */\nimport type { MetarReport } from \"@metweave/core\";\nimport type { MetarParseErrorCode } from \"@metweave/core\";\nimport { MetarParseError, MetarSourceError } from \"@metweave/core\";\nimport { parse } from \"@metweave/parser\";\n\n// 错误类复出(API 完整性):消费方从 sources 子路径 import 即可 catch 两类错误,\n// 不必再从伞包主入口双入口引入(类实体与 core 同一,instanceof 双向成立)。\nexport { MetarParseError, MetarSourceError } from \"@metweave/core\";\n\n/**\n * One fetched observation row: station, verbatim raw report, and source-provided coordinates (city-level for IEM China rows).\n * 单条取数观测行:站名、原样报文、源站坐标(IEM 中国行为城市级粗坐标)。\n */\nexport interface MetarObservation {\n /** 站名(ICAO) */\n station: string;\n /** 报文原文(IEM 通路无类型词,IEM 自带坐标为城市级粗坐标——定位请使用站点元数据) */\n raw: string;\n lat: number | null;\n lon: number | null;\n}\n\n/**\n * Options for getMetars: external abort signal and request timeout (either firing aborts).\n * getMetars 的选项:外部取消信号与请求超时(任一触发即中止)。\n */\nexport interface GetMetarsOptions {\n /** 外部取消信号(与超时组合,任一触发即中止) */\n signal?: AbortSignal;\n /** 请求超时毫秒数(触发即中止并抛出超时错误) */\n timeoutMs?: number;\n}\n\n/**\n * Station metadata for lookup joins: authoritative coordinates overriding the source's city-level ones.\n * 站点元数据(联表用):坐标为权威定位源,覆盖源站的城市级粗坐标。\n */\nexport interface StationRef {\n /** 站名(ICAO) */\n icao: string;\n /** 纬度(WGS-84) */\n lat: number;\n /** 经度(WGS-84) */\n lon: number;\n /** 站点名称(进 tooltip 标题) */\n name?: string;\n}\n\n/**\n * Per-row failure detail (the onUnparseable callback argument): one observation row failed to parse wholesale (missing station/time etc.).\n * 逐行容错的失败详情(onUnparseable 回调入参):一行观测报文解析整体失败(站名/时组缺失等)。\n * `code` is the machine-readable failure code (MetarParseError's stable contract); `error` is the narrative message.\n * code 是机读失败码(MetarParseError 的稳定契约),error 是中文 message。\n */\nexport interface UnparseableReport {\n /** 源站站名(IEM 行的 station 字段) */\n station: string;\n /** 报文原文 */\n raw: string;\n /** 机读失败码(missing-station / missing-time / invalid-time) */\n code: MetarParseErrorCode;\n /** 解析失败原因(Error.message,中文) */\n error: string;\n}\n\n/**\n * Options for getMetarReports: fetch options plus station metadata and the per-row tolerance callback.\n * getMetarReports 的选项:取数选项 + 站点联表元数据 + 逐行容错回调。\n */\nexport interface GetMetarReportsOptions extends GetMetarsOptions {\n /** 按 icao 联表的站点元数据:命中则用其精确坐标与名称,未命中回落源站坐标 */\n stations?: readonly StationRef[];\n /**\n * 逐行容错(缺省关闭):传入时解析失败的行跳过并逐条回调——失败可观测,不静默;\n * 不传时任一失败仍聚合抛出(默认行为不变,全有或全无)。\n */\n onUnparseable?: (failure: UnparseableReport) => void;\n /**\n * 紧凑模式:parse 产物剥除全部 span 字段(存储体积约 -32%,入库供屏场景推荐);\n * 缺省 undefined = 完整模式(span 在位,原文回溯用)。\n */\n spans?: false;\n}\n\n/**\n * A map-ready report item (structurally compatible with MetarLayerItem): the { report, position, title } triple —\n * report is the parsed IR (raw text preserved verbatim on report.raw; field values via toValues(report)),\n * position is WGS-84 [lat, lon], title is the hover title.\n * 可直接喂给地图适配器(如 addMetarLayer)的报文项(与 MetarLayerItem 结构兼容):\n * `{ report, position, title }` 三件套——report 是已解析的 IR(报文原文在 report.raw 原样保真,\n * 需要字段值走 toValues(report)),position 是 WGS-84 [lat, lon],title 是悬浮提示标题。\n */\nexport interface MetarReportItem {\n /** 已解析的报文 IR(原文经 report.raw 原样保真回溯) */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon]) */\n position: [number, number];\n /** 站点提示(「ICAO 站名」;适配器缺省回落 IR 站名) */\n title: string;\n}\n\nconst IEM_BASE = \"https://mesonet.agron.iastate.edu\";\n\n/**\n * The IEM whole-network currents endpoint (CORS-open, direct browser access). Defaults to the China ASOS network (39 stations).\n * IEM 整网实况端点(CORS 全开,浏览器可直连)。缺省中国 ASOS 网(39 站)。\n * @param network - IEM network name (e.g. CN__ASOS, RU__ASOS, IA_ASOS). IEM 网络名。\n */\nexport function iemCurrentsUrl(network = \"CN__ASOS\"): string {\n return `${IEM_BASE}/api/1/currents.json?network=${encodeURIComponent(network)}`;\n}\n\n/** 观测记录守卫:station/raw 均须为字符串(只查键不查类型是旧洞)。 */\nfunction isObservation(v: unknown): v is MetarObservation {\n if (typeof v !== \"object\" || v === null) return false;\n const rec = v as { station?: unknown; raw?: unknown };\n return typeof rec.station === \"string\" && typeof rec.raw === \"string\";\n}\n\n/**\n * Fetch IEM whole-network current observations (station/raw/coordinates passed through).\n * 拉取 IEM 整网实况观测(station/raw/坐标透传)。\n * No-silent-failure discipline on the fetch side / 不静默纪律的取数面:\n * - 空数组是错误网络名的典型形态(IEM 对未知 network 返回 200 + 空 data,合法网络不会返回空)——显式 throw;\n * - fetch 网络层失败(断网/DNS)包装为中文可读错误(原始 message 保留在文末与 cause);\n * - 超时/外部 AbortSignal 触发的拒绝不包装、原样透传(保持取消语义)。\n */\nexport async function getMetars(\n network = \"CN__ASOS\",\n options: GetMetarsOptions = {},\n): Promise<MetarObservation[]> {\n // 手工组合超时与外部信号(不依赖 AbortSignal.any:兼容 Node 20 全系;setTimeout 可被测试假时钟接管)\n const controller = new AbortController();\n const external = options.signal;\n const forward = (): void => {\n controller.abort(external?.reason);\n };\n if (external !== undefined) {\n if (external.aborted) forward();\n else external.addEventListener(\"abort\", forward, { once: true });\n }\n const timer =\n options.timeoutMs === undefined\n ? undefined\n : setTimeout(\n () =>\n controller.abort(\n // 超时中止原因即最终抛出的错误:code \"timeout\"(外部 signal 取消不在此列,reason 原样透传)\n new MetarSourceError(\n \"timeout\",\n network,\n `IEM 请求超时(>${options.timeoutMs}ms,network=${network})`,\n ),\n ),\n options.timeoutMs,\n );\n try {\n let res: Response;\n try {\n res = await fetch(iemCurrentsUrl(network), { signal: controller.signal });\n } catch (err) {\n // 取消/超时引发的拒绝(controller 已中止)原样透传——保持 AbortSignal 语义\n //(超时场景透传的即上方 MetarSourceError \"timeout\");\n // 其余(断网/DNS 的 TypeError 等)包装为 MetarSourceError \"network\",原始 message 保留在文末与 cause\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"network\",\n network,\n `网络请求失败(源:IEM,network=${network}):请检查网络连通性后重试(${reason})`,\n { cause: err },\n );\n }\n if (!res.ok) {\n throw new MetarSourceError(\n \"http-error\",\n network,\n `IEM HTTP ${res.status}(network=${network})`,\n );\n }\n let body: unknown;\n try {\n body = await res.json();\n } catch (err) {\n // HTTP 200 但响应体不是 JSON(企业代理/防火墙拦截页的典型形态)——裸 SyntaxError 打穿属错误面缺口;\n // 归入 bad-schema 机读码,人话提示指向代理环境。中止引发的拒绝不在此列(保持取消语义透传)\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应不是合法 JSON(可能被代理/防火墙拦截,network=${network}):${reason}`,\n { cause: err },\n );\n }\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"data\" in body) ||\n !Array.isArray(body.data)\n ) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:缺少 data 数组(network=${network},schema 不符)`,\n );\n }\n const rows = body.data;\n if (!rows.every(isObservation)) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:data 存在 station/raw 非字符串的记录(network=${network},schema 不符)`,\n );\n }\n if (rows.length === 0) {\n // 合法网络不会返回空(整网实况端点)——空 data 几乎必然是网络名写错(IEM 对未知网络名返回 200 + 空数组)\n throw new MetarSourceError(\n \"empty-data\",\n network,\n `IEM 返回空数据(network=${network})——请核对 IEM 网络名(如 CN__ASOS/RU__ASOS,参考 https://mesonet.agron.iastate.edu/sites/networks.php)`,\n );\n }\n return rows;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", forward);\n }\n}\n\n/**\n * Fetch → parse → locate in one step: returns map-ready report items.\n * 取数 → 解析 → 定位一步到位:返回可直接喂给地图适配器的报文项数组。\n *\n * 定位规则:stations 命中的站用元数据精确坐标(并带站名),未命中的回落源站坐标\n * (IEM 中国站为城市级粗坐标,严肃场景请传站点元数据——见 examples 的 stations.json 用法);\n * 源站坐标为 null 且无元数据的行跳过。\n * 失败语义(不静默,两种模式):缺省任一报文解析整体失败(站名/时组缺失)即聚合抛出,绝不静默丢行;\n * 传 options.onUnparseable 则逐行容错——失败行跳过并逐条回调(可观测),好行照常返回。\n */\nexport async function getMetarReports(\n network = \"CN__ASOS\",\n options: GetMetarReportsOptions = {},\n): Promise<MetarReportItem[]> {\n const { stations, onUnparseable, spans, ...fetchOptions } = options;\n const table = new Map((stations ?? []).map((s) => [s.icao, s] as const));\n const observations = await getMetars(network, fetchOptions);\n const items: MetarReportItem[] = [];\n const failures: string[] = [];\n for (const obs of observations) {\n const station = table.get(obs.station);\n const lat = station?.lat ?? obs.lat;\n const lon = station?.lon ?? obs.lon;\n if (lat === null || lon === null) continue;\n try {\n items.push({\n report: parse(obs.raw, spans === false ? { spans: false } : undefined),\n position: [lat, lon],\n title: `${obs.station}${station?.name ? ` ${station.name}` : \"\"}`,\n });\n } catch (err) {\n // parse 对字符串输入只抛 MetarParseError(code 稳定契约);其他异常属编程错误,直接上抛不降级\n if (!(err instanceof MetarParseError)) throw err;\n if (onUnparseable !== undefined) {\n onUnparseable({ station: obs.station, raw: obs.raw, code: err.code, error: err.message });\n continue;\n }\n failures.push(`${obs.station}: ${err.message}`);\n }\n }\n if (failures.length > 0) {\n // 聚合错误机读化(原裸 Error):code 'batch-parse-failed' 稳定契约;raw 字段在此承载\n // 汇总信息(网络名/条数/逐条站名与原因)而非单条报文原文——语义调整见 core/errors.ts 注释\n const summary = `network=${network}; failures=${failures.length}; ${failures.join(\"; \")}`;\n throw new MetarParseError(\n \"batch-parse-failed\",\n summary,\n `报文解析失败 ${failures.length} 条(network=${network})——${failures.slice(0, 3).join(\";\")}${failures.length > 3 ? \"……\" : \"\"}`,\n );\n }\n return items;\n}\n"],"mappings":"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"}
|
package/dist/sources.d.cts
CHANGED
|
@@ -64,6 +64,11 @@ export interface GetMetarReportsOptions extends GetMetarsOptions {
|
|
|
64
64
|
* 不传时任一失败仍聚合抛出(默认行为不变,全有或全无)。
|
|
65
65
|
*/
|
|
66
66
|
onUnparseable?: (failure: UnparseableReport) => void;
|
|
67
|
+
/**
|
|
68
|
+
* 紧凑模式:parse 产物剥除全部 span 字段(存储体积约 -32%,入库供屏场景推荐);
|
|
69
|
+
* 缺省 undefined = 完整模式(span 在位,原文回溯用)。
|
|
70
|
+
*/
|
|
71
|
+
spans?: false;
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
69
74
|
* A map-ready report item (structurally compatible with MetarLayerItem): the { report, position, title } triple —
|
package/dist/sources.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ export interface GetMetarReportsOptions extends GetMetarsOptions {
|
|
|
64
64
|
* 不传时任一失败仍聚合抛出(默认行为不变,全有或全无)。
|
|
65
65
|
*/
|
|
66
66
|
onUnparseable?: (failure: UnparseableReport) => void;
|
|
67
|
+
/**
|
|
68
|
+
* 紧凑模式:parse 产物剥除全部 span 字段(存储体积约 -32%,入库供屏场景推荐);
|
|
69
|
+
* 缺省 undefined = 完整模式(span 在位,原文回溯用)。
|
|
70
|
+
*/
|
|
71
|
+
spans?: false;
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
69
74
|
* A map-ready report item (structurally compatible with MetarLayerItem): the { report, position, title } triple —
|
package/dist/sources.js
CHANGED
|
@@ -74,7 +74,7 @@ async function getMetars(network = "CN__ASOS", options = {}) {
|
|
|
74
74
|
* 传 options.onUnparseable 则逐行容错——失败行跳过并逐条回调(可观测),好行照常返回。
|
|
75
75
|
*/
|
|
76
76
|
async function getMetarReports(network = "CN__ASOS", options = {}) {
|
|
77
|
-
const { stations, onUnparseable, ...fetchOptions } = options;
|
|
77
|
+
const { stations, onUnparseable, spans, ...fetchOptions } = options;
|
|
78
78
|
const table = new Map((stations ?? []).map((s) => [s.icao, s]));
|
|
79
79
|
const observations = await getMetars(network, fetchOptions);
|
|
80
80
|
const items = [];
|
|
@@ -86,7 +86,7 @@ async function getMetarReports(network = "CN__ASOS", options = {}) {
|
|
|
86
86
|
if (lat === null || lon === null) continue;
|
|
87
87
|
try {
|
|
88
88
|
items.push({
|
|
89
|
-
report: parse(obs.raw),
|
|
89
|
+
report: parse(obs.raw, spans === false ? { spans: false } : void 0),
|
|
90
90
|
position: [lat, lon],
|
|
91
91
|
title: `${obs.station}${station?.name ? ` ${station.name}` : ""}`
|
|
92
92
|
});
|
package/dist/sources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sources.js","names":["MetarSourceError","MetarParseError"],"sources":["../src/sources.ts"],"sourcesContent":["/**\n * metweave/sources — 取数 helper(伞包子路径)。\n *\n * 纪律:只做端点模板 + fetch + 取数侧整理(解析、定位联表)——缓存/质控/归档等\n * 数据管理逻辑永不进入开源包;核心包(core/parser/render)零网络代码。\n * 不静默:HTTP 失败、响应 schema 不符、报文解析失败一律 throw,绝不吞错返回空数组。\n * 错误面机读化:五路失败一律抛 MetarSourceError(code 稳定契约 + network 网络名字段;\n * message 权威文案为中文;英文用 @metweave/core 导出的 EN_MESSAGES[code] 查表,\n * 或渲染层 locale:\"en\" 整卡切换(card locale 已内置 code→英文映射))。\n */\nimport type { MetarReport } from \"@metweave/core\";\nimport type { MetarParseErrorCode } from \"@metweave/core\";\nimport { MetarParseError, MetarSourceError } from \"@metweave/core\";\nimport { parse } from \"@metweave/parser\";\n\n// 错误类复出(API 完整性):消费方从 sources 子路径 import 即可 catch 两类错误,\n// 不必再从伞包主入口双入口引入(类实体与 core 同一,instanceof 双向成立)。\nexport { MetarParseError, MetarSourceError } from \"@metweave/core\";\n\n/**\n * One fetched observation row: station, verbatim raw report, and source-provided coordinates (city-level for IEM China rows).\n * 单条取数观测行:站名、原样报文、源站坐标(IEM 中国行为城市级粗坐标)。\n */\nexport interface MetarObservation {\n /** 站名(ICAO) */\n station: string;\n /** 报文原文(IEM 通路无类型词,IEM 自带坐标为城市级粗坐标——定位请使用站点元数据) */\n raw: string;\n lat: number | null;\n lon: number | null;\n}\n\n/**\n * Options for getMetars: external abort signal and request timeout (either firing aborts).\n * getMetars 的选项:外部取消信号与请求超时(任一触发即中止)。\n */\nexport interface GetMetarsOptions {\n /** 外部取消信号(与超时组合,任一触发即中止) */\n signal?: AbortSignal;\n /** 请求超时毫秒数(触发即中止并抛出超时错误) */\n timeoutMs?: number;\n}\n\n/**\n * Station metadata for lookup joins: authoritative coordinates overriding the source's city-level ones.\n * 站点元数据(联表用):坐标为权威定位源,覆盖源站的城市级粗坐标。\n */\nexport interface StationRef {\n /** 站名(ICAO) */\n icao: string;\n /** 纬度(WGS-84) */\n lat: number;\n /** 经度(WGS-84) */\n lon: number;\n /** 站点名称(进 tooltip 标题) */\n name?: string;\n}\n\n/**\n * Per-row failure detail (the onUnparseable callback argument): one observation row failed to parse wholesale (missing station/time etc.).\n * 逐行容错的失败详情(onUnparseable 回调入参):一行观测报文解析整体失败(站名/时组缺失等)。\n * `code` is the machine-readable failure code (MetarParseError's stable contract); `error` is the narrative message.\n * code 是机读失败码(MetarParseError 的稳定契约),error 是中文 message。\n */\nexport interface UnparseableReport {\n /** 源站站名(IEM 行的 station 字段) */\n station: string;\n /** 报文原文 */\n raw: string;\n /** 机读失败码(missing-station / missing-time / invalid-time) */\n code: MetarParseErrorCode;\n /** 解析失败原因(Error.message,中文) */\n error: string;\n}\n\n/**\n * Options for getMetarReports: fetch options plus station metadata and the per-row tolerance callback.\n * getMetarReports 的选项:取数选项 + 站点联表元数据 + 逐行容错回调。\n */\nexport interface GetMetarReportsOptions extends GetMetarsOptions {\n /** 按 icao 联表的站点元数据:命中则用其精确坐标与名称,未命中回落源站坐标 */\n stations?: readonly StationRef[];\n /**\n * 逐行容错(缺省关闭):传入时解析失败的行跳过并逐条回调——失败可观测,不静默;\n * 不传时任一失败仍聚合抛出(默认行为不变,全有或全无)。\n */\n onUnparseable?: (failure: UnparseableReport) => void;\n}\n\n/**\n * A map-ready report item (structurally compatible with MetarLayerItem): the { report, position, title } triple —\n * report is the parsed IR (raw text preserved verbatim on report.raw; field values via toValues(report)),\n * position is WGS-84 [lat, lon], title is the hover title.\n * 可直接喂给地图适配器(如 addMetarLayer)的报文项(与 MetarLayerItem 结构兼容):\n * `{ report, position, title }` 三件套——report 是已解析的 IR(报文原文在 report.raw 原样保真,\n * 需要字段值走 toValues(report)),position 是 WGS-84 [lat, lon],title 是悬浮提示标题。\n */\nexport interface MetarReportItem {\n /** 已解析的报文 IR(原文经 report.raw 原样保真回溯) */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon]) */\n position: [number, number];\n /** 站点提示(「ICAO 站名」;适配器缺省回落 IR 站名) */\n title: string;\n}\n\nconst IEM_BASE = \"https://mesonet.agron.iastate.edu\";\n\n/**\n * The IEM whole-network currents endpoint (CORS-open, direct browser access). Defaults to the China ASOS network (39 stations).\n * IEM 整网实况端点(CORS 全开,浏览器可直连)。缺省中国 ASOS 网(39 站)。\n * @param network - IEM network name (e.g. CN__ASOS, RU__ASOS, IA_ASOS). IEM 网络名。\n */\nexport function iemCurrentsUrl(network = \"CN__ASOS\"): string {\n return `${IEM_BASE}/api/1/currents.json?network=${encodeURIComponent(network)}`;\n}\n\n/** 观测记录守卫:station/raw 均须为字符串(只查键不查类型是旧洞)。 */\nfunction isObservation(v: unknown): v is MetarObservation {\n if (typeof v !== \"object\" || v === null) return false;\n const rec = v as { station?: unknown; raw?: unknown };\n return typeof rec.station === \"string\" && typeof rec.raw === \"string\";\n}\n\n/**\n * Fetch IEM whole-network current observations (station/raw/coordinates passed through).\n * 拉取 IEM 整网实况观测(station/raw/坐标透传)。\n * No-silent-failure discipline on the fetch side / 不静默纪律的取数面:\n * - 空数组是错误网络名的典型形态(IEM 对未知 network 返回 200 + 空 data,合法网络不会返回空)——显式 throw;\n * - fetch 网络层失败(断网/DNS)包装为中文可读错误(原始 message 保留在文末与 cause);\n * - 超时/外部 AbortSignal 触发的拒绝不包装、原样透传(保持取消语义)。\n */\nexport async function getMetars(\n network = \"CN__ASOS\",\n options: GetMetarsOptions = {},\n): Promise<MetarObservation[]> {\n // 手工组合超时与外部信号(不依赖 AbortSignal.any:兼容 Node 20 全系;setTimeout 可被测试假时钟接管)\n const controller = new AbortController();\n const external = options.signal;\n const forward = (): void => {\n controller.abort(external?.reason);\n };\n if (external !== undefined) {\n if (external.aborted) forward();\n else external.addEventListener(\"abort\", forward, { once: true });\n }\n const timer =\n options.timeoutMs === undefined\n ? undefined\n : setTimeout(\n () =>\n controller.abort(\n // 超时中止原因即最终抛出的错误:code \"timeout\"(外部 signal 取消不在此列,reason 原样透传)\n new MetarSourceError(\n \"timeout\",\n network,\n `IEM 请求超时(>${options.timeoutMs}ms,network=${network})`,\n ),\n ),\n options.timeoutMs,\n );\n try {\n let res: Response;\n try {\n res = await fetch(iemCurrentsUrl(network), { signal: controller.signal });\n } catch (err) {\n // 取消/超时引发的拒绝(controller 已中止)原样透传——保持 AbortSignal 语义\n //(超时场景透传的即上方 MetarSourceError \"timeout\");\n // 其余(断网/DNS 的 TypeError 等)包装为 MetarSourceError \"network\",原始 message 保留在文末与 cause\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"network\",\n network,\n `网络请求失败(源:IEM,network=${network}):请检查网络连通性后重试(${reason})`,\n { cause: err },\n );\n }\n if (!res.ok) {\n throw new MetarSourceError(\n \"http-error\",\n network,\n `IEM HTTP ${res.status}(network=${network})`,\n );\n }\n let body: unknown;\n try {\n body = await res.json();\n } catch (err) {\n // HTTP 200 但响应体不是 JSON(企业代理/防火墙拦截页的典型形态)——裸 SyntaxError 打穿属错误面缺口;\n // 归入 bad-schema 机读码,人话提示指向代理环境。中止引发的拒绝不在此列(保持取消语义透传)\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应不是合法 JSON(可能被代理/防火墙拦截,network=${network}):${reason}`,\n { cause: err },\n );\n }\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"data\" in body) ||\n !Array.isArray(body.data)\n ) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:缺少 data 数组(network=${network},schema 不符)`,\n );\n }\n const rows = body.data;\n if (!rows.every(isObservation)) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:data 存在 station/raw 非字符串的记录(network=${network},schema 不符)`,\n );\n }\n if (rows.length === 0) {\n // 合法网络不会返回空(整网实况端点)——空 data 几乎必然是网络名写错(IEM 对未知网络名返回 200 + 空数组)\n throw new MetarSourceError(\n \"empty-data\",\n network,\n `IEM 返回空数据(network=${network})——请核对 IEM 网络名(如 CN__ASOS/RU__ASOS,参考 https://mesonet.agron.iastate.edu/sites/networks.php)`,\n );\n }\n return rows;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", forward);\n }\n}\n\n/**\n * Fetch → parse → locate in one step: returns map-ready report items.\n * 取数 → 解析 → 定位一步到位:返回可直接喂给地图适配器的报文项数组。\n *\n * 定位规则:stations 命中的站用元数据精确坐标(并带站名),未命中的回落源站坐标\n * (IEM 中国站为城市级粗坐标,严肃场景请传站点元数据——见 examples 的 stations.json 用法);\n * 源站坐标为 null 且无元数据的行跳过。\n * 失败语义(不静默,两种模式):缺省任一报文解析整体失败(站名/时组缺失)即聚合抛出,绝不静默丢行;\n * 传 options.onUnparseable 则逐行容错——失败行跳过并逐条回调(可观测),好行照常返回。\n */\nexport async function getMetarReports(\n network = \"CN__ASOS\",\n options: GetMetarReportsOptions = {},\n): Promise<MetarReportItem[]> {\n const { stations, onUnparseable, ...fetchOptions } = options;\n const table = new Map((stations ?? []).map((s) => [s.icao, s] as const));\n const observations = await getMetars(network, fetchOptions);\n const items: MetarReportItem[] = [];\n const failures: string[] = [];\n for (const obs of observations) {\n const station = table.get(obs.station);\n const lat = station?.lat ?? obs.lat;\n const lon = station?.lon ?? obs.lon;\n if (lat === null || lon === null) continue;\n try {\n items.push({\n report: parse(obs.raw),\n position: [lat, lon],\n title: `${obs.station}${station?.name ? ` ${station.name}` : \"\"}`,\n });\n } catch (err) {\n // parse 对字符串输入只抛 MetarParseError(code 稳定契约);其他异常属编程错误,直接上抛不降级\n if (!(err instanceof MetarParseError)) throw err;\n if (onUnparseable !== undefined) {\n onUnparseable({ station: obs.station, raw: obs.raw, code: err.code, error: err.message });\n continue;\n }\n failures.push(`${obs.station}: ${err.message}`);\n }\n }\n if (failures.length > 0) {\n // 聚合错误机读化(原裸 Error):code 'batch-parse-failed' 稳定契约;raw 字段在此承载\n // 汇总信息(网络名/条数/逐条站名与原因)而非单条报文原文——语义调整见 core/errors.ts 注释\n const summary = `network=${network}; failures=${failures.length}; ${failures.join(\"; \")}`;\n throw new MetarParseError(\n \"batch-parse-failed\",\n summary,\n `报文解析失败 ${failures.length} 条(network=${network})——${failures.slice(0, 3).join(\";\")}${failures.length > 3 ? \"……\" : \"\"}`,\n );\n }\n return items;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sources.js","names":["MetarSourceError","MetarParseError"],"sources":["../src/sources.ts"],"sourcesContent":["/**\n * metweave/sources — 取数 helper(伞包子路径)。\n *\n * 纪律:只做端点模板 + fetch + 取数侧整理(解析、定位联表)——缓存/质控/归档等\n * 数据管理逻辑永不进入开源包;核心包(core/parser/render)零网络代码。\n * 不静默:HTTP 失败、响应 schema 不符、报文解析失败一律 throw,绝不吞错返回空数组。\n * 错误面机读化:五路失败一律抛 MetarSourceError(code 稳定契约 + network 网络名字段;\n * message 权威文案为中文;英文用 @metweave/core 导出的 EN_MESSAGES[code] 查表,\n * 或渲染层 locale:\"en\" 整卡切换(card locale 已内置 code→英文映射))。\n */\nimport type { MetarReport } from \"@metweave/core\";\nimport type { MetarParseErrorCode } from \"@metweave/core\";\nimport { MetarParseError, MetarSourceError } from \"@metweave/core\";\nimport { parse } from \"@metweave/parser\";\n\n// 错误类复出(API 完整性):消费方从 sources 子路径 import 即可 catch 两类错误,\n// 不必再从伞包主入口双入口引入(类实体与 core 同一,instanceof 双向成立)。\nexport { MetarParseError, MetarSourceError } from \"@metweave/core\";\n\n/**\n * One fetched observation row: station, verbatim raw report, and source-provided coordinates (city-level for IEM China rows).\n * 单条取数观测行:站名、原样报文、源站坐标(IEM 中国行为城市级粗坐标)。\n */\nexport interface MetarObservation {\n /** 站名(ICAO) */\n station: string;\n /** 报文原文(IEM 通路无类型词,IEM 自带坐标为城市级粗坐标——定位请使用站点元数据) */\n raw: string;\n lat: number | null;\n lon: number | null;\n}\n\n/**\n * Options for getMetars: external abort signal and request timeout (either firing aborts).\n * getMetars 的选项:外部取消信号与请求超时(任一触发即中止)。\n */\nexport interface GetMetarsOptions {\n /** 外部取消信号(与超时组合,任一触发即中止) */\n signal?: AbortSignal;\n /** 请求超时毫秒数(触发即中止并抛出超时错误) */\n timeoutMs?: number;\n}\n\n/**\n * Station metadata for lookup joins: authoritative coordinates overriding the source's city-level ones.\n * 站点元数据(联表用):坐标为权威定位源,覆盖源站的城市级粗坐标。\n */\nexport interface StationRef {\n /** 站名(ICAO) */\n icao: string;\n /** 纬度(WGS-84) */\n lat: number;\n /** 经度(WGS-84) */\n lon: number;\n /** 站点名称(进 tooltip 标题) */\n name?: string;\n}\n\n/**\n * Per-row failure detail (the onUnparseable callback argument): one observation row failed to parse wholesale (missing station/time etc.).\n * 逐行容错的失败详情(onUnparseable 回调入参):一行观测报文解析整体失败(站名/时组缺失等)。\n * `code` is the machine-readable failure code (MetarParseError's stable contract); `error` is the narrative message.\n * code 是机读失败码(MetarParseError 的稳定契约),error 是中文 message。\n */\nexport interface UnparseableReport {\n /** 源站站名(IEM 行的 station 字段) */\n station: string;\n /** 报文原文 */\n raw: string;\n /** 机读失败码(missing-station / missing-time / invalid-time) */\n code: MetarParseErrorCode;\n /** 解析失败原因(Error.message,中文) */\n error: string;\n}\n\n/**\n * Options for getMetarReports: fetch options plus station metadata and the per-row tolerance callback.\n * getMetarReports 的选项:取数选项 + 站点联表元数据 + 逐行容错回调。\n */\nexport interface GetMetarReportsOptions extends GetMetarsOptions {\n /** 按 icao 联表的站点元数据:命中则用其精确坐标与名称,未命中回落源站坐标 */\n stations?: readonly StationRef[];\n /**\n * 逐行容错(缺省关闭):传入时解析失败的行跳过并逐条回调——失败可观测,不静默;\n * 不传时任一失败仍聚合抛出(默认行为不变,全有或全无)。\n */\n onUnparseable?: (failure: UnparseableReport) => void;\n /**\n * 紧凑模式:parse 产物剥除全部 span 字段(存储体积约 -32%,入库供屏场景推荐);\n * 缺省 undefined = 完整模式(span 在位,原文回溯用)。\n */\n spans?: false;\n}\n\n/**\n * A map-ready report item (structurally compatible with MetarLayerItem): the { report, position, title } triple —\n * report is the parsed IR (raw text preserved verbatim on report.raw; field values via toValues(report)),\n * position is WGS-84 [lat, lon], title is the hover title.\n * 可直接喂给地图适配器(如 addMetarLayer)的报文项(与 MetarLayerItem 结构兼容):\n * `{ report, position, title }` 三件套——report 是已解析的 IR(报文原文在 report.raw 原样保真,\n * 需要字段值走 toValues(report)),position 是 WGS-84 [lat, lon],title 是悬浮提示标题。\n */\nexport interface MetarReportItem {\n /** 已解析的报文 IR(原文经 report.raw 原样保真回溯) */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon]) */\n position: [number, number];\n /** 站点提示(「ICAO 站名」;适配器缺省回落 IR 站名) */\n title: string;\n}\n\nconst IEM_BASE = \"https://mesonet.agron.iastate.edu\";\n\n/**\n * The IEM whole-network currents endpoint (CORS-open, direct browser access). Defaults to the China ASOS network (39 stations).\n * IEM 整网实况端点(CORS 全开,浏览器可直连)。缺省中国 ASOS 网(39 站)。\n * @param network - IEM network name (e.g. CN__ASOS, RU__ASOS, IA_ASOS). IEM 网络名。\n */\nexport function iemCurrentsUrl(network = \"CN__ASOS\"): string {\n return `${IEM_BASE}/api/1/currents.json?network=${encodeURIComponent(network)}`;\n}\n\n/** 观测记录守卫:station/raw 均须为字符串(只查键不查类型是旧洞)。 */\nfunction isObservation(v: unknown): v is MetarObservation {\n if (typeof v !== \"object\" || v === null) return false;\n const rec = v as { station?: unknown; raw?: unknown };\n return typeof rec.station === \"string\" && typeof rec.raw === \"string\";\n}\n\n/**\n * Fetch IEM whole-network current observations (station/raw/coordinates passed through).\n * 拉取 IEM 整网实况观测(station/raw/坐标透传)。\n * No-silent-failure discipline on the fetch side / 不静默纪律的取数面:\n * - 空数组是错误网络名的典型形态(IEM 对未知 network 返回 200 + 空 data,合法网络不会返回空)——显式 throw;\n * - fetch 网络层失败(断网/DNS)包装为中文可读错误(原始 message 保留在文末与 cause);\n * - 超时/外部 AbortSignal 触发的拒绝不包装、原样透传(保持取消语义)。\n */\nexport async function getMetars(\n network = \"CN__ASOS\",\n options: GetMetarsOptions = {},\n): Promise<MetarObservation[]> {\n // 手工组合超时与外部信号(不依赖 AbortSignal.any:兼容 Node 20 全系;setTimeout 可被测试假时钟接管)\n const controller = new AbortController();\n const external = options.signal;\n const forward = (): void => {\n controller.abort(external?.reason);\n };\n if (external !== undefined) {\n if (external.aborted) forward();\n else external.addEventListener(\"abort\", forward, { once: true });\n }\n const timer =\n options.timeoutMs === undefined\n ? undefined\n : setTimeout(\n () =>\n controller.abort(\n // 超时中止原因即最终抛出的错误:code \"timeout\"(外部 signal 取消不在此列,reason 原样透传)\n new MetarSourceError(\n \"timeout\",\n network,\n `IEM 请求超时(>${options.timeoutMs}ms,network=${network})`,\n ),\n ),\n options.timeoutMs,\n );\n try {\n let res: Response;\n try {\n res = await fetch(iemCurrentsUrl(network), { signal: controller.signal });\n } catch (err) {\n // 取消/超时引发的拒绝(controller 已中止)原样透传——保持 AbortSignal 语义\n //(超时场景透传的即上方 MetarSourceError \"timeout\");\n // 其余(断网/DNS 的 TypeError 等)包装为 MetarSourceError \"network\",原始 message 保留在文末与 cause\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"network\",\n network,\n `网络请求失败(源:IEM,network=${network}):请检查网络连通性后重试(${reason})`,\n { cause: err },\n );\n }\n if (!res.ok) {\n throw new MetarSourceError(\n \"http-error\",\n network,\n `IEM HTTP ${res.status}(network=${network})`,\n );\n }\n let body: unknown;\n try {\n body = await res.json();\n } catch (err) {\n // HTTP 200 但响应体不是 JSON(企业代理/防火墙拦截页的典型形态)——裸 SyntaxError 打穿属错误面缺口;\n // 归入 bad-schema 机读码,人话提示指向代理环境。中止引发的拒绝不在此列(保持取消语义透传)\n if (controller.signal.aborted) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应不是合法 JSON(可能被代理/防火墙拦截,network=${network}):${reason}`,\n { cause: err },\n );\n }\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"data\" in body) ||\n !Array.isArray(body.data)\n ) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:缺少 data 数组(network=${network},schema 不符)`,\n );\n }\n const rows = body.data;\n if (!rows.every(isObservation)) {\n throw new MetarSourceError(\n \"bad-schema\",\n network,\n `IEM 响应异常:data 存在 station/raw 非字符串的记录(network=${network},schema 不符)`,\n );\n }\n if (rows.length === 0) {\n // 合法网络不会返回空(整网实况端点)——空 data 几乎必然是网络名写错(IEM 对未知网络名返回 200 + 空数组)\n throw new MetarSourceError(\n \"empty-data\",\n network,\n `IEM 返回空数据(network=${network})——请核对 IEM 网络名(如 CN__ASOS/RU__ASOS,参考 https://mesonet.agron.iastate.edu/sites/networks.php)`,\n );\n }\n return rows;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", forward);\n }\n}\n\n/**\n * Fetch → parse → locate in one step: returns map-ready report items.\n * 取数 → 解析 → 定位一步到位:返回可直接喂给地图适配器的报文项数组。\n *\n * 定位规则:stations 命中的站用元数据精确坐标(并带站名),未命中的回落源站坐标\n * (IEM 中国站为城市级粗坐标,严肃场景请传站点元数据——见 examples 的 stations.json 用法);\n * 源站坐标为 null 且无元数据的行跳过。\n * 失败语义(不静默,两种模式):缺省任一报文解析整体失败(站名/时组缺失)即聚合抛出,绝不静默丢行;\n * 传 options.onUnparseable 则逐行容错——失败行跳过并逐条回调(可观测),好行照常返回。\n */\nexport async function getMetarReports(\n network = \"CN__ASOS\",\n options: GetMetarReportsOptions = {},\n): Promise<MetarReportItem[]> {\n const { stations, onUnparseable, spans, ...fetchOptions } = options;\n const table = new Map((stations ?? []).map((s) => [s.icao, s] as const));\n const observations = await getMetars(network, fetchOptions);\n const items: MetarReportItem[] = [];\n const failures: string[] = [];\n for (const obs of observations) {\n const station = table.get(obs.station);\n const lat = station?.lat ?? obs.lat;\n const lon = station?.lon ?? obs.lon;\n if (lat === null || lon === null) continue;\n try {\n items.push({\n report: parse(obs.raw, spans === false ? { spans: false } : undefined),\n position: [lat, lon],\n title: `${obs.station}${station?.name ? ` ${station.name}` : \"\"}`,\n });\n } catch (err) {\n // parse 对字符串输入只抛 MetarParseError(code 稳定契约);其他异常属编程错误,直接上抛不降级\n if (!(err instanceof MetarParseError)) throw err;\n if (onUnparseable !== undefined) {\n onUnparseable({ station: obs.station, raw: obs.raw, code: err.code, error: err.message });\n continue;\n }\n failures.push(`${obs.station}: ${err.message}`);\n }\n }\n if (failures.length > 0) {\n // 聚合错误机读化(原裸 Error):code 'batch-parse-failed' 稳定契约;raw 字段在此承载\n // 汇总信息(网络名/条数/逐条站名与原因)而非单条报文原文——语义调整见 core/errors.ts 注释\n const summary = `network=${network}; failures=${failures.length}; ${failures.join(\"; \")}`;\n throw new MetarParseError(\n \"batch-parse-failed\",\n summary,\n `报文解析失败 ${failures.length} 条(network=${network})——${failures.slice(0, 3).join(\";\")}${failures.length > 3 ? \"……\" : \"\"}`,\n );\n }\n return items;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#endregion
|
|
3
|
+
//#region src/stations-cn.ts
|
|
4
|
+
/** 中国 39 站整网元数据(可直接传给 `getMetarReports` 的 `stations` 参数) */
|
|
5
|
+
const CN_STATIONS = [
|
|
6
|
+
{
|
|
7
|
+
icao: "ZBAA",
|
|
8
|
+
name: "Beijing Intl, BJ, CN",
|
|
9
|
+
lat: 40.082,
|
|
10
|
+
lon: 116.603,
|
|
11
|
+
elevM: 31
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
icao: "ZBAD",
|
|
15
|
+
name: "Beijing/Daxing Arpt, BJ, CN",
|
|
16
|
+
lat: 39.501,
|
|
17
|
+
lon: 116.412,
|
|
18
|
+
elevM: 25
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
icao: "ZBTJ",
|
|
22
|
+
name: "Tianjin/Binhai Intl, TJ, CN",
|
|
23
|
+
lat: 39.124,
|
|
24
|
+
lon: 117.346,
|
|
25
|
+
elevM: 4
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
icao: "ZBSJ",
|
|
29
|
+
name: "Zhengding Arpt, HA, CN",
|
|
30
|
+
lat: 38.281,
|
|
31
|
+
lon: 114.697,
|
|
32
|
+
elevM: 70
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
icao: "ZBHH",
|
|
36
|
+
name: "Hohhot/Baita Intl, NM, CN",
|
|
37
|
+
lat: 40.854,
|
|
38
|
+
lon: 111.827,
|
|
39
|
+
elevM: 1081
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
icao: "ZBYN",
|
|
43
|
+
name: "Taiyuan/Wusu Intl, SX, CN",
|
|
44
|
+
lat: 37.747,
|
|
45
|
+
lon: 112.628,
|
|
46
|
+
elevM: 778
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
icao: "ZSPD",
|
|
50
|
+
name: "Shanghai/Pudong Intl, SH, CN",
|
|
51
|
+
lat: 31.146,
|
|
52
|
+
lon: 121.8,
|
|
53
|
+
elevM: 4
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
icao: "ZSSS",
|
|
57
|
+
name: "Shanghai/Hongqiao Intl, SH, CN",
|
|
58
|
+
lat: 31.209,
|
|
59
|
+
lon: 121.337,
|
|
60
|
+
elevM: 2
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
icao: "ZSHC",
|
|
64
|
+
name: "Hangzhou/Xiaoshan Intl, ZJ, CN",
|
|
65
|
+
lat: 30.229,
|
|
66
|
+
lon: 120.434,
|
|
67
|
+
elevM: 8
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
icao: "ZSNJ",
|
|
71
|
+
name: "Nanjing/Lukou Intl, JS, CN",
|
|
72
|
+
lat: 31.739,
|
|
73
|
+
lon: 118.863,
|
|
74
|
+
elevM: 27
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
icao: "ZSNB",
|
|
78
|
+
name: "Ningbo/Lishe Intl, ZJ, CN",
|
|
79
|
+
lat: 29.827,
|
|
80
|
+
lon: 121.462,
|
|
81
|
+
elevM: 5
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
icao: "ZSQD",
|
|
85
|
+
name: "Qingdao/Jiaodong Arpt, SD, CN",
|
|
86
|
+
lat: 36.362,
|
|
87
|
+
lon: 120.087,
|
|
88
|
+
elevM: 2
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
icao: "ZSAM",
|
|
92
|
+
name: "Xiamen-Gaoqi Intl, FJ, CN",
|
|
93
|
+
lat: 24.546,
|
|
94
|
+
lon: 118.131,
|
|
95
|
+
elevM: 13
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
icao: "ZSFZ",
|
|
99
|
+
name: "Fuzhou/Changle Intl, FJ, CN",
|
|
100
|
+
lat: 25.936,
|
|
101
|
+
lon: 119.666,
|
|
102
|
+
elevM: 14
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
icao: "ZSOF",
|
|
106
|
+
name: "Hefei/Xinqiao Intl, AH, CN",
|
|
107
|
+
lat: 31.99,
|
|
108
|
+
lon: 116.965,
|
|
109
|
+
elevM: 64
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
icao: "ZGGG",
|
|
113
|
+
name: "Guangzhou/Baiyun Intl, GD, CN",
|
|
114
|
+
lat: 23.392,
|
|
115
|
+
lon: 113.307,
|
|
116
|
+
elevM: 11
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
icao: "ZGSZ",
|
|
120
|
+
name: "Shenzhen/Boan Intl, GD, CN",
|
|
121
|
+
lat: 22.639,
|
|
122
|
+
lon: 113.803,
|
|
123
|
+
elevM: 18
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
icao: "ZGOW",
|
|
127
|
+
name: "Jieyang/Chaoshan Intl, GD, CN",
|
|
128
|
+
lat: 23.55,
|
|
129
|
+
lon: 116.505,
|
|
130
|
+
elevM: 4
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
icao: "ZGKL",
|
|
134
|
+
name: "Guilin/Liangjiang Intl, GX, CN",
|
|
135
|
+
lat: 25.22,
|
|
136
|
+
lon: 110.04,
|
|
137
|
+
elevM: 151
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
icao: "ZGNN",
|
|
141
|
+
name: "Nanning/Wuwei Intl, GX, CN",
|
|
142
|
+
lat: 22.609,
|
|
143
|
+
lon: 108.173,
|
|
144
|
+
elevM: 128
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
icao: "ZGHA",
|
|
148
|
+
name: "Changsha/Huanghua Arpt, HN, CN",
|
|
149
|
+
lat: 28.18,
|
|
150
|
+
lon: 113.219,
|
|
151
|
+
elevM: 61
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
icao: "ZHCC",
|
|
155
|
+
name: "Zhengzhou/Xinzheng Arpt, HA, CN",
|
|
156
|
+
lat: 34.52,
|
|
157
|
+
lon: 113.834,
|
|
158
|
+
elevM: 149
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
icao: "ZHEC",
|
|
162
|
+
name: "Ezhou Huahu Arpt, HB, CN",
|
|
163
|
+
lat: 30.3424,
|
|
164
|
+
lon: 115.0389,
|
|
165
|
+
elevM: 21
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
icao: "ZHHH",
|
|
169
|
+
name: "Wuhan/Tianhe Intl, HB, CN",
|
|
170
|
+
lat: 30.783,
|
|
171
|
+
lon: 114.205,
|
|
172
|
+
elevM: 33
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
icao: "ZJHK",
|
|
176
|
+
name: "Haikou/Meilan Intl, HI, CN",
|
|
177
|
+
lat: 19.934,
|
|
178
|
+
lon: 110.445,
|
|
179
|
+
elevM: 21
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
icao: "ZJSY",
|
|
183
|
+
name: "Sanya/Phoenix Intl, HN, CN",
|
|
184
|
+
lat: 18.303,
|
|
185
|
+
lon: 109.412,
|
|
186
|
+
elevM: 27
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
icao: "ZUUU",
|
|
190
|
+
name: "Chengdu/Shuangliu Intl, CQ, CN",
|
|
191
|
+
lat: 30.576,
|
|
192
|
+
lon: 103.95,
|
|
193
|
+
elevM: 494
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
icao: "ZUTF",
|
|
197
|
+
name: "Chengdu/Tianfu Arpt, CQ, CN",
|
|
198
|
+
lat: 30.304,
|
|
199
|
+
lon: 104.436,
|
|
200
|
+
elevM: 443
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
icao: "ZUCK",
|
|
204
|
+
name: "Chongqing/Jiangbei Intl, CQ, CN",
|
|
205
|
+
lat: 29.718,
|
|
206
|
+
lon: 106.639,
|
|
207
|
+
elevM: 416
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
icao: "ZUGY",
|
|
211
|
+
name: "Guizhou/Longdongbao Arpt, GZ, CN",
|
|
212
|
+
lat: 26.538,
|
|
213
|
+
lon: 106.801,
|
|
214
|
+
elevM: 1130
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
icao: "ZPPP",
|
|
218
|
+
name: "Kunming/Changshui Intl, YN, CN",
|
|
219
|
+
lat: 25.107,
|
|
220
|
+
lon: 102.934,
|
|
221
|
+
elevM: 2104
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
icao: "ZLLL",
|
|
225
|
+
name: "Lanzhou/Zhongchuan Arpt, GS, CN",
|
|
226
|
+
lat: 36.513,
|
|
227
|
+
lon: 103.623,
|
|
228
|
+
elevM: 1939
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
icao: "ZLXY",
|
|
232
|
+
name: "Xianyang Intl, SN, CN",
|
|
233
|
+
lat: 34.449,
|
|
234
|
+
lon: 108.752,
|
|
235
|
+
elevM: 478
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
icao: "ZWWW",
|
|
239
|
+
name: "Ürümqi/Diwopu Arpt, XJ, CN",
|
|
240
|
+
lat: 43.907,
|
|
241
|
+
lon: 87.474,
|
|
242
|
+
elevM: 654
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
icao: "ZWSH",
|
|
246
|
+
name: "Kashgar Arpt, XJ, CN",
|
|
247
|
+
lat: 39.542,
|
|
248
|
+
lon: 76.019,
|
|
249
|
+
elevM: 1374
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
icao: "ZYHB",
|
|
253
|
+
name: "Harbin/Taiping Arpt, HL, CN",
|
|
254
|
+
lat: 45.628,
|
|
255
|
+
lon: 126.259,
|
|
256
|
+
elevM: 134
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
icao: "ZYCC",
|
|
260
|
+
name: "Changchun/Longjia Intl, JL, CN",
|
|
261
|
+
lat: 43.993,
|
|
262
|
+
lon: 125.682,
|
|
263
|
+
elevM: 211
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
icao: "ZYTL",
|
|
267
|
+
name: "Dalian/Zhoushuizi Intl, LN, CN",
|
|
268
|
+
lat: 38.961,
|
|
269
|
+
lon: 121.556,
|
|
270
|
+
elevM: 33
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
icao: "ZYTX",
|
|
274
|
+
name: "Shenyang/Taoxian Intl, LN, CN",
|
|
275
|
+
lat: 41.639,
|
|
276
|
+
lon: 123.485,
|
|
277
|
+
elevM: 56
|
|
278
|
+
}
|
|
279
|
+
];
|
|
280
|
+
//#endregion
|
|
281
|
+
exports.CN_STATIONS = CN_STATIONS;
|
|
282
|
+
|
|
283
|
+
//# sourceMappingURL=stations-cn.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stations-cn.cjs","names":[],"sources":["../src/stations-cn.data.ts","../src/stations-cn.ts"],"sourcesContent":["/**\n * 由 `pnpm gen:stations` 机械再生——单一来源 = aviationweather.gov/api/data/metar 一次拉取\n * (采集纪律与字段口径见 scripts/generate-stations.mjs);请勿手改。\n * 生成时刻:2026-09-11T08:29:45.262Z\n */\n\nexport interface CnStationRaw {\n icao: string;\n name: string;\n lat: number;\n lon: number;\n elevM: number;\n}\n\nexport const CN_STATIONS_SOURCE = \"aviationweather.gov/api/data/metar\";\n\nexport const CN_STATIONS_GENERATED_AT = \"2026-09-11T08:29:45.262Z\";\n\nexport const CN_STATIONS_DATA: readonly CnStationRaw[] = [\n {\n icao: \"ZBAA\",\n name: \"Beijing Intl, BJ, CN\",\n lat: 40.082,\n lon: 116.603,\n elevM: 31,\n },\n {\n icao: \"ZBAD\",\n name: \"Beijing/Daxing Arpt, BJ, CN\",\n lat: 39.501,\n lon: 116.412,\n elevM: 25,\n },\n {\n icao: \"ZBTJ\",\n name: \"Tianjin/Binhai Intl, TJ, CN\",\n lat: 39.124,\n lon: 117.346,\n elevM: 4,\n },\n {\n icao: \"ZBSJ\",\n name: \"Zhengding Arpt, HA, CN\",\n lat: 38.281,\n lon: 114.697,\n elevM: 70,\n },\n {\n icao: \"ZBHH\",\n name: \"Hohhot/Baita Intl, NM, CN\",\n lat: 40.854,\n lon: 111.827,\n elevM: 1081,\n },\n {\n icao: \"ZBYN\",\n name: \"Taiyuan/Wusu Intl, SX, CN\",\n lat: 37.747,\n lon: 112.628,\n elevM: 778,\n },\n {\n icao: \"ZSPD\",\n name: \"Shanghai/Pudong Intl, SH, CN\",\n lat: 31.146,\n lon: 121.8,\n elevM: 4,\n },\n {\n icao: \"ZSSS\",\n name: \"Shanghai/Hongqiao Intl, SH, CN\",\n lat: 31.209,\n lon: 121.337,\n elevM: 2,\n },\n {\n icao: \"ZSHC\",\n name: \"Hangzhou/Xiaoshan Intl, ZJ, CN\",\n lat: 30.229,\n lon: 120.434,\n elevM: 8,\n },\n {\n icao: \"ZSNJ\",\n name: \"Nanjing/Lukou Intl, JS, CN\",\n lat: 31.739,\n lon: 118.863,\n elevM: 27,\n },\n {\n icao: \"ZSNB\",\n name: \"Ningbo/Lishe Intl, ZJ, CN\",\n lat: 29.827,\n lon: 121.462,\n elevM: 5,\n },\n {\n icao: \"ZSQD\",\n name: \"Qingdao/Jiaodong Arpt, SD, CN\",\n lat: 36.362,\n lon: 120.087,\n elevM: 2,\n },\n {\n icao: \"ZSAM\",\n name: \"Xiamen-Gaoqi Intl, FJ, CN\",\n lat: 24.546,\n lon: 118.131,\n elevM: 13,\n },\n {\n icao: \"ZSFZ\",\n name: \"Fuzhou/Changle Intl, FJ, CN\",\n lat: 25.936,\n lon: 119.666,\n elevM: 14,\n },\n {\n icao: \"ZSOF\",\n name: \"Hefei/Xinqiao Intl, AH, CN\",\n lat: 31.99,\n lon: 116.965,\n elevM: 64,\n },\n {\n icao: \"ZGGG\",\n name: \"Guangzhou/Baiyun Intl, GD, CN\",\n lat: 23.392,\n lon: 113.307,\n elevM: 11,\n },\n {\n icao: \"ZGSZ\",\n name: \"Shenzhen/Boan Intl, GD, CN\",\n lat: 22.639,\n lon: 113.803,\n elevM: 18,\n },\n {\n icao: \"ZGOW\",\n name: \"Jieyang/Chaoshan Intl, GD, CN\",\n lat: 23.55,\n lon: 116.505,\n elevM: 4,\n },\n {\n icao: \"ZGKL\",\n name: \"Guilin/Liangjiang Intl, GX, CN\",\n lat: 25.22,\n lon: 110.04,\n elevM: 151,\n },\n {\n icao: \"ZGNN\",\n name: \"Nanning/Wuwei Intl, GX, CN\",\n lat: 22.609,\n lon: 108.173,\n elevM: 128,\n },\n {\n icao: \"ZGHA\",\n name: \"Changsha/Huanghua Arpt, HN, CN\",\n lat: 28.18,\n lon: 113.219,\n elevM: 61,\n },\n {\n icao: \"ZHCC\",\n name: \"Zhengzhou/Xinzheng Arpt, HA, CN\",\n lat: 34.52,\n lon: 113.834,\n elevM: 149,\n },\n {\n icao: \"ZHEC\",\n name: \"Ezhou Huahu Arpt, HB, CN\",\n lat: 30.3424,\n lon: 115.0389,\n elevM: 21,\n },\n {\n icao: \"ZHHH\",\n name: \"Wuhan/Tianhe Intl, HB, CN\",\n lat: 30.783,\n lon: 114.205,\n elevM: 33,\n },\n {\n icao: \"ZJHK\",\n name: \"Haikou/Meilan Intl, HI, CN\",\n lat: 19.934,\n lon: 110.445,\n elevM: 21,\n },\n {\n icao: \"ZJSY\",\n name: \"Sanya/Phoenix Intl, HN, CN\",\n lat: 18.303,\n lon: 109.412,\n elevM: 27,\n },\n {\n icao: \"ZUUU\",\n name: \"Chengdu/Shuangliu Intl, CQ, CN\",\n lat: 30.576,\n lon: 103.95,\n elevM: 494,\n },\n {\n icao: \"ZUTF\",\n name: \"Chengdu/Tianfu Arpt, CQ, CN\",\n lat: 30.304,\n lon: 104.436,\n elevM: 443,\n },\n {\n icao: \"ZUCK\",\n name: \"Chongqing/Jiangbei Intl, CQ, CN\",\n lat: 29.718,\n lon: 106.639,\n elevM: 416,\n },\n {\n icao: \"ZUGY\",\n name: \"Guizhou/Longdongbao Arpt, GZ, CN\",\n lat: 26.538,\n lon: 106.801,\n elevM: 1130,\n },\n {\n icao: \"ZPPP\",\n name: \"Kunming/Changshui Intl, YN, CN\",\n lat: 25.107,\n lon: 102.934,\n elevM: 2104,\n },\n {\n icao: \"ZLLL\",\n name: \"Lanzhou/Zhongchuan Arpt, GS, CN\",\n lat: 36.513,\n lon: 103.623,\n elevM: 1939,\n },\n {\n icao: \"ZLXY\",\n name: \"Xianyang Intl, SN, CN\",\n lat: 34.449,\n lon: 108.752,\n elevM: 478,\n },\n {\n icao: \"ZWWW\",\n name: \"\\u00dcr\\u00fcmqi/Diwopu Arpt, XJ, CN\",\n lat: 43.907,\n lon: 87.474,\n elevM: 654,\n },\n {\n icao: \"ZWSH\",\n name: \"Kashgar Arpt, XJ, CN\",\n lat: 39.542,\n lon: 76.019,\n elevM: 1374,\n },\n {\n icao: \"ZYHB\",\n name: \"Harbin/Taiping Arpt, HL, CN\",\n lat: 45.628,\n lon: 126.259,\n elevM: 134,\n },\n {\n icao: \"ZYCC\",\n name: \"Changchun/Longjia Intl, JL, CN\",\n lat: 43.993,\n lon: 125.682,\n elevM: 211,\n },\n {\n icao: \"ZYTL\",\n name: \"Dalian/Zhoushuizi Intl, LN, CN\",\n lat: 38.961,\n lon: 121.556,\n elevM: 33,\n },\n {\n icao: \"ZYTX\",\n name: \"Shenyang/Taoxian Intl, LN, CN\",\n lat: 41.639,\n lon: 123.485,\n elevM: 56,\n },\n];\n","/**\n * `metweave/stations-cn` —— 中国 39 站静态元数据(ICAO / 名称 / WGS-84 坐标 / 标高米)。\n *\n * 数据来源 aviationweather.gov(一次采集,`pnpm gen:stations` 单源再生,见 stations-cn.data.ts 头注);\n * 用途:`getMetarReports(network, { stations })` 联表——IEM 自带的站点坐标是城市级粗定位,\n * 传入本表即得精确机场坐标与站名(「四字码 → 站名」开箱即用)。\n * 事实性元数据,随包分发;观测报文数据不在此列(许可边界见主 README「许可」)。\n */\nimport type { StationRef } from \"./sources.js\";\nimport { CN_STATIONS_DATA } from \"./stations-cn.data.js\";\n\n/** 一个中国站点的静态元数据(StationRef 联表形态 + 标高) */\nexport interface CnStation extends StationRef {\n /** 标高(米,上游即米制口径) */\n elevM: number;\n}\n\n/** 中国 39 站整网元数据(可直接传给 `getMetarReports` 的 `stations` 参数) */\nexport const CN_STATIONS: readonly CnStation[] = CN_STATIONS_DATA;\n"],"mappings":";;;;ACkBA,MAAa,cAAoC;CDC/C;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;ACjR+C"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StationRef } from "./sources.cjs";
|
|
2
|
+
//#region src/stations-cn.d.ts
|
|
3
|
+
/** 一个中国站点的静态元数据(StationRef 联表形态 + 标高) */
|
|
4
|
+
export interface CnStation extends StationRef {
|
|
5
|
+
/** 标高(米,上游即米制口径) */
|
|
6
|
+
elevM: number;
|
|
7
|
+
}
|
|
8
|
+
/** 中国 39 站整网元数据(可直接传给 `getMetarReports` 的 `stations` 参数) */
|
|
9
|
+
export declare const CN_STATIONS: readonly CnStation[];
|
|
10
|
+
//#endregion
|
|
11
|
+
//# sourceMappingURL=stations-cn.d.cts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StationRef } from "./sources.js";
|
|
2
|
+
//#region src/stations-cn.d.ts
|
|
3
|
+
/** 一个中国站点的静态元数据(StationRef 联表形态 + 标高) */
|
|
4
|
+
export interface CnStation extends StationRef {
|
|
5
|
+
/** 标高(米,上游即米制口径) */
|
|
6
|
+
elevM: number;
|
|
7
|
+
}
|
|
8
|
+
/** 中国 39 站整网元数据(可直接传给 `getMetarReports` 的 `stations` 参数) */
|
|
9
|
+
export declare const CN_STATIONS: readonly CnStation[];
|
|
10
|
+
//#endregion
|
|
11
|
+
//# sourceMappingURL=stations-cn.d.ts.map
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
//#endregion
|
|
2
|
+
//#region src/stations-cn.ts
|
|
3
|
+
/** 中国 39 站整网元数据(可直接传给 `getMetarReports` 的 `stations` 参数) */
|
|
4
|
+
const CN_STATIONS = [
|
|
5
|
+
{
|
|
6
|
+
icao: "ZBAA",
|
|
7
|
+
name: "Beijing Intl, BJ, CN",
|
|
8
|
+
lat: 40.082,
|
|
9
|
+
lon: 116.603,
|
|
10
|
+
elevM: 31
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
icao: "ZBAD",
|
|
14
|
+
name: "Beijing/Daxing Arpt, BJ, CN",
|
|
15
|
+
lat: 39.501,
|
|
16
|
+
lon: 116.412,
|
|
17
|
+
elevM: 25
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
icao: "ZBTJ",
|
|
21
|
+
name: "Tianjin/Binhai Intl, TJ, CN",
|
|
22
|
+
lat: 39.124,
|
|
23
|
+
lon: 117.346,
|
|
24
|
+
elevM: 4
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
icao: "ZBSJ",
|
|
28
|
+
name: "Zhengding Arpt, HA, CN",
|
|
29
|
+
lat: 38.281,
|
|
30
|
+
lon: 114.697,
|
|
31
|
+
elevM: 70
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
icao: "ZBHH",
|
|
35
|
+
name: "Hohhot/Baita Intl, NM, CN",
|
|
36
|
+
lat: 40.854,
|
|
37
|
+
lon: 111.827,
|
|
38
|
+
elevM: 1081
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
icao: "ZBYN",
|
|
42
|
+
name: "Taiyuan/Wusu Intl, SX, CN",
|
|
43
|
+
lat: 37.747,
|
|
44
|
+
lon: 112.628,
|
|
45
|
+
elevM: 778
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
icao: "ZSPD",
|
|
49
|
+
name: "Shanghai/Pudong Intl, SH, CN",
|
|
50
|
+
lat: 31.146,
|
|
51
|
+
lon: 121.8,
|
|
52
|
+
elevM: 4
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
icao: "ZSSS",
|
|
56
|
+
name: "Shanghai/Hongqiao Intl, SH, CN",
|
|
57
|
+
lat: 31.209,
|
|
58
|
+
lon: 121.337,
|
|
59
|
+
elevM: 2
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
icao: "ZSHC",
|
|
63
|
+
name: "Hangzhou/Xiaoshan Intl, ZJ, CN",
|
|
64
|
+
lat: 30.229,
|
|
65
|
+
lon: 120.434,
|
|
66
|
+
elevM: 8
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
icao: "ZSNJ",
|
|
70
|
+
name: "Nanjing/Lukou Intl, JS, CN",
|
|
71
|
+
lat: 31.739,
|
|
72
|
+
lon: 118.863,
|
|
73
|
+
elevM: 27
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
icao: "ZSNB",
|
|
77
|
+
name: "Ningbo/Lishe Intl, ZJ, CN",
|
|
78
|
+
lat: 29.827,
|
|
79
|
+
lon: 121.462,
|
|
80
|
+
elevM: 5
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
icao: "ZSQD",
|
|
84
|
+
name: "Qingdao/Jiaodong Arpt, SD, CN",
|
|
85
|
+
lat: 36.362,
|
|
86
|
+
lon: 120.087,
|
|
87
|
+
elevM: 2
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
icao: "ZSAM",
|
|
91
|
+
name: "Xiamen-Gaoqi Intl, FJ, CN",
|
|
92
|
+
lat: 24.546,
|
|
93
|
+
lon: 118.131,
|
|
94
|
+
elevM: 13
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
icao: "ZSFZ",
|
|
98
|
+
name: "Fuzhou/Changle Intl, FJ, CN",
|
|
99
|
+
lat: 25.936,
|
|
100
|
+
lon: 119.666,
|
|
101
|
+
elevM: 14
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
icao: "ZSOF",
|
|
105
|
+
name: "Hefei/Xinqiao Intl, AH, CN",
|
|
106
|
+
lat: 31.99,
|
|
107
|
+
lon: 116.965,
|
|
108
|
+
elevM: 64
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
icao: "ZGGG",
|
|
112
|
+
name: "Guangzhou/Baiyun Intl, GD, CN",
|
|
113
|
+
lat: 23.392,
|
|
114
|
+
lon: 113.307,
|
|
115
|
+
elevM: 11
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
icao: "ZGSZ",
|
|
119
|
+
name: "Shenzhen/Boan Intl, GD, CN",
|
|
120
|
+
lat: 22.639,
|
|
121
|
+
lon: 113.803,
|
|
122
|
+
elevM: 18
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
icao: "ZGOW",
|
|
126
|
+
name: "Jieyang/Chaoshan Intl, GD, CN",
|
|
127
|
+
lat: 23.55,
|
|
128
|
+
lon: 116.505,
|
|
129
|
+
elevM: 4
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
icao: "ZGKL",
|
|
133
|
+
name: "Guilin/Liangjiang Intl, GX, CN",
|
|
134
|
+
lat: 25.22,
|
|
135
|
+
lon: 110.04,
|
|
136
|
+
elevM: 151
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
icao: "ZGNN",
|
|
140
|
+
name: "Nanning/Wuwei Intl, GX, CN",
|
|
141
|
+
lat: 22.609,
|
|
142
|
+
lon: 108.173,
|
|
143
|
+
elevM: 128
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
icao: "ZGHA",
|
|
147
|
+
name: "Changsha/Huanghua Arpt, HN, CN",
|
|
148
|
+
lat: 28.18,
|
|
149
|
+
lon: 113.219,
|
|
150
|
+
elevM: 61
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
icao: "ZHCC",
|
|
154
|
+
name: "Zhengzhou/Xinzheng Arpt, HA, CN",
|
|
155
|
+
lat: 34.52,
|
|
156
|
+
lon: 113.834,
|
|
157
|
+
elevM: 149
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
icao: "ZHEC",
|
|
161
|
+
name: "Ezhou Huahu Arpt, HB, CN",
|
|
162
|
+
lat: 30.3424,
|
|
163
|
+
lon: 115.0389,
|
|
164
|
+
elevM: 21
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
icao: "ZHHH",
|
|
168
|
+
name: "Wuhan/Tianhe Intl, HB, CN",
|
|
169
|
+
lat: 30.783,
|
|
170
|
+
lon: 114.205,
|
|
171
|
+
elevM: 33
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
icao: "ZJHK",
|
|
175
|
+
name: "Haikou/Meilan Intl, HI, CN",
|
|
176
|
+
lat: 19.934,
|
|
177
|
+
lon: 110.445,
|
|
178
|
+
elevM: 21
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
icao: "ZJSY",
|
|
182
|
+
name: "Sanya/Phoenix Intl, HN, CN",
|
|
183
|
+
lat: 18.303,
|
|
184
|
+
lon: 109.412,
|
|
185
|
+
elevM: 27
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
icao: "ZUUU",
|
|
189
|
+
name: "Chengdu/Shuangliu Intl, CQ, CN",
|
|
190
|
+
lat: 30.576,
|
|
191
|
+
lon: 103.95,
|
|
192
|
+
elevM: 494
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
icao: "ZUTF",
|
|
196
|
+
name: "Chengdu/Tianfu Arpt, CQ, CN",
|
|
197
|
+
lat: 30.304,
|
|
198
|
+
lon: 104.436,
|
|
199
|
+
elevM: 443
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
icao: "ZUCK",
|
|
203
|
+
name: "Chongqing/Jiangbei Intl, CQ, CN",
|
|
204
|
+
lat: 29.718,
|
|
205
|
+
lon: 106.639,
|
|
206
|
+
elevM: 416
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
icao: "ZUGY",
|
|
210
|
+
name: "Guizhou/Longdongbao Arpt, GZ, CN",
|
|
211
|
+
lat: 26.538,
|
|
212
|
+
lon: 106.801,
|
|
213
|
+
elevM: 1130
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
icao: "ZPPP",
|
|
217
|
+
name: "Kunming/Changshui Intl, YN, CN",
|
|
218
|
+
lat: 25.107,
|
|
219
|
+
lon: 102.934,
|
|
220
|
+
elevM: 2104
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
icao: "ZLLL",
|
|
224
|
+
name: "Lanzhou/Zhongchuan Arpt, GS, CN",
|
|
225
|
+
lat: 36.513,
|
|
226
|
+
lon: 103.623,
|
|
227
|
+
elevM: 1939
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
icao: "ZLXY",
|
|
231
|
+
name: "Xianyang Intl, SN, CN",
|
|
232
|
+
lat: 34.449,
|
|
233
|
+
lon: 108.752,
|
|
234
|
+
elevM: 478
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
icao: "ZWWW",
|
|
238
|
+
name: "Ürümqi/Diwopu Arpt, XJ, CN",
|
|
239
|
+
lat: 43.907,
|
|
240
|
+
lon: 87.474,
|
|
241
|
+
elevM: 654
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
icao: "ZWSH",
|
|
245
|
+
name: "Kashgar Arpt, XJ, CN",
|
|
246
|
+
lat: 39.542,
|
|
247
|
+
lon: 76.019,
|
|
248
|
+
elevM: 1374
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
icao: "ZYHB",
|
|
252
|
+
name: "Harbin/Taiping Arpt, HL, CN",
|
|
253
|
+
lat: 45.628,
|
|
254
|
+
lon: 126.259,
|
|
255
|
+
elevM: 134
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
icao: "ZYCC",
|
|
259
|
+
name: "Changchun/Longjia Intl, JL, CN",
|
|
260
|
+
lat: 43.993,
|
|
261
|
+
lon: 125.682,
|
|
262
|
+
elevM: 211
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
icao: "ZYTL",
|
|
266
|
+
name: "Dalian/Zhoushuizi Intl, LN, CN",
|
|
267
|
+
lat: 38.961,
|
|
268
|
+
lon: 121.556,
|
|
269
|
+
elevM: 33
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
icao: "ZYTX",
|
|
273
|
+
name: "Shenyang/Taoxian Intl, LN, CN",
|
|
274
|
+
lat: 41.639,
|
|
275
|
+
lon: 123.485,
|
|
276
|
+
elevM: 56
|
|
277
|
+
}
|
|
278
|
+
];
|
|
279
|
+
//#endregion
|
|
280
|
+
export { CN_STATIONS };
|
|
281
|
+
|
|
282
|
+
//# sourceMappingURL=stations-cn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stations-cn.js","names":[],"sources":["../src/stations-cn.data.ts","../src/stations-cn.ts"],"sourcesContent":["/**\n * 由 `pnpm gen:stations` 机械再生——单一来源 = aviationweather.gov/api/data/metar 一次拉取\n * (采集纪律与字段口径见 scripts/generate-stations.mjs);请勿手改。\n * 生成时刻:2026-09-11T08:29:45.262Z\n */\n\nexport interface CnStationRaw {\n icao: string;\n name: string;\n lat: number;\n lon: number;\n elevM: number;\n}\n\nexport const CN_STATIONS_SOURCE = \"aviationweather.gov/api/data/metar\";\n\nexport const CN_STATIONS_GENERATED_AT = \"2026-09-11T08:29:45.262Z\";\n\nexport const CN_STATIONS_DATA: readonly CnStationRaw[] = [\n {\n icao: \"ZBAA\",\n name: \"Beijing Intl, BJ, CN\",\n lat: 40.082,\n lon: 116.603,\n elevM: 31,\n },\n {\n icao: \"ZBAD\",\n name: \"Beijing/Daxing Arpt, BJ, CN\",\n lat: 39.501,\n lon: 116.412,\n elevM: 25,\n },\n {\n icao: \"ZBTJ\",\n name: \"Tianjin/Binhai Intl, TJ, CN\",\n lat: 39.124,\n lon: 117.346,\n elevM: 4,\n },\n {\n icao: \"ZBSJ\",\n name: \"Zhengding Arpt, HA, CN\",\n lat: 38.281,\n lon: 114.697,\n elevM: 70,\n },\n {\n icao: \"ZBHH\",\n name: \"Hohhot/Baita Intl, NM, CN\",\n lat: 40.854,\n lon: 111.827,\n elevM: 1081,\n },\n {\n icao: \"ZBYN\",\n name: \"Taiyuan/Wusu Intl, SX, CN\",\n lat: 37.747,\n lon: 112.628,\n elevM: 778,\n },\n {\n icao: \"ZSPD\",\n name: \"Shanghai/Pudong Intl, SH, CN\",\n lat: 31.146,\n lon: 121.8,\n elevM: 4,\n },\n {\n icao: \"ZSSS\",\n name: \"Shanghai/Hongqiao Intl, SH, CN\",\n lat: 31.209,\n lon: 121.337,\n elevM: 2,\n },\n {\n icao: \"ZSHC\",\n name: \"Hangzhou/Xiaoshan Intl, ZJ, CN\",\n lat: 30.229,\n lon: 120.434,\n elevM: 8,\n },\n {\n icao: \"ZSNJ\",\n name: \"Nanjing/Lukou Intl, JS, CN\",\n lat: 31.739,\n lon: 118.863,\n elevM: 27,\n },\n {\n icao: \"ZSNB\",\n name: \"Ningbo/Lishe Intl, ZJ, CN\",\n lat: 29.827,\n lon: 121.462,\n elevM: 5,\n },\n {\n icao: \"ZSQD\",\n name: \"Qingdao/Jiaodong Arpt, SD, CN\",\n lat: 36.362,\n lon: 120.087,\n elevM: 2,\n },\n {\n icao: \"ZSAM\",\n name: \"Xiamen-Gaoqi Intl, FJ, CN\",\n lat: 24.546,\n lon: 118.131,\n elevM: 13,\n },\n {\n icao: \"ZSFZ\",\n name: \"Fuzhou/Changle Intl, FJ, CN\",\n lat: 25.936,\n lon: 119.666,\n elevM: 14,\n },\n {\n icao: \"ZSOF\",\n name: \"Hefei/Xinqiao Intl, AH, CN\",\n lat: 31.99,\n lon: 116.965,\n elevM: 64,\n },\n {\n icao: \"ZGGG\",\n name: \"Guangzhou/Baiyun Intl, GD, CN\",\n lat: 23.392,\n lon: 113.307,\n elevM: 11,\n },\n {\n icao: \"ZGSZ\",\n name: \"Shenzhen/Boan Intl, GD, CN\",\n lat: 22.639,\n lon: 113.803,\n elevM: 18,\n },\n {\n icao: \"ZGOW\",\n name: \"Jieyang/Chaoshan Intl, GD, CN\",\n lat: 23.55,\n lon: 116.505,\n elevM: 4,\n },\n {\n icao: \"ZGKL\",\n name: \"Guilin/Liangjiang Intl, GX, CN\",\n lat: 25.22,\n lon: 110.04,\n elevM: 151,\n },\n {\n icao: \"ZGNN\",\n name: \"Nanning/Wuwei Intl, GX, CN\",\n lat: 22.609,\n lon: 108.173,\n elevM: 128,\n },\n {\n icao: \"ZGHA\",\n name: \"Changsha/Huanghua Arpt, HN, CN\",\n lat: 28.18,\n lon: 113.219,\n elevM: 61,\n },\n {\n icao: \"ZHCC\",\n name: \"Zhengzhou/Xinzheng Arpt, HA, CN\",\n lat: 34.52,\n lon: 113.834,\n elevM: 149,\n },\n {\n icao: \"ZHEC\",\n name: \"Ezhou Huahu Arpt, HB, CN\",\n lat: 30.3424,\n lon: 115.0389,\n elevM: 21,\n },\n {\n icao: \"ZHHH\",\n name: \"Wuhan/Tianhe Intl, HB, CN\",\n lat: 30.783,\n lon: 114.205,\n elevM: 33,\n },\n {\n icao: \"ZJHK\",\n name: \"Haikou/Meilan Intl, HI, CN\",\n lat: 19.934,\n lon: 110.445,\n elevM: 21,\n },\n {\n icao: \"ZJSY\",\n name: \"Sanya/Phoenix Intl, HN, CN\",\n lat: 18.303,\n lon: 109.412,\n elevM: 27,\n },\n {\n icao: \"ZUUU\",\n name: \"Chengdu/Shuangliu Intl, CQ, CN\",\n lat: 30.576,\n lon: 103.95,\n elevM: 494,\n },\n {\n icao: \"ZUTF\",\n name: \"Chengdu/Tianfu Arpt, CQ, CN\",\n lat: 30.304,\n lon: 104.436,\n elevM: 443,\n },\n {\n icao: \"ZUCK\",\n name: \"Chongqing/Jiangbei Intl, CQ, CN\",\n lat: 29.718,\n lon: 106.639,\n elevM: 416,\n },\n {\n icao: \"ZUGY\",\n name: \"Guizhou/Longdongbao Arpt, GZ, CN\",\n lat: 26.538,\n lon: 106.801,\n elevM: 1130,\n },\n {\n icao: \"ZPPP\",\n name: \"Kunming/Changshui Intl, YN, CN\",\n lat: 25.107,\n lon: 102.934,\n elevM: 2104,\n },\n {\n icao: \"ZLLL\",\n name: \"Lanzhou/Zhongchuan Arpt, GS, CN\",\n lat: 36.513,\n lon: 103.623,\n elevM: 1939,\n },\n {\n icao: \"ZLXY\",\n name: \"Xianyang Intl, SN, CN\",\n lat: 34.449,\n lon: 108.752,\n elevM: 478,\n },\n {\n icao: \"ZWWW\",\n name: \"\\u00dcr\\u00fcmqi/Diwopu Arpt, XJ, CN\",\n lat: 43.907,\n lon: 87.474,\n elevM: 654,\n },\n {\n icao: \"ZWSH\",\n name: \"Kashgar Arpt, XJ, CN\",\n lat: 39.542,\n lon: 76.019,\n elevM: 1374,\n },\n {\n icao: \"ZYHB\",\n name: \"Harbin/Taiping Arpt, HL, CN\",\n lat: 45.628,\n lon: 126.259,\n elevM: 134,\n },\n {\n icao: \"ZYCC\",\n name: \"Changchun/Longjia Intl, JL, CN\",\n lat: 43.993,\n lon: 125.682,\n elevM: 211,\n },\n {\n icao: \"ZYTL\",\n name: \"Dalian/Zhoushuizi Intl, LN, CN\",\n lat: 38.961,\n lon: 121.556,\n elevM: 33,\n },\n {\n icao: \"ZYTX\",\n name: \"Shenyang/Taoxian Intl, LN, CN\",\n lat: 41.639,\n lon: 123.485,\n elevM: 56,\n },\n];\n","/**\n * `metweave/stations-cn` —— 中国 39 站静态元数据(ICAO / 名称 / WGS-84 坐标 / 标高米)。\n *\n * 数据来源 aviationweather.gov(一次采集,`pnpm gen:stations` 单源再生,见 stations-cn.data.ts 头注);\n * 用途:`getMetarReports(network, { stations })` 联表——IEM 自带的站点坐标是城市级粗定位,\n * 传入本表即得精确机场坐标与站名(「四字码 → 站名」开箱即用)。\n * 事实性元数据,随包分发;观测报文数据不在此列(许可边界见主 README「许可」)。\n */\nimport type { StationRef } from \"./sources.js\";\nimport { CN_STATIONS_DATA } from \"./stations-cn.data.js\";\n\n/** 一个中国站点的静态元数据(StationRef 联表形态 + 标高) */\nexport interface CnStation extends StationRef {\n /** 标高(米,上游即米制口径) */\n elevM: number;\n}\n\n/** 中国 39 站整网元数据(可直接传给 `getMetarReports` 的 `stations` 参数) */\nexport const CN_STATIONS: readonly CnStation[] = CN_STATIONS_DATA;\n"],"mappings":";;;ACkBA,MAAa,cAAoC;CDC/C;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;CACA;EACE,MAAM;EACN,MAAM;EACN,KAAK;EACL,KAAK;EACL,OAAO;CACT;ACjR+C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metweave",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Show live airport weather on a Leaflet map in ten lines — METAR parsing, report cards and map layers, straight from public sources.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"airport",
|
|
@@ -50,15 +50,25 @@
|
|
|
50
50
|
"types": "./dist/sources.d.cts",
|
|
51
51
|
"default": "./dist/sources.cjs"
|
|
52
52
|
}
|
|
53
|
+
},
|
|
54
|
+
"./stations-cn": {
|
|
55
|
+
"import": {
|
|
56
|
+
"types": "./dist/stations-cn.d.ts",
|
|
57
|
+
"default": "./dist/stations-cn.js"
|
|
58
|
+
},
|
|
59
|
+
"require": {
|
|
60
|
+
"types": "./dist/stations-cn.d.cts",
|
|
61
|
+
"default": "./dist/stations-cn.cjs"
|
|
62
|
+
}
|
|
53
63
|
}
|
|
54
64
|
},
|
|
55
65
|
"publishConfig": {
|
|
56
66
|
"access": "public"
|
|
57
67
|
},
|
|
58
68
|
"dependencies": {
|
|
59
|
-
"@metweave/core": "0.1.
|
|
60
|
-
"@metweave/parser": "0.1.
|
|
61
|
-
"@metweave/render": "0.1.
|
|
69
|
+
"@metweave/core": "0.1.1",
|
|
70
|
+
"@metweave/parser": "0.1.1",
|
|
71
|
+
"@metweave/render": "0.1.1"
|
|
62
72
|
},
|
|
63
73
|
"engines": {
|
|
64
74
|
"node": ">=20"
|