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 中所有地图型页面共享的唯一地图主流程承载层。