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

33 KiB
Raw Permalink Blame History

地图模块现状与改造方案

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

    • 地图容器入口;
    • 地图初始化;
    • 拉取图层配置与图例配置;
    • 监听菜单切换、基地切换、缩放联动;
    • 挂载 MapLegendMapFilterMapControllerBaseLayerSwitcher
  • 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.vuemap.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_pointeq_pointfb_pointvp_pointva_pointsg_pointdw_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 内容由 popupHtmlgeneratePopupHtml(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.vueMapLegend.vueMapFilter.vue 都直接调 Store 和地图实例;
  • map.ts 同时负责:
    • 请求;
    • 缓存;
    • 图例结构整理;
    • 图层勾选;
    • 地图显隐;
    • 业务互斥;
  • map.ol.ts 同时负责:
    • OpenLayers 适配;
    • 业务过滤;
    • 区域裁切;
    • 弹窗;
    • 图例控制;
    • 锚点显示策略。

2. 缺乏清晰的单向数据流

  • 图层树 checked、图例 checked、Feature _legendVisible、Layer visible 都是状态源;
  • 任何一个点修改后都可能触发联动,难以推断最终状态;
  • 某些流程依赖顺序正确才能表现正常。

3. 业务规则分散

  • 互斥规则分散在 LayerController.vuemap.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.vuefetchMapConfigs() 里对图层做了 splice(0, 2) 临时截断;
  • 日志仍显示“临时限制”,说明线上逻辑可能仍处于测试状态。

2. 存在未定义引用

  • GisView.vue 切换地图后调用 fetchPointData(),但项目中未找到定义;
  • map.ts 中使用了 wq_lineTimewt_lineTime,但项目中未找到定义。

3. 请求去重逻辑未真正落地

  • map.ts 中生成了 requestIdentifier,但未实际用于请求去重;
  • requestParamsCopy 也未参与有效逻辑。

4. 图层状态与渲染状态容易失同步

  • layer.checkedcheckedLayerKeys、图例 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 视频监控站互斥;
  • 环保设施与环保设施在建互斥;
  • 珍稀鱼类与沿程鱼类互斥;
  • 环保设施图例自动补充;
  • 水电开发状况菜单的缩放级别动态图层;
  • 水质断面多接口拼接;
  • 生态流量三级数据结构处理。

建议实现方式

定义规则接口:

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)
  • 修复未定义方法和未定义变量;
  • 清理重复日志、无效代码、注释垃圾;
  • 补充类型定义;
  • 建立 layerByKeylegendByNameEn 等基础索引;
  • 将互斥规则从组件内搬到 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. 优先级最高的首批改造项

如果本轮改造只做最有价值的一批,建议优先按以下顺序处理:

  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. 结论

当前地图模块功能已经较完整,但代码结构处于“功能可用、结构偏重、规则分散、性能隐患较多”的阶段。继续在现有结构上叠加新需求,后续维护成本会快速上升。

建议本次改造不要只做局部优化,而应围绕以下核心原则整体推进:

  • 单向数据流;
  • 业务规则收口;
  • 渲染层职责下沉;
  • 类型与索引先行;
  • 先止血,再拆层,再提速。

按照本文档的分阶段方案推进,可以在不一次性推翻现有功能的前提下,逐步完成地图模块的结构升级。