Files
MapLibreParticleLab/docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md
OpenAI Codex 979a1deeb4
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
docs: investigate next production field source stage
2026-09-04 15:59:17 +08:00

18 KiB
Raw Permalink Blame History

STEP-052 正式数据源接入下一阶段调查报告

1. Git / 调查基线

  • 仓库:MapLibreParticleLab
  • 分支:particle/main
  • Starting HEAD:e4ab1b9d421a4559fa3627df07ee91dcd9b680e4
  • origin/particle/main:e4ab1b9d421a4559fa3627df07ee91dcd9b680e4
  • STEP-049:CLOSED / PASS
  • STEP-051:CLOSED / PASS
  • 调查性质:代码与既有文档只读;本任务不修改功能代码。

2. 结论摘要

STEP-051 已把一条真实可验证的 local production-like 链路接到 Core:

asset metadata + binary payload → LocalParticleFieldFeed → ParticleFieldDescriptor → ParticleFieldDataAdapter → ParticleFieldSnapshot → RasterArraySource → Core Timeline/Runtime Field Window → Renderer。

但它仍不是正式生产数据源。当前 local metadata 是新的 maplibre-particle-field/v1,payload 仍复用仓库已有的 canonical particle-fixtures/*/field.f32;RasterArraySource 直接拥有资源请求、candidate 收集、解析和 Timeline 初始化。仓库没有 Particle 专属的 feed coordinator、forecast catalog/manifest、异步 field-arrival API、retry/timeout/stale policy 或专用 transport cache。

MapLibre 通用 FileSource 已提供 asset、filesystem、HTTP、offline database 等资源能力,但这不等于已有 Weather Service。当前最合理的下一实现阶段不是直接绑定天气供应商,而是先建立 provider-neutral feed/manifest 与低频 arrival coordinator,并用 Local/File/Mock transport 验证。之后再单独接入真实 HTTP/provider。

3. STEP-051 后的真实数据链路

3.1 Local feed 起点与终点

入口和 contract:

  • include/mbgl/particle/field/local_particle_field_feed.hpp
  • src/mbgl/particle/field/local_particle_field_feed.cpp
  • metadata:particle-production/field-00.json 至 field-03.json
  • Android metadata:platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-production/

LocalParticleFieldFeed::load(metadataText, payload) 只处理 provider-neutral metadata 和内存 bytes,调用唯一正式 ParticleFieldDataAdapter::decode()。它不打开文件、不请求网络、不保存缓存,也不认识 Android、Timeline 或 Renderer。

STEP-051 的 Android ParticleCoreLayerValidationActivity.kt 使用 production-fields style source,metadata 走 asset://integration/particle-production/field-*.json,binary 仍来自既有 asset://integration/particle-fixtures/*/field.f32。这已经脱离 fixture metadata/wrapper,但 payload 内容尚是验证用 canonical binary,而非远程生产数据。

3.2 Provider-neutral 边界

正式 neutral 边界位于:

ParticleFieldDescriptor + payload bytes → ParticleFieldDataAdapter → immutable ParticleFieldSnapshot。

include/mbgl/particle/field/field_data_adapter.hpp 的 Descriptor/Snapshot 不含 URL、token、HTTP client、provider name、Android object 或 GPU ownership。Descriptor/Snapshot 当前满足 WIND/GLOBAL 的 Runtime 最小输入;Ocean-only field、mask、更多物理变量尚未进入本阶段 contract。

当前仍留在上游/Source 的内容是资源 URL、metadata 文本、文件请求、asset 读取和 candidate 编排。它们不应进入 RasterParticleLayer 或 ParticleFieldSnapshot。

4. Adapter、Snapshot 与 Source 现状

4.1 Adapter / Snapshot

  • ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor&, const std::vector<char>&, uint64_t) 是唯一正式 decode API。
  • ParticleFieldDescriptor 已有 fieldID、fieldType、displayDomain、generation、validTime、dimensions、bounds、noData、dataVersion、byteLength、stride、layout、encoding、可选 checksum。
  • ParticleFieldSnapshot 自有 metadata、velocity 和 valid arrays;不拥有外部 bytes、FileSource、GPU texture 或 Timeline。
  • generation 必须由 producer/source 在 adapter 前提供,adapter 不把 0 改成 1;fieldId 与 validTime 由 producer metadata 提供。
  • OfflineFixtureParticleAdapter 仍存在,但只是 fixture-v1 validation wrapper,不是正式 production feed。

4.2 RasterArraySource / submit

实际文件:

  • include/mbgl/style/sources/raster_array_source.hpp
  • src/mbgl/style/sources/raster_array_source.cpp
  • src/mbgl/style/sources/raster_array_source_impl.hpp
  • src/mbgl/style/conversion/source.cpp

STEP-051 新增 RasterArrayLocalFieldDescriptor、local candidates 和 submitInitialRuntimeFieldWindow(T0, T1)。Source 对每个 metadata/field URL 使用 FileSource::request(Resource::source(...)),收齐后交给 LocalParticleFieldFeed,再由 bridge 建立 authoritative initial T0/T1。后续 Timeline 从 local candidates 构造 entries,使用 Snapshot 的 producer validTime。

已有 updateRuntimeFieldWindow(ParticleFieldSnapshot) 是已有 T0/T1 之后的最短 submit 路径,继续实现 OLD_T1 → NEW_T0、only NEW_T1、Duplicate NOOP 和 fail-closed。它不是一个独立的外部 feed queue,也没有 feed-level arrival state、manifest index 或持久化生命周期。

4.3 Timeline / Playback

RasterArrayTimelineRuntime 位于 raster_array_source.hpp/.cpp,只消费 immutable Snapshot entries 和严格递增 timestamp,持有 Core-owned cursor/speed/paused/gap/invalid 状态;cache capacity 当前为 8。

这属于 Timeline memory cache,不是 transport/download cache,也不是 persistent field cache。Timeline 已足够消费外部 Snapshot,下一阶段不应重写其 state machine 或 Playback ownership。缺口在 Source/feed producer 如何按时间目录、到达、失败和预取 Snapshot。

5. 仓库已有 transport / network / cache 能力

已有但属于 MapLibre 通用基础设施

  • include/mbgl/storage/file_source.hpp:异步 request()、Resource、cache-only 能力、pause/resume 和资源转发。
  • include/mbgl/storage/resource.hpp:asset/file/network 资源请求语义、loading method、storage policy、etag/expiry 等 HTTP/cache 元数据。
  • src/mbgl/storage/file_source_manager.cpp:按类型与配置复用 FileSource 实例。
  • platform/android/MapLibreAndroid/src/cpp/asset_manager_file_source.cpp:Android asset:// 读取。
  • platform/android/MapLibreAndroid/src/cpp/http_file_source.cpp 与 include/mbgl/storage/http_file_source.hpp:Android 通用 HTTP FileSource。
  • include/mbgl/storage/database_file_source.hpp:MapLibre ambient cache/offline database 的 put、invalidate、clear、容量和 revalidation API。
  • include/mbgl/util/lru_cache.hpp:通用内存 LRU 工具。
  • include/mbgl/storage/response.hpp:通用成功、NotFound、Server、Connection、RateLimit、etag、expires、retryAfter 等 Response/Error 字段。

这些能力可以作为 STEP-053 的底层 transport 适配点,但目前没有 Particle field 的 manifest、字段语义、时间序列目录、payload checksum policy、retry/stale policy 或 adapter 错误映射。

当前不存在的 Particle 正式能力

搜索当前 src/、include/、Android JNI/Host 和测试区域,没有发现:

  • Weather Service client、Open-Meteo/Windy/vendor client;
  • Particle provider abstraction、provider registry 或 provider-specific decoder;
  • GRIB/NetCDF/external weather decoder;
  • Particle remote manifest/forecast endpoint contract;
  • Particle 专用 download queue、transport coordinator、persistent field cache;
  • Particle retry、timeout、stale-data、gap inventory 或 availability state policy;
  • token/auth/config 与 Particle field service 的绑定。

因此不能把 MapLibre 的通用 HTTP/FileSource 误认成 Weather Service 已接入。

6. Cache、preload 与数据生命周期

当前存在四种不同层次,不能合并:

  1. RasterArrayTimelineRuntime 的 entries/cache capacity 8:已经解码的 Snapshot memory cache,服务 Timeline cursor。
  2. RasterArraySource 的 local/runtime candidate vectors:Source-owned preload candidates,服务初始窗口和后续 boundary。
  3. MapLibre FileSource/DatabaseFileSource ambient/offline cache:通用资源缓存,按 Resource URL/HTTP metadata 工作,不理解 fieldId、validTime、generation 或 Snapshot。
  4. Renderer GPU slots:RasterParticleLayer/validation layer 的 T0/T1 texture 与 simulation state,不是下载缓存,不能由数据源层直接管理。

STEP-051 当前是四个资源请求全部到达后构建 Timeline;没有独立的 manifest-first inventory、有限并发下载、field dedup、retry 或 stale policy。已有 MapLibre cache 可以降低资源获取成本,但不能替代 Particle feed lifecycle。

建议下一阶段先定义低频 field arrival/availability 状态和 manifest 顺序,明确:manifest 可用但 payload 缺失、metadata reject、payload mismatch、gap、stale、retry exhausted 时如何保持 last valid Field。Persistent cache 不应在没有 feed key/manifest 生命周期之前先做;第一步只需要 memory/injected transport validation,是否接 DatabaseFileSource 应后置。

7. Forecast manifest/catalog 最小模型

不要把所有未来业务字段塞入 Descriptor。进入正式 feed 必要的最小分层如下。

已属于 ParticleFieldDescriptor/Snapshot

  • field identity:fieldID
  • producer revision:generation
  • forecast validTime
  • fieldType / displayDomain
  • grid width/height、bounds
  • vector layout/encoding/noData
  • expected byte length、stride、data version、可选 payload checksum

这些字段是解码、插值兼容性、fail-closed 和 Renderer canonical input 所必需的。

应属于上游 manifest/catalog,而不是 Snapshot/Renderer

  • forecast runTime/cycle
  • leadTime(可由 runTime 与 validTime 推导,但目录可显式记录)
  • ordered snapshot list / window membership
  • grid identity、resolution、coverage version
  • metadata URL 与 payload URL/resource key
  • source revision、availability、retry hint、expiry/stale policy

manifest 可以有 source/provider identity,但它不应穿过 Adapter 进入 Snapshot,也不应被 Renderer 或 Runtime Window 解释为业务 provider。

目前不应新增

不要仅为“以后可能需要”加入 level、ensemble、unit system、auth token、HTTP headers、vendor schema、download path、cache database key 或 ocean mask 字段。若未来 Wave/Tide/Current 需要这些语义,应单独设计并遵守 Ocean-only 与 mask 边界。

8. Weather Service 直接接入判断

当前不具备安全的直接接入条件。虽然 Android 有 HTTPFileSource,MapLibre 有通用 Response/cache/error,当前 Particle 仍缺少:

  • provider-specific metadata/payload decoder 与其到 neutral Descriptor 的映射;
  • forecast run/lead/valid-time inventory;
  • provider-independent feed API 和 arrival ordering;
  • retry/timeout/stale/gap policy;
  • service auth/config ownership;
  • 可在不改 Renderer/Timeline 的情况下将网络数据稳定送入 Snapshot submit。

直接接入会把 HTTP/provider 选择、目录和失败策略塞进 RasterArraySource 或 Android Host,容易越过冻结边界。结论:先建立 provider-neutral feed/manifest coordinator,再以独立阶段接 Weather Service;真实 network 不是 STEP-052 后的立即实现目标。

9. 候选下一阶段

候选 目标 新架构/模块 Network/Provider Timeline/Renderer Android 主要风险
A. 直接 Weather Service 立即接真实远程天气接口 新增 client、decoder、auth、生命周期 需要 不应修改,但 Source 会膨胀 需要一次高成本 build/test 缺少 manifest、retry、provider 边界,容易把业务逻辑塞入 Core/Host
B. Provider-neutral Feed/Manifest Coordinator 先定义 manifest、低频 arrival、Snapshot submit 和错误生命周期,用 Local/File/Mock transport 验证 一个薄跨平台 feed/coordinator;不建立 Provider Framework 不需要;可注入 Local/Mock transport 不改 state machine;不改 Renderer 若只做 Core 可不需要;若补入口则合并一次 arrival ordering、initial T0/T1 和 last-valid 语义需冻结
C. Transport Cache/Preload 先建立持久缓存、重试、过期和预取 新增 cache policy/lifecycle 可不需要 不改 Renderer,但会依赖未定义 feed key 建议需要 过早把 MapLibre ambient cache 当 Particle cache,生命周期和 key 尚未稳定
D. Provider Decoder 先做 GRIB/NetCDF 或某一供应商 decoder 明显 provider-specific 模块 通常需要 输出应是 neutral Snapshot 视入口而定 当前无真实 provider contract,容易锁死错误 schema

候选 B 能以最小修改面冻结真正缺口,并兼容未来 Local/File、HTTP 和 provider decoder;C 应在 B 的 manifest/key/error contract 之后做,A/D 应后置。

10. 唯一推荐的下一实现阶段

目标范围:

  1. 定义最小 provider-neutral field manifest/catalog 与 ordered snapshot arrival contract。
  2. 将 metadata/payload transport 与 ParticleFieldDataAdapter、ParticleFieldSnapshot、现有 initial T0/T1/update submit 接口连接起来。
  3. 以 injected Local/File/Mock transport 验证 initial T0/T1、subsequent T1、strict validTime ordering、duplicate、gap、invalid、retry exhausted 和 last-valid hold。
  4. 只做低频异步 callback/future 或等价 arrival API,不做 Android per-frame JNI,不拥有 Timeline cursor/speed/pause。
  5. 建立结构化 feed availability/error 分类,明确 metadata reject、payload mismatch、not-yet-available、gap 和 stale 的边界。
  6. 保持 provider/vendor identity、URL、auth、HTTP headers 和 cache policy 在上游 feed/transport 层,不进入 Snapshot、Runtime 或 Renderer。

输入:manifest/catalog entry、metadata bytes、payload bytes,或注入的 transport response。

输出:已通过正式 Adapter 的 ParticleFieldSnapshot/arrival result;初始窗口和后续窗口仍调用现有 Core submit/update 语义。

允许修改:新增薄的 Core-neutral feed/coordinator、Source 的最小接线、定向 C++ tests、local/mock transport test seam 和必要日志/文档。若增加 Android validation 入口,应与同一阶段其他功能合并一次 build/install。

禁止修改:Timeline state machine、Playback、Runtime Field Window 语义、RasterParticleLayer/GPU simulation/trail、WeatherColorLayer、Adapter 合法性、Provider Framework、Weather Service、HTTP client、vendor decoder、第二套 Snapshot/Timeline/Window。

STEP-053 默认不需要真实 network、服务器、真实 provider 或 Android APK;先完成 Linux/Core/OpenGL 精确验证即可。只有要验证 Android Host 的低频 feed arrival/错误可观测性时才需要一次 Android build/install。

11. 建议的后续 2~4 阶段顺序

  1. STEP-053 Provider-neutral Field Feed / Manifest 与 Arrival Coordinator:先冻结 manifest、arrival、错误和 last-valid 生命周期,使用 Local/File/Mock transport。
  2. STEP-054 HTTP Transport 与 Weather Service Adapter:在不改变 neutral Snapshot/Runtime/Renderer 的前提下,把真实 HTTP/provider decoder 置于 feed 上游;此阶段才处理 auth/config 和真实服务契约。
  3. STEP-055 Field Cache、Retry、Stale 与 Preload Hardening:基于已冻结的 manifest key、generation、validTime 和 error policy,决定 memory/cache/database 组合、重试和容量。

这三个阶段足够表达当前真实缺口,不提前规划更多 provider 或 Ocean field 变体。

12. 平台 / Core / Renderer 责任边界

  • Transport/platform:负责 FileSource/HTTP/asset 的资源获取、平台凭据配置和底层 request cancellation;不生成 Timeline 状态。
  • Feed/coordinator(建议跨平台 Core-owned seam):负责 manifest 顺序、低频 arrival、metadata/payload 配对、调用正式 Adapter、错误分类和 submit 意图;不负责 GPU。
  • ParticleFieldDataAdapter:只做 descriptor/payload contract validation 与 canonical Snapshot construction。
  • RasterArraySource:保持 Source-owned authoritative T0/T1、Timeline 接线和已有 Window update;不变成 provider client。
  • Timeline/Playback:Core-owned,消费 Snapshot 时间和 low-frequency feed 结果;不请求网络。
  • Renderer:只消费规范化 Snapshot/Window,继续执行 GPU upload/reuse/simulation/trail;不包含 provider/network/vendor/cache policy。
  • Android/Kotlin:只提供低频用户意图和可选资源配置;不拥有 Timeline、Window、feed state 或 per-frame JNI。

13. Android 成本、技术债与风险

后续实现应遵守:先 Linux/Core/OpenGL precise build 与定向测试,再由用户在 Windows 11 对 192.168.1.146 用一次 APK 完成综合验证;若使用 ADB,必须显式 adb -s 192.168.1.146 ...。不为单个 MIX 文本问题单独触发 Android 周期。

Validation UI 的 auto MIX 文本滞后、WAITING/INVALID 视觉提示不足继续是 NON-BLOCKING observability,不阻塞正式 feed 架构,不推荐作为 STEP-053 主任务。Rotation/Viewport 自动恢复继续保留技术债;STEP-053 若不触及 lifecycle/surface/source reload,不合并修复。

主要风险是把 manifest/catalog 与 Descriptor 重复建模,或让 Source 直接拥有下载/重试/缓存策略。应优先薄化并用测试锁定:Snapshot immutable、validTime strict ordering、invalid/gap fail-closed、last valid Field、Window reuse、MIX property-only 和 Renderer 分离。

14. 调查结论

当前可以进入 STEP-053 实现 MD 编写,但不能跳过 provider-neutral feed/manifest 与 arrival 生命周期直接实现 Weather Service。STEP-051 production-like local feed 作为已验证过渡层保留;STEP-052 本身不修改代码、不引入网络、不新增缓存或 Provider。