13 KiB
Shared Map Viewport Component Specification
1. Scope
本文档定义 Trail Mate 中“共享地图视口组件”的规格。
它约束的不是某一个页面,而是所有“以地图作为主背景或主内容承载层”的页面都应共同遵守的地图组件边界。
当前直接受此文档约束的页面包括:
GPS / 地图页面Node Info / 节点详情页面
后续任何新页面只要需要:
- 地图底图渲染
- 地图拖动
- 地图缩放
- 地图图层切换
- 地图语义覆盖层投影
都应优先复用本组件,而不是在页面内再次实现一套地图逻辑。
本文档是“组件职责与边界规格”,不是具体代码设计稿;但后续实现必须能被本文档解释。
2. Current Confusions
在进入重构前,必须先承认当前系统中存在以下混淆:
GPS页面已经拥有一套相对完整的地图能力,但它和页面业务状态耦合过深,不能直接当成通用组件复用。Node Info页面当前又实现了一套独立的地图逻辑,这不是复用,而是平行实现。- “地图页面”和“地图组件”不是同一个对象。
- “瓦片引擎”也不是“地图组件”本身,它只是底层能力的一部分。
- 图层切换、拖动、缩放、投影这些语义应属于共享地图视口,而不是某个页面私有行为。
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 核心目标
共享地图视口组件必须提供以下稳定能力:
- 在统一组件模型下承载地图底图。
- 在统一状态语义下支持拖动与缩放。
- 在统一规则下支持图层切换。
- 为页面提供稳定的地理点到屏幕坐标投影能力。
- 允许页面在地图之上叠加页面私有的语义元素。
- 让多个页面共享同一套地图主流程,而不是共享几段 helper。
4.2 非目标
本组件当前不是:
- 页面导航容器
- 联系人或节点业务模型
- GPS 页面专属状态机
- 团队页面专属状态机
- 离线地图下载器
- 地图数据准备工具
5. Responsibilities
共享地图视口组件负责:
- 创建并维护地图底图承载区域。
- 维护统一的 camera / viewport 状态。
- 协调基础底图与叠加图层渲染选项。
- 驱动底层瓦片后端进行 tile 计算、加载与布局。
- 向页面提供投影查询能力。
- 管理地图语义覆盖层宿主容器。
- 管理拖动、缩放、图层切换这三类交互的共同语义。
- 暴露“当前视口状态”和“当前地图可用性状态”。
共享地图视口组件不负责:
- 决定某个页面应该显示哪些业务字段。
- 决定页面右侧信息列内容。
- 直接持有联系人、节点、GPS 页面、团队页面的业务模型。
- 在页面里自行定义“某个标记代表什么”。
- 让页面直接操作 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_sourcemap_contour_enabledmap_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 = OSM1 = Terrain2 = Satellite
切换基础底图时:
- 页面不重建。
- 地图视口对象不重建。
- 视口 camera 状态尽量保持。
- 地图语义覆盖层仍由页面持有,不因切底图而丢失。
- 底层 tile backend 应刷新底图渲染状态。
8.3 叠加图层规则
Contour Overlay 是叠加层,不是基础底图的一种。
切换 contour 时:
- 不改变 active base source。
- 不改变页面语义覆盖层。
- 不改变页面的业务聚焦对象。
- 只改变地图底图之上的 contour 可见性与加载策略。
8.4 缺图语义
当切换到某图层而当前视口无图时:
- 不允许页面崩塌成黑屏。
- 不允许页面 silently fail。
- 组件应维持稳定的地图容器结构。
- 组件应给出“当前图层缺图”的可观察状态或一次性通知。
页面可决定如何展示该通知,但不应自己再实现一套缺图判断。
8.5 图层切换入口语义
共享地图视口约束的是“图层切换语义”,不是“按钮必须长在同一个坐标”。
允许:
GPS / 地图页将Layer按钮放在自己的控制区Node Info页将Layer按钮放在底部中间
不允许:
- 不同页面拥有不同的基础底图枚举
- 不同页面对
Contour有不同含义 - 不同页面各自实现不同的缺图判断和图层归一化逻辑
因此,页面可以拥有自己的触发 chrome,但图层切换后的状态变化、合法值集合、缺图语义与持久化后果必须完全一致。
9. Camera and Interaction Semantics
9.1 拖动
当页面允许拖动时:
- 拖动作用于地图视口
- 地图语义覆盖层随之移动
- 页面固定 chrome 不移动
9.2 缩放
共享地图视口必须支持“页面指定缩放锚点语义”。
原因是不同页面的缩放锚点不同:
GPS页可能围绕 self / screen center / follow targetNode 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
以下实现方式在本规格下视为非法:
- 在页面文件中再次实现
map_source归一化。 - 在页面文件中再次实现基础 tile 路径拼接规则。
- 在页面文件中再次实现世界像素投影主流程。
- 在页面文件中再次维护独立的 tile image 阵列与 tile 生命周期。
- 在页面中直接操纵底层 tile cache 细节。
- 把
GPS页面整体当成组件硬复用到别的页面。 - 把页面固定 chrome 混入地图语义覆盖层一起拖动。
- 在页面内再次实现一套页面私有的图层归一化、等高线切换语义或缺图判定。
12. Consequences
一旦接受本规格,后续重构就会被明确约束为:
Node Info当前那套平行地图实现必须被移除,而不是继续扩展。GPS页面当前的地图主流程必须被剥离出页面专属状态。- 共享地图视口组件会成为多个页面共同依赖的唯一地图主入口。
- 后续任何地图能力增强,例如更多图层、更多标记、更多交互,都优先加在共享组件,而不是加在页面私有分支上。
13. Summary Baseline
一句话总结:
共享地图视口组件不是“另一个地图页面”,也不是“几段共用 helper”,而是 Trail Mate 中所有地图型页面共享的唯一地图主流程承载层。