地图渲染模块架构设计文档

36 阅读4分钟

1. 架构概述

本项目采用高吞吐量、低开销的增量渲染架构来处理海量船舶和航线的展示。为了避免 Vue 响应式系统在处理海量 DOM 节点时的性能瓶颈,地图渲染核心完全与 Vue 的视图模板解耦,采用原生 TypeScript + Leaflet API 结合状态机缓存的策略。

整个模块采用分层解耦的设计:

  1. 基础设施层hifleet-loader.tsanimation.ts(负责 SDK 异步加载与 requestAnimationFrame 防抖)。
  2. 状态控制层(适配器)useHifleetMap.ts(桥接 Vue 生命周期与 Leaflet 实例)。
  3. 渲染引擎层(核心)hifleet-map-voyage-renderer.ts 及其辅助文件(执行具体的增量 DOM 操作)。
  4. 组件展示层MapStage.vue(提供容器和事件抛出)。

2. 核心设计模式

2.1 增量更新与快照缓存 (Incremental Update & Snapshot Caching)

hifleet-map-voyage-renderer.ts 中,为每个航次维护了独立的 VoyageLayerState。每次渲染时,不会销毁重建图层,而是生成一组“快照签名 (Key)”与上一次比对:

  • renderedLineKey:包含了选中状态标识和轨迹坐标字符串。
  • renderedPointKey:包含了地图层级、选中状态和可视区域内的轨迹点。
  • renderedMarkerPositionKey:船舶经纬度字符串。
  • renderedMarkerIconKey:包含船名、颜色、航向、选中状态。

优势:当航次属性未发生能导致视觉变化的改变时,直接 return 阻断 Leaflet DOM 操作,将性能损耗降至最低。

2.2 渲染风暴抑制 (Render Storm Coalescing)

地图的 zoompan 操作会在极短时间内触发成百上千次重绘事件。 在 animation.ts 中通过封装 requestAnimationFrame(即 createRafTask),确保在同一帧内,无论触发多少次渲染指令,都合并为一次执行。这在 useHifleetMap.tsscheduleRefresh 函数中被完美应用。

2.3 实时数据的降级渲染 (Degraded Rendering for Realtime Data)

针对实时事件 VoyageRealtimeEvent

  • 若收到 voyage.point (点事件):意味着轨迹发生变化,调用全量的 updateVoyageLayer 重新计算轨迹线和点。
  • 若收到 voyage.snapshot (快照事件):意味着仅仅是船舶移动或摘要数据变化,调用轻量的 updateVoyageMarker 仅更新图标位置和旋转角度,而不重算海量轨迹。

3. 模块职责边界

3.1 src/utils/map/hifleet-map-voyage-renderer.ts

职责:单例工厂,闭包维护了所有的 layerStates(状态集)和 iconCache(图标缓存)。 核心流程

  1. renderVoyages 接收全量数据,比对增删。
  2. 过滤掉离开地图视野的轨迹点。
  3. 调用 syncRouteLinesyncPointLayersyncShipMarker 进行细粒度的 DOM diff。
  4. 提供 clear 方法在组件卸载时回收所有 Leaflet 内存。

3.2 src/utils/map/hifleet-map-icons.ts

职责:纯函数集合,负责生成 Leaflet 所需的 HTML 字符串 (divIcon)。 设计考量

  • 内部实现了 escapeHtml 以防御 XSS 注入。
  • 通过 CSS Variables (--ship-color, --ship-rotation) 将状态变化交由浏览器硬件加速处理,而不是每次重写 HTML。

3.3 src/utils/map/hifleet-map-rendering.ts

职责:渲染辅助函数集。 设计考量:将序列化逻辑 (serializeLatLng)、颜色计算 (getPointColor) 和图层销毁逻辑 (removeMissingVoyageLayers) 抽离,保持 renderer 主文件的纯净。

3.4 src/composables/useHifleetMap.ts

职责:作为 Vue Store 和 Renderer 之间的适配器。 设计考量

  • 隐式处理了 HiFleet SDK 特有的 changeMap 卫星图切换残留 Bug(引入 global 中间态)。
  • 监听 DOM 的 ResizeObserver 自动调用 invalidateSize
  • 将 Leaflet 实例包裹在 shallowRef 中,防止 Vue 递归劫持 Leaflet 内部庞大而复杂的对象树,引发极端的性能问题。

4. 后续扩展与维护指南

4.1 新增渲染层

如果需要新增渲染图层(如:渲染所有的停靠港口),请不要在现有的 hifleet-map-voyage-renderer.ts 中堆砌代码。应当参考该文件,新建一个 hifleet-map-port-renderer.ts,然后在 useHifleetMap.ts 中实例化它。保持不同业务实体的渲染器相互独立。

4.2 缓存键 (Cache Key) 维护

如果未来 VoyageSummary 新增了影响 UI 的属性(例如:根据吃水深度改变线条粗细),必须同步更新对应函数的 Key 生成逻辑。 例如:修改 syncRouteLine 中的 lineKey,加入吃水深度变量。否则图层将不会响应数据变化。

4.3 图标内存泄漏防护

目前图标采用了 Map 进行字符串级的单例缓存。如果未来产生了极度分散的变量(如每艘船都有完全不重复且无限生成的渐变色),可能导致 iconCache 内存无限膨胀。此时需引入 LRU (Least Recently Used) 算法或定期清理策略。