33 KiB
地图模块现状与改造方案
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:
- 调用
MapClass.init()初始化 OpenLayers; - 调用
initPopupOverlay()挂载悬停 Popup; - 调用
fetchMapConfigs()并行请求:- 地图图层配置;
- 全量图例配置;
- 当前页面图例配置;
mapStore.setLegendData()初始化图例原始数据与映射;mapStore.setLayerData()初始化图层树和选中图层 key;mapStore.loadAllLayerData()递归加载所有有 URL 的图层数据;loadLayerData()将锚点数据写入缓存并直接调用mapClass.addInitDataLayer()预创建锚点图层;- 全部锚点加载完成后,再调用
updateLayerData()统一控制初始显示状态; - 图例根据当前选中的图层动态生成并展示。
现状特点
- 当前是“初始化阶段一次性预加载大多数锚点图层,再通过显隐控制”的策略;
- 图例显示被设计为“锚点完成后再显示”;
- 地图实例、Store、UI 组件三方互相直接调用。
4.2 基础图层加载与切换
基础底图主要由 LayerController.vue 和 map.ol.ts 协同完成:
LayerController.vue中监听图层树配置;- 对类型为
GISMap的节点解析paramJson,通过getMapConfig()补齐 GeoServer 地址; - 调用
mapClass.addBaseDataLayer()初始化基础图层; - 同时调用
controlBaseLayerTreeShowAndHidden()控制显隐; BaseLayerSwitcher再通过baseLayerSwitcher()在矢量 / 地形 / 影像底图之间切换。
当前基础图层类型主要包括:
- WMTS:GeoServer 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 接口;
- 将返回记录补充:
iconCodecode_idtm
- 然后缓存到:
pointDataCache[layerKey]layer.data- 合并后的
pointData
- 若地图上还不存在该图层,则直接调用
mapClass.addInitDataLayer()创建VectorLayer。
当前锚点渲染方式
- 每个
pointMap图层对应一个VectorLayer; - 每个点被转为
Feature<Point>; - Feature 上维护多个运行态字段:
_iconUrl_labelText_legendVisible_regionVisible_layerKey
- 样式函数
createPointStyle()在渲染时综合判断:- 图例是否显示;
- 是否在基地区域内;
- 当前缩放级别;
- 点之间距离阈值;
- 是否悬停。
4.5 图例动态生成与点击联动
图例数据来源于两部分:
legendDataOriginal:后端返回的全量图例;legendDataSelected:根据当前选中的图层动态筛选后的图例结构。
当前机制:
- 初始化时拉取全部图例;
- 通过
layerCode与图层树选中结果建立关联; mapStore.updateLayerData()重新计算当前选中的图例树;MapLegend监听legendDataSelected进行展示;- 点击图例项后:
- 更新 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_pointstinfo_video_pointstinfo_gjllz_pointwt_pointwq_ownWq_pointwq_countryWq_pointfp_pointeq_pointfb_pointvp_pointva_pointsg_point
5. 当前数据流与调用关系
当前主数据流可概括为:
GisView.vue拉取配置;mapStore保存图层树、图例、缓存;LayerController.vue/MapLegend.vue/MapFilter.vue直接操作mapStore;mapStore又直接调用mapClass;mapClass继续转发到map.ol.ts;map.ol.ts内部维护图层注册表和 Feature 运行态;- 地图状态变化后,组件又通过监听 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.vueLayerController.vueMapLegend.vueMapFilter.vueBaseLayerSwitcher.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.tstypes/map-legend.tstypes/map-point.tstypes/map-rule.ts
图层类型建议
BaseMapLayerOverlayLayerPointLayerRegionMaskLayerDynamicLayer
图层运行态建议(待考虑)
将运行态从后端配置中剥离,不再直接把运行态写回原始配置:
config: 后端返回配置,只读;runtime:visibleloadedloadingerrorfeatureCountlastRequestKeylastUpdatedAt
9.2 统一图层树、图例、地图显隐状态源
建议明确一个唯一状态源:
- 图层是否勾选:只认
selectedLayerKeys; - 图例是否勾选:只认
selectedLegendKeys; - 地图渲染层不再存业务真相,只接收派生后的可见性差量。
派生关系建议
selectedLayerKeys-> 派生当前显示的图例组;selectedLayerKeys + selectedLegendKeys + currentBaseId-> 派生 Feature 可见性;selectedBaseMapKey-> 派生当前底图组合;timeRange + filterConditions-> 派生图层请求签名。
9.3 建立图层索引与图例索引
为提升速度,初始化配置后立即建立索引:
layerByKeylegendByNameEnlegendByLayerCodechildrenByParentKeylayerDescendantsByKeyfeatureIndexByLayerAndLegendKey
优化收益
- 避免每次
findLayerByKey()递归; - 图例切换时直接定位对应图层和对应 feature 集合;
- 图层树勾选联动时只处理差异节点。
9.4 抽离请求解析器
当前 GisUrlList.ts 的匹配逻辑散落在 loadLayerData() 中,建议拆出:
map-layer-request-resolver.ts
职责:
- 根据
layer.key/layer.title/layer.url解析真实请求; - 生成标准请求结构:
urlmethodfiltersortcacheKey
进一步建议
- 在应用启动时将
GisUrlList.ts预编译成多索引字典; - 不要在每次
loadLayerData()内重新构建urlListDict; - 请求签名应真正参与去重和缓存。
9.5 重新设计锚点加载策略(待考虑)
建议从“全量预加载”改为“分层加载”:
第一阶段
- 首屏只加载默认勾选图层;
- 图例也只基于已加载的默认勾选图层生成;
第二阶段(待考虑)
- 对常用图层做后台低优先级预取;
- 非常规图层在首次勾选时再加载;
第三阶段(待考虑)
- 对时间敏感或高频筛选图层使用缓存失效策略:
cacheKey = layerKey + requestSignature- 基于时间范围、基地、专题条件生成。
配套措施(待考虑)
- 增加并发控制,例如同时最多 4 到 6 个接口请求;
- 增加可取消请求,避免菜单切换时旧请求回写新页面;
- 记录每个图层单独 loading 和 error 状态。
9.6 重构点图层渲染管理
建议将点图层改造成“图层级管理 + 索引级更新”模式。
当前问题
- 图例点击靠遍历全量 feature;
- 基地裁切靠遍历全量 feature;
- hover 时所有点图层都重绘。
改造建议
在 PointLayerManager 中维护以下索引:
featuresByLayerKeyfeaturesByLegendKeyfeaturesByBaseIdfeatureById
改造后的显隐策略
- 图例点击时,只处理该图例对应 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 视频监控站互斥;
- 环保设施与环保设施在建互斥;
- 珍稀鱼类与沿程鱼类互斥;
- 环保设施图例自动补充;
- 水电开发状况菜单的缩放级别动态图层;
- 水质断面多接口拼接;
- 生态流量三级数据结构处理。
建议实现方式
定义规则接口:
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. 推荐目录结构
建议重构后目录大致如下:
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. 优先级最高的首批改造项
如果本轮改造只做最有价值的一批,建议优先按以下顺序处理:
-
修复明显问题:
- 去掉
splice(0, 2); - 修复
fetchPointData()未定义; - 修复
wq_lineTime/wt_lineTime未定义; - 清理无效代码与重复日志。
- 去掉
-
建立统一状态源:
selectedLayerKeysselectedLegendKeyscurrentBaseIdcurrentBaseMapKey
-
抽离请求解析器和规则引擎:
- URL 映射规则统一;
- 互斥规则统一;
- 特殊图例补充规则统一。
-
拆
map.ts:- 配置状态;
- 运行状态;
- 数据缓存;
- 行为编排。
-
拆
map.ol.ts:- 底图管理;
- 锚点管理;
- Popup 管理;
- 区域裁切管理。
15. 改造后的预期收益
完成改造后,地图模块应达到以下效果:
- 新增图层和专题能力时,不需要修改多个组件和多个文件;
- 图层树、图例、地图显隐的状态不再互相打架;
- 首屏加载更快,图层切换更轻;
- 地图代码从“堆逻辑”变成“可扩展的稳定结构”;
- 后续增加 3D 共用逻辑、专题聚合图层、更多筛选条件时,改造成本明显下降。
16. 结论
当前地图模块功能已经较完整,但代码结构处于“功能可用、结构偏重、规则分散、性能隐患较多”的阶段。继续在现有结构上叠加新需求,后续维护成本会快速上升。
建议本次改造不要只做局部优化,而应围绕以下核心原则整体推进:
- 单向数据流;
- 业务规则收口;
- 渲染层职责下沉;
- 类型与索引先行;
- 先止血,再拆层,再提速。
按照本文档的分阶段方案推进,可以在不一次性推翻现有功能的前提下,逐步完成地图模块的结构升级。