WholeProcessPlatform/frontend/docs/地图模块现状与改造方案.md

1159 lines
33 KiB
Markdown
Raw Permalink 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.

# 地图模块现状与改造方案
## 1. 文档目的
本文档用于完整梳理当前地图模块的技术实现、业务能力、数据流转、关键规则、已知问题与性能瓶颈,并给出可分阶段落地的重构方案。目标是:
- 让地图模块职责边界清晰,便于后续新增功能;
- 降低组件、Store、地图引擎之间的耦合
- 优化基础图层、锚点、图例、筛选、基地裁切等功能的响应速度;
- 为后续 2D/3D 扩展、复杂专题图层、更多筛选维度、更多交互能力预留稳定结构。
---
## 2. 当前模块范围
当前地图模块覆盖以下业务能力:
- 地图初始化与 2D/3D 切换;
- GeoServer 基础底图、叠加图层、区域矢量图层加载;
- Java 后端锚点数据请求、缓存、渲染;
- 右侧图层树勾选控制图层显示与隐藏;
- 左侧图例基于图层选中结果动态生成;
- 图例项点击控制锚点显隐;
- 基地选择后底图裁切与区域外锚点隐藏;
- 地图悬停 Popup、点击详情弹窗
- 搜索、时间筛选、容量筛选、基地筛选;
- 某些业务图层的互斥显示与特殊联动;
- 局部专题菜单下的缩放级别联动动态图层。
---
## 3. 当前技术实现概览
## 3.1 技术栈
- 地图引擎OpenLayers 作为 2D 主引擎Cesium 作为 3D 引擎入口;
- 状态管理Pinia
- UI 框架Vue 3 + `script setup` + Ant Design Vue
- 数据来源:
- GeoServer基础底图、WMTS、XYZ、GeoJSON 边界;
- Java 后端接口:图层配置、图例配置、锚点数据;
- 业务配置:
- 图层配置与图例配置来自后端;
- 锚点接口映射规则由 `src/store/modules/GisUrlList.ts` 维护。
## 3.2 核心文件职责
- `src/components/gis/GisView.vue`
- 地图容器入口;
- 地图初始化;
- 拉取图层配置与图例配置;
- 监听菜单切换、基地切换、缩放联动;
- 挂载 `MapLegend`、`MapFilter`、`MapController`、`BaseLayerSwitcher`。
- `src/components/gis/map.class.ts`
- 地图能力门面类;
- 对外屏蔽 OpenLayers / Cesium 差异;
- 当前主要起到转发作用。
- `src/components/gis/map.ol.ts`
- OpenLayers 主实现;
- 负责底图、锚点图层、Popup、图层显隐、区域裁切、量算、定位等
- 当前承担了过多业务规则与渲染控制逻辑。
- `src/store/modules/map.ts`
- 地图图层树、图例、锚点、缓存的核心 Store
- 负责图层选中状态、图例选中状态、锚点请求、缓存、图例联动、图层联动;
- 当前是业务与渲染耦合最重的文件之一。
- `src/components/mapController/LayerController.vue`
- 图层树 UI
- 负责初始基础图层加载;
- 勾选事件直接调用 Store 更新图层与图例状态;
- 包含多个互斥业务规则。
- `src/components/mapLegend/index.vue`
- 图例展示;
- 基于当前选中图层动态显示;
- 图例点击后直接驱动地图锚点显隐。
- `src/components/mapFilter/index.vue`
- 搜索、基地筛选、容量筛选、时间筛选;
- 直接依赖 `mapStore` 和地图实例;
- 部分筛选逻辑通过直接修改图例和锚点实现。
- `src/components/BaseLayerSwitcher/index.vue`
- 底图模式切换器;
- 切换矢量 / 地形 / 影像底图。
- `src/components/gis/gisUtils.ts`
- 图层结构拍平;
- 基础图层 URL 处理;
- 图例数据转对象;
- 若干地图布局相关工具。
- `src/api/map.ts`
- 拉取地图配置和图例配置。
---
## 4. 当前业务能力与逻辑链路
## 4.1 初始化流程
当前初始化主要发生在 `GisView.vue`
1. 调用 `MapClass.init()` 初始化 OpenLayers
2. 调用 `initPopupOverlay()` 挂载悬停 Popup
3. 调用 `fetchMapConfigs()` 并行请求:
- 地图图层配置;
- 全量图例配置;
- 当前页面图例配置;
4. `mapStore.setLegendData()` 初始化图例原始数据与映射;
5. `mapStore.setLayerData()` 初始化图层树和选中图层 key
6. `mapStore.loadAllLayerData()` 递归加载所有有 URL 的图层数据;
7. `loadLayerData()` 将锚点数据写入缓存并直接调用 `mapClass.addInitDataLayer()` 预创建锚点图层;
8. 全部锚点加载完成后,再调用 `updateLayerData()` 统一控制初始显示状态;
9. 图例根据当前选中的图层动态生成并展示。
### 现状特点
- 当前是“初始化阶段一次性预加载大多数锚点图层,再通过显隐控制”的策略;
- 图例显示被设计为“锚点完成后再显示”;
- 地图实例、Store、UI 组件三方互相直接调用。
## 4.2 基础图层加载与切换
基础底图主要由 `LayerController.vue``map.ol.ts` 协同完成:
- `LayerController.vue` 中监听图层树配置;
- 对类型为 `GISMap` 的节点解析 `paramJson`,通过 `getMapConfig()` 补齐 GeoServer 地址;
- 调用 `mapClass.addBaseDataLayer()` 初始化基础图层;
- 同时调用 `controlBaseLayerTreeShowAndHidden()` 控制显隐;
- `BaseLayerSwitcher` 再通过 `baseLayerSwitcher()` 在矢量 / 地形 / 影像底图之间切换。
当前基础图层类型主要包括:
- WMTSGeoServer WMTS 瓦片;
- XYZ例如 DEM 或其他切片图层;
- vector部分矢量底图或区域配置。
### 自然保护区图层特点
- 属于叠加在底图之上的 GIS 图层;
- 显隐仍通过基础图层控制逻辑处理;
- 与区域裁切场景存在较强耦合。
## 4.3 图层树勾选控制
右侧图层树由 `LayerController.vue` 驱动:
- 树节点勾选结果通过 `broadcast()` 调用 `mapStore.updateLayerData()`
- 图层树中包含多种特殊规则:
- 视频监控站与 AI 视频监控站互斥;
- 环保设施和环保设施在建互斥;
- 珍稀鱼类与沿程鱼类互斥;
- 初始选中状态通过递归遍历图层树生成。
### 现状问题
- 互斥逻辑分散在 UI 组件和 Store 两处;
- “图层树状态”与“地图真实状态”并非单向流;
- 图层节点既承担展示,又携带业务配置与运行态字段。
## 4.4 锚点数据加载
锚点数据由 `mapStore.loadAllLayerData()``loadLayerData()` 负责:
- 遍历图层树中全部带 URL 的图层;
- 使用 `GisUrlList.ts` 将图层 key / title / url 映射成真实请求地址、筛选条件、排序规则;
- 请求 Java 接口;
- 将返回记录补充:
- `iconCode`
- `code`
- `_id`
- `tm`
- 然后缓存到:
- `pointDataCache[layerKey]`
- `layer.data`
- 合并后的 `pointData`
- 若地图上还不存在该图层,则直接调用 `mapClass.addInitDataLayer()` 创建 `VectorLayer`
### 当前锚点渲染方式
- 每个 `pointMap` 图层对应一个 `VectorLayer`
- 每个点被转为 `Feature<Point>`
- Feature 上维护多个运行态字段:
- `_iconUrl`
- `_labelText`
- `_legendVisible`
- `_regionVisible`
- `_layerKey`
- 样式函数 `createPointStyle()` 在渲染时综合判断:
- 图例是否显示;
- 是否在基地区域内;
- 当前缩放级别;
- 点之间距离阈值;
- 是否悬停。
## 4.5 图例动态生成与点击联动
图例数据来源于两部分:
- `legendDataOriginal`:后端返回的全量图例;
- `legendDataSelected`:根据当前选中的图层动态筛选后的图例结构。
当前机制:
1. 初始化时拉取全部图例;
2. 通过 `layerCode` 与图层树选中结果建立关联;
3. `mapStore.updateLayerData()` 重新计算当前选中的图例树;
4. `MapLegend` 监听 `legendDataSelected` 进行展示;
5. 点击图例项后:
- 更新 Store 中图例 checked 状态;
- 若是锚点图层,则调用 `mapClass.setLegendPointVisible()`
- 若是 GIS 图层,则调用 `controlBaseLayerTreeShowAndHidden()`
### 环保设施特殊逻辑
- 勾选 `fp_point`、`eq_point`、`fb_point`、`vp_point`、`va_point`、`sg_point`、`dw_point` 中任意图层时,图例自动补充“环保设施”分组;
- 全部取消时自动移除该图例分组。
## 4.6 筛选与搜索
`MapFilter` 负责以下功能:
- 装机容量筛选;
- 锚点关键字搜索;
- 时间筛选;
- 基地筛选;
- 个别专题下的鱼类分布筛选。
当前实现方式:
- 搜索下拉列表从 `pointData` 里计算;
- 只展示当前已选中图层中的锚点;
- 容量筛选直接修改图例状态,并调用 `map.mdLayerShowOrHidden()` 控制 feature 显隐;
- 时间筛选通过 `mapStore.updateSearchTimeRange()` 清缓存、删图层、重新请求;
- 基地切换通过监听 `JidiSelectEventStore.selectedItem` 联动地图区域裁切。
## 4.7 基地选择与区域裁切
基地逻辑入口在 `GisView.vue` 监听器中:
- 当基地变化时,调用 `mapClass.jdPanelControlShowAndHidden()`
- `map.ol.ts` 内会:
- 更新当前 `BASEID`
- 请求基地 GeoJSON 边界;
- 隐藏所有锚点;
- 通过射线法判断锚点是否在区域内;
- 标记 `_regionVisible`
- 调整视角;
- 对底图应用遮罩。
### 该能力的业务效果
- 只显示当前基地区域内的锚点;
- 底图裁切到当前基地范围;
- 全选时取消遮罩并显示全部锚点。
## 4.8 Popup 与点击详情
当前交互如下:
- `pointermove` 时调用 `detectFeatureAtPixel()` 检查命中点要素;
- 若命中图标区域,则:
- 更新鼠标指针;
- 更新 `hoveredFeatureId`
- 调用 `showPopup()` 展示悬停浮窗;
- 重绘所有点图层;
- `click` 命中点要素时,打开详情弹窗并将 feature 属性写入 `modelStore`
### 当前特点
- Popup 内容由 `popupHtml``generatePopupHtml(props)` 生成;
- 命中检测采用逐像素回调加距离二次判断;
- 所有点图层在 hover 状态切换时都会 `layer.changed()`
## 4.9 菜单与缩放级别联动
在水电开发状况菜单下:
- 地图缩放级别大于等于 12 时自动添加一组动态图层;
- 缩回 12 以下时自动移除;
- 菜单切换时会根据当前层级重新判断是否添加。
当前动态图层 key 包括:
- `dw_point`
- `stinfo_video_point`
- `stinfo_gjllz_point`
- `wt_point`
- `wq_ownWq_point`
- `wq_countryWq_point`
- `fp_point`
- `eq_point`
- `fb_point`
- `vp_point`
- `va_point`
- `sg_point`
---
## 5. 当前数据流与调用关系
当前主数据流可概括为:
1. `GisView.vue` 拉取配置;
2. `mapStore` 保存图层树、图例、缓存;
3. `LayerController.vue` / `MapLegend.vue` / `MapFilter.vue` 直接操作 `mapStore`
4. `mapStore` 又直接调用 `mapClass`
5. `mapClass` 继续转发到 `map.ol.ts`
6. `map.ol.ts` 内部维护图层注册表和 Feature 运行态;
7. 地图状态变化后,组件又通过监听 Store 再次更新 UI。
### 现有结构本质
当前是“多入口、多方向写状态”的结构:
- 组件可以改 Store
- 组件可以直接调地图;
- Store 可以直接调地图;
- 地图内部又维护一套运行态;
- 图例、图层树、地图显隐存在多份状态镜像。
这会导致:
- 状态同步成本高;
- 某些场景下容易出现 UI 勾选状态与实际地图状态不一致;
- 新增规则时只能继续在现有逻辑上叠加 `if/else`
---
## 6. 当前存在的主要问题
## 6.1 架构层面问题
### 1. UI、业务编排、地图渲染耦合过深
- `LayerController.vue`、`MapLegend.vue`、`MapFilter.vue` 都直接调 Store 和地图实例;
- `map.ts` 同时负责:
- 请求;
- 缓存;
- 图例结构整理;
- 图层勾选;
- 地图显隐;
- 业务互斥;
- `map.ol.ts` 同时负责:
- OpenLayers 适配;
- 业务过滤;
- 区域裁切;
- 弹窗;
- 图例控制;
- 锚点显示策略。
### 2. 缺乏清晰的单向数据流
- 图层树 checked、图例 checked、Feature `_legendVisible`、Layer visible 都是状态源;
- 任何一个点修改后都可能触发联动,难以推断最终状态;
- 某些流程依赖顺序正确才能表现正常。
### 3. 业务规则分散
- 互斥规则分散在 `LayerController.vue``map.ts`
- 环保设施图例补充逻辑在 `map.ts`
- 装机容量筛选逻辑在 `MapFilter.vue`
- 基地区域过滤逻辑在 `map.ol.ts`
- 这些规则没有统一归口,新增业务会继续扩大分散度。
## 6.2 性能问题
### 1. 初始化阶段预加载过重
- `loadAllLayerData()` 会递归处理大量图层;
- 即使图层未勾选,也会提前请求并建图层;
- 页面首次进入时网络压力和首屏渲染压力较大。
### 2. 递归遍历频繁
- 图层树查找 `findLayerByKey()` 多次递归;
- 图例生成 `getlegendData()` 递归;
- `updateLayerData()` 每次变化都要递归更新 checked 状态;
- 搜索项计算 `anchorPointOptions` 也会递归收集图层 key。
### 3. 锚点显隐是全量 feature 遍历
- 图例点击时逐个遍历 feature
- 基地裁切时逐个 feature 做点在面内判断;
- hover 时所有点图层重绘;
- 缩放变化会重复执行样式计算。
### 4. 底图切换会 remove / add 图层
- `baseLayerSwitcher()` 每次切换都移除旧底图再新增;
- 对 WMTS / XYZ 来说会造成重复创建图层对象和重复请求开销。
## 6.3 一致性与可靠性问题
### 1. 存在明显临时代码与未收敛逻辑
- `GisView.vue``fetchMapConfigs()` 里对图层做了 `splice(0, 2)` 临时截断;
- 日志仍显示“临时限制”,说明线上逻辑可能仍处于测试状态。
### 2. 存在未定义引用
- `GisView.vue` 切换地图后调用 `fetchPointData()`,但项目中未找到定义;
- `map.ts` 中使用了 `wq_lineTime`、`wt_lineTime`,但项目中未找到定义。
### 3. 请求去重逻辑未真正落地
- `map.ts` 中生成了 `requestIdentifier`,但未实际用于请求去重;
- `requestParamsCopy` 也未参与有效逻辑。
### 4. 图层状态与渲染状态容易失同步
- `layer.checked`、`checkedLayerKeys`、图例 checked、Feature 可见状态是多份数据;
- 其中任意一份被跳过更新,就可能出现视觉不一致。
### 5. 部分代码存在明显遗留痕迹
- `addInitDataLayer()` 中存在无意义语句 `3;`
- 有重复日志;
- 有注释掉的大量旧逻辑;
- 说明代码迁移过程中缺乏结构性收口。
## 6.4 可维护性问题
### 1. Store 过大
- `map.ts` 既是数据仓库,又是调度器,又是业务规则中心;
- 后续新增图层类型、筛选类型、专题逻辑时,文件会持续膨胀。
### 2. MapOl 职责过多
- 当前 `map.ol.ts` 既是 OpenLayers 适配器又是图层经理、锚点经理、Popup 经理、区域裁切经理;
- 单文件理解和修改成本已经偏高。
### 3. 缺少规范化类型系统
- 大量 `any`
- 图层配置、图例配置、锚点数据、请求映射结构都没有稳定类型;
- 特殊图层规则依赖字符串常量散落在各处。
---
## 7. 改造目标
本次改造建议达成以下目标:
### 7.1 结构目标
- 建立清晰的四层结构:视图层、应用编排层、领域状态层、地图渲染层;
- 收拢业务规则,不再散落到多个 UI 组件和地图引擎实现中;
- 建立稳定的数据模型与类型定义。
### 7.2 性能目标
- 首屏只加载必要图层;
- 选中图层优先加载,未选中图层延后或懒加载;(待考虑)
- 图例切换、基地切换、搜索筛选尽量使用索引和差量更新;
- 减少重复递归、重复建图层、重复样式重算。
### 7.3 维护目标
- 新增图层类型、专题规则、图例规则时有固定扩展点;
- 2D/3D 共享统一业务层,底层只替换渲染适配器;
- 可以较容易增加埋点、日志、调试工具和单元测试。
---
## 8. 目标架构设计
建议将地图模块拆分为四层:
## 8.1 视图层
只负责展示和用户交互,不直接编写复杂业务逻辑。
建议保留的组件:
- `GisView.vue`
- `LayerController.vue`
- `MapLegend.vue`
- `MapFilter.vue`
- `BaseLayerSwitcher.vue`
视图层职责:
- 展示树、图例、筛选项、底图切换器;
- 分发用户动作;
- 订阅 Store 派生结果;
- 不直接调用 `map.ol.ts` 里的业务方法。
## 8.2 应用编排层
新增地图编排层,建议命名为:
- `src/modules/map/application/map-orchestrator.ts`
-`src/components/gis/core/orchestrator.ts`
职责:
- 承接 UI 动作;
- 统一处理“图层勾选、图例勾选、基地切换、时间筛选、搜索定位、底图切换”等命令;
- 控制加载顺序;
- 根据状态差异驱动地图渲染层;
- 管理互斥规则和特殊规则。
### 推荐模式
所有 UI 动作统一转换成 Command
- `toggleLayer(layerKey)`
- `toggleLegend(layerKey, legendKey)`
- `switchBaseMap(baseMapKey)`
- `changeBaseRegion(baseId)`
- `changeTimeRange(range)`
- `focusPoint(pointId)`
## 8.3 领域状态层
保留 Pinia但将当前大 Store 拆分为多个职责明确的 Store / Service
- `mapConfigStore`
- 保存图层树、图例原始配置、页面配置;
- `mapViewStateStore`
- 保存当前选中图层、选中图例、当前底图、当前基地、当前时间范围、当前缩放级别;
- `mapDataStore`
- 保存锚点缓存、请求状态、加载状态、错误状态;
- `mapRuleEngine`
- 保存互斥规则、派生规则、特殊专题规则。
## 8.4 地图渲染层
保留 `MapClass` 作为统一门面,但将 `MapOl` 拆分成多个 Manager
- `ol-map-adapter.ts`
- 只负责地图实例生命周期;
- `ol-base-layer-manager.ts`
- 负责 WMTS / XYZ / GeoJSON / 底图切换;
- `ol-point-layer-manager.ts`
- 负责锚点图层创建、更新、索引、显隐;
- `ol-popup-manager.ts`
- 负责 hover / click / popup
- `ol-region-mask-manager.ts`
- 负责基地裁切、遮罩、点在面内过滤;
- `ol-interaction-manager.ts`
- 负责缩放监听、量算、定位等。
这样做后,`MapOl` 只保留聚合职责,不再直接承载全部细节。
---
## 9. 详细改造方案
## 9.1 统一领域模型
首先建立类型定义,避免继续依赖 `any`
建议新增:
- `types/map-layer.ts`
- `types/map-legend.ts`
- `types/map-point.ts`
- `types/map-rule.ts`
### 图层类型建议
- `BaseMapLayer`
- `OverlayLayer`
- `PointLayer`
- `RegionMaskLayer`
- `DynamicLayer`
### 图层运行态建议(待考虑)
将运行态从后端配置中剥离,不再直接把运行态写回原始配置:
- `config`: 后端返回配置,只读;
- `runtime`:
- `visible`
- `loaded`
- `loading`
- `error`
- `featureCount`
- `lastRequestKey`
- `lastUpdatedAt`
## 9.2 统一图层树、图例、地图显隐状态源
建议明确一个唯一状态源:
- 图层是否勾选:只认 `selectedLayerKeys`
- 图例是否勾选:只认 `selectedLegendKeys`
- 地图渲染层不再存业务真相,只接收派生后的可见性差量。
### 派生关系建议
- `selectedLayerKeys` -> 派生当前显示的图例组;
- `selectedLayerKeys + selectedLegendKeys + currentBaseId` -> 派生 Feature 可见性;
- `selectedBaseMapKey` -> 派生当前底图组合;
- `timeRange + filterConditions` -> 派生图层请求签名。
## 9.3 建立图层索引与图例索引
为提升速度,初始化配置后立即建立索引:
- `layerByKey`
- `legendByNameEn`
- `legendByLayerCode`
- `childrenByParentKey`
- `layerDescendantsByKey`
- `featureIndexByLayerAndLegendKey`
### 优化收益
- 避免每次 `findLayerByKey()` 递归;
- 图例切换时直接定位对应图层和对应 feature 集合;
- 图层树勾选联动时只处理差异节点。
## 9.4 抽离请求解析器
当前 `GisUrlList.ts` 的匹配逻辑散落在 `loadLayerData()` 中,建议拆出:
- `map-layer-request-resolver.ts`
职责:
- 根据 `layer.key` / `layer.title` / `layer.url` 解析真实请求;
- 生成标准请求结构:
- `url`
- `method`
- `filter`
- `sort`
- `cacheKey`
### 进一步建议
- 在应用启动时将 `GisUrlList.ts` 预编译成多索引字典;
- 不要在每次 `loadLayerData()` 内重新构建 `urlListDict`
- 请求签名应真正参与去重和缓存。
## 9.5 重新设计锚点加载策略(待考虑)
建议从“全量预加载”改为“分层加载”:
### 第一阶段
- 首屏只加载默认勾选图层;
- 图例也只基于已加载的默认勾选图层生成;
### 第二阶段(待考虑)
- 对常用图层做后台低优先级预取;
- 非常规图层在首次勾选时再加载;
### 第三阶段(待考虑)
- 对时间敏感或高频筛选图层使用缓存失效策略:
- `cacheKey = layerKey + requestSignature`
- 基于时间范围、基地、专题条件生成。
### 配套措施(待考虑)
- 增加并发控制,例如同时最多 4 到 6 个接口请求;
- 增加可取消请求,避免菜单切换时旧请求回写新页面;
- 记录每个图层单独 loading 和 error 状态。
## 9.6 重构点图层渲染管理
建议将点图层改造成“图层级管理 + 索引级更新”模式。
### 当前问题
- 图例点击靠遍历全量 feature
- 基地裁切靠遍历全量 feature
- hover 时所有点图层都重绘。
### 改造建议
`PointLayerManager` 中维护以下索引:
- `featuresByLayerKey`
- `featuresByLegendKey`
- `featuresByBaseId`
- `featureById`
### 改造后的显隐策略
- 图例点击时,只处理该图例对应 feature 集合;
- 基地切换时,优先按 `baseId` 做粗筛,再对边界交叉区域做点在面内判断;
- 图层勾选时只切换对应 `VectorLayer.visible`
- 非必要不重新 `addFeatures()`
### 样式策略建议
- 保留 style function但减少在 style function 内做复杂业务判断;
-`_legendVisible`、`_regionVisible`、`_layerVisible` 预先计算为单个 `renderVisible` 标记;
- 样式函数只做:
- 是否可见;
- 当前缩放级别;
- 当前 hover 状态;
- 图标与文本样式计算。
## 9.7 底图与叠加层管理重构(待考虑)
建议明确区分三类图层:
- 主底图:矢量 / 地形 / 影像三选一;
- 基础叠加层:自然保护区、行政边界等可附加;
- 专题锚点层:水电工程、生态流量、水质站、视频站等。
### 当前问题
- 底图切换会 remove / add `customBaseLayer`
- 主底图和叠加底图概念混杂;
- `controlBaseLayerTreeShowAndHidden()` 实际控制的不全是底图。
### 改造建议
- 维护 `currentBaseMapKey`
- 主底图预创建,切换时只改 visible
- 叠加层作为独立 overlay 管理;
- 自然保护区等图层明确归类为 overlay不与主底图复用同一逻辑。
## 9.8 图例模块重构
建议把“图例结构”和“图例选中态”彻底分离:
- `legendSchema`: 后端原始图例树;
- `legendVisibleTree`: 根据当前图层选择派生;
- `legendCheckedSet`: 当前选中的图例 key 集。
### 这样做的好处
- 后端原始图例树不再被运行态污染;
- 图例点击只更新 `legendCheckedSet`
- 图层变化只重算当前显示哪些图例,不直接修改原始结构;
- 更容易实现“重置图例”、“记忆用户图例偏好”。
## 9.9 基地裁切与区域过滤重构
建议将基地逻辑完全收口到 `RegionMaskManager`
### 改造重点
- 缓存基地 GeoJSON
- 一次解析边界,多次复用;
- 对点位先按 `baseId` 粗筛,再按 polygon 精筛;
- 避免每次基地切换都重新请求已缓存边界;
- 底图遮罩和锚点区域过滤使用同一份区域上下文。
### 进一步优化
- 若后端能返回基地与锚点的归属关系,可优先直接按 `baseId` 过滤;
- 点在面内判断只作为兜底校验;
- 对非常大的多边形可预处理 bbox先做 bbox 命中,再做射线法。 -
- 有一个 baseID 来传值 直接请求接口数据加载
- const url = this.hydropBaseConfig.geojson_url + `&cql_filter=BASEID='${this.BASEID}'`; console.log('正在请求裁切数据:', url);
// 等待数据加载
const geoJsonData = await this.loadGeoJsonData(url);
## 9.10 Popup 与交互重构
建议将 Popup 逻辑从 `MapOl` 中抽离:
- `PopupManager` 负责:
- hover 命中检测;
- popup 位置;
- popup 内容渲染;
- click 详情回调;
### 优化方向
- 命中检测尽量只针对当前可见的点图层;
- 对 hover 更新增加最小移动阈值;
- 只重绘当前命中 feature 所在图层,而不是所有点图层;
- Popup 内容生成应支持模板注册,不直接在地图层拼接业务 HTML。
## 9.11 业务规则引擎化
建议把分散的特殊规则抽到统一规则层。
### 当前应纳入规则引擎的规则
- 视频监控站与 AI 视频监控站互斥;
- 环保设施与环保设施在建互斥;
- 珍稀鱼类与沿程鱼类互斥;
- 环保设施图例自动补充;
- 水电开发状况菜单的缩放级别动态图层;
- 水质断面多接口拼接;
- 生态流量三级数据结构处理。
### 建议实现方式
定义规则接口:
```ts
interface MapBusinessRule {
id: string;
when(context: RuleContext): boolean;
apply(context: RuleContext): RulePatch;
}
```
这样新增业务时只加规则文件,不再继续往组件和 Store 塞判断。
## 9.12 组件层改造建议
### `GisView.vue`
- 只保留地图容器、初始化、模块挂载;
- 配置请求交给 `mapOrchestrator.initialize(pageKey)`
- 菜单切换、基地切换也只发送命令。
### `LayerController.vue`
- 只处理树控件展示与事件抛出;
- 互斥逻辑不要写在组件中;
- 初始底图加载不要放在组件 watch 里,改到编排层统一处理。
### `MapLegend.vue`
- 只负责展示图例树和发出切换事件;
- 不直接调用 `mapClass`
### `MapFilter.vue`
- 搜索、容量、时间、基地变化全部改为发送标准命令;
- 过滤后的锚点候选列表由 Store 派生,不在组件里自行拼装业务逻辑。
### `BaseLayerSwitcher.vue`
- 只负责切换主底图;
- 不关心图层树中 `customBaseLayer` 的内部结构。
## 9.13 日志、监控与调试能力(待考虑)
建议补充以下调试能力:
- 图层加载耗时;
- 单图层接口耗时;
- 首屏加载耗时;
- 当前可见图层列表;
- 当前可见锚点数量;
- 当前选中图例数量;
- 当前缓存命中率;
- 当前基地裁切耗时。
可以在开发模式下提供一个简易的地图调试面板,便于排查性能与状态同步问题。
---
## 10. 分阶段落地方案
建议采用“四阶段渐进式改造”,避免一次性大改导致风险过高。
## 第 1 阶段:收口现状与止血
目标:不改变功能表现,先把明显风险收口。
建议内容:
- 去掉测试遗留逻辑,如 `splice(0, 2)`
- 修复未定义方法和未定义变量;
- 清理重复日志、无效代码、注释垃圾;
- 补充类型定义;
- 建立 `layerByKey`、`legendByNameEn` 等基础索引;
- 将互斥规则从组件内搬到 Store 或规则层统一处理;
- 实现真实请求去重和请求取消。
## 第 2 阶段:拆 Store 与编排层
目标:建立单向数据流。
建议内容:
- 新增 `mapOrchestrator`
-`map.ts` 为配置、状态、数据三个模块;
- UI 组件只发送命令;
- 图例派生、图层派生、筛选派生全部收口到编排层。
## 第 3 阶段:拆地图渲染层
目标:把 `MapOl` 大文件拆开。
建议内容:
-`BaseLayerManager`
-`PointLayerManager`
-`PopupManager`
-`RegionMaskManager`
- 明确地图引擎层只负责“渲染执行”,不处理业务真相。
## 第 4 阶段:性能强化与能力扩展
目标:让模块适合长期迭代。
建议内容:
- 实现懒加载与后台预取;
- feature 索引化;
- 地图交互性能优化;
- 支持规则插件化;
- 补充测试与性能指标看板;
- 为 3D 共用业务层做适配。
---
## 11. 推荐目录结构
建议重构后目录大致如下:
```text
src/
modules/
map/
application/
map-orchestrator.ts
map-commands.ts
map-rule-engine.ts
domain/
types/
map-layer.ts
map-legend.ts
map-point.ts
map-filter.ts
services/
layer-request-resolver.ts
legend-deriver.ts
layer-deriver.ts
infrastructure/
stores/
map-config.store.ts
map-view-state.store.ts
map-data.store.ts
repositories/
map-config.repository.ts
map-point.repository.ts
render/
map.class.ts
ol/
ol-map-adapter.ts
ol-base-layer-manager.ts
ol-point-layer-manager.ts
ol-popup-manager.ts
ol-region-mask-manager.ts
ol-interaction-manager.ts
components/
gis/
GisView.vue
mapController/
LayerController.vue
mapLegend/
index.vue
mapFilter/
index.vue
BaseLayerSwitcher/
index.vue
```
---
## 12. 重点业务的改造策略
## 12.1 基础图层
策略:
- 单独定义主底图与叠加图层;
- 预创建主底图;
- 切换时仅改 visible
- `customBaseLayer` 不再作为特殊魔法 key 到处透传。
## 12.2 水电工程锚点
策略:
- 作为标准 `PointLayer` 处理;
- 保留当前图标、标签、距离阈值、Popup 规则;
-`PointLayerManager` 中建立 feature 索引。
## 12.3 生态流量三级数据
策略:
- 在领域层定义树结构转换器;
- Store 不直接关心层级深浅;
- 图层树组件只拿已经标准化后的树。
## 12.4 水质监测断面三接口拼接
策略:
- 不在 UI 或 Store 中散写拼接逻辑;
- 建立专题数据聚合器,例如 `water-quality-point-aggregator.ts`
- 对外输出统一的 `PointLayerData`
## 12.5 视频监控站与 AI 视频监控站互斥
策略:
- 写成标准业务规则;
- 由规则引擎统一裁剪勾选结果;
- UI 不再内嵌业务互斥判断。
## 12.6 自然保护区与区域底图
策略:
- 统一归类为 `OverlayLayer`
- 与主底图切换分离;
- 与基地裁切共享区域上下文。
---
## 13. 测试与验收建议
## 13.1 功能验收清单
- 页面首次进入,默认勾选图层正确显示;
- 图层树勾选与取消勾选,地图表现与图例表现一致;
- 图例点击后,仅影响对应锚点;
- 基地切换后,区域外锚点正确隐藏;
- 底图切换不影响锚点和图例状态;
- 时间筛选后相关图层正确重载;
- 视频监控站与 AI 视频监控站互斥稳定;
- 菜单切换后图层、图例、筛选项正确重置;
- 悬停 Popup 与点击详情正常。
## 13.2 性能验收指标
建议至少跟踪:
- 首屏地图可交互时间;
- 默认勾选图层加载总耗时;
- 单图层首次加载耗时;
- 图层勾选后锚点出现时间;
- 图例点击后的显隐响应时间;
- 基地切换后的裁切完成时间;
- 缓存命中率;
- 页面切换时旧请求是否被取消。
## 13.3 自动化测试建议
建议补充三类测试:
- 单元测试:
- 图例派生;
- 图层互斥规则;
- 请求解析器;
- 缓存 key 生成;
- 集成测试:
- 图层勾选 -> 图例变化 -> 地图显隐;
- 基地切换 -> 区域裁切 -> 搜索列表更新;
- 端到端测试:
- 地图初始化;
- 图层切换;
- 图例切换;
- 筛选联动;
- Popup 和详情弹窗。
---
## 14. 优先级最高的首批改造项
如果本轮改造只做最有价值的一批,建议优先按以下顺序处理:
1. 修复明显问题:
- 去掉 `splice(0, 2)`
- 修复 `fetchPointData()` 未定义;
- 修复 `wq_lineTime` / `wt_lineTime` 未定义;
- 清理无效代码与重复日志。
2. 建立统一状态源:
- `selectedLayerKeys`
- `selectedLegendKeys`
- `currentBaseId`
- `currentBaseMapKey`
3. 抽离请求解析器和规则引擎:
- URL 映射规则统一;
- 互斥规则统一;
- 特殊图例补充规则统一。
4.`map.ts`
- 配置状态;
- 运行状态;
- 数据缓存;
- 行为编排。
5.`map.ol.ts`
- 底图管理;
- 锚点管理;
- Popup 管理;
- 区域裁切管理。
---
## 15. 改造后的预期收益
完成改造后,地图模块应达到以下效果:
- 新增图层和专题能力时,不需要修改多个组件和多个文件;
- 图层树、图例、地图显隐的状态不再互相打架;
- 首屏加载更快,图层切换更轻;
- 地图代码从“堆逻辑”变成“可扩展的稳定结构”;
- 后续增加 3D 共用逻辑、专题聚合图层、更多筛选条件时,改造成本明显下降。
---
## 16. 结论
当前地图模块功能已经较完整,但代码结构处于“功能可用、结构偏重、规则分散、性能隐患较多”的阶段。继续在现有结构上叠加新需求,后续维护成本会快速上升。
建议本次改造不要只做局部优化,而应围绕以下核心原则整体推进:
- 单向数据流;
- 业务规则收口;
- 渲染层职责下沉;
- 类型与索引先行;
- 先止血,再拆层,再提速。
按照本文档的分阶段方案推进,可以在不一次性推翻现有功能的前提下,逐步完成地图模块的结构升级。