feat: add WeatherV2 wind canonical converter

This commit is contained in:
OpenAI Codex
2026-09-04 21:08:07 +08:00
parent 5fc922785d
commit 79d4aa6385
17 changed files with 965 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
# STEP-054 真实 WeatherV2 风数据提取与 Canonical 测试数据生成
- 状态:`IMPLEMENTED / WAITING_NEXT_ANDROID_INTEGRATION_VALIDATION`
- Starting HEAD:`5fc922785d8ec007127d48cd3ab5dadce270b947`
- 分支:`particle/main`
- 目标:将 WeatherV2 ECMWF HRES 的真实 `WVECBIN1` 风场无损转换为已冻结的 `maplibre-particle-field/v1` Android 测试 asset;不实现网络、Provider、React Native Binding 或 Android 综合绘制。
## 实际数据与格式依据
读取 WeatherV2 源目录:
`/root/sourceserver/weatherV2/src/dev/_runtime/dev_pipeline_data/datasets/ecmwf-hres/wind/2026080600`
按既定时次生成 T0~T3:`2026080700`、`2026080706`、`2026080712`、`2026080718`。四帧均实际存在,均为日本及周边的完整区域 grid:`513×769`、bounds `112.5, 0.0, 157.5, 55.77657301866769`,单源文件 `5,128,989` bytes。
WeatherV2 `src/dev/core/bin_export.py` 的 `_export_vector_target()` 先以 `decode_wind()` 从原 Windy `wm_grid_257` tile 解码 U/V,再按 `WVECBIN1 + uint32-le header length + JSON header + U float32-le plane + V float32-le plane + speed float32-le plane + valid uint8 plane` 写入 `wind_vectors.bin`。数组 row-major;Web Mercator tile y 向南增加,因此 row 是 north-to-south、column 是 west-to-east。
`src/dev/layers/wind.py` 明确读取 `decoded_r` 为 U、`decoded_g` 为 V;writer metadata 说明 U eastward-positive、V northward-positive、screen y 使用 `-v`。QBIN exporter 和 NSFT exporter 都重新使用同一 `decode_wind()` Field:QBIN `u_q/v_q` 的 quantization metadata 明确为 `m/s`(`scale=0.01`),NSFT wind 也从该 Field 量化。因此本次原始 U/V 单位已由 writer/下游 decoder 确认为 `m/s`,没有 kt 换算、scale/offset 还原或额外量化。
`wind_vectors.bin` 是 WeatherV2 pipeline 对完整已拼接 U/V grid 的 particle/vector export;QBIN 是同源单 tile量化分发格式,NSFT 是同源单 tile量化 PMTiles 格式。三者共享 decoded U/V、valid 和 grid/bounds 语义,但本任务直接读取完整 WVECBIN1,不经 QBIN/NSFT 拼瓦片。
## 转换工具与输出
新增独立工具:
- `tools/weatherv2_wind_canonical/convert_weatherv2_wind.py`
- `tools/weatherv2_wind_canonical/README.md`
工具参数为 `--run-dir`、`--valid-time`、`--output`;源文件仅只读。它严格校验 magic、embedded header 与 sidecar 一致性、fields/dtype、长度、bounds 和有效 U/V,再输出既有 `maplibre-particle-field/v1` metadata 与 `float32-le interleaved-uv` payload。输出 `validTime` 是真实 UTC epoch milliseconds;`generation` 和 `dataVersion` 同为 producer-owned epoch milliseconds,四帧严格递增。
输出目录:
`platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-weatherv2/ecmwf-hres-wind-20260807/`
其中 `dataset.json` 只是开发期四帧索引,不是 Weather Manifest,也不进入 Snapshot。每个 validTime 子目录包含 `metadata.json`、`field.f32`、`conversion_summary.json`。
未重采样、未裁剪;每个 canonical payload 保留原完整 `513×769` 范围,`3,155,976` bytes。有效 U/V 逐 float32 bit-exact 复制;source `valid=0` 或非有限 U/V 会映射为既有 `noData=-9999` 的 U/V pair,绝不写成 `(0,0)`。当前四个实际 frame 均为 `394,497` valid、`0` invalid,但转换器仍保留该 fail-closed mapping。
## 生成结果与验证
| validTime | SHA-256 | U min/max (m/s) | V min/max (m/s) | speed min/max (m/s) |
| --- | --- | --- | --- | --- |
| 2026080700 | `363e2cd2e77501f6bceecbb4aaa68c4f9ef46998a60319ae5b7aba3548995283` | -35.1724 / 22.5011 | -25.4723 / 28.2009 | 0.0037 / 35.2783 |
| 2026080706 | `ef7a8e4b1c47f652f8f7012de16a87c16e3b25556a8f4686629f80514426c451` | -37.1826 / 24.1255 | -28.2276 / 25.0581 | 0.0105 / 37.7632 |
| 2026080712 | `d86a6a51fd5216cf9368756962a5366fd32a627afd40cfde235ba7358c02c5a0` | -37.3931 / 24.7693 | -28.2666 / 27.8099 | 0.0244 / 37.4291 |
| 2026080718 | `970785b0334a9d1511b8897874a81f681814a32b1e0b90e37cf64b77ca9fa39b` | -31.9560 / 28.5575 | -26.1394 / 27.1316 | 0.0210 / 33.6781 |
- 四帧 metadata 可解析、payload length 与 `width×height×8` 一致,validTime 严格递增,四个 SHA-256 均不同:PASS。
- 每帧中心、四角附近和内部有效样本 source→canonical U/V 的最大绝对误差均为 `0.0`:PASS。
- T0 重复转换的 SHA-256 与已生成 payload 相同:PASS。
- 转换器 Python syntax、正常转换和无效 source 的非零失败路径:PASS。
- 既有 Core canonical/Adapter/Host ingress 定向测试:5/5 PASS。
## 冻结边界与未执行项
未修改 `ParticleFieldDescriptor`、`ParticleFieldSnapshot`、`ParticleFieldDataAdapter`、Timeline、Playback、Runtime Field Window、Renderer、Android Kotlin/JNI 或 WeatherV2 原始数据。未将 Weather Service、HTTP、Provider、QBIN/NSFT 网络、缓存或目录扫描加入 MapLibre Runtime。
未执行 Android Gradle、APK、ADB、设备/模拟器测试或 Jest。下一阶段才使用本 asset 做 MapLibre map + WeatherColorLayer + RasterParticleLayer 综合 Android 验收;本 Step 不提前进入该范围。