WholeProcessPlatform/frontend/docs/地图模块-3D Cesium实施计划.md
2026-07-14 16:09:01 +08:00

481 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 地图模块 3D Cesium 实施计划
## 1. 目标
在现有地图体系下补齐 `3D(Cesium)` 视图能力,使其尽量复用 `2D(OpenLayers)` 的业务编排结果,满足以下要求:
- 3D 默认进入中国视角,不再落到美国区域。
- 3D 初始化时显示过渡动画,再定位到中国。
- 3D 保留基础底图加载能力,但不提供底图切换面板。
- 3D 支持锚点展示。
- 3D 支持与 2D 一致的鼠标悬停 popup样式复用现有 popup。
- 3D 支持与 2D 一致的锚点点击行为,点击后打开同一套详情弹框逻辑。
- 菜单切换、图层树勾选、图例勾选后3D 锚点与 2D 一样联动变化。
- 3D 必须复用 `eng_point` 的层级显示规则,中型水电站仅在 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上显示。
## 2. 2D 现状梳理
当前 `2D` 的能力并不是散落在页面里,而是已经形成了比较清晰的分层:
### 2.1 页面入口
- `src/components/gis/GisView.vue`
- 负责挂载地图容器、popup 容器、地图控制器、图例、图层树。
- 页面只调用 `mapOrchestrator.mountView()``handlePageChange()`,不直接拼地图业务。
### 2.2 地图编排层
- `src/modules/map/application/map-orchestrator.ts`
- 负责页面初始化、菜单切换、图层配置加载、图例配置加载、基地切换、缩放联动。
- 2D/3D 都应尽量复用这一层,避免在 Cesium 侧另起一套业务流程。
### 2.3 地图统一门面
- `src/components/gis/map.class.ts`
- 对外暴露统一接口,如:
- `addBaseDataLayer`
- `addInitDataLayer`
- `mdLayerTreeShowOrHidden`
- `setLegendPointVisible`
- `controlBaseLayerTreeShowAndHidden`
- `initPopupOverlay`
- `flyTopanto`
- 业务层只认这套接口,不应感知底层是 `OpenLayers` 还是 `Cesium`
### 2.4 2D 地图能力落点
- `src/components/gis/map.ol.ts`
- 已实现的关键能力:
- 默认中国视角初始化
- 基础底图加载与切换
- 点位图层注册和显隐
- 图例控制单类锚点显隐
- 鼠标 hover popup
- click 打开详情弹窗
- 基地裁切与底图遮罩
- 缩放/移动后的批量 popup 和标签刷新
### 2.5 2D 锚点与 popup 支撑
- `src/components/gis/ol/point-layer-manager.ts`
- 负责将点位数据转成地图要素、分层、索引、图例显隐。
- `src/components/gis/ol/popup-manager.ts`
- 负责 hover 命中、popup DOM 内容复用、批量 popup。
### 2.6 图层树和图例交互
- `src/components/mapController/LayerController.vue`
- 图层树勾选后走 `mapOrchestrator.applyLayerSelection()`
- `src/components/mapLegend/index.vue`
- 图例点击后走 `mapOrchestrator.toggleLegend()` / `toggleLegendBatch()`
- `src/store/modules/map.ts`
- 真正把缓存数据、图层状态、图例状态应用到地图实例
- 也就是说,`3D` 只要把统一接口补齐,就能直接接住现有业务流
### 2.7 2D 已有特殊业务规则
- 锚点点击逻辑已在 `src/components/gis/map.ol.ts` 实现:
- 命中锚点后,`ylfb` 走 `ylfbModalVisible`
- 其他锚点走 `modalVisible`
- 标题和参数都直接取当前锚点数据
- `eng_point` 的中型水电站显示阈值已在现有链路中实现:
- 阈值常量位于 `src/modules/map/application/map-orchestrator.ts`
- 过滤逻辑位于 `src/store/modules/map.ts``filterPointLayerDataForDisplay()`
- `3D` 不应重新发明一套规则,而应复用这套现有过滤结果
- 部分点位会随着缩放级别提升显示得更多,不是简单的“图层开/关”:
- `eng_point` 的中型电站要到 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上才显示
- 水电开发页在缩放跨过 `12` 级时会自动补开一批动态图层
- `2D` 内部还存在点位密度阈值、近邻点展开、批量 popup 阈值等缩放联动规则
- `2D` 对锚点存在碰撞检测与密度控制,不是所有点始终全量显示:
- 图标和标签会分别经过碰撞计算
- 低缩放下会因为密度、碰撞、区域裁切、图例状态而隐藏部分点
- 高缩放下会逐步放开限制,显示更多锚点和标签
## 3. 3D 当前现状与缺口
### 3.1 已有内容
- `src/components/gis/map.cesium.ts`
- 当前只完成了最小化 `Cesium.Viewer` 初始化。
- 已有基础缩放、截图、`flyTopanto()` 雏形。
### 3.2 当前主要缺口
- `init()` 里虽然定义了 `flyToChina()`,但没有真正执行,也没有 `resolve(viewer)`,初始化流程不完整。
- 默认底图仍是 ArcGIS 全球影像,因此启动时容易落到美国默认视角。
- `addBaseDataLayer()` 为空,无法接住当前 `GISMap/customBaseLayer` 逻辑。
- `addInitDataLayer()` 为空,无法展示锚点。
- `mdLayerTreeShowOrHidden()` 为空,菜单切换时 3D 锚点不会同步显隐。
- `setLegendPointVisible()` 为空,图例勾选不会影响 3D 锚点。
- `initPopupOverlay()` 为空,无法复用 2D popup 容器。
- 缺少点击命中与详情弹框联动3D 当前无法做到“点击锚点与 2D 同行为”。
- `controlBaseLayerTreeShowAndHidden()` 为空3D 基础底图和叠加层无法受现有图层树控制。
- 尚未验证 `eng_point` 的缩放阈值规则在 3D 下是否能完全复用,存在实现时遗漏中型站点显隐的风险。
- 目前没有承接 `2D` 中“放大到具体层级显示更多锚点”的缩放联动显示机制。
- 目前没有承接 `2D` 中图标/标签的碰撞检测逻辑,后续若直接全量渲染,视觉效果会与 `2D` 明显不一致。
- 当前没有建立 `Cesium entity / datasource / imageryLayer` 的注册表,后续联动无从下手。
## 4. 实施原则
- 不重做业务编排,优先复用 `map-orchestrator.ts``src/store/modules/map.ts`
- 不额外引入一套 3D 专用图例和图层状态,仍以现有 store 为唯一运行态来源。
- 不单独复制 `eng_point` 的中型站显示规则,优先复用现有 `mapStore` 过滤结果。
- 不单独新建一套点击弹框协议,优先保持与 `map.ol.ts` 相同的命中后行为。
- 对“缩放越大显示越多锚点”这类规则,优先复用现有缩放阈值与显示门闩,不做拍脑袋的新规则。
- 对碰撞检测,优先与 `2D` 保持同方向行为:低缩放控量,高缩放放开,而不是 3D 全量堆点。
- 先做最小闭环,再逐步补能力:
- 第一步保证 3D 能正确初始化并定位中国。
- 第二步保证基础底图能显示。
- 第三步保证锚点能显示。
- 第四步保证 popup、图层树、图例联动打通。
- 3D 不做“基础地图切换 UI”但底层仍要支持 `customBaseLayer + addBaseDataLayer(..., true)` 这一逻辑。
## 5. 分步骤实施计划
### Step 1修正 3D 初始化并默认定位中国 ✅️
目标:
- 切到 3D 后稳定初始化 viewer。
- 首屏出现一个轻量加载动画。
- 初始化完成后飞到中国默认视角。
计划:
- 修复 `map.cesium.ts``init()`
- 保证初始化后一定 `resolve(this.viewer)`
- 执行 `flyToChina()`
- 补一个可控的启动过渡动画。
- 统一中国默认参数:
- 中心经纬度建议复用 `104.5, 36.5`
- 相机高度先按全国视角收口,后续再微调
- 如果切回 2D 再切到 3D仍然要能正确重建 viewer。
验收:
- 点击 `3D` 按钮后,不再出现美国默认视角。
- 首屏有加载过渡。
- 进入后稳定落在中国。
### Step 2展示 3D 基础底图 ✅️
目标:
- 3D 不显示空球。
- 保留“基础底图 + 3D 专用底图地址”能力。
- 不提供底图切换面板,但底层能吃现有配置。
计划:
-`MapCesium` 内建立基础图层注册表。
- 实现 `addBaseDataLayer(layer, isShow)`
-`customBaseLayer` 使用 `url_3d`
-`BASEMAP-img`、`BASEMAP-white` 预留兼容能力
- **必须处理 `wmts` 类型图层**2D 通过 `WMTS` source 加载3D 使用 `Cesium.WebMapTileServiceImageryProvider`,从 URL 参数中提取 LAYER/TILEMATRIXSET
- **必须提供 `tileMatrixLabels`**Cesium 默认用纯数字作为 tileMatrix 参数值,但本系统 WMTS 服务器要求 `TileMatrixSet:zoom` 格式(如 `EPSG:3857_hbb_zrbhq_l13:4`)。需构建 `["TileMatrixSet:0", "TileMatrixSet:1", ...]` 传入 `tileMatrixLabels`
- 其他类型:`raster-dem`→XYZ、MapServer→ArcGIS、其余 →WMS
- **注意**:还存在一个 `key = "powerBaseStation"` 的基地底图图层,该图层**初始化时不加载**(已在 `IGNORED_BASE_LAYER_KEYS` 中忽略),但后续可通过图层树手动勾选显示。
- 实现 `controlBaseLayerTreeShowAndHidden()`
- 让图层树勾选能控制 3D 底图显隐
- 根据你的要求UI 层先不开放 `BaseLayerSwitcher` 的 3D 切换行为,只保留默认基础底图加载。
验收:
- 切到 3D 后可稳定看到基础底图。
- 图层初始化时 `addBaseDataLayer(this.baseLayerConfig, checked, true)` 的思路在 3D 可落地。
### Step 3打通 3D 锚点展示 ✅️
目标:
- 3D 能展示和 2D 同源的锚点数据。
- 菜单切换后3D 能跟随页面数据切换。
计划:
-`MapCesium` 内建立点位图层注册结构,按 `layerKey -> entity[]` 管理。
- 实现 `addInitDataLayer(pointData, layerType)`
- 复用当前点位数据结构
- 解析 `lgtd/lttd/iconCode/titleName/stnm/anchoPointState/popupHtml`
- 复用现有图标资源路径
- Entity ID 使用 `${layerKey}:${item._id || `${iconType || 'point'}_${stcd}`}`,与 2D 对齐,确保同 stcd 不同 sttpMap 的点不被覆盖
- 确保 `eng_point` 仍按现有 `mapStore` 过滤后的数据展示,中型站点只在 `7.5` 以上层级出现
- 为后续缩放驱动的增量显示预留运行态字段(`_legendVisible`、`_regionVisible`、`_densityVisible`),不把所有点简单一次性硬渲染
- 实现 `mdLayerTreeShowOrHidden(layerType, checked)`
- 图层树控制整个点图层显隐
- 实现 `removePointLayer()` / `hasLayer()`
- 便于后续页面切换和重载
验收:
- 切到 3D 后当前页默认勾选图层的锚点能显示。
- 切换菜单后3D 锚点跟随变化。
- 取消勾选图层后,对应锚点消失。
- `eng_point` 在低于 `7.5` 时不显示中型水电站,高于 `7.5` 时正常显示。
### Step 4打通 3D 基地裁切jdPanelControlShowAndHidden ✅️
目标:
- 3D 点击基地面板后,与 2D 行为一致。
- 加载基地边界 GeoJSON只显示区域内锚点隐藏区域外锚点。
- 取消基地时恢复显示所有锚点。
计划:
-`MapCesium` 中捕获 `hydropBase` 配置,与 2D 一样在 `addBaseDataLayer` 中记录。
- 实现 `jdPanelControlShowAndHidden(baseid, isAll)`
- `isAll=false`:清除 3D 球面遮罩实体、恢复所有锚点区域可见性、清除裁切状态。
- `isAll=true`:先隐藏所有锚点,异步加载基地边界 GeoJSON执行点在面判断后仅显示区域内锚点同时在球面绘制裁切边界多边形`applyRegionMask`),最后飞行到区域视野。
-`createEntityPropertiesBag` 中新增 `_regionVisible` 属性,初始为 `true`
-`applyPointVisibilityRefresh` 中跳过 `_regionVisible !== false` 的实体,确保裁切后碰撞检测不会把区域外锚点重新显示出来。
- 复用 `extractPolygonCoords` / `isPointInRing` 射线法判断逻辑(与 `region-mask-manager.ts` 一致)。
- 支持 `AbortController` 取消前一次未完成的请求,避免响应顺序错乱。
- `applyRegionMask` 将 GeoJSON 多边形转换为 Cesium `PolygonHierarchy`,在球面绘制半透明填充 + 可见边界的区域多边形,模拟 2D 底图 Canvas 裁切遮罩的视觉效果。
验收:
- 点击基地后3D 只显示该基地区域内的锚点,区域外锚点隐藏。
- 取消基地后,所有锚点恢复正常显示。
- 快速切换基地不会出现旧数据覆盖新数据的问题。
- 多次切换 2D/3D 后基地裁切行为正常。
### Step 5打通 3D popup 与 hover
目标:
- 3D 鼠标悬停时显示与 2D 一样的 popup。
- popup 样式复用现有 DOM 和样式,不再单独做一版。
计划:
- 实现 `initPopupOverlay(popupContainer)`
- 继续复用 `GisView.vue` 中现有 popup 容器
- 在 Cesium 中增加鼠标移动命中:
- 基于 `ScreenSpaceEventHandler`
- hover 到锚点时读取 entity 附带属性
- popup 内容生成逻辑复用 2D 现有字段:
- 优先使用 `popupHtml`
- 否则走通用字段渲染
- 处理离开锚点时隐藏 popup。
验收:
- 鼠标移入锚点出现 popup。
- 样式和 2D 保持一致。
- 移出后 popup 正常消失。
### Step 6补齐缩放驱动显示与碰撞检测
目标:
- 3D 在低缩放下控制锚点数量,避免全量堆叠。
- 3D 在放大到具体层级后,逐步显示更多锚点。
- 3D 的图标/标签显示效果尽量接近 2D不出现严重遮挡。
计划:
- 先承接现有明确可复用的缩放规则:
- `eng_point``7.5` 阈值
- 水电开发页的 `12` 级动态图层联动
- 再补 3D 侧的显示门闩:
- 低缩放下根据缩放级别、图层状态、图例状态控制 entity 显隐
- 高缩放下逐步放开限制,显示更多锚点
- 为 3D 补一版轻量碰撞控制:
- 至少先控制标签碰撞,避免名称完全叠在一起
- 如有必要,再补图标级碰撞或屏幕网格抽稀
- 明确与 2D 对齐的目标不是“算法完全一样”,而是“视觉行为一致”:
- 低缩放控量
- 高缩放增量显示
- 关键锚点不被完全淹没
验收:
- 3D 在全国视角下不会一次性挤满所有锚点。
- 放大到具体层级后会看到更多锚点逐步出现。
- 标签和锚点不会大面积重叠到不可读。
### Step 7打通 3D 锚点点击详情 ✅️
目标:
- 3D 点击锚点后,行为与 2D 一致。
- 继续复用现有详情弹框状态和参数结构。
计划:
- 在 Cesium 中增加点击命中:
- 基于 `ScreenSpaceEventHandler`
- 命中锚点后读取 entity 绑定的原始业务数据
- 复用 `2D` 当前点击行为:
- `sttpMap === 'ylfb'` 时打开 `ylfbModalVisible`
- 其他锚点打开 `modalVisible`
- `params`、`title` 赋值逻辑与 `map.ol.ts` 保持一致
- 避免 hover popup 与 click 弹框互相干扰。
验收:
- 点击 3D 锚点后可打开与 2D 一致的详情弹框。
- `ylfb` 和普通锚点分支行为一致。
- 点击空白区域不会误触发弹框。
### Step 8打通图例联动 ✅️
目标:
- 图例勾选后3D 只显示对应状态的锚点。
计划:
- 实现 `setLegendPointVisible(layerKey, anchoPointState, checked)`
- 复用 `anchoPointState` 作为图例匹配字段
- entity 级别维护显示状态
- 如果后续需要,也可补一个 3D 版本的图例索引表,避免每次全量遍历。
验收:
- 图例单项勾选/取消时3D 对应类别锚点同步显隐。
- 图例分组勾选/取消时3D 行为与 2D 保持一致。
### Step 9补足收口与稳定性
目标:
- 让 3D 能稳定参与现有地图生命周期。
计划:
- 清理 Cesium 事件监听、viewer、entity、datasource避免重复切换内存泄漏。
- 检查 `switchView('2D' | '3D')` 前后 popup、底图、图层注册是否被正确重置。
- 检查 hover、click、图例、图层树、缩放阈值这几条联动链路在 3D 是否完全闭环。
- 视情况补最小诊断日志,确认菜单切换与图层联动没有漏项。
验收:
- 2D/3D 多次来回切换不报错。
- 菜单切换、图层切换、图例切换行为稳定。
## 6. 推荐落地顺序
按你的要求,建议严格按下面顺序推进:
1. 默认定位到中国 + 启动动画
2. 展示基础底图
3. 展示锚点 3.5. 补基地裁切jdPanelControlShowAndHidden
4. 补 hover popup
5. 补缩放驱动显示与碰撞检测
6. 补锚点点击详情
7. 补图层树联动
8. 补图例联动
9. 做切换稳定性收口
## 9. 必须补齐的隐藏逻辑清单
下面这些逻辑不是表面接口能直接看出来的,但如果 `3D` 不补,最终效果就会和 `2D` 明显不一致。
### 9.1 图层与菜单切换
- 菜单切换时,只隐藏旧页点图层,不隐藏底图。
- 图层树勾选前要先经过互斥规则归一化:
- `rare_fish_point``fish_along_point` 互斥
- `stinfo/stinfo_video_point``stinfo_ai_video_point` 互斥
- `facilities``facilities_built` 两个分支互斥
- 点图层取消勾选时只隐藏,不删除缓存;重新勾选时优先从缓存恢复。
- **图层树勾选时按 `type` 字段走不同分支**(见 `updateLayerData`
- `type === 'pointMap'` → 走 `handlePointMapLayer` → 调用 `mdLayerTreeShowOrHidden`(控制锚点显隐)
- `type === 'GISMap'` → 走 `controlBaseLayerTreeShowAndHidden`(控制底图/叠加影像图层显隐)
- 3D Cesium 必须同时实现这两个方法才能与图层树完整联动。
### 9.2 图例与点内显隐
- 图例控制的是图层内部锚点是否可见,不是图层本身是否可见。
- 图层恢复显示时,图例要先整层全关,再按当前 checked 状态逐项恢复。
- 环保设施图例组当前是只展示不交互。
### 9.3 缩放驱动显示
- `eng_point` 的中型水电站只在 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上显示。
- 水电开发页缩放跨过 `12` 级时,会自动补开一批动态图层;缩回去时再自动收口。
- `2D` 存在"缩放越大显示越多"的行为,不是所有锚点在所有层级都等量显示。
- `2D` 还存在 hover、批量 popup、密度控制等缩放阈值
- hover 低于一定缩放不触发
- 高缩放会进入批量 popup 模式
- 点位会随着缩放提升逐步放开显示限制
- **图标动态缩放**`scale = 0.7 + (zoom - 4.5) * 0.08`,钳位到 `[0.5, 3.0]`
- **字体动态大小**`fontSize = clamp(12 * scale, 10, 24)`,即 10px~24px 之间随缩放变化。
- **文字在图标上方**2D 中 `text.offsetY = -22 * scale`,负值表示文字偏移到图标上方。
- **注意**:上述 2D scale 公式是针对 OpenLayers 2D 投影校准的Cesium 3D 球面视角需要更高的基准值(建议 1.0 起步),且 `scaleByDistance` 在 9,000km 中国高度处不应过度压缩(建议 near/far 范围设为 1km→2x, 20,000km→0.6x)。
- **Cesium 中必须使用 `CallbackProperty` 实现动态缩放**3D 中 `billboard.scale` 不能是静态值,否则放大后图标不会跟随变大。使用 `CallbackProperty` 每帧重新计算当前 zoom 对应的 scale与 2D `getDynamicPointScale` 行为对齐。
### 9.4 锚点碰撞与控量
- `2D` 中锚点是否可见,不只受图层开关控制,还受以下门闩共同影响:
- 图例可见性
- 区域裁切可见性
- 密度阈值
- 图标碰撞结果
- 标签碰撞结果
- 低缩放下,为避免堆叠,部分锚点或标签会被主动隐藏。
- 高缩放下,会逐步放开图标/标签限制,显示更多内容。
- 因此 `3D` 不能简单把所有 entity 永久全开,否则视觉密度会明显失控。
### 9.5 Popup 与点击
- hover popup 不是全时开启的,有缩放门槛。
- 点击和 hover 的命中对象是图标本体,不是任意文字区域。
- `ylfb` 与普通锚点的点击详情弹框走不同业务分支。
### 9.6 基地与区域裁切
- 基地切换不仅影响搜索候选,还会影响地图上的点位可见范围。
- 区域裁切会直接把区域外点位隐藏,不是只改底图遮罩。
### 9.7 筛选器特殊逻辑
- 装机容量筛选本质是批量切 `eng_point` 图例状态,不是简单切图层。
- 鱼类分布模式会接管 `ylfb_point` 的数据、缓存、图例与显隐。
- 时间筛选当前只重载指定时间相关图层,不是全量重载。
### 9.8 数据进入 addInitDataLayer 前的预处理
- 所有点位数据在进入 `addInitDataLayer` 前会经过 `normalizePointLayerItems()` 过滤:
- 没有 `iconCode``code !== 'colorLayer'` 的项会被抛弃
- `baseId == 'all'``anchoPointState` 结尾为 `_nbuilt` 的项会被抛弃
- `_id` 默认值生成规则:如果项没有 `_id`,会生成 `${sttpMap || iconType || 'point'}_${stcd}`
- **stcd+经纬度重复锚点处理**:同一个 layerKey 下,相同 stcd 和经纬度但不同 sttpMap/anchoPointState 的锚点需要全部保留,不能因为 Feature/Entity ID 冲突而被覆盖。2D 使用 `${layerKey}:${_id}` 作为 ID其中 `_id` 默认为 `${sttpMap}_${stcd}`,天然区分了同 stcd 不同业务类型的点。
### 9.9 图标渐进加载机制
- 2D 中图标不是一次性全加载完。有 `loading → loaded → error` 三种状态跟踪。
- 只有 `loaded` 状态的图标才参与样式渲染loader 失败的不渲染。
- 如果在图标加载过程中就触发了样式刷新,需要通过 `requestAnimationFrame` 回刷。
- 3D Cesium 中如果要实际渲染图标纹理,同样需要这种渐进加载机制,或在实现时先以简单 billboard 色块兜底。
### 9.10 密度分档与近邻点元数据
- 每个锚点的 `distance` 字段表示密度分档值(不是物理距离),值越大表示越稀疏。
- 不同 density 值对应不同的 `minZoom` 显示门槛(见 `nearby-point-rules.ts``densityDisplayRules`)。
- 全量数据加载后store 会调用 `attachNearbyPointRuntimeMeta()` 给所有点打上 `_nearbyRule`、`_nearbyGroupId`、`_nearbyPriority` 等元数据。
- **孤立点例外规则**:如果一个点在当前视口内 ~56px 范围内没有同 layerKey 的其他可见点,它可以绕过密度门槛直接显示。
## 7. 预计修改文件
- `src/components/gis/map.cesium.ts`
- 3D 地图主体实现,主改动文件
- `src/components/gis/map.class.ts`
- 视情况补齐 2D/3D 切换后 popup 初始化和服务切换细节
- `src/components/gis/GisView.vue`
- 视情况控制 3D 下的底图切换器显示策略、加载态挂载位置
- `src/components/mapController/index.vue`
- 若需要补 3D 初始化后的交互控制
## 8. 当前建议
建议先只做 `Step 1`,也就是:
- 修正 Cesium 初始化完成逻辑
- 增加 3D 启动加载动画
- 默认飞到中国
这一步最小、最容易验证,也能先把“打开 3D 看起来不对”的核心问题解决掉。