Files
trail-mate/docs/uiux/components/shared_map_viewport.md
T

13 KiB
Raw Blame History

Shared Map Viewport Component Specification

1. Scope

本文档定义 Trail Mate 中“共享地图视口组件”的规格。

它约束的不是某一个页面,而是所有“以地图作为主背景或主内容承载层”的页面都应共同遵守的地图组件边界。

当前直接受此文档约束的页面包括:

  • GPS / 地图 页面
  • Node Info / 节点详情 页面

后续任何新页面只要需要:

  • 地图底图渲染
  • 地图拖动
  • 地图缩放
  • 地图图层切换
  • 地图语义覆盖层投影

都应优先复用本组件,而不是在页面内再次实现一套地图逻辑。

本文档是“组件职责与边界规格”,不是具体代码设计稿;但后续实现必须能被本文档解释。


2. Current Confusions

在进入重构前,必须先承认当前系统中存在以下混淆:

  1. GPS 页面已经拥有一套相对完整的地图能力,但它和页面业务状态耦合过深,不能直接当成通用组件复用。
  2. Node Info 页面当前又实现了一套独立的地图逻辑,这不是复用,而是平行实现。
  3. “地图页面”和“地图组件”不是同一个对象。
  4. “瓦片引擎”也不是“地图组件”本身,它只是底层能力的一部分。
  5. 图层切换、拖动、缩放、投影这些语义应属于共享地图视口,而不是某个页面私有行为。
  6. Layer 按钮出现在不同页面的不同位置,不等于图层切换语义可以按页面各自定义。

如果不先把这些混淆切开,后续任何“先支持功能再说”的实现,都会把系统重新带回双轨地图逻辑。


3. Distinctions

3.1 地图页面 != 地图组件

GPS 页面是一个完整页面对象,除了地图之外还包含:

  • GPS fix 状态
  • follow self 策略
  • 队友标记
  • 路线/轨迹覆盖层
  • 页面标题与状态信息
  • 页面快捷操作

这些都不是共享地图视口本体。

共享地图视口组件只负责“地图如何被显示、平移、缩放、切图层、投影叠加层”,不负责页面的业务含义。

3.2 瓦片引擎 != 地图视口

现有 map_tiles.* 是底层瓦片与投影能力,负责:

  • tile 计算
  • tile 对象管理
  • tile 加载与缓存
  • 地图源目录与文件路径
  • 等高线叠加层
  • 屏幕投影

它是共享地图视口的后端基础,不应被页面直接当成页面组件来使用。

3.3 页面覆盖信息 != 地图语义覆盖层

以下元素属于页面覆盖信息:

  • 顶部栏
  • 节点 ID
  • 左下角经纬度文本
  • 右侧信息列
  • 独立于地图移动的页面按钮

以下元素属于地图语义覆盖层:

  • 节点标记
  • 自身标记
  • 队友标记
  • 连线
  • 距离标签
  • 路径/轨迹

地图语义覆盖层必须和地图视口同步移动;页面覆盖信息必须稳定悬浮,不得跟随拖动漂移。

3.4 视口状态 != 页面业务状态

地图视口状态是:

  • 当前缩放级别
  • 当前平移偏移
  • 当前基础底图
  • 当前叠加图层开关
  • 当前是否允许交互
  • 当前视口是否有可用底图

页面业务状态是:

  • GPS 页是否 follow self
  • Node Info 页当前查看的是哪个节点
  • 页面上显示哪些右侧信息项
  • 节点详情页的缩放锚点是谁

页面业务状态可以驱动地图视口,但不应与地图视口内部状态混在一起。

3.5 基础底图 != 叠加图层

基础底图是“互斥的一选一”:

  • OSM
  • Terrain
  • Satellite

叠加图层是“附着在基础底图上的可选层”:

  • Contour Overlay

切换基础底图与开关叠加图层,语义不同,不能混成一个随意的“layer mode”。


4. Component Goals

4.1 核心目标

共享地图视口组件必须提供以下稳定能力:

  1. 在统一组件模型下承载地图底图。
  2. 在统一状态语义下支持拖动与缩放。
  3. 在统一规则下支持图层切换。
  4. 为页面提供稳定的地理点到屏幕坐标投影能力。
  5. 允许页面在地图之上叠加页面私有的语义元素。
  6. 让多个页面共享同一套地图主流程,而不是共享几段 helper。

4.2 非目标

本组件当前不是:

  • 页面导航容器
  • 联系人或节点业务模型
  • GPS 页面专属状态机
  • 团队页面专属状态机
  • 离线地图下载器
  • 地图数据准备工具

5. Responsibilities

共享地图视口组件负责:

  1. 创建并维护地图底图承载区域。
  2. 维护统一的 camera / viewport 状态。
  3. 协调基础底图与叠加图层渲染选项。
  4. 驱动底层瓦片后端进行 tile 计算、加载与布局。
  5. 向页面提供投影查询能力。
  6. 管理地图语义覆盖层宿主容器。
  7. 管理拖动、缩放、图层切换这三类交互的共同语义。
  8. 暴露“当前视口状态”和“当前地图可用性状态”。

共享地图视口组件不负责:

  1. 决定某个页面应该显示哪些业务字段。
  2. 决定页面右侧信息列内容。
  3. 直接持有联系人、节点、GPS 页面、团队页面的业务模型。
  4. 在页面里自行定义“某个标记代表什么”。
  5. 让页面直接操作 tile cache、tile record、文件路径拼接等底层细节。

6. Module Ownership

6.1 modules/ui_shared 的职责

modules/ui_shared 负责共享地图视口的页面无关接口与组件壳层,至少包括:

  • 组件公开 API
  • 视口状态模型
  • 页面接入约束
  • 交互语义约束
  • 地图语义覆盖层宿主抽象

换句话说,页面应该依赖 ui_shared 中的共享地图视口组件,而不是自己直接拼装底层瓦片逻辑。

6.2 platform/esp/* 的职责

平台层负责地图视口所依赖的具体后端能力,至少包括:

  • LVGL 对象级实现
  • 瓦片加载与缓存
  • 文件系统路径与资源查找
  • 等高线叠加渲染
  • 坐标系转换实现
  • 平台相关的内存/加载预算控制

平台层提供的是“后端适配”,不是页面语义。

6.3 页面层的职责

页面层只负责自己的业务使用方式,例如:

  • GPS 页决定 self/队友/轨迹这些覆盖层语义
  • Node Info 页决定目标节点/自身节点/连线/距离这些覆盖层语义
  • 页面决定自己需要哪些固定信息栏与按钮

页面层不再自建地图底图逻辑。


7. State Boundaries

7.1 持久配置状态

以下状态属于应用配置,组件读取但不私自定义:

  • map_source
  • map_contour_enabled
  • map_coord_system

这些状态的持久化归应用配置系统,组件只消费其当前值或接收页面显式下发。

7.2 视口运行时状态

以下状态属于共享地图视口组件本体:

  • 当前 zoom
  • 当前 pan_x / pan_y
  • 当前基础底图
  • 当前 contour 是否开启
  • 当前视口是否允许拖动
  • 当前视口是否允许缩放
  • 当前视口是否具备可用地图数据
  • 当前 anchor / projection cache
  • 当前渲染中的 tile state 摘要

7.3 页面驱动状态

以下状态由页面拥有,再作为输入喂给视口:

  • 视口聚焦对象
  • 页面是否允许 follow
  • 页面希望缩放围绕谁发生
  • 页面要画哪些语义标记与线段
  • 页面是否要在视口之上显示固定 UI chrome

7.4 后端缓存状态

以下状态属于后端,不应越过组件边界暴露给页面自由操作:

  • tile records
  • decoded image cache
  • missing tile notice once flags
  • contour overlay cache
  • tile object eviction state

页面可以读到摘要,不可以改写内部细节。


8. Layer Switching Semantics

8.1 基础规则

共享地图视口必须支持“页内图层切换”,且切换不应要求页面重建。

8.2 基础底图规则

基础底图始终恰有一个 active source:

  • 0 = OSM
  • 1 = Terrain
  • 2 = Satellite

切换基础底图时:

  1. 页面不重建。
  2. 地图视口对象不重建。
  3. 视口 camera 状态尽量保持。
  4. 地图语义覆盖层仍由页面持有,不因切底图而丢失。
  5. 底层 tile backend 应刷新底图渲染状态。

8.3 叠加图层规则

Contour Overlay 是叠加层,不是基础底图的一种。

切换 contour 时:

  1. 不改变 active base source。
  2. 不改变页面语义覆盖层。
  3. 不改变页面的业务聚焦对象。
  4. 只改变地图底图之上的 contour 可见性与加载策略。

8.4 缺图语义

当切换到某图层而当前视口无图时:

  1. 不允许页面崩塌成黑屏。
  2. 不允许页面 silently fail。
  3. 组件应维持稳定的地图容器结构。
  4. 组件应给出“当前图层缺图”的可观察状态或一次性通知。

页面可决定如何展示该通知,但不应自己再实现一套缺图判断。

8.5 图层切换入口语义

共享地图视口约束的是“图层切换语义”,不是“按钮必须长在同一个坐标”。

允许:

  • GPS / 地图 页将 Layer 按钮放在自己的控制区
  • Node Info 页将 Layer 按钮放在底部中间

不允许:

  • 不同页面拥有不同的基础底图枚举
  • 不同页面对 Contour 有不同含义
  • 不同页面各自实现不同的缺图判断和图层归一化逻辑

因此,页面可以拥有自己的触发 chrome,但图层切换后的状态变化、合法值集合、缺图语义与持久化后果必须完全一致。


9. Camera and Interaction Semantics

9.1 拖动

当页面允许拖动时:

  • 拖动作用于地图视口
  • 地图语义覆盖层随之移动
  • 页面固定 chrome 不移动

9.2 缩放

共享地图视口必须支持“页面指定缩放锚点语义”。

原因是不同页面的缩放锚点不同:

  • GPS 页可能围绕 self / screen center / follow target
  • Node Info 页必须围绕目标节点

因此缩放行为不能硬编码为某一页面私有规则。

共享缩放等级契约固定为:

  • 默认缩放:12
  • 最小缩放:0
  • 最大缩放:18

页面可以决定“用户看到的首帧是否因为缺图而降级到其它最近可用级别”,但不能在页面内部再次私有化一套不同的最小/最大缩放范围。

补充约束:

  • “用户请求改变 zoom” 与 “当前 zoom 是否具备中心瓦片” 必须是两个分离判断
  • 首帧或自动选级可以参考瓦片可用性
  • 但交互缩放不得因为缺少中心瓦片而被静默拦截成 no-op

9.3 Follow

follow 不是共享地图视口的通用默认行为,而是页面策略。

共享地图视口只提供:

  • camera 移动能力
  • anchor 计算能力
  • 拖动后 camera 偏移保持能力

是否“自动跟随某个对象”,由页面自己声明。

9.4 无地理目标时

当页面没有可用地理目标时,组件必须允许进入“无地图语义能力”的降级态。

在该状态下:

  • 地图底图可为空或仅为背景
  • 拖动可被禁用
  • 缩放可被禁用
  • 页面固定信息仍可正常显示

10. Page Integration Contracts

10.1 Node Info 页面接入要求

Node Info 页面通过共享地图视口组件获得:

  • 地图底图
  • 拖动能力
  • 缩放能力
  • 图层切换能力
  • 坐标投影能力

Node Info 页面自己提供:

  • 目标节点标记
  • 自身标记
  • 两点连线
  • 距离标签
  • 左上 ID、左下经纬度、右侧信息列、右下缩放按钮、底部中间 Layer 按钮等固定 chrome

补充约束:

  • Node Info 页允许在地图上方叠加固定 chrome,但不得再覆盖持续存在的半透明雾化层或右侧遮罩来“压暗”底图
  • Node Info 页的拖动只改变 camera 偏移;其缩放锚点始终仍是目标节点

10.2 GPS 页面接入要求

GPS 页面通过共享地图视口组件获得:

  • 地图底图
  • 拖动/缩放
  • 图层切换
  • 坐标投影

GPS 页面自己提供:

  • self marker
  • team markers
  • 轨迹/路线覆盖层
  • follow 策略
  • 页面状态信息

11. Illegal Implementations

以下实现方式在本规格下视为非法:

  1. 在页面文件中再次实现 map_source 归一化。
  2. 在页面文件中再次实现基础 tile 路径拼接规则。
  3. 在页面文件中再次实现世界像素投影主流程。
  4. 在页面文件中再次维护独立的 tile image 阵列与 tile 生命周期。
  5. 在页面中直接操纵底层 tile cache 细节。
  6. 把 GPS 页面整体当成组件硬复用到别的页面。
  7. 把页面固定 chrome 混入地图语义覆盖层一起拖动。
  8. 在页面内再次实现一套页面私有的图层归一化、等高线切换语义或缺图判定。

12. Consequences

一旦接受本规格,后续重构就会被明确约束为:

  1. Node Info 当前那套平行地图实现必须被移除,而不是继续扩展。
  2. GPS 页面当前的地图主流程必须被剥离出页面专属状态。
  3. 共享地图视口组件会成为多个页面共同依赖的唯一地图主入口。
  4. 后续任何地图能力增强,例如更多图层、更多标记、更多交互,都优先加在共享组件,而不是加在页面私有分支上。

13. Summary Baseline

一句话总结:

共享地图视口组件不是“另一个地图页面”,也不是“几段共用 helper”,而是 Trail Mate 中所有地图型页面共享的唯一地图主流程承载层。