Browse Source

docs: 添加 Mapbox 替换 maptalks 设计规格

全量切 mapbox-gl、官方样式底图、保留 MapLayer/mapHelper 薄封装与 Tasks 容器搬迁。
main
xiaosi 3 weeks ago
parent
commit
0cec8cdeb1
  1. 174
      docs/superpowers/specs/2026-08-27-mapbox-migration-design.md

174
docs/superpowers/specs/2026-08-27-mapbox-migration-design.md

@ -0,0 +1,174 @@
# Mapbox 全量替换 maptalks
日期:2026-08-27
范围:`MapLayer` / `mapHelper` / `config/map` + Monitor / Tasks / Replay
状态:待用户确认
## 1. 背景与目标
现状运行时仍是 `maptalks@1.12.1` + 天地图 WMTS。共享层 `MapLayer` 单例经 `commonRefs('map')` + `mapHelper` 暴露;`MonitorView` / `TasksView` / `ReplayView` 直接使用 maptalks Marker / VectorLayer / LineString / setBaseLayer。
目标:
1. 引擎从 **maptalks 全量切到 mapbox-gl**
2. 底图用 **Mapbox 官方样式**:卫星 / 街道;环境变量 `APP_MAPBOX_TOKEN`
3. 保留常驻 `MapLayer` + `commonRefs('map')` + `mapHelper` **薄封装**
4. Monitor / Tasks / Replay **全部**改 Mapbox API;删除 `maptalks` 依赖
5. Tasks 航线编辑继续 `replaceMapContainer` / `restoreMapContainer` 搬迁共享地图容器
6. 设备点、航点、航迹、绑定线统一用 **GeoJSON Source + Layer**
非目标:
- 不做统一 `MapService` 大门面
- 不保留天地图 / 双 provider
- 不改球形总览 Three.js(若已有或后续独立做)
- 本轮不重做机巢/无人机 pin 图标视觉(可先 `circle` / 基础 `symbol`,后续再接 pin)
## 2. 方案选型
| 方案 | 结论 |
|------|------|
| A. 薄封装替换 MapLayer/mapHelper,页面直用 mapbox API | **采用** |
| B. 统一 MapService 门面 | 本轮不做,抽象成本高 |
| C. 每页独立地图实例 | 与常驻地图 + Tasks 搬迁语义冲突 |
底图:Mapbox 官方 `satellite-streets` / `streets`,不再使用天地图 WMTS。
渲染:GeoJSON Source + Layer(非 DOM Marker 为主路径)。
## 3. 共享层
### 3.1 `src/config/map.js`
- `token`:`import.meta.env.APP_MAPBOX_TOKEN || import.meta.env.VITE_MAPBOX_TOKEN || ''`
- 保留 `center` / `zoom`(现有 `APP_MAP_CENTER_*` / `APP_MAP_ZOOM`
- 导出:
```js
export const MAP_STYLES = {
satellite: 'mapbox://styles/mapbox/satellite-streets-v12',
street: 'mapbox://styles/mapbox/streets-v12',
}
```
- 删除 `createTiandituLayerOptions` / `createTiandituBaseLayer`
### 3.2 `src/layout/MapLayer.vue`
- 引入 `mapbox-gl` 与 CSS
- `mapboxgl.accessToken = mapConfig.token`
- 缺 token:打错误日志,`commonRefs.setRef('map', null)`,不抛崩
- 有 token:
```js
map = new mapboxgl.Map({
container: 'map',
style: MAP_STYLES.satellite,
center: mapConfig.center, // [lng, lat]
zoom: mapConfig.zoom,
minZoom: 3,
attributionControl: false,
})
```
- `commonRefs.setRef('map', map)` + `mapHelper.setMap(map)`
- `onUnmounted`:`map.remove()`,清 ref / helper
### 3.3 `src/core/mapHelper.js`
保留对外方法,内部改 Mapbox:
| 方法 | 行为 |
|------|------|
| `setMap` / `getMap` | 持有 `mapboxgl.Map` |
| `toggleMapMode({ is2D, is3D })` | 2D:`setPitch(0)` `setBearing(0)`,关闭 dragRotate/pitch;3D:pitch≈45,开启旋转/倾角 |
| `replaceMapContainer(newParent)` | 保存 view → 把 `#map` DOM 挪到 `newParent``map.resize()` |
| `restoreMapContainer()` | 移回原父节点 → 恢复 center/zoom/pitch/bearing → `map.resize()` |
去掉 maptalks `checkSize`;一律 `resize()`
### 3.4 依赖与 env
- `package.json`:增加 `mapbox-gl`;删除 `maptalks`
- `.env.example` / `.env.development` / `.env.production`
- 增加 `APP_MAPBOX_TOKEN=`
- 删除或降级 `APP_TIANDITU_TOKEN` 说明(本轮不再使用)
- 保留中心/缩放变量
- CSS:去掉 `.maptalks-wrapper` 特例;Tasks 宿主适配 `#map` / `.mapboxgl-map`
## 4. 页面层
坐标约定:统一 `[lng, lat]`
`map.setStyle(...)` 会清掉自定义 source/layer —— **切换样式后必须在 `style.load` 重建业务层并回填数据**
### 4.1 MonitorView
| 能力 | Mapbox 落地 |
|------|-------------|
| 设备点 | source `monitor-devices` + `circle`(或基础 `symbol`)+ 可选 `symbol` 文本层 |
| 选中态 | `feature-state` 或按 selectedId 重写 properties / paint |
| 绑定虚线 | source `monitor-binding` + `line`(`line-dasharray`);仅「选中机巢 + 其飞行中无人机」或「选中飞行无人机 + 所属机巢」 |
| 卫星/地图 | `map.setStyle(MAP_STYLES[mode])`;`style.load` 后重建 devices/binding 层 |
| fitAll | `fitBounds`;清选中回 overview |
| 选中飞向 | `flyTo` / `easeTo` |
| 比例尺 | 保留现有 zoom→米估算,或挂 `ScaleControl`(二选一,UI 位置对齐现工具条) |
| 点击选中 | `queryRenderedFeatures` 命中设备层 |
离开 Monitor:移除本页 source/layer 与事件,避免污染 Tasks/Replay。
### 4.2 TasksView
- 进入编辑:`mapHelper.replaceMapContainer(host)` → `resize()``toggleMapMode({ is2D: true })`
- 航线:source `task-route`(LineString)+ `task-waypoints`(Point features,可带序号属性)
- 地图点击加点:`map.on('click', ...)` → `e.lngLat`
- 离开:清业务层 → `restoreMapContainer()`
### 4.3 ReplayView
- source `replay-traj` line + 起/终/当前点(可同一 points source 不同 layer filter)
- `fitBounds` 包住轨迹
- 离开清层
## 5. 数据流与生命周期
```mermaid
flowchart LR
MainContainer --> MapLayer
MapLayer -->|"commonRefs + mapHelper"| Pages
Pages --> Monitor
Pages --> Tasks
Pages --> Replay
Tasks -->|"replace/restore DOM"| MapLayer
```
1. 登录后壳层挂载 → `MapLayer` 初始化 Mapbox → 写入 ref
2. 业务页 `await commonRefs.getRef('map')` 拿到实例后加自己的 source/layer
3. 路由离开必须卸本页图层与监听;Tasks 额外 restore 容器
4. 无 token:地图不初始化,业务页需容忍 `map == null`(空态/提示,不白屏)
## 6. 风险与处理
| 风险 | 处理 |
|------|------|
| `setStyle` 清空业务层 | 统一 `style.load` 重建 + 数据回填函数 |
| Tasks 搬迁后尺寸为 0 | replace/restore 后立刻 `resize()`;必要时 `requestAnimationFrame` 再 resize |
| token 无效/缺省 | 跳过 init + 日志;页面降级 |
| 旧文档按 maptalks/天地图写 | 本 spec 为准;相关旧计划标注过时,不在本轮改文 |
| 包体变大 | 可接受;不引入 `maptalks.mapboxgl` 双引擎 |
## 7. 验收
- [ ] 配置有效 `APP_MAPBOX_TOKEN` 后登录可见 Mapbox 卫星底图
- [ ] 缺 token 不白屏崩,有明确控制台错误
- [ ] Monitor:卫星↔街道切换后点/线仍在;选中飞向;fitAll;绑定虚线条件正确;比例尺可见
- [ ] Tasks:编辑器内地图可点加点、航线/航点绘制;离开恢复主壳地图位置
- [ ] Replay:航迹线 + fitBounds
- [ ] `package.json``maptalks`;源码无 `import ... maptalks`
- [ ] `1920×1080` 冒烟:Monitor 主路径可用
## 8. 实现顺序(计划阶段细化)
1. 依赖与 env / `config/map` / `MapLayer` / `mapHelper`
2. Monitor 点线与底图切换 / fit / 绑定线
3. Replay 航迹
4. Tasks 搬迁 + 航线编辑交互
5. 删 maptalks 残留 CSS/引用;冒烟验收
Loading…
Cancel
Save