diff --git a/frontend-sjgl/docs/地图模块-3D Cesium实施计划.md b/frontend-sjgl/docs/地图模块-3D Cesium实施计划.md deleted file mode 100644 index 9b22b1b0..00000000 --- a/frontend-sjgl/docs/地图模块-3D Cesium实施计划.md +++ /dev/null @@ -1,479 +0,0 @@ -# 地图模块 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. 做切换稳定性收口 - -## 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 看起来不对”的核心问题解决掉。 - -## 9. 必须补齐的隐藏逻辑清单 - -下面这些逻辑不是表面接口能直接看出来的,但如果 `3D` 不补,最终效果就会和 `2D` 明显不一致。 - -### 9.1 图层与菜单切换 ✅️ - -- ✅ 菜单切换时,只隐藏旧页点图层,不隐藏底图。(编排器 `handlePageChange` 控制,Cesium 被动接收数据) -- ✅ 图层树勾选前要先经过互斥规则归一化:(store 层 `updateLayerData` 处理,Cesium 不感知) - - `rare_fish_point` 与 `fish_along_point` 互斥 - - `stinfo/stinfo_video_point` 与 `stinfo_ai_video_point` 互斥 - - `facilities` 与 `facilities_built` 两个分支互斥 -- ✅ 点图层取消勾选时只隐藏,不删除缓存;重新勾选时优先从缓存恢复。`mdLayerTreeShowOrHidden` 设置 `_layerVisible` 标志,不删除 `pointLayerRegistry` 条目。 -- ✅ **图层树勾选时按 `type` 字段走不同分支**(见 `updateLayerData`,store 层处理): - - `type === 'pointMap'` → 走 `handlePointMapLayer` → 调用 `mdLayerTreeShowOrHidden`(已实现,L1624) - - `type === 'GISMap'` → 走 `controlBaseLayerTreeShowAndHidden`(已实现,L1345) - - 3D Cesium 已同时实现这两个方法。 - -### 9.2 图例与点内显隐 ✅️ - -- ✅ 图例控制的是图层内部锚点是否可见,不是图层本身是否可见。`setLegendPointVisible` 使用 `_legendVisible` 标志,与 `_layerVisible` 独立。 -- ✅ 图层恢复显示时,图例要先整层全关,再按当前 checked 状态逐项恢复。支持 `anchoPointState=''` 全关 → 逐项 `(state, true)` 恢复两步模式(L1764-1787)。 -- ✅ 环保设施图例组当前是只展示不交互。(编排器层面控制,Cesium 不感知) - -### 9.3 缩放驱动显示 ✅️ - -- ✅ `eng_point` 的中型水电站只在 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上显示。(store `filterPointLayerDataForDisplay` 在数据进入 Cesium 前过滤) -- ✅ 水电开发页缩放跨过 `12` 级时,会自动补开一批动态图层;缩回去时再自动收口。(编排器层面处理) -- ✅ `2D` 存在"缩放越大显示越多"的行为。碰撞检测中密度门控逐级放开。 -- ✅ `2D` 还存在 hover、批量 popup、密度控制等缩放阈值: - - hover 低于 ~zoom 4.5 不触发(`HOVER_POPUP_MAX_HEIGHT = 9_000_000`) - - 高缩放进入批量 popup 模式(`BATCH_POPUP_MODE_ZOOM = 14.9`) - - 点位随着缩放提升逐步放开密度限制(密度门控 + `DENSITY_ISOLATION_THRESHOLD`) -- ✅ **图标动态缩放**:`CallbackProperty` 实现 `billboard.scale`,公式 `clamp(0.7 + (zoom - 4.5) * 0.035, 0.4, 1.2)`(L1522)。 -- ✅ **字体动态大小**:`CallbackProperty` 实现 `label.font`,钳位 `[10, 20]`(L1535)。 -- ✅ **文字在图标上方**:`CallbackProperty` 实现 `label.pixelOffset`,负 Y 偏移(L1549)。 -- ✅ **Cesium 中必须使用 `CallbackProperty` 实现动态缩放**:已使用 3 个 `CallbackProperty`(scale/font/offset),`isConstant=false` 每帧重算。 - -### 9.4 锚点碰撞与控量 ✅️ - -- ✅ `2D` 中锚点是否可见,不只受图层开关控制,还受以下门闩共同影响: - - 图例可见性 `_legendVisible` - - 区域裁切可见性 `_regionVisible` - - 密度阈值(碰撞循环中 inline 计算,逐级放开) - - 图标碰撞结果 `_iconCollisionVisible` - - 标签碰撞结果 `_labelCollisionVisible` -- ✅ 低缩放下,为避免堆叠,部分锚点或标签会被主动隐藏。(`refreshPointLabelVisibility` 碰撞检测,L2117-2455) -- ✅ 高缩放下,会逐步放开图标/标签限制,显示更多内容。(密度门控 + 碰撞容差随 zoom 调整) -- ✅ 因此 `3D` 不能简单把所有 entity 永久全开。所有 entity 初始 `_iconCollisionVisible = _labelCollisionVisible = true`,碰撞检测运行后动态调整。 - -### 9.5 Popup 与点击 ✅️ - -- ✅ hover popup 不是全时开启的,有缩放门槛。相机高度 > `HOVER_POPUP_MAX_HEIGHT` 时不触发 hover。 -- ✅ 点击和 hover 的命中对象是图标本体,不是任意文字区域。`ScreenSpaceEventHandler` 基于 `scene.pick` 命中 entity。 -- ✅ `ylfb` 与普通锚点的点击详情弹框走不同业务分支。`sttpMap === 'ylfb'` 打开 `ylfbModalVisible`,其他走 `modalVisible`(L366-375)。 - -### 9.6 基地与区域裁切 ✅️ - -- ✅ 基地切换不仅影响搜索候选,还会影响地图上的点位可见范围。`jdPanelControlShowAndHidden` 实现(L1684-1748)。 -- ✅ 区域裁切会直接把区域外点位隐藏,不是只改底图遮罩。`filterPointsByRegion` 设置 `_regionVisible = false`(L1824-1848),遮罩多边形 + 裁剪双重处理。 - -### 9.7 筛选器特殊逻辑 ✅️ - -- ✅ 装机容量筛选本质是批量切 `eng_point` 图例状态,不是简单切图层。(store 层处理,Cesium 通过 `setLegendPointVisible` 响应) -- ✅ 鱼类分布模式会接管 `ylfb_point` 的数据、缓存、图例与显隐。(store 层处理) -- ✅ 时间筛选当前只重载指定时间相关图层,不是全量重载。(store 层处理) - -### 9.8 数据进入 addInitDataLayer 前的预处理 ✅️ - -- ✅ 所有点位数据在进入 `addInitDataLayer` 前会经过 `normalizePointLayerItems()` 过滤:(store 层 `map.ts` 执行,Cesium 接收时已过滤) - - 没有 `iconCode` 且 `code !== 'colorLayer'` 的项会被抛弃 - - `baseId == 'all'` 且 `anchoPointState` 结尾为 `_nbuilt` 的项会被抛弃 -- ✅ `_id` 默认值生成规则:store 层生成 `${sttpMap || iconType || 'point'}_${stcd}`。 -- ✅ **stcd+经纬度重复锚点处理**:Cesium entity ID 使用 `${layerKey}:${item._id}`,与 2D 对齐,同 stcd 不同 sttpMap 的点不会互相覆盖。 - -### 9.9 图标渐进加载机制 - -- ❌ 2D 中图标不是一次性全加载完。有 `loading → loaded → error` 三种状态跟踪。 -- ❌ 只有 `loaded` 状态的图标才参与样式渲染,loader 失败的不渲染。 -- ❌ 如果在图标加载过程中就触发了样式刷新,需要通过 `requestAnimationFrame` 回刷。 -- ❌ 3D Cesium 中当前所有 entity 同步创建,billboard `image` URL 直接赋值,无渐进加载。Cesium 内部纹理加载无显式状态跟踪。 - -### 9.10 密度分档与近邻点元数据 ✅️ - -- ✅ 每个锚点的 `distance` 字段表示密度分档值(不是物理距离),值越大表示越稀疏。(已使用 `getEntityDensityValue` 从 `_rawData.distance` 读取,L2460) -- ✅ 不同 density 值对应不同的 `minZoom` 显示门槛。`getEntityDensityMinZoom` 映射 `nearby-point-rules.ts` 的 `densityDisplayRules`(L2491)。 -- ✅ 全量数据加载后,store 会调用 `attachNearbyPointRuntimeMeta()` 给所有点打上 `_nearbyRule`、`_nearbyGroupId`、`_nearbyPriority` 等元数据。(store 层处理,Cesium 碰撞检测中读取) -- ✅ **孤立点例外规则**:如果一个点在当前视口内 ~56px 范围内没有同 layerKey 的其他可见点,它可以绕过密度门槛直接显示。`DENSITY_ISOLATION_THRESHOLD = 56` + `shouldBypassDensityGateForIsolatedEntity`(L2506-2556)。 diff --git a/frontend-sjgl/docs/地图模块-OpenLayers抽吸与碰撞分析.md b/frontend-sjgl/docs/地图模块-OpenLayers抽吸与碰撞分析.md deleted file mode 100644 index 3c0e97d0..00000000 --- a/frontend-sjgl/docs/地图模块-OpenLayers抽吸与碰撞分析.md +++ /dev/null @@ -1,343 +0,0 @@ -# 地图模块-OpenLayers抽吸与碰撞分析 - -## 1. 背景 - -当前地图 2D 主引擎已经切换为 OpenLayers,项目依赖版本为 `ol@^10.8.0`。 -本次关注的问题有两个: - -- 点位“抽吸”逻辑不符合业务预期; -- 文字碰撞时把整个点位一起隐藏,而不是仅隐藏文字。 - -典型场景是两个相邻电站,例如“积石峡”和“公伯峡”: - -- 可用代号 `1`、`2` 表示两个相邻点; -- 默认缩放下只展示 `1`; -- 放大到某个层级后,`1` 隐藏,`2` 展示; -- 再继续放大后,`1` 和 `2` 都展示,但 `2` 需要“抽吸/拉开”显示,避免与 `1` 重叠; -- 如果只是文字发生碰撞,希望点图标保留,仅文字隐藏或延后显示。 - -本文件先整理当前代码现状和问题成因,不直接修改实现。 -如果需要查看**当前已经落地的抽吸规则、默认参数和调参入口**,请同时参考: - -- `docs/地图模块-抽吸规则与参数说明.md` - -## 2. 当前代码结构 - -当前前端 GIS 运行链路如下: - -1. `src/components/gis/GisView.vue` -2. `src/modules/map/application/map-orchestrator.ts` -3. `src/components/gis/map.class.ts` -4. `src/components/gis/map.ol.ts` -5. `src/components/gis/ol/point-layer-manager.ts` -6. `src/components/gis/ol/popup-manager.ts` - -各层职责简述: - -- `GisView.vue` - - 地图入口组件; - - 挂载地图容器、图例、筛选器、控制器; - - 通过 `mapOrchestrator.mountView()` 初始化地图; - - 菜单切换时通过 `handlePageChange()` 触发页面地图重载。 - -- `map.class.ts` - - 地图门面类; - - 当前 2D 实现实际绑定的是 `MapOl`; - - 3D 实现才会切到 `MapCesium`。 - -- `map.ol.ts` - - OpenLayers 主实现; - - 负责地图初始化、底图加载、点位样式、Popup、区域裁切、量算等; - - 当前“点位渲染规则”和“碰撞相关逻辑”主要集中在这个文件。 - -- `point-layer-manager.ts` - - 负责点图层创建、Feature 灌入、按图层控制显隐; - - 点位图层统一使用 `VectorLayer + VectorSource`。 - -- `popup-manager.ts` - - 管理 hover popup 和高缩放下的批量 popup; - - 内部额外做了一套基于边界框的碰撞判断。 - -## 3. 当前点位渲染链路 - -### 3.1 点图层创建 - -`point-layer-manager.ts` 中的 `ensureLayer()` 会统一创建点图层: - -- 图层类型为 `VectorLayer`; -- 图层上开启了 `declutter: true`; -- 样式函数统一走 `map.ol.ts` 中的 `createPointStyle()`。 - -也就是说,当前所有点位的“是否显示”和“图标/文字如何避让”,都收敛到 OpenLayers 的 declutter 机制和样式函数里。 - -### 3.2 点样式生成 - -`map.ol.ts` 的 `createPointStyle()` 当前做了这些事: - -- 读取 `_iconUrl` 和 `_labelText`; -- 如果图例不可见、区域不可见,则直接返回 `null`; -- 如果 `distance` 不满足当前缩放级别要求,也直接返回 `null`; -- 图标使用 `Icon`; -- 文字使用 `Text`; -- 图标和文字都设置了 `declutterMode: 'declutter'`; -- 最终把图标和文字一起放进同一个 `Style` 返回。 - -这意味着: - -- 点图标和文字不是两个独立的渲染对象; -- 它们在当前实现中属于同一个样式结果; -- 只要这组样式参与碰撞判定,图标和文字的命运就是绑定的。 - -### 3.3 当前已有的“抽稀”逻辑 - -当前项目中确实有一层“缩放越小,显示越少”的处理,但它不是精细的“抽吸”。 - -`map.ol.ts` 的 `shouldRenderFeatureByDistance()` 逻辑是: - -- 小缩放下要求 `distance` 更大才允许显示; -- 缩放越大,允许显示的点逐渐增多; -- 本质上是基于后端数据字段 `distance` 做全局抽稀。 - -这个逻辑的特点是: - -- 是全局阈值,不是成对点位或同组点位的专属规则; -- 只能决定“这个点显示还是不显示”; -- 不能表达“1 和 2 是近邻关系,并且在不同缩放阶段按业务优先级切换显示”。 - -### 3.4 当前批量 Popup 的碰撞检测 - -`popup-manager.ts` 在高缩放批量 popup 模式下,又额外做了一层碰撞判断: - -- 先估算图标和文字合并后的锚点边界框; -- 如果边界框碰撞,则当前要素直接不参与 popup 渲染; -- 随后再判断 popup 矩形之间是否碰撞。 - -这说明当前“图标 + 文字”被视为一个整体,不仅体现在 OpenLayers 的 declutter 里,也体现在 popup 的辅助判断里。 - -## 4. 当前仓库里“抽吸”相关代码现状 - -仓库中确实存在历史上的“抽吸”实现,但不在当前 OpenLayers 主链路里。 - -相关文件: - -- `src/utils/leaflet/leaflet.inflatable-markers-group.js` -- `src/components/gis/map.leaflet.ts` - -现状判断: - -- `leaflet.inflatable-markers-group.js` 是旧 Leaflet 时代的抽吸插件; -- 它内部有 `inflateAsManyAsPossible()`、`_zoomend()` 等典型抽吸逻辑; -- 但当前 `map.class.ts` 实际实例化的是 `new MapOl()`; -- `map.leaflet.ts` 已经不在主运行路径上。 - -结论是: - -- 当前页面上看到的点位行为,不是 Leaflet 抽吸插件在工作; -- 现在的显示结果主要来自 OpenLayers 的 `declutter`、当前样式函数和 `distance` 抽稀逻辑; -- 因此“已经有抽吸但是不太对”,更准确地说是“历史上有抽吸代码,但现在主链路没有真正使用那套机制”。 - -## 5. 问题分析 - -### 5.1 为什么当前实现做不到你要的分阶段抽吸 - -你描述的目标并不是简单“避让”,而是一个明确的分阶段显示策略: - -1. 默认只显示 `1`; -2. 缩放到阶段 A 时,隐藏 `1`,改为显示 `2`; -3. 缩放到阶段 B 时,`1` 和 `2` 都显示; -4. 阶段 B 下,`2` 还要相对 `1` 做抽吸/偏移,避免完全重叠。 - -而当前代码缺少以下能力: - -- 没有“近邻点关系”配置 - - 代码里没有看到类似 `groupId`、`neighborIds`、`priority`、`displayLevel` 这样的结构; - - 系统不知道“积石峡”和“公伯峡”是一组特殊近邻点。 - -- 没有“分阶段显示规则”配置 - - 当前只有全局的 `distance` 阈值判断; - - 没有按缩放阶段表达“谁替代谁显示、谁先隐藏、谁后展示”。 - -- 没有“抽吸偏移”计算 - - 当前点要素坐标就是原始经纬度转换结果; - - 没有根据缩放级别和邻近关系生成偏移坐标; - - 也没有“展开后沿圆周/沿固定方向偏移”的逻辑。 - -- 当前 declutter 是被动避让,不是主动编排 - - declutter 只会尽量避免重叠; - - 它不会理解业务上的“1 是主点、2 是附点”; - - 也不会自动实现“先 1,后 2,再两个都展示且 2 偏移”的规则。 - -所以当前看到的结果不稳定,本质原因不是某一个参数不对,而是“现有机制和目标能力不是一类问题”。 - -### 5.2 为什么文字碰撞会把整个点隐藏 - -这是当前实现里最关键的原因: - -- 点图标和文字是在同一个 `Style` 里返回的; -- 图层开启了 `declutter: true`; -- 图标和文字又都设置了 `declutterMode: 'declutter'`。 - -在这种组织方式下,当前要素的碰撞判定是按“一个整体渲染单元”处理的,而不是“图标一套规则,文字一套规则”。 - -因此会出现下面的现象: - -- 图标本身其实不冲突; -- 但文字边界框发生碰撞; -- 最终被隐藏的是整条样式结果; -- 用户视觉上就会觉得“明明只是字挤了,为什么点也没了”。 - -这和你现在观察到的问题是一致的。 - -### 5.3 为什么当前逻辑还会进一步放大这个问题 - -除了 OpenLayers 自身的 declutter 组织方式,代码里还有两层因素会继续放大问题: - -- `shouldRenderFeatureByDistance()` 会先根据 `distance` 直接过滤点位; -- `popup-manager.ts` 在批量 popup 模式下,也把“图标 + 文字”当作合并边界框做碰撞模拟。 - -也就是说,当前系统对近邻点位的处理是: - -- 先用全局 `distance` 规则筛掉一部分; -- 再用 OpenLayers declutter 继续过滤; -- 高缩放时 popup 层再做一轮碰撞跳过。 - -这三层叠加后,显示行为更偏向“谁先撞谁消失”,而不是“按业务规则分阶段展开”。 - -## 6. 结合你的案例做具体映射 - -如果以近邻点 `1`、`2` 为例,当前代码里实际上缺的是下面这些业务语义: - -- `1` 和 `2` 属于同一近邻组; -- 组内有主次优先级; -- 在不同 zoom 区间内有不同展示策略; -- 在最大放大阶段需要给 `2` 一个偏移位; -- 偏移后图标和文字还要分别控制碰撞策略。 - -当前实现只能表达: - -- 某个点在当前 zoom 下是否允许渲染; -- 某个点图例是否可见; -- 某个点是否被区域裁切隐藏; -- 某个点的图标和文字整体是否参与 declutter。 - -它并不能表达: - -- “1 替代 2 展示”; -- “2 替代 1 展示”; -- “1 和 2 一起展示,但 2 抽吸展开”; -- “只隐藏文字,不隐藏图标”。 - -所以你现在遇到的问题是结构性问题,不是简单调一个 `declutter` 参数就能彻底解决。 - -## 7. 后续修改时建议优先动的层 - -如果下一步要真正改这块,建议优先从下面几层入手: - -### 7.1 先补“近邻点显示规则” - -建议不要直接把业务规则写死在样式函数里,而是先增加一层近邻点编排规则,例如: - -- `groupId` -- `priority` -- `showZoomMin` -- `showZoomMax` -- `replaceAtZoom` -- `expandAtZoom` -- `expandOffset` - -这样才能表达: - -- 默认只显示主点; -- 中间层级切换成副点; -- 更大层级两个都显示; -- 副点在展示时做偏移。 - -### 7.2 抽吸不要依赖 declutter 自动完成 - -抽吸本质上是“主动布局”问题,不是“被动避让”问题。 - -建议后续把近邻点抽吸拆成单独逻辑: - -- 先根据原始点数据识别近邻组; -- 再根据当前 zoom 产出“本次真正参与渲染的点集合”; -- 对需要展开的点,生成偏移后的渲染坐标; -- 最后再交给图层渲染。 - -这样会比直接指望 OpenLayers 的 declutter 更可控。 - -### 7.3 图标层和文字层要分开控制 - -要实现“文字碰撞时只隐藏文字,不隐藏点图标”,后续最重要的一点是把图标和文字的渲染职责拆开。 - -至少要做到下面二选一: - -- 图标层和文字层拆成两个独立图层; -- 或者把图标与文字拆成可独立决策的渲染单元。 - -目标是: - -- 图标优先保留; -- 文字单独避让; -- 文本碰撞时只压文字,不压图标。 - -如果仍然维持“同一个样式对象里同时放 image 和 text”的组织方式,这个问题大概率还会持续存在。 - -### 7.4 Popup 碰撞策略也要和新规则同步 - -后续如果点位抽吸和文字单独避让改了,`popup-manager.ts` 里的边界框逻辑也要一起调整。 - -否则会出现: - -- 图标已经正常显示; -- 文字也按新规则显示了; -- 但 popup 层仍然沿用旧的合并边界框; -- 最终又在 popup 阶段把结果打回去。 - -## 8. 建议的改造顺序 - -为了降低风险,建议后续改造按以下顺序推进: - -1. 先梳理近邻点数据结构 - - 确定近邻点如何分组; - - 确定主次优先级和缩放阈值从哪里来。 - -2. 再实现“显示决策层” - - 在真正生成 Style 之前,先决定哪些点应该显示; - - 哪些点应该隐藏; - - 哪些点需要偏移展开。 - -3. 然后拆分图标和文字碰撞 - - 先保证点图标稳定显示; - - 再单独做文字避让。 - -4. 最后调整 popup 逻辑 - - 保证 popup 的碰撞判定和最终可见点保持一致。 - -## 9. 涉及文件清单 - -本次分析重点涉及以下文件: - -- `src/components/gis/GisView.vue` -- `src/components/gis/map.class.ts` -- `src/components/gis/map.ol.ts` -- `src/components/gis/ol/point-layer-manager.ts` -- `src/components/gis/ol/popup-manager.ts` -- `src/utils/leaflet/leaflet.inflatable-markers-group.js` -- `src/components/gis/map.leaflet.ts` -- `package.json` - -## 10. 当前结论 - -当前地图问题可以归纳为两点: - -1. 现在的 OpenLayers 主链路并没有真正使用旧 Leaflet 的抽吸机制; -2. 当前点图标和文字被作为一个整体参与 declutter,所以文字碰撞时会连点一起隐藏。 - -所以后续如果要改,方向不应该只是“调避让参数”,而应该是: - -- 增加近邻点分阶段显示规则; -- 增加主动抽吸/偏移布局; -- 把图标和文字拆成可独立控制的渲染单元; -- 同步修正 popup 的碰撞判断。 - -在这个基础上,再进入代码修改会更稳。 diff --git a/frontend-sjgl/docs/地图模块-抽吸规则与参数说明.md b/frontend-sjgl/docs/地图模块-抽吸规则与参数说明.md deleted file mode 100644 index 65c9e71e..00000000 --- a/frontend-sjgl/docs/地图模块-抽吸规则与参数说明.md +++ /dev/null @@ -1,316 +0,0 @@ -# 地图模块-抽吸规则与参数说明 - -## 1. 文档目的 - -本文档用于记录当前地图点位“抽吸”功能的现行实现、默认参数、分层级显示规则和后续调参方式。 -本文件描述的是**当前已经落地并验证通过**的规则,适合作为后续维护和继续调参的依据。 - -相关文档: - -- `docs/地图模块-详细说明.md`:地图整体架构说明 -- `docs/地图模块-OpenLayers抽吸与碰撞分析.md`:问题成因与改造分析 - -## 2. 当前实现范围 - -当前抽吸逻辑运行在 OpenLayers 主链路中,核心文件如下: - -1. `src/modules/map/domain/nearby-point-rules.ts` -2. `src/store/modules/map.ts` -3. `src/components/gis/map.ol.ts` -4. `src/components/gis/ol/point-layer-manager.ts` - -各文件职责: - -- `nearby-point-rules.ts` - - 近邻点规则入口 - - 自动识别配置入口 - - 近邻点运行时元数据挂载 -- `map.ts` - - 点位数据加载完成后,统一触发近邻点分组和元数据写回 -- `map.ol.ts` - - 决定某个点在当前缩放级别是否显示 - - 决定文字是否允许显示 - - 承担图标与文字的手工碰撞控制 -- `point-layer-manager.ts` - - 负责展开阶段的几何偏移 - - 负责把同组点位按扇形/环形布局散开 - -## 3. 当前抽吸规则 - -### 3.1 近邻点自动识别 - -当前采用自动识别近邻点,不再靠固定站名手工枚举。 - -识别原则: - -- 只在同一 `layerKey` 内做近邻识别 -- 按点位空间距离自动聚组 -- 满足最小组大小后,才认为是近邻组 -- 组内自动生成显示优先级 `priority` - -这样做的目的: - -- 避免跨图层混组 -- 避免后续新增点位还要手工补名字 -- 让抽吸能力可以覆盖整批业务点 - -### 3.2 分层级显示规则 - -当前显示规则以 `replaceAtZoom` 和 `expandAtZoom` 为核心: - -1. `zoom < replaceAtZoom` - - 只显示组内主点 - - 即 `priority = 1` -2. `replaceAtZoom <= zoom < expandAtZoom` - - 显示组内前两个点 - - 即 `priority <= 2` - - 当前这样设计是为了避免主点在放大过程中突然消失 -3. `zoom >= expandAtZoom` - - 同组点全部显示 - - 同时进入展开布局阶段 - -补充规则: - -- 如果某个点配置了 `showZoomMin` 或 `showZoomMax`,则先受这两个值约束 -- 没有进入近邻组的点,不受抽吸规则影响 - -### 3.3 文字显示规则 - -当前文字显示必须依赖点位显示。 - -也就是说: - -- 点不显示,文字一定不显示 -- 点显示后,文字还要继续通过碰撞判断才会显示 - -当前文字碰撞规则: - -- 文字要避让已保留的图标占位盒 -- 文字要避让已保留的其他文字 -- 图标与文字不再分别交给 OpenLayers 的 `declutter` 自动处理 -- 当前走的是项目内的手工碰撞控制 - -## 4. 当前默认参数 - -当前默认参数集中维护在: - -- `src/modules/map/domain/nearby-point-rules.ts` - -当前采用统一配置对象: - -```ts -type NearbyPointConfig = { - autoRule: NearbyPointAutoRule; - layoutRule: NearbyPointLayoutRule; - densityDisplayRules: NearbyPointDensityDisplayRule[]; -}; -``` - -其中: - -- `autoRule`:控制近邻点自动识别、缩放切换和基础展开半径 -- `layoutRule`:控制展开阶段的单圈容量、扇形角度和外圈间距 -- `densityDisplayRules`:控制 `distance` 密度分档在不同缩放级别的基础显示资格 - -### 4.1 自动识别与缩放阶段参数 - -当前值如下: - -| 参数 | 当前值 | 作用 | -|---|---:|---| -| `distanceThresholdMeters` | `9000` | 近邻点自动识别距离阈值 | -| `replaceAtZoom` | `10.5` | 进入“主点 + 次点”阶段的缩放级别 | -| `expandAtZoom` | `12.2` | 进入“全部显示并展开”阶段的缩放级别 | -| `expandOffsetPx` | `48` | 第一圈展开的基础偏移半径 | -| `angleStepDeg` | `55` | 默认角度步长参考值 | -| `minGroupSize` | `2` | 最小成组数量 | - -### 4.2 展开布局参数 - -当前值如下: - -| 参数 | 当前值 | 作用 | -|---|---:|---| -| `ringSize` | `5` | 单圈最多容纳的次点数量 | -| `fanAngleForTwoDeg` | `110` | 单圈剩余 `2` 个点时的扇形总角度 | -| `fanAngleForFourDeg` | `150` | 单圈剩余 `3` 到 `4` 个点时的扇形总角度 | -| `fanAngleForManyDeg` | `180` | 单圈剩余 `5` 个及以上点时的扇形总角度 | -| `ringGapPx` | `22` | 外圈最小增量半径 | -| `ringGapFactor` | `0.85` | 外圈相对基础半径的增量系数 | - -### 4.3 密度分档显示参数 - -这里的 `distance` 不是物理距离,而是点位密度分档值。 -数值越大表示越稀疏,越早参与显示候选;数值越小表示越密集,需要更高缩放级别才参与显示。 - -当前值如下: - -| `minDensityValue` | `minZoom` | 作用 | -|---:|---:|---| -| `1500000` | `0` | 很稀疏的点,从小缩放级别就可参与显示 | -| `800000` | `6.1` | 稍密集的点,从中低缩放开始参与显示 | -| `400000` | `8.1` | 中密度点,需要进一步放大 | -| `50000` | `10.6` | 高密度点,需要较高缩放级别 | -| `0` | `12.2` | 极密集点,只有更高缩放才进入候选 | - -## 5. 当前展开布局 - -展开阶段由 `point-layer-manager.ts` 控制。 - -### 5.1 布局特点 - -当前布局不是简单把点随机散开,而是按以下方式处理: - -- 主点保留原位置 -- 同组其他点围绕主点展开 -- 展开方向优先偏下方 -- 单圈数量由 `ringSize` 控制 -- 点数较多时进入第二圈 -- 第二圈半径按 `ringGapPx` 和 `ringGapFactor` 继续增大,避免继续叠在一起 - -### 5.2 为什么优先向下展开 - -当前优先向下展开,主要是为了: - -- 尽量给上方文字留空间 -- 降低文字压住上方点的概率 -- 让地图上的抽吸形态更稳定 - -## 6. 当前实现的几个关键约束 - -### 6.1 不使用淡入淡出动画 - -目前已移除点和文字的淡入淡出动画。 - -原因: - -- 抽吸状态切换和动画叠加时,容易让点和文字出现闪烁 -- 会干扰对“当前到底该不该显示”的判断 - -当前阶段优先保证: - -- 显示规则稳定 -- 点和文字状态一致 -- 放大缩小时不出现明显错位 - -### 6.2 不再依赖 OL 的自动 declutter 决定图标命运 - -当前图标和文字都设置为 `declutterMode: 'none'`,由项目内逻辑统一控制。 - -这样做的原因: - -- 避免图标和文字落在两套碰撞体系里 -- 避免出现“文字先出来、点没出来” -- 避免文字压住其他点却仍然显示 - -## 7. 常见现象与调参建议 - -### 7.1 成组太少 - -表现: - -- 相近点仍然容易互相盖住 -- 进入展开前看起来还是压在一起 - -优先调整: - -- 增大 `distanceThresholdMeters` - -### 7.2 成组太多 - -表现: - -- 距离并不算近的点也被当成一组 -- 放大后过早进入抽吸关系 - -优先调整: - -- 减小 `distanceThresholdMeters` - -### 7.3 展开太晚 - -表现: - -- 放大到较深层级前仍然挤在一起 - -优先调整: - -- 减小 `expandAtZoom` - -### 7.4 展开太早 - -表现: - -- 中等缩放时地图上就出现较明显散开效果 - -优先调整: - -- 增大 `expandAtZoom` - -### 7.5 展开后间距不够 - -表现: - -- 虽然已经展开,但图标还是比较挤 - -优先调整: - -- 增大 `expandOffsetPx` -- 或增大 `ringGapPx` -- 或增大 `ringGapFactor` - -### 7.6 单圈散开角度不合适 - -表现: - -- 次点虽然已经展开,但仍然集中在一侧 -- 或者扇形过大,展开形态显得太散 - -优先调整: - -- 调整 `fanAngleForTwoDeg` -- 调整 `fanAngleForFourDeg` -- 调整 `fanAngleForManyDeg` - -### 7.7 主点在放大过程中突然消失 - -当前已经通过 `replaceAtZoom <= zoom < expandAtZoom` 阶段显示 `priority <= 2` 进行修正。 -如果后续又出现类似现象,优先检查: - -- `map.ol.ts` 中 `shouldRenderNearbyFeature()` 的阶段逻辑 -- 是否误改成只显示 `priority = 2` - -### 7.8 明明周围看起来空,文字还是不显示 - -优先检查: - -- 图标碰撞盒是否过大 -- 文字碰撞盒是否过于保守 -- 候选集是否发生重复统计 - -相关代码主要在: - -- `src/components/gis/map.ol.ts` - -## 8. 后续维护建议 - -建议后续继续保持以下约定: - -1. 抽吸参数继续统一放在 `nearby-point-rules.ts` -2. 显示决策继续集中在 `map.ol.ts` -3. 几何偏移继续集中在 `point-layer-manager.ts` -4. 后续调参优先改 `DEFAULT_NEARBY_POINT_CONFIG`,不要直接把数值散落回业务代码 -5. 如果继续加动画,必须放在抽吸状态机完全稳定之后 -6. 如果继续优化 popup,也要同步遵循“点先于文字”的显示原则 - -## 9. 当前结论 - -当前这版抽吸实现已经形成了比较稳定的主线: - -- 自动识别近邻点 -- 统一缩放阶段显示规则 -- 展开阶段几何偏移 -- 文字依赖点显示 -- 文字避让图标与文字 - -后续如果继续调优,建议先从参数层开始,不要先动主逻辑结构。 diff --git a/frontend-sjgl/docs/地图模块-详细说明.md b/frontend-sjgl/docs/地图模块-详细说明.md deleted file mode 100644 index 1dbede0c..00000000 --- a/frontend-sjgl/docs/地图模块-详细说明.md +++ /dev/null @@ -1,621 +0,0 @@ -# 地图模块-详细说明 - -## 1. 文档目的 - -本文档用于说明当前地图模块的真实运行结构、初始化时序、图层与图例联动、点位渲染规则、筛选与缩放行为,以及近期已经落地的关键约束。 - -本文档描述的是“当前可运行版本”的现状,不展开抽吸参数细节与专项问题分析。相关补充文档如下: - -- `docs/地图模块-抽吸规则与参数说明.md` -- `docs/地图模块-OpenLayers抽吸与碰撞分析.md` - -## 2. 当前技术栈 - -- 前端框架:Vue 3 + TypeScript + Ant Design Vue -- 状态管理:Pinia -- 2D 地图引擎:OpenLayers 10.8.0 -- 3D 地图引擎:Cesium -- 地图服务:GeoServer / WMTS / XYZ -- 运行模式:后台配置驱动 + 前台运行时编排 - -说明: - -- 当前 2D 主链路已经完全围绕 OpenLayers 组织。 -- 项目中仍保留 Leaflet 相关依赖和旧代码,但不是当前主执行路径。 - -## 3. 模块分层 - -当前地图模块可以按 6 层理解: - -1. 页面入口层 -2. 应用编排层 -3. 地图能力门面层 -4. 地图引擎执行层 -5. Store 运行态层 -6. 前台交互组件层 - -对应核心文件如下: - -- 页面入口层 - - `src/components/gis/GisView.vue` -- 应用编排层 - - `src/modules/map/application/map-orchestrator.ts` -- 地图能力门面层 - - `src/components/gis/map.class.ts` -- 地图引擎执行层 - - `src/components/gis/map.ol.ts` - - `src/components/gis/map.cesium.ts` - - `src/components/gis/ol/point-layer-manager.ts` - - `src/components/gis/ol/popup-manager.ts` -- Store 运行态层 - - `src/store/modules/map.ts` - - `src/modules/map/stores/map-config.store.ts` - - `src/modules/map/stores/map-data.store.ts` - - `src/modules/map/stores/map-view.store.ts` -- 前台交互组件层 - - `src/components/mapLegend/index.vue` - - `src/components/mapFilter/index.vue` - - `src/components/mapController/index.vue` - - `src/components/mapController/LayerController.vue` - - `src/components/BaseLayerSwitcher/index.vue` - -## 4. 整体原则 - -当前地图模块遵循以下几个运行原则: - -### 4.1 地图容器全局常驻 - -地图由 `GisView.vue` 常驻挂载,不会随着菜单切换被销毁重建。页面切换本质上是: - -- 重新计算 `pageKey` -- 重新加载该页图层配置和图例配置 -- 用新的运行态覆盖旧页面显示内容 - -### 4.2 配置驱动 - -前端不把全部图层和图例硬编码死,而是依赖后端返回的: - -- 图层树配置 -- 图例配置 -- 页面级图例配置 -- 各业务点位接口数据 - -### 4.3 运行态绝对优先 - -初始化默认值只在第一次派生时生效;一旦进入运行态,后续图层勾选、图例勾选、筛选器切换,都必须以当前用户操作后的状态为准,不能在加载完成后回退到默认状态。 - -## 5. 页面入口层 - -### 5.1 `GisView.vue` - -`GisView.vue` 的职责主要是: - -- 提供地图容器 `#mapContainer` -- 提供 popup 挂载容器 -- 挂载图例、筛选器、控制器、底图切换器 -- 根据路由计算 `pageKey` -- 在挂载时调用 `mapOrchestrator.mountView()` -- 在页面切换时交给编排器重载页面 - -当前页面层已经不再直接承载复杂业务逻辑,复杂链路都收敛到了编排器和 Store。 - -## 6. 应用编排层 - -### 6.1 `map-orchestrator.ts` 的职责 - -`map-orchestrator.ts` 是当前地图业务的总调度中心,主要负责: - -- 初始化地图壳与 popup -- 加载页面图层配置和图例配置 -- 初始化 GIS 基础图层 -- 触发点位图层加载 -- 管理缩放监听和基地切换监听 -- 处理图层树勾选 -- 处理图例勾选 -- 处理时间筛选、容量筛选、搜索定位 -- 处理水电开发菜单下的缩放联动图层 - -### 6.2 页面初始化链路 - -当前初始化顺序如下: - -1. `GisView.vue` 调用 `mapOrchestrator.mountView()` -2. `MapClass.init()` 初始化地图实例 -3. 页面级 popup 容器挂到地图实例 -4. 绑定缩放监听和基地监听 -5. 并行请求图层树配置与图例配置 -6. 图例接口先返回时,立即生成图例运行态 -7. 图层树接口返回后,生成默认勾选图层并初始化 GIS 基础图层 -8. 再按图层配置批量加载点位数据 - -这里有两个当前已落地的关键时序调整: - -- 图例转圈只跟图例接口绑定,不再等待所有锚点接口完成。 -- 筛选器数据会随着已加载图层逐步出现,不再必须等全量点位接口结束。 - -### 6.3 页面切换 - -页面切换时地图不销毁,只重载当前页面的: - -- 图层树配置 -- 图例配置 -- 点位数据 -- 当前页面的默认运行态 - -如果仍处于同一个 `pageKey` 且用户之前修改过勾选态,会尽量复用当前运行态,而不是重新回放默认勾选。 - -### 6.4 缩放联动 - -当前编排层主要有两类缩放联动: - -- `eng_point` 缩放联动 - - 大型电站始终允许显示 - - 中型电站仅在 `zoom > 7.5` 时进入显示候选 -- 水电开发菜单动态图层联动 - - `HYDRO_DYNAMIC_LAYER_KEYS` 在缩放跨越阈值时参与加载与显隐编排 - -## 7. 地图能力门面层 - -### 7.1 `map.class.ts` - -`MapClass` 是地图能力统一门面,屏蔽 2D / 3D 引擎差异,对外暴露统一方法。 - -当前常用能力包括: - -- `init()` -- `initPopupOverlay()` -- `addBaseDataLayer()` -- `addInitDataLayer()` -- `mdLayerTreeShowOrHidden()` -- `controlBaseLayerTreeShowAndHidden()` -- `setLegendPointVisible()` -- `hasLayer()` -- `hasBaseLayer()` -- `removePointLayer()` -- `flyTopanto()` -- `switchView()` - -当前业务主执行层仍然是 `MapOl`。 - -## 8. 地图引擎执行层 - -### 8.1 OpenLayers 主执行层 - -`map.ol.ts` 负责 OpenLayers 地图的实际渲染与交互,是当前 2D 地图主执行文件。 - -当前承担的核心能力包括: - -- 地图实例初始化 -- 底图与 GIS 图层管理 -- 点位图层渲染 -- 图标与文字样式生成 -- hover popup 与批量 popup -- 基地范围裁切 -- 高缩放交互 -- 测量、截图、飞行定位等基础地图能力 - -### 8.2 Cesium 执行层 - -`map.cesium.ts` 目前更多是保留 3D 入口和部分基础能力,复杂业务点位与碰撞规则并未与 OpenLayers 完全对齐。 - -结论上,当前真实业务规则仍然以 OpenLayers 链路为准。 - -## 9. Store 分层 - -### 9.1 `map-config.store.ts` - -负责配置态数据: - -- 图层树配置 -- 图层配置索引 -- 全量原始图例配置 -- 图例索引 -- 页面级图例配置 -- 配置接口加载状态 - -当前这里拆分出了两个加载态: - -- `configLoading` -- `legendLoading` - -其中 `legendLoading` 单独控制图例转圈。 - -### 9.2 `map-data.store.ts` - -负责数据态: - -- `pointData` -- `pointDataCache` -- `layerLoadState` -- 地图整体 `loading` - -这里的关键设计是: - -- 每个点图层有独立缓存 -- `pointData` 是由缓存合并出来的运行态数据 -- 单个图层命中缓存或接口返回后,就会立即回填 `pointData` - -因此当前搜索筛选下拉已经支持“渐进可用”,不再只能等全部接口结束。 - -### 9.3 `map-view.store.ts` - -负责视图运行态: - -- 当前勾选图层 `checkedLayerKeys` -- 图例勾选状态 `legendCheckedState` -- 搜索时间范围 -- 当前基地 -- 当前缩放级别 - -### 9.4 `src/store/modules/map.ts` - -这是当前地图运行逻辑最重的 Store,负责串联配置、缓存和地图实例。 - -主要职责包括: - -- 生成图例运行态 -- 批量加载点位数据 -- 控制图层显隐 -- 控制图例显隐 -- 管理请求缓存与并发加载 -- 刷新点图层显示数据 -- 处理 `eng_point` 的显示过滤 -- 在全量点位准备完成后挂载近邻点元数据 - -## 10. 图层分类 - -当前地图里的图层大体可以分成三类: - -### 10.1 `GISMap` - -这类图层对应 GIS 基础图层或专题底图,由 `addBaseDataLayer()` 加载到 `layerRegistry`。 - -当前已补充的规则: - -- 勾选 `GISMap` 时,不能只做显示/隐藏。 -- 如果地图实例中不存在该图层,需要先重新加载,再显示。 - -### 10.2 点位图层 - -这类图层由接口返回锚点数据,最终进入 `PointLayerManager` 维护的 `VectorLayer`。 - -### 10.3 其他专题图层 - -例如梯级流域、区域裁切辅助层等,这些更多由 `map.ol.ts` 单独控制。 - -## 11. 图层数据加载机制 - -### 11.1 配置与数据分离 - -当前初始化时,配置加载与点位数据加载是两条链: - -- 图层树配置决定有哪些层、哪些默认勾选 -- 图例配置决定图例结构和图例项元数据 -- 点位数据接口决定地图上实际锚点内容 - -### 11.2 加载顺序 - -`loadAllLayerData()` 的主要逻辑是: - -1. 先收集所有带 URL 的图层 -2. 把默认勾选图层作为高优先级任务 -3. 收集完成后全量并发加载,不再按 4 个一组分批发起 -4. 每个图层完成后立即写入缓存 -5. 每个图层命中缓存或返回成功后,立即增量更新 `pointData` -6. 全部图层结束后,再统一: - - 回填每层原始数据 - - 挂载近邻点运行时元数据 - - 刷新各图层显示数据 - - 用最新运行态收尾 - -### 11.3 初始化阶段的图例绝对性 - -当前已修复一个关键问题: - -- 图例虽然可能在界面上还在 loading -- 但只要图例配置已经返回,默认隐藏的图例项在点位首批入图时就必须生效 - -也就是说,现在不会再出现: - -- loading 过程中先显示默认隐藏点 -- loading 结束后才消失 - -## 12. 图例机制 - -### 12.1 图例来源 - -图例运行态由以下数据共同决定: - -- 原始全量图例配置 -- 当前页面图例配置 -- 当前已勾选图层 -- 当前图例勾选状态 - -### 12.2 图例派生 - -图例最终显示的是 `legendDataSelected`,而不是全量图例。 - -派生逻辑核心原则如下: - -- 只有当前勾选图层对应的图例项才进入页面图例 -- 图例勾选状态是运行态绝对值 -- 环保设施分组有单独规则 - -### 12.3 图例加载时机 - -当前图例面板的 loading 只跟图例接口绑定: - -- 图例接口返回后立即停转 -- 图例内容在图层默认勾选同步后立即派生 -- 不再等待所有锚点接口完成 - -## 13. 筛选器机制 - -### 13.1 `MapFilter` 的职责 - -筛选器当前包含以下能力: - -- 时间范围筛选 -- 装机容量筛选 -- 搜索定位 -- 个别页面的专题筛选项 - -### 13.2 搜索下拉来源 - -搜索下拉使用 `mapDataStore.pointData` 作为数据源。 - -当前行为已经调整为: - -- 某个图层一旦命中缓存,筛选器即可出现该图层点位 -- 某个图层接口一旦返回成功,筛选器也会立即出现该图层点位 - -因此筛选器当前是“渐进展示”,而不是“全量完成后一次性展示”。 - -### 13.3 装机容量筛选 - -装机容量筛选针对 `eng_point` 图层,当前规则如下: - -- `all` - - 恢复大型与中型图例 -- `large_eng_built` - - 只保留 `large_eng_` 前缀相关图例 - - 地图上只显示大型电站点位 -- `mid_eng_built` - - 只保留 `mid_eng_` 前缀相关图例 - - 地图上只显示中型电站点位 - -当前容量切换不再只是影响筛选器本地下拉,而是会同步驱动: - -- 图例运行态 -- `eng_point` 点位显示 - -## 14. `eng_point` 专项规则 - -`eng_point` 是当前地图模块中的特殊图层,存在独立显示规则。 - -### 14.1 缩放规则 - -在数据入图阶段就生效: - -- 大型电站始终允许显示 -- 中型电站仅在 `zoom > 7.5` 时允许进入显示候选 - -这不是单纯的图例控制,而是点位入图时的过滤逻辑。 - -### 14.2 图例规则 - -点位入图之后,仍然必须再经过图例运行态控制: - -- 允许入图,不等于最终可见 -- 只要图例勾选为隐藏,该类点位就必须隐藏 - -### 14.3 容量筛选规则 - -装机容量下拉会进一步批量控制 `eng_point` 的图例项,使图例与点位同步切换。 - -## 15. 点位显示裁决链 - -当前点位最终是否显示,至少会经过以下几层判断: - -1. 图层是否勾选 -2. 图例是否勾选 -3. 基地范围是否允许 -4. `eng_point` 缩放过滤是否通过 -5. `distance` 密度分档是否通过 -6. 近邻点显示阶段是否通过 -7. 图标是否已加载完成 -8. 当前缩放下是否进入碰撞控制 - -这几层不是互斥关系,而是叠加裁决。 - -## 16. 点位渲染与样式 - -### 16.1 基础样式 - -当前点位渲染包括: - -- 图标 -- 文字标签 -- 标签换行与截断 -- 缩放相关的图标与文字样式变化 - -### 16.2 图标加载约束 - -当前已经补充图标加载状态缓存: - -- 图标未加载完成时,不显示该点 -- 图标未加载完成时,文字也不先显示 -- 图标未加载完成时,不参与碰撞候选 - -这样可以避免“文字先出来、图标还是空的”的问题。 - -## 17. 抽吸、密度与碰撞 - -### 17.1 专项文档 - -抽吸与密度分档的详细参数请查看: - -- `docs/地图模块-抽吸规则与参数说明.md` - -### 17.2 当前裁决顺序 - -当前点位显示顺序可以理解为: - -1. 密度分档 -2. 近邻抽吸 -3. 图标碰撞 -4. 文字碰撞 - -补充约束: - -- 只有满足可见性条件的点,才能进入碰撞候选集 -- 被图例或缩放过滤掉的点,不能再去占用碰撞资格 -- 视图内无近邻的孤立点允许局部放行 - -### 17.3 高缩放特例 - -当前在高缩放层级 `zoom >= 15` 时,行为与中低缩放不同: - -- 只处理视口内点位 -- 点与文字不再做碰撞淘汰 -- 视口内符合基础显示条件的点和文字全部显示 -- 同时进入批量 popup 模式 - -这条规则的目标是: - -- 保证深度放大后信息完整可见 -- 避免高缩放下还因为碰撞把点或文字压掉 - -## 18. Popup 机制 - -### 18.1 两类 popup - -当前 popup 分成两类: - -- hover popup -- batch popup - -### 18.2 hover popup - -鼠标悬停时由 `pointermove` 驱动,只在非 batch popup 模式下显示。 - -### 18.3 batch popup - -在 `zoom >= 15` 时进入批量 popup 模式: - -- 只基于当前视口内点位 -- 拖动地图时持续刷新 -- 但通过 `requestAnimationFrame` 做节流 -- `moveend` 后立即再做一次强制刷新 - -当前这样做是为了兼顾两件事: - -- 保留“拖动时 popup 跟着更新”的原交互 -- 避免拖动过程中每个事件都重建整批 DOM - -### 18.4 惯性平移已关闭 - -当前地图拖动已经关闭 OpenLayers 默认惯性平移: - -- 普通拖动仍然保留 -- 松手后不再继续滑动一小段 - -这样可以减少高缩放 popup 场景下“松手还轻微位移”的观感问题。 - -## 19. 基地与区域过滤 - -基地切换是当前地图模块的关键能力之一,主要影响: - -- 地图裁切范围 -- 当前基地相关点位的可见性 -- 部分专题图层显示 - -整体过程通常是: - -1. 外部基地选择源变化 -2. 编排器接收到基地 ID -3. 地图实例切换基地范围 -4. 点位更新 `_regionVisible` -5. 样式函数按区域可见性决定是否显示 - -## 20. 典型交互链路 - -### 20.1 图层树勾选 - -1. `LayerController` 触发勾选变化 -2. 编排器归一化图层 key -3. `map.ts` 更新运行态图层勾选 -4. 地图实例更新图层显隐 -5. 图例重新派生 - -补充: - -- `GISMap` 勾选时,如果地图里没有该图层,会先补载,再显示 - -### 20.2 图例勾选 - -1. 图例组件触发点击 -2. 编排器转发图例切换 -3. `map.ts` 更新图例运行态 -4. 对应点位立即更新显隐 -5. 图例树同步更新灰态 - -### 20.3 筛选器操作 - -1. 用户切换时间、容量、搜索等条件 -2. 编排器接收命令 -3. 视情况更新视图运行态或重载数据 -4. 地图实例与图例运行态同步刷新 - -### 20.4 缩放操作 - -1. OpenLayers 视图触发 `change:resolution` -2. 编排器更新当前缩放级别 -3. 处理 `eng_point` 阈值联动 -4. 水电开发菜单下处理动态图层联动 -5. `map.ol.ts` 处理高缩放 popup 与点位显示策略 - -## 21. 当前重点文件清单 - -- `src/components/gis/GisView.vue` -- `src/components/gis/map.class.ts` -- `src/components/gis/map.ol.ts` -- `src/components/gis/map.cesium.ts` -- `src/components/gis/gisUtils.ts` -- `src/components/gis/mapurlManage.ts` -- `src/components/gis/ol/point-layer-manager.ts` -- `src/components/gis/ol/popup-manager.ts` -- `src/modules/map/application/map-orchestrator.ts` -- `src/modules/map/stores/map-config.store.ts` -- `src/modules/map/stores/map-data.store.ts` -- `src/modules/map/stores/map-view.store.ts` -- `src/store/modules/map.ts` -- `src/components/mapLegend/index.vue` -- `src/components/mapFilter/index.vue` -- `src/components/mapController/index.vue` -- `src/components/mapController/LayerController.vue` -- `src/components/BaseLayerSwitcher/index.vue` - -## 22. 当前结论 - -当前地图模块已经形成比较清晰的主干结构: - -- `GisView` 负责挂载与页面入口 -- `map-orchestrator` 负责业务编排 -- `MapClass` 负责能力门面 -- `MapOl` 负责 2D 主执行 -- `map.ts + 三个拆分 store` 共同维护配置、数据与视图运行态 -- 图层树、图例、筛选器、popup、高缩放规则都已经围绕这条主干收口 - -相较旧版说明,当前最重要的现状变化有 8 点: - -1. 图例 loading 已与锚点全量加载解耦 -2. 筛选器数据已支持渐进展示 -3. 初始化阶段默认隐藏图例项会立即生效 -4. 运行态勾选不会在 loading 完成后被默认值覆盖 -5. `eng_point` 有独立的缩放与容量规则 -6. 高缩放下视口内点和文字全部显示,不再做碰撞淘汰 -7. batch popup 保留拖动跟随,但增加了节流 -8. 地图拖动惯性已关闭 - -后续如果继续扩展地图功能,建议仍然优先沿着“编排层 + Store 分层 + OpenLayers 执行层”这条主干扩展,不要再把复杂业务回填到页面组件里。 diff --git a/frontend-sjgl/docs/地图模块现状与改造方案.md b/frontend-sjgl/docs/地图模块现状与改造方案.md deleted file mode 100644 index 181bb0e0..00000000 --- a/frontend-sjgl/docs/地图模块现状与改造方案.md +++ /dev/null @@ -1,1158 +0,0 @@ -# 地图模块现状与改造方案 - -## 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()` 在矢量 / 地形 / 影像底图之间切换。 - -当前基础图层类型主要包括: - -- 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 接口; -- 将返回记录补充: - - `iconCode` - - `code` - - `_id` - - `tm` -- 然后缓存到: - - `pointDataCache[layerKey]` - - `layer.data` - - 合并后的 `pointData` -- 若地图上还不存在该图层,则直接调用 `mapClass.addInitDataLayer()` 创建 `VectorLayer`。 - -### 当前锚点渲染方式 - -- 每个 `pointMap` 图层对应一个 `VectorLayer`; -- 每个点被转为 `Feature`; -- 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. 结论 - -当前地图模块功能已经较完整,但代码结构处于“功能可用、结构偏重、规则分散、性能隐患较多”的阶段。继续在现有结构上叠加新需求,后续维护成本会快速上升。 - -建议本次改造不要只做局部优化,而应围绕以下核心原则整体推进: - -- 单向数据流; -- 业务规则收口; -- 渲染层职责下沉; -- 类型与索引先行; -- 先止血,再拆层,再提速。 - -按照本文档的分阶段方案推进,可以在不一次性推翻现有功能的前提下,逐步完成地图模块的结构升级。 diff --git a/frontend-sjgl/docs/地图模块重构实施清单.md b/frontend-sjgl/docs/地图模块重构实施清单.md deleted file mode 100644 index 0a977c48..00000000 --- a/frontend-sjgl/docs/地图模块重构实施清单.md +++ /dev/null @@ -1,380 +0,0 @@ -# 地图模块重构实施清单 - -## 1. 使用说明 - -本文档是地图模块重构的执行清单,只保留当前已经明确、可以直接落地的实施项。 - -以下内容暂不纳入本清单: - -- 仍需进一步讨论的方案; -- 依赖后端配合后才能确定的优化项; -- 暂未确定收益与改造成本的预研项; -- 调试面板、埋点体系、长期性能平台化建设等扩展项。 - -执行原则: - -- 先止血,再拆层,再提速; -- 每一步都要保证现有业务可用; -- 每个阶段结束后都要做功能回归; -- 不在同一阶段同时改动 UI、Store、地图渲染层的大块逻辑。 - ---- - -## 2. 本轮重构目标 - -本轮只完成以下确定目标: - -- 收口当前明显缺陷和遗留代码; -- 建立基础类型和索引; -- 将分散在组件中的核心业务规则收口; -- 拆分地图 Store,建立更清晰的数据职责; -- 抽离统一编排层; -- 拆分 `map.ol.ts` 的核心管理职责; -- 保证图层树、图例、锚点显隐链路更清晰; -- 为后续继续重构预留稳定结构。 - ---- - -## 3. 阶段一:现状收口与问题修复 - -## 3.1 清理明显遗留问题 - -- [ ] 删除 `map.ol.ts` 中无意义代码和重复日志 -- [ ] 清理大段失效注释,保留必要说明 -- [ ] 统一地图相关文件中的临时 `console.log` - -涉及文件: - -- `src/components/gis/map.ol.ts` - -验收标准: - -- 页面可正常编译; -- 首次进入地图页面不报错; -- 控制台不再出现未定义函数或变量错误; -- 默认图层、图例、锚点仍可正常显示。 - -## 3.2 补充基础类型定义 - -- [ ] 新增地图图层类型定义 -- [ ] 新增图例结构类型定义 -- [ ] 新增锚点数据类型定义 -- [ ] 新增筛选参数类型定义 -- [ ] 为 `map.ts`、`map.class.ts`、`map.ol.ts` 替换第一批高频 `any` - -建议新增文件: - -- `src/modules/map/types/map-layer.ts` -- `src/modules/map/types/map-legend.ts` -- `src/modules/map/types/map-point.ts` -- `src/modules/map/types/map-filter.ts` - -验收标准: - -- 核心地图模块类型边界可读; -- 新增类型后不影响现有运行; -- `map.ts`、`map.ol.ts` 中关键方法参数不再全部使用 `any`。 - -## 3.3 建立基础索引 - -- [x] 建立 `layerByKey` 索引 -- [x] 建立 `legendByNameEn` 索引 -- [x] 建立 `legendByLayerCode` 索引 -- [x] 将 `findLayerByKey()` 的高频调用逐步替换为索引访问 -- [x] 将图例派生中的重复递归改为“初始化索引 + 增量查询” - -优先改造位置: - -- `src/store/modules/map.ts` - -验收标准: - -- 图层查找和图例查找不再完全依赖全量递归; -- 图层勾选、图例切换后响应速度不下降; -- 功能表现与现状一致。 - ---- - -## 4. 阶段二:规则收口 - -## 4.1 收口互斥规则 - -- [x] 将图层互斥逻辑从 `LayerController.vue` 中移出 -- [x] 将视频监控站与 AI 视频监控站互斥统一到规则函数 -- [x] 将环保设施与环保设施在建互斥统一到规则函数 -- [x] 将珍稀鱼类与沿程鱼类互斥统一到规则函数 -- [x] 保证互斥规则只有一个入口执行 - -建议新增文件: - -- `src/modules/map/domain/map-layer-rules.ts` - -涉及文件: - -- `src/components/mapController/LayerController.vue` -- `src/store/modules/map.ts` - -验收标准: - -- 图层树勾选时互斥结果稳定; -- 不再同时在组件和 Store 两处维护同一套互斥规则; -- 勾选结果、图例结果、地图显示结果保持一致。 - -## 4.2 收口图例派生规则 - -- [x] 将“图层选中后生成图例”逻辑提炼为独立函数 -- [x] 将“环保设施图例自动补充”逻辑提炼为独立规则 -- [x] 分离图例原始结构与图例选中状态 -- [x] 保证 `legendDataOriginal` 不再被运行态逻辑污染 - -建议新增文件: - -- `src/modules/map/domain/legend-deriver.ts` - -涉及文件: - -- `src/store/modules/map.ts` -- `src/components/mapLegend/index.vue` - -验收标准: - -- 图例显示仍按当前选中图层正确联动; -- 图例点击后锚点显隐正确; -- 图例原始结构与运行时状态职责更清晰。 - ---- - -## 5. 阶段三:拆分 Store - -## 5.1 拆分配置状态 - -- [ ] 抽离图层树配置状态 -- [ ] 抽离图例原始配置状态 -- [ ] 抽离页面地图配置加载逻辑 - -建议新增文件: - -- `src/modules/map/stores/map-config.store.ts` - -当前迁移来源: - -- `src/store/modules/map.ts` -- `src/api/map.ts` - -## 5.2 拆分运行状态 - -- [ ] 抽离当前选中图层 key -- [ ] 抽离当前选中图例 key -- [ ] 抽离当前基地、时间范围、缩放级别等运行态 -- [ ] 统一“当前地图状态”的唯一来源 - -建议新增文件: - -- `src/modules/map/stores/map-view.store.ts` - -## 5.3 拆分数据缓存 - -- [ ] 抽离锚点缓存 -- [ ] 抽离图层加载状态 -- [ ] 抽离锚点合并数据 `pointData` -- [ ] 为每个图层维护独立 `loading / loaded / error` - -建议新增文件: - -- `src/modules/map/stores/map-data.store.ts` - -验收标准: - -- 原 `map.ts` 体积明显下降; -- 配置、运行态、数据缓存职责分离; -- 现有组件仍可通过兼容方式读取数据; -- 默认图层、图例、锚点加载不受影响。 - ---- - -## 6. 阶段四:建立统一编排层 - -## 6.1 新增地图编排器 - -- [x] 新增地图模块统一编排入口 -- [x] 将地图初始化流程迁移到编排器 -- [x] 将图层勾选、图例勾选、基地切换、时间筛选、搜索定位改为统一命令 -- [x] 收口 `GisView.vue` 中的配置加载与监听联动逻辑 - -建议新增文件: - -- `src/modules/map/application/map-orchestrator.ts` - -## 6.2 统一入口命令 - -- [x] 定义 `initialize(pageKey)` -- [x] 定义 `toggleLayer(layerKey)` -- [x] 定义 `toggleLegend(layerKey, legendKey)` -- [x] 定义 `changeBaseId(baseId)` -- [x] 定义 `changeTimeRange(range)` -- [x] 定义 `focusPoint(pointId)` - -涉及文件: - -- `src/components/gis/GisView.vue` -- `src/components/mapController/LayerController.vue` -- `src/components/mapLegend/index.vue` -- `src/components/mapFilter/index.vue` - -验收标准: - -- 地图相关 UI 组件不再直接拼复杂业务逻辑; -- 组件交互都通过统一入口触发; -- 图层树、图例、筛选的联动链路更容易追踪。 - ---- - -## 7. 阶段五:拆分 OpenLayers 渲染层 - -## 7.1 拆分点图层管理 - -- [ ] 从 `map.ol.ts` 中抽离锚点图层创建逻辑 -- [ ] 从 `map.ol.ts` 中抽离锚点显隐逻辑 -- [ ] 从 `map.ol.ts` 中抽离图例控制锚点逻辑 -- [ ] 在点图层管理模块中维护图层注册表 - -建议新增文件: - -- `src/components/gis/ol/point-layer-manager.ts` - -当前迁移来源: - -- `src/components/gis/map.ol.ts` - -## 7.2 拆分 Popup 管理 - -- [ ] 抽离 `initPopupOverlay()` -- [ ] 抽离 `detectFeatureAtPixel()` -- [ ] 抽离 `showPopup()` -- [ ] 收口 hover 和 click 交互逻辑 - -建议新增文件: - -- `src/components/gis/ol/popup-manager.ts` - -## 7.3 拆分区域过滤管理 - -- [x] 抽离基地切换后的区域裁切逻辑 -- [x] 抽离点在面内判断逻辑 -- [x] 抽离区域外锚点显隐逻辑 -- [x] 抽离遮罩应用与清理逻辑 - -建议新增文件: - -- `src/components/gis/ol/region-mask-manager.ts` - -验收标准: - -- `map.ol.ts` 文件长度和职责明显收缩; -- 点图层、Popup、基地裁切三类逻辑各自独立; -- 现有地图交互行为不变。 - ---- - -## 8. 阶段六:组件瘦身 - -## 8.1 `GisView.vue` - -- [x] 仅保留地图容器、地图初始化调用、组件挂载 -- [x] 移除配置请求和复杂 watch 编排逻辑 -- [x] 只通过编排器触发初始化和页面切换 - -## 8.2 `LayerController.vue` - -- [x] 只保留树渲染与勾选事件 -- [x] 移除互斥规则 -- [x] 移除基础图层初始化编排逻辑 - -## 8.3 `MapLegend.vue` - -- [x] 只保留图例渲染与点击事件分发 -- [x] 不直接调用地图实例 - -## 8.4 `MapFilter.vue` - -- [x] 只保留表单与用户输入 -- [x] 搜索、容量、时间、基地变化全部通过编排器分发 -- [x] 不直接修改图例运行态和锚点显隐 - -验收标准: - -- 组件文件职责单一; -- UI 组件中不再出现大段业务逻辑; -- 地图相关行为主要集中在编排层和 Store。 - ---- - -## 9. 阶段七:性能优化 - -## 9.1 请求与缓存优化 - -- [x] 将 `GisUrlList.ts` 预编译为索引字典,而不是每次请求时重建 -- [x] 实现真实请求去重 -- [x] 增加请求取消能力,避免菜单切换时旧请求回写 -- [x] 对图层加载增加并发控制 -- [x] 将缓存 key 与图层 key、筛选条件绑定 - -## 9.2 锚点显示优化 - -- [x] 图例切换时只处理目标图例对应的锚点集合 -- [x] 图层切换时优先切换图层可见性,不重复加点 -- [x] 减少 hover 时全量点图层重绘 -- [x] 减少样式函数中的业务判断复杂度 - -验收标准: - -- 首屏加载时长不高于当前; -- 图层勾选、图例勾选、基地切换响应速度优于当前; -- 菜单快速切换时不出现旧数据回写。 - ---- - -## 10. 回归测试清单 - -每完成一个阶段,至少回归以下功能: - -- [x] 地图初始化正常 -- [x] 默认基础图层正常显示 -- [x] 图层树勾选与取消勾选正常 -- [x] 图例随图层选中动态显示 -- [x] 图例点击能正确控制锚点显隐 -- [x] 基地切换后区域裁切和锚点过滤正常 -- [x] 锚点 hover Popup 正常 -- [x] 锚点点击详情弹窗正常 -- [x] 时间筛选、容量筛选、搜索定位正常 -- [x] 视频监控站与 AI 视频监控站互斥正常 -- [x] 菜单切换后地图数据不串页 - ---- - -## 11. 实施顺序建议 - -推荐严格按以下顺序执行: - -1. 阶段一:现状收口与问题修复 -2. 阶段二:规则收口 -3. 阶段三:拆分 Store -4. 阶段四:建立统一编排层 -5. 阶段五:拆分 OpenLayers 渲染层 -6. 阶段六:组件瘦身 -7. 阶段七:性能优化 - -不建议直接跳到渲染层拆分或性能优化,否则会在旧结构上重复返工。 - ---- - -## 12. 本轮交付标准 - -当本清单全部完成后,应达到以下结果: - -- 地图模块核心职责划分清晰; -- 图层树、图例、锚点、筛选、基地切换链路可追踪; -- 关键规则不再散落在多个组件里; -- `map.ts` 和 `map.ol.ts` 体积明显下降; -- 新增地图业务时有明确扩展点; -- 后续继续做底图体系优化、长期性能建设时不需要再推翻本轮结构。 diff --git a/frontend-sjgl/package.json b/frontend-sjgl/package.json index 72fb6471..fcc7ec75 100644 --- a/frontend-sjgl/package.json +++ b/frontend-sjgl/package.json @@ -3,8 +3,8 @@ "version": "1.2.0", "scripts": { "dev": "vite serve --mode development", - "build": "vite build --mode production", - "build:mvn": "vite build --mode production", + "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode production", + "build:mvn": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode production", "serve": "vite preview", "lint": "eslint src/**/*.{ts,js,vue} --fix", "prettier": "prettier --write ." @@ -12,7 +12,6 @@ "dependencies": { "@ant-design/icons-vue": "^7.0.1", "@element-plus/icons-vue": "^2.0.10", - "@turf/turf": "^7.3.5", "@types/js-cookie": "^3.0.2", "@univerjs-pro/exchange-client": "0.15.0", "@univerjs/core": "0.14.0", @@ -25,24 +24,20 @@ "antdv-draggable-modal": "^1.1.6", "axios": "^1.2.0", "better-scroll": "^2.4.2", - "cesium": "^1.141.0", "dayjs": "^1.11.20", "default-passive-events": "^2.0.0", "dom-to-image": "^2.6.0", "echarts": "^5.2.2", "element-plus": "^2.2.27", - "esri-leaflet": "^3.0.19", "exceljs": "^4.4.0", "file-saver": "^2.0.5", "js-base64": "^3.7.5", "js-cookie": "^3.0.1", "jsencrypt": "^3.3.2", "jszip": "^3.10.1", - "leaflet": "^1.9.4", "lodash": "^4.18.1", "moment": "^2.30.1", "nprogress": "^0.2.0", - "ol": "^10.8.0", "path-browserify": "^1.0.1", "path-to-regexp": "^6.2.0", "pdfjs-dist": "^6.0.227", @@ -75,6 +70,7 @@ "@typescript-eslint/parser": "^5.19.0", "@vitejs/plugin-vue": "^4.0.0", "autoprefixer": "^10.4.13", + "cross-env": "^10.1.0", "eslint": "^8.14.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.0.0", @@ -87,7 +83,6 @@ "tailwindcss": "^3.2.4", "typescript": "latest", "vite": "^4.0.3", - "vite-plugin-cesium": "^1.2.22", "vite-plugin-svg-icons": "^2.0.1", "vue-tsc": "latest" }, diff --git a/frontend-sjgl/src/api/map.ts b/frontend-sjgl/src/api/map.ts deleted file mode 100644 index 35ad43ea..00000000 --- a/frontend-sjgl/src/api/map.ts +++ /dev/null @@ -1,61 +0,0 @@ -import request from '@/utils/request'; - -// 获取图例列表 -export function getModuleMapLegendList(params?: { moduleId?: string }) { - const url = params?.moduleId - ? `/mapLegend/getModuleMapLegendList?moduleId=${params.moduleId}` - : '/mapLegend/getModuleMapLegendList'; - return request({ - url, - method: 'get' - }); -} -// 获取地图配置列表 -export function getMapList(data: any) { - return request({ - url: '/mapmodule/getMapData', - method: 'post', - data - }); -} -// 获取梯级流域地图 -export function getQgcRvcd(data: any) { - return request({ - url: '/eng/base/rsvrcscdb/getQgcRvcd', - method: 'post', - data - }); -} -// 获取梯级流域下拉框列表 -export function getRvcdList(data: any) { - return request({ - url: '/eng/base/rsvrcscdb/rvcd', - method: 'post', - data - }); -} - -// 获取梯级流域下拉框图表数据 -export function getKendoList(data: any) { - return request({ - url: '/eng/base/rsvrcscdb/GetKendoList', - method: 'post', - data - }); -} -// 鱼类分布查询 - 站点查询 -export function getFishPointList(data: any) { - return request({ - url: '/wte/we/fishPoint/qgc/GetKendoListCust', - method: 'post', - data - }); -} -// 鱼类分布查询 -鱼查询 -export function getFishList(data: any) { - return request({ - url: '/wte/we/fishList/GetKendoListCust', - method: 'post', - data - }); -} diff --git a/frontend-sjgl/src/assets/icons/locationIcon.png b/frontend-sjgl/src/assets/icons/locationIcon.png deleted file mode 100644 index fc01013d..00000000 Binary files a/frontend-sjgl/src/assets/icons/locationIcon.png and /dev/null differ diff --git a/frontend-sjgl/src/assets/images/map-dixingtu.png b/frontend-sjgl/src/assets/images/map-dixingtu.png deleted file mode 100644 index bc1969bf..00000000 Binary files a/frontend-sjgl/src/assets/images/map-dixingtu.png and /dev/null differ diff --git a/frontend-sjgl/src/assets/images/map-shiliangtu.png b/frontend-sjgl/src/assets/images/map-shiliangtu.png deleted file mode 100644 index 6cb6bd12..00000000 Binary files a/frontend-sjgl/src/assets/images/map-shiliangtu.png and /dev/null differ diff --git a/frontend-sjgl/src/assets/images/map-yingxiangtu.png b/frontend-sjgl/src/assets/images/map-yingxiangtu.png deleted file mode 100644 index c8204b1f..00000000 Binary files a/frontend-sjgl/src/assets/images/map-yingxiangtu.png and /dev/null differ diff --git a/frontend-sjgl/src/assets/images/nineSections-dixing.png b/frontend-sjgl/src/assets/images/nineSections-dixing.png deleted file mode 100644 index f39453ca..00000000 Binary files a/frontend-sjgl/src/assets/images/nineSections-dixing.png and /dev/null differ diff --git a/frontend-sjgl/src/assets/images/nineSections-shiliang.png b/frontend-sjgl/src/assets/images/nineSections-shiliang.png deleted file mode 100644 index 767d2373..00000000 Binary files a/frontend-sjgl/src/assets/images/nineSections-shiliang.png and /dev/null differ diff --git a/frontend-sjgl/src/assets/images/nineSections-yingxiang.png b/frontend-sjgl/src/assets/images/nineSections-yingxiang.png deleted file mode 100644 index 1a785430..00000000 Binary files a/frontend-sjgl/src/assets/images/nineSections-yingxiang.png and /dev/null differ diff --git a/frontend-sjgl/src/components/MapModal/setting.config.ts b/frontend-sjgl/src/components/MapModal/setting.config.ts index 94d9c34b..31c3bef1 100644 --- a/frontend-sjgl/src/components/MapModal/setting.config.ts +++ b/frontend-sjgl/src/components/MapModal/setting.config.ts @@ -1,16 +1,3 @@ -// import { -// NormalDatePickerFilter, -// NormalFishFilter, -// NormalFishFilter1, -// NormalSelectFilter, -// NormalStcdFilter, -// NormalWEFilter, -// NormalYearPickerFilter -// } from '@zebras/qgc-share/components/mapModal/index' -// import { Session } from '@zebras/qgc-share/service/Session' -// import getUrl from '@zebras/qgc-share/utils/isQGCrul' -// import { Utility } from '@zebras/qgc-share/utils/Utility' - // // 水电站 √ const ENGTabs: Array = [ { diff --git a/frontend-sjgl/src/components/gis/GisView.vue b/frontend-sjgl/src/components/gis/GisView.vue deleted file mode 100644 index 57f2525f..00000000 --- a/frontend-sjgl/src/components/gis/GisView.vue +++ /dev/null @@ -1,195 +0,0 @@ - - - - diff --git a/frontend-sjgl/src/components/gis/TjCascadeChart.vue b/frontend-sjgl/src/components/gis/TjCascadeChart.vue deleted file mode 100644 index 1d2d1c33..00000000 --- a/frontend-sjgl/src/components/gis/TjCascadeChart.vue +++ /dev/null @@ -1,673 +0,0 @@ - - - - - diff --git a/frontend-sjgl/src/components/gis/TjLayerModal.vue b/frontend-sjgl/src/components/gis/TjLayerModal.vue deleted file mode 100644 index 91d2e865..00000000 --- a/frontend-sjgl/src/components/gis/TjLayerModal.vue +++ /dev/null @@ -1,537 +0,0 @@ - - - - - diff --git a/frontend-sjgl/src/components/gis/gisUtils.ts b/frontend-sjgl/src/components/gis/gisUtils.ts deleted file mode 100644 index 2466a5fd..00000000 --- a/frontend-sjgl/src/components/gis/gisUtils.ts +++ /dev/null @@ -1,537 +0,0 @@ -// import { Session } from '@zebras/qgc-share/service/Session' -import domtoimage from 'dom-to-image'; -import { - offset2, - drawDotImg2, - drawDotImg1, - offset1, - drawDotImg3, - offset3, - drawDotImg5, - offset5 -} from '@/utils/GisUrlList'; -declare global { - interface Window { - __lyConfigs?: { - theme?: string; - [key: string]: any; // 根据实际配置结构补充具体字段,或使用索引签名兼容其他属性 - }; - __mapMode?: string; // 建议同时声明代码中用到的其他全局变量 - } -} - -/** - * 根据镜头高度获取地图级别 - * @param {Number} height - */ -const A = 40487.57; -const B = 0.00007096758; -const C = 91610.74; -const D = -40467.74; - -/** - * 图例数据转对象 - * @param data 图例数据 - * @returns 以nameEn为下标的图例数据 - */ -export const legendData2Obj = (data: any[]) => { - const _tempData: any = {}; - const f = (_data: any[]) => { - _data.forEach(item => { - // childrenList有值表示这是一个分组 - if (item?.childrenList && item.childrenList?.length > 0) { - f(item.childrenList); - } else { - _tempData[item.nameEn] = item; - } - }); - }; - f(data); - return _tempData; -}; - -/** - * 添加标签偏移量等属性到item中 - * @param item - 待添加属性的item - * @param index - item的索引 - * @param labelType - 标签类型 - */ -export const appendOffsetPropties = (item: any, index: any, labelType = 2) => { - let drawDotImg = null; - let offset = null; - if (labelType == 2) { - drawDotImg = drawDotImg1; - offset = offset1; - addItemProperty(item, index, drawDotImg, offset); - } else if (labelType == 3) { - drawDotImg = drawDotImg2; - offset = offset2; - addItemProperty(item, index, drawDotImg, offset); - } else if (labelType == 4) { - drawDotImg = drawDotImg3; - offset = offset3; - addItemProperty2(item, index, drawDotImg, offset); - } else if (labelType == 5) { - drawDotImg = drawDotImg5; - offset = offset5; - addItemProperty2(item, index, drawDotImg, offset); - } -}; - -const addItemProperty2 = ( - item: any, - index: any, - drawDotImg: any, - offset: any -) => { - if (!drawDotImg || !offset) return; - item.icon_image = - index % 2 !== 0 - ? drawDotImg[item.anchoPointState]?.left || '' - : drawDotImg[item.anchoPointState]?.right || ''; - item.text_anchor = index % 2 !== 0 ? 'right' : 'left'; - item.text_offset = offset[item.icon_image]?.text_offset ?? [-10, -1.8]; - item.text_offset2 = offset[item.icon_image]?.text_offset2 ?? [-10, -1.8]; - item.icon_offset = [ - offset[item.icon_image]?.icon_x ?? -200, - offset[item.icon_image]?.icon_y ?? -50 - ]; - item.icon_offset2 = offset[item.icon_image]?.icon_offset2 ?? [-200, -50]; - item.billboard_offset = [ - offset[item.icon_image]?.billboard_x ?? -80, - offset[item.icon_image]?.billboard_y ?? -15 - ]; - item.label_offset = offset[item.icon_image]?.labelOffset ?? [100, 41]; -}; - -export const addItemProperty = ( - item: any, - index: any, - drawDotImg: any, - offset: any -) => { - if (!drawDotImg || !offset) return; - item.icon_image = - index % 2 !== 0 - ? drawDotImg[item.anchoPointState]?.left || '' - : drawDotImg[item.anchoPointState]?.right || ''; - item.text_offset = [ - offset[item.icon_image]?.text_x ?? -10, - offset[item.icon_image]?.text_y ?? -1.8 - ]; - item.icon_offset = [ - offset[item.icon_image]?.icon_x ?? -200, - offset[item.icon_image]?.icon_y ?? -50 - ]; - item.billboard_offset = [ - offset[item.icon_image]?.billboard_x ?? -80, - offset[item.icon_image]?.billboard_y ?? -15 - ]; - item.label_offset = offset[item.icon_image]?.labelOffset ?? [100, 41]; -}; - -/** - * 设置popName属性 - * @param item - 待设置popName的item - */ -export const setPopName = (item: any) => { - if (item.sttp === 'ENG') { - item.popName = item.ennm || item.titleName; - } else if (item.sttp === 'ylfb') { - if (item?.ftp?.length > 20) { - item.popName = item.ftp.slice(0, 20) + '...'; - } else { - item.popName = item?.ftp; - } - } else if (item.sttp === 'WE_FISH') { - item.popName = - item.fishList?.[0]?.fishName + `(${item.fishList?.[0]?.ptypeName})`; - item.popName1 = item.total + '尾'; - } else { - item.popName = item.stnm || item.titleName; - } -}; - -/** - * 获取图例配置中选中项的key - * @param data - 图例配置数据 - * @returns 选中项的key数组 - */ -export const getCheckedLayerConfigs = (data: any[]): any[] => { - const rs: any[] = []; - const f = (arr: any[] = []) => { - let count = 0; - arr.forEach((item: any) => { - if (item?.children && item.children.length > 0) { - const childrenCount = f(item.children); - if (childrenCount) { - rs.push(item.key); - } - } else if (item.checked) { - count++; - rs.push(item.key); // 对应图例的layerCode - } - }); - return count; - }; - f(data); - return rs; -}; - -// type mapType = "" | "pointMap" | "gisLayer" - -/** - * 将图层配置数据转换为一维数组 - * @param data - 图层配置数据 - * @returns 一维数组 - */ -export const layerConfig2Flat = (data: any): any[] => { - const rs: any[] = []; - const f = (arr: any[] = []) => { - arr.forEach((item: any) => { - const { type } = item; - if (type) { - if (type == 'GISMap') { - if (item?.children && item.children.length > 0) { - f(item.children); - } else { - rs.push(item); - } - } else if (type == 'pointMap') { - if ( - item.url || - item.title === '国家水文站' || - item.title === '自建水文站' - ) { - rs.push(item); - } else if (item.children.length > 0) { - f(item.children); - } - } - } else if (item.children.length > 0) { - f(item.children); - } - }); - }; - f(data); - return rs; -}; - -/** - * 替换图层配置中的 URL - * @param layerConfigsArr - 图层配置数组 - * @param urlList - URL 配置列表 - * @returns 更新后的图层配置数组 - */ -export const replaceUrl = (layerConfigsArr: any[], urlList: any[]): any[] => { - const bDict: Record = {}; - - // 构建 URL 映射字典 - for (let i = 0; i < urlList.length; i++) { - const key = urlList[i].url; - const title = urlList[i].title; - const keyType = urlList[i].keyType; - - if (!bDict[key]) bDict[key] = []; - if (!bDict[title]) bDict[title] = []; - if (!bDict[keyType]) bDict[keyType] = []; - - bDict[key].push(urlList[i]); - bDict[title].push(urlList[i]); - if (bDict[keyType]) { - bDict[keyType].push(urlList[i]); - } - } - - // 替换图层配置中的 URL - for (let i = 0; i < layerConfigsArr.length; i++) { - const obj = layerConfigsArr[i]; - const key = obj.url; - const title = obj.title; - const keyType = obj.key; - - const bObjList = bDict[keyType] || bDict[title] || bDict[key]; - - if (bObjList && bObjList.length > 0) { - const bObj = bObjList[0]; - obj.url = bObj.url; - obj.urlThd = bObj.url; - obj.params = bObj.params; - obj.orders = bObj.orders; - } else { - obj.url = ''; - } - } - - return layerConfigsArr; -}; - -/** - * 获取基础图层配置 - * @param config - 接口获取的配置数据 - * @returns 基础图层配置 - */ -export const getMapConfig = (config: any) => { - const r = { ...config }; - const baseUrlObj = import.meta.env.VITE_APP_MAP_URL; - if (baseUrlObj) { - r.url = baseUrlObj + r.url; // 'http://localhost:8088' - r.url_3d = baseUrlObj + r.url_3d; - // r.url = baseUrlObj.url + r.url //baseUrlObj.url - // r.url_3d = baseUrlObj.url + r.url_3d - if (r.geojson_url) { - r.geojson_url = baseUrlObj + r.geojson_url; - } - } - return r; -}; - -/** - * 重置地图元素位置 - */ -export const resetMapElPos = () => { - const legend = document.querySelector('#qgc-legendtl') as HTMLElement; // 图例 - // const filter = document.querySelector('#map-filter-container') as HTMLElement // 全局表单 - const controller = document.querySelector('#map-controller') as HTMLElement; // 地图工具栏 - const baselayer = document.querySelector('#map-baselayer') as HTMLElement; // 底图模式切换 - - if (legend) { - legend.style.left = '0'; - legend.style.bottom = '0'; - } - if (controller) { - controller.style.right = '480px'; - controller.style.bottom = '114px'; - } - if (baselayer) { - baselayer.style.right = '480px'; - baselayer.style.bottom = '20px'; - } -}; - -/** - * 根据位置获取布局数据列表 - * @param data - 布局数据 - * @param position - 位置 - * @returns 布局数据列表 - */ -const getListByPosition = (data: any, position: string) => - data?.data?.filter((el: any) => el.position === position && el.code); - -/** - * 设置地图组件位置 - * @param layoutType - 布局类型 - * @param data - 布局数据 - * @param offset - 偏移量 - */ -export const setMapLegendPos = ( - layoutType: string, - data: any, - offset = 456 -) => { - const menuStateString = localStorage.getItem('menuState'); //处理澜沧江左侧菜单状态 - const menuState = - menuStateString !== null ? JSON.parse(menuStateString) : true; - const _theme = localStorage.getItem('ly-theme') || window.__lyConfigs?.theme; - const leftEle = document.querySelector('#page-layout-left') as HTMLElement; - const rightEle = document.querySelector('#page-layout-right') as HTMLElement; - const bottomEle = document.querySelector( - '#page-layout-bottom' - ) as HTMLElement; - const legend = document.querySelector('#qgc-legendtl') as HTMLElement; // 图例 - const filter = document.querySelector('#map-filter-container') as HTMLElement; // 全局表单 - const compassControl = document.querySelector( - '#map-compassControl' - ) as HTMLElement; // 全局表单 - const controller = document.querySelector('#map-controller') as HTMLElement; // 地图工具栏 - const monitor = document.querySelector('#map-monitor') as HTMLElement; // 地图工具栏 - const baselayer = document.querySelector('#map-baselayer') as HTMLElement; // 底图模式切换 - // const vd = document.querySelector('#vd_operate') as HTMLElement // 底部视频 - const left = [ - 'layout1', - 'layout2', - 'layout3', - 'layout4', - 'layout6', - 'layout7', - 'layout8', - 'layout9', - 'layout10', - 'layout11', - 'layout14', - 'layout15', - 'layout16', - 'layout17' - ]; // 左侧布局 - const right = [ - 'layout1', - 'layout2', - 'layout3', - 'layout4', - 'layout5', - 'layout6', - 'layout8', - 'layout10', - 'layout11', - 'layout15', - 'layout16', - 'layout17' - ]; // 右侧布局 - const bottom1 = [ - 'layout1', - 'layout6', - 'layout8', - 'layout9', - 'layout10', - 'layout16' - ]; // 三行底部布局 - const bottom2 = ['layout2', 'layout15']; // 四行底部布局 - const w = `${offset}px`; - const l = `${_theme === 'ly-8' ? (menuState ? 643 : 510) : offset}px`; - let b = `0px`; - const le = ['layout17', 'layout10'].includes(layoutType) ? 0 : 1; - const leftList = getListByPosition(data, 'left'); - const rightList = getListByPosition(data, 'right'); - const bottomList = getListByPosition(data, 'bottom'); - let bottom = '0'; - - if (_theme === 'ly-8') { - if (window.__mapMode === '3D') { - b = `200px`; - } else { - b = `${menuState ? 200 : 50}px`; - } - } - - if (bottomList?.length > 0) { - if (bottom1.includes(layoutType)) { - bottom = 'calc((100% - 16px) / 3 + 8px)'; - } - if (bottom2.includes(layoutType)) { - bottom = 'calc((100% - 24px) / 4 + 8px)'; - } - } else { - // 没有底部布局时,底部高度为 - bottom = '28px'; - } - - const rle = ['layout6'].includes(layoutType) || bottom != '28px' ? 0 : 1; - - const leftHide = leftEle?.classList?.contains('hide'); - const rightHide = rightEle?.classList?.contains('hide'); - const bottomHide = bottomEle?.classList?.contains('hide'); - if (legend) { - legend.style.left = - !leftHide && left.includes(layoutType) && leftList?.length > le ? l : b; - legend.style.bottom = bottomHide - ? '0' - : bottomList?.length > 0 - ? bottom - : '12px'; - } - if (filter) { - if (layoutType === 'layout10') { - filter.style.left = - !leftHide && left.includes(layoutType) && leftList?.length > 1 ? l : b; - } else { - filter.style.left = - !leftHide && - left.includes(layoutType) && - leftList?.length > 0 && - layoutType !== 'layout17' - ? l - : b; - } - } - if (compassControl) { - if (layoutType === 'layout10') { - compassControl.style.left = - !leftHide && left.includes(layoutType) && leftList?.length > 1 ? l : b; - } else { - compassControl.style.left = - !leftHide && - left.includes(layoutType) && - leftList?.length > 0 && - layoutType !== 'layout17' - ? l - : b; - } - } - if (controller) { - controller.style.right = - !rightHide && right.includes(layoutType) && rightList?.length > rle - ? w - : '0'; - controller.style.bottom = bottomHide ? '0' : bottom; - } - if (monitor) { - monitor.style.right = - !rightHide && right.includes(layoutType) && rightList?.length > rle - ? w - : '0'; - // monitor.style.bottom = bottomHide ? '0' : bottom - } - if (baselayer) { - baselayer.style.right = - !rightHide && right.includes(layoutType) && rightList?.length > rle - ? `calc(${w} + 60px)` - : '60px'; - baselayer.style.bottom = bottomHide ? '0' : bottom; - } -}; - -export const altitudeToZoom = (height: number) => { - const lv = - Math.round(D + (A - D) / (1 + Math.pow(Number(height) / C, B))) + 1; - return lv > -1 ? lv : 0; -}; -/** - * 根据地图级别获取镜头高度 - * @param {Number} zoom - */ -export const zoomToAltitude = (zoom: number) => { - return Math.round(C * Math.pow((A - D) / (zoom - D) - 1, 1 / B)); -}; - -export const mapOutPut = (imageUrl: string) => { - const canvas = document.createElement('canvas'); - const downloadElement = document.createElement('a'); - const mapElem = document.getElementById('mapContainer'); - if (mapElem == null) { - return; - } - const context = canvas.getContext('2d')!; - canvas.width = mapElem.offsetWidth; - canvas.height = mapElem.offsetHeight; - const image = new Image(); - image.src = imageUrl; - image.onload = () => { - context.drawImage(image, 0, 0); - const elem: any = document.getElementById('qgc-legendtl'); - const l = elem?.style?.left === '' ? 0 : parseInt(elem?.style?.left); - if (elem) { - domtoimage - .toPng(elem, { - quality: 1.0, - width: elem.offsetWidth + (l + 24), - height: mapElem.offsetHeight - 20 - }) - .then((legendUrl: string) => { - const image = new Image(); - image.src = legendUrl; - image.width = elem.offsetWidth; - image.onload = () => { - context.drawImage(image, 0, 0); - downloadElement.href = canvas.toDataURL('image/png'); - downloadElement.download = '地图截图'; - downloadElement.click(); - }; - }) - .catch(() => {}); - } else { - downloadElement.href = canvas.toDataURL('image/png'); - downloadElement.download = 'download'; - downloadElement.click(); - } - }; -}; diff --git a/frontend-sjgl/src/components/gis/map.cesium.ts b/frontend-sjgl/src/components/gis/map.cesium.ts deleted file mode 100644 index 51f76b20..00000000 --- a/frontend-sjgl/src/components/gis/map.cesium.ts +++ /dev/null @@ -1,2809 +0,0 @@ -import * as Cesium from 'cesium'; -import type { MDOptions } from './map.class'; -import type { MapInterface, layer } from './map.d'; -import { getIconPath } from '@/utils/index'; -import { - generatePopupHtml, - shouldPreferEng2Popup, - getNormalizedPopupSttpMap -} from '@/utils/popupHtmlGenerator'; -import { useModelStore } from '@/store/modules/model'; -import { getNearbyPointDensityDisplayRules } from '@/modules/map/domain/nearby-point-rules'; -import { - LoadOSGB, - removeQxsy, - osgbChangeClick, - osgbLocation, - type OSGBItem -} from './osgbUtils'; - -export class MapCesium implements MapInterface { - private viewer: Cesium.Viewer | null = null; - private _ready = false; - private _pendingOSGBItems: OSGBItem[] = []; - private clickEventHandler: Cesium.ScreenSpaceEventHandler | null = null; - private popupElement: HTMLElement | null = null; - private hoveredEntityId: string | null = null; - private hoverRafId: number | null = null; // requestAnimationFrame 节流 - private removePostRenderListener: (() => void) | null = null; - private removeCameraChangedListener: (() => void) | null = null; - private isBatchPopupMode = false; - private batchPopupContainer: HTMLDivElement | null = null; - private batchPopupItems: Array<{ - entity: Cesium.Entity; - element: HTMLDivElement; - popupWidth: number; - popupHeight: number; - }> = []; - private batchPostRenderRemover: (() => void) | null = null; - private containerId = ''; - private containerElement: HTMLElement | null = null; - private loadingOverlayElement: HTMLDivElement | null = null; - private loadingSpinnerAnimation: Animation | null = null; - private flightFallbackTimer: number | null = null; - private imageryBaseLayer: Cesium.ImageryLayer | null = null; - private baseLayerRegistry = new Map(); - private baseLayerAliasMap = new Map(); - private pointLayerRegistry = new Map(); - private hydropBaseConfig: any = null; - private BASEID = ''; - private currentClipGeoJson: any = null; - private clipRequestController: AbortController | null = null; - private maskPolygonEntities: Cesium.Entity[] = []; - private originalBgColor: Cesium.Color | null = null; - private skyBoxShown = true; - private skyAtmosphereShown = true; - private readonly IGNORED_BASE_LAYER_KEYS = new Set([ - 'powerBaseStation', - 's_hydropBase_wfs' - ]); - private readonly PRIMARY_BASE_LAYER_KEYS = new Set([ - 'customBaseLayer', - 's_province_boundaries', - 'BASEMAP-white', - 'BASEMAP-img' - ]); - - private readonly CHINA_CENTER = { lng: 104.5, lat: 36.5 }; - private readonly CHINA_BOOT_HEIGHT = 20000000; - private readonly CHINA_DEFAULT_HEIGHT = 9000000; - private readonly CHINA_VIEW_ORIENTATION = { - heading: Cesium.Math.toRadians(-6), - pitch: Cesium.Math.toRadians(-90), - roll: 0 - }; - private readonly CESIUM_ZOOM_FORMULA_A = 40487.57; - private readonly CESIUM_ZOOM_FORMULA_B = 0.00007096758; - private readonly CESIUM_ZOOM_FORMULA_C = 91610.74; - private readonly CESIUM_ZOOM_FORMULA_D = -40467.74; - private readonly HOVER_POPUP_MAX_HEIGHT = 9_000_000; // ~zoom 4.5,中国视角以上不触发 hover - private readonly BATCH_POPUP_MODE_ZOOM = 14.9; // zoom >= 14.9 进入批量固定模式(float 容差避免 15 刚好踩边界) - - // ==================== Step 6: 碰撞检测 ==================== - private collisionRefreshFrameId: number | null = null; - private labelVisibilityDebounceTimerId: number | null = null; - private readonly COLLISION_GRID_CELL_SIZE = 96; // 与 2D 一致 - private readonly DENSITY_ISOLATION_THRESHOLD = 56; // 孤立点 pixel 阈值 - private readonly COLLISION_LABEL_PADDING = 4; // 标签碰撞 padding - - // 图标缩放可调参数(只影响 3D Cesium,不参与 2D 计算) - // scale = base + (zoom - 4.5) * rate,钳位到 [min, max] - // zoom≈4.5 是中国全国视角,zoom≈15 是最大放大级别 - private readonly ICON_MIN_SCALE = 0.4; // 最小缩放 - private readonly ICON_MAX_SCALE = 1.2; // 最大缩放(限制放大后图标不超过 1.2x 基础值) - private readonly ICON_BASE_SCALE = 0.7; // zoom≈4.5 基准(中国视角图标 ≈ 原始尺寸) - private readonly ICON_SCALE_RATE = 0.035; // 每级 zoom 增长(放大 10 级才增加 ~0.35) - - // 标签文字可调参数 - // fontSize = clamp(base * scale, min, max),scale = 0.7 + (zoom-4.5)*0.08 - private readonly LABEL_FONT_MIN = 10; // 最小字号(px),改大 → 小缩放时文字更清晰 - private readonly LABEL_FONT_MAX = 20; // 最大字号(px) - private readonly LABEL_FONT_BASE = 16; // 基准乘数,改大 → 所有缩放级别文字都变大 - private readonly LABEL_OFFSET_SINGLE = 14; // 单行偏移系数,改大 → 文字离图标更远 - private readonly LABEL_OFFSET_MULTI = 30; // 多行偏移系数 - - // ==================== Step 1: 初始化 + 飞中国 ==================== - - async init(container: HTMLElement, _rectangle?: any): Promise { - try { - this.containerId = container.id; - this.containerElement = container; - this.showLoadingOverlay(container); - const token = 'bearer b734a443-2c8f-4f4a-8698-44828cc5f709'; - - this.viewer = new Cesium.Viewer(container, { - animation: false, - timeline: false, - baseLayerPicker: false, - fullscreenButton: false, - vrButton: false, - geocoder: false, - homeButton: false, - sceneModePicker: false, - navigationHelpButton: false, - infoBox: false, - selectionIndicator: false, - shouldAnimate: true, - requestRenderMode: false, - targetFrameRate: 30, - terrainProvider: new Cesium.EllipsoidTerrainProvider() // 默认不使用地形 - }); - this.viewer.cesiumWidget.creditContainer.style.display = 'none'; - const layers = this.viewer.imageryLayers; - layers.removeAll(); - const provider = new Cesium.UrlTemplateImageryProvider({ - url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', - maximumLevel: 19, - credit: - 'Esri, DigitalGlobe, GeoEye, i-cubed, USDA FSA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community' - }); - const customTerrain = await Cesium.CesiumTerrainProvider.fromUrl( - import.meta.env.VITE_APP_MAP_URL + '/Terrain?token=' + token, // 指向包含 layer.json 的目录 - { - requestVertexNormals: true - } - ); - - // 应用地形到Viewer - this.viewer.terrainProvider = customTerrain; - - this.imageryBaseLayer = layers.addImageryProvider(provider); - this.imageryBaseLayer.show = true; - - this.viewer.scene.globe.depthTestAgainstTerrain = true; - this.viewer.scene.globe.show = true; - this.viewer.scene.fog.enabled = false; - this.viewer.scene.fxaa = false; - this.viewer.useBrowserRecommendedResolution = true; - - await this.flyToChina(); - - this.setupClickHandler(); - this.setupCameraPopupReset(); - - this._ready = true; - console.log('[Cesium] init complete, viewer ready'); - - // 初始化完成后等 5 秒再加载倾斜摄影,确保渲染管线稳定 - setTimeout(() => { - this.flushPendingOSGB(); - }, 5000); - - return this.viewer; - } catch (error) { - this.hideLoadingOverlay(); - console.error('Cesium Init Critical Error:', error); - throw error; - } - } - - private flyToChina(): Promise { - return new Promise(resolve => { - if (!this.viewer) { - this.hideLoadingOverlay(); - resolve(); - return; - } - - // Step 1: 瞬跳至高空俯视中国 - this.viewer.camera.cancelFlight(); - this.viewer.camera.setView({ - destination: Cesium.Cartesian3.fromDegrees( - this.CHINA_CENTER.lng, - this.CHINA_CENTER.lat, - this.CHINA_BOOT_HEIGHT - ), - orientation: this.CHINA_VIEW_ORIENTATION - }); - this.viewer.scene.requestRender(); - - // Step 2: 等一帧让 setView 生效后,飞入到默认视角 - requestAnimationFrame(() => { - const finish = () => { - this.clearFlightFallbackTimer(); - this.hideLoadingOverlay(); - resolve(); - }; - - this.clearFlightFallbackTimer(); - this.flightFallbackTimer = window.setTimeout(() => { - finish(); - }, 2600); - - this.viewer!.camera.flyTo({ - destination: Cesium.Cartesian3.fromDegrees( - this.CHINA_CENTER.lng, - this.CHINA_CENTER.lat, - this.CHINA_DEFAULT_HEIGHT - ), - orientation: this.CHINA_VIEW_ORIENTATION, - duration: 1.8, - complete: finish, - cancel: finish - }); - }); - }); - } - - private clearFlightFallbackTimer() { - if (this.flightFallbackTimer !== null) { - window.clearTimeout(this.flightFallbackTimer); - this.flightFallbackTimer = null; - } - } - - private showLoadingOverlay(container: HTMLElement) { - this.hideLoadingOverlay(); - - const overlay = document.createElement('div'); - overlay.setAttribute('data-map-cesium-loading', 'true'); - overlay.style.position = 'absolute'; - overlay.style.inset = '0'; - overlay.style.display = 'flex'; - overlay.style.flexDirection = 'column'; - overlay.style.alignItems = 'center'; - overlay.style.justifyContent = 'center'; - overlay.style.gap = '12px'; - overlay.style.background = - 'linear-gradient(180deg, rgba(7, 20, 36, 0.72) 0%, rgba(7, 20, 36, 0.46) 100%)'; - overlay.style.zIndex = '30'; - overlay.style.backdropFilter = 'blur(2px)'; - overlay.style.pointerEvents = 'auto'; - - const spinner = document.createElement('div'); - spinner.style.width = '36px'; - spinner.style.height = '36px'; - spinner.style.borderRadius = '50%'; - spinner.style.border = '3px solid rgba(255, 255, 255, 0.25)'; - spinner.style.borderTopColor = '#ffffff'; - this.loadingSpinnerAnimation = spinner.animate( - [{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }], - { duration: 900, iterations: Infinity, easing: 'linear' } - ); - - const label = document.createElement('div'); - label.textContent = '3D场景加载中...'; - label.style.color = '#ffffff'; - label.style.fontSize = '14px'; - label.style.lineHeight = '20px'; - label.style.letterSpacing = '0.5px'; - label.style.textShadow = '0 1px 2px rgba(0, 0, 0, 0.35)'; - - overlay.appendChild(spinner); - overlay.appendChild(label); - container.appendChild(overlay); - this.loadingOverlayElement = overlay; - } - - private hideLoadingOverlay() { - if (this.loadingSpinnerAnimation) { - this.loadingSpinnerAnimation.cancel(); - this.loadingSpinnerAnimation = null; - } - if (this.loadingOverlayElement?.parentNode) { - this.loadingOverlayElement.parentNode.removeChild( - this.loadingOverlayElement - ); - } - this.loadingOverlayElement = null; - } - - // ==================== Step 7: 点击锚点打开详情弹框 ==================== - - private setupClickHandler() { - if (!this.viewer) return; - if (this.clickEventHandler) { - this.clickEventHandler.destroy(); - } - - const modelStore = useModelStore(); - const canvas = this.viewer.scene.canvas; - this.clickEventHandler = new Cesium.ScreenSpaceEventHandler(canvas); - - // 默认箭头光标,与 2D 一致(不显示拖拽小手) - canvas.style.cursor = 'default'; - - // 鼠标移动:光标样式 + hover popup(rAF 节流,批量模式下只改光标) - let pendingMoveEvent: { endPosition: Cesium.Cartesian2 } | null = null; - - this.clickEventHandler.setInputAction( - (move: { endPosition: Cesium.Cartesian2 }) => { - pendingMoveEvent = move; - - if (this.hoverRafId !== null) return; // 已有待处理的帧,跳过 - this.hoverRafId = requestAnimationFrame(() => { - this.hoverRafId = null; - const evt = pendingMoveEvent; - pendingMoveEvent = null; - if (!evt) return; - - const scene = this.viewer?.scene; - if (!scene) return; - - const cameraHeight = this.getCurrentCameraHeight(); - // 缩放门槛:中国视角以上不触发 hover - if (cameraHeight > this.HOVER_POPUP_MAX_HEIGHT) { - canvas.style.cursor = 'default'; - this.hidePopup(); - return; - } - - const picked = scene.pick(evt.endPosition); - const entity = - Cesium.defined(picked) && picked.id instanceof Cesium.Entity - ? (picked.id as Cesium.Entity) - : null; - - // 检测是否命中可交互的锚点 - if (entity && this.isEntityInteractive(entity)) { - canvas.style.cursor = 'pointer'; - - // 批量模式下单点 hover 不触发 popup - if (this.isBatchPopupMode) return; - - // 同一个实体不重复刷新 - if (this.hoveredEntityId === entity.id) { - const position = entity.position?.getValue( - this.viewer!.clock.currentTime - ); - if (position) { - this.updatePopupPosition(position); - } - return; - } - - this.showPopupForEntity(entity); - return; - } - - canvas.style.cursor = 'default'; - // 批量模式下不隐藏批量 popup - if (!this.isBatchPopupMode) { - this.hidePopup(); - } - }); - }, - Cesium.ScreenSpaceEventType.MOUSE_MOVE - ); - - // 点击:打开详情弹框 - this.clickEventHandler.setInputAction( - (click: { position: Cesium.Cartesian2 }) => { - const scene = this.viewer?.scene; - if (!scene) return; - - const picked = scene.pick(click.position); - if (!Cesium.defined(picked) || !picked.id) return; - - const entity = picked.id as Cesium.Entity; - if (!this.isEntityInteractive(entity)) return; - - const rawData: Record = (entity as any)._rawData || {}; - - if (rawData.sttpMap === 'ylfb') { - modelStore.ylfbModalVisible = true; - modelStore.params = rawData; - } else { - modelStore.modalVisible = true; - modelStore.params = rawData; - modelStore.title = rawData.titleName || rawData.stnm; - } - }, - Cesium.ScreenSpaceEventType.LEFT_CLICK - ); - } - - // ==================== Step 5: hover popup + 批量 popup ==================== - - /** 监听相机移动结束:检测缩放阈值切换批量模式 */ - private setupCameraPopupReset() { - if (!this.viewer) return; - - if (this.removePostRenderListener) { - this.removePostRenderListener(); - this.removePostRenderListener = null; - } - if (this.removeCameraChangedListener) { - this.removeCameraChangedListener(); - this.removeCameraChangedListener = null; - } - - // camera.changed:相机运动中实时刷新碰撞检测(debounce 150ms) - this.removeCameraChangedListener = - this.viewer.camera.changed.addEventListener(() => { - this.requestRefreshPointLabelVisibility(false, 150); - }); - - this.removePostRenderListener = this.viewer.camera.moveEnd.addEventListener( - () => { - const zoom = this.getZoomLevelFromCameraHeight( - this.getCurrentCameraHeight() - ); - - if (zoom >= this.BATCH_POPUP_MODE_ZOOM) { - if (!this.isBatchPopupMode) { - this.enableBatchPopupMode(); - } else { - // 已处于批量模式:重建 popup(与 2D moveend 行为对齐) - // 拖拽/平移后视口内可能出现新的可见锚点,仅 postRender 重定位无法创建新 popup - // 同时修复 zoom 进出循环后 popup 消失的问题 - this.rebuildBatchPopupsIfNeeded(); - } - } else { - if (this.isBatchPopupMode) { - this.disableBatchPopupMode(); - } - // hover popup 重定位而非隐藏,避免缩放结束后闪烁 - if (this.hoveredEntityId) { - this.repositionHoverPopup(); - } - } - - // Step 6: 碰撞检测 — 相机停止移动后重新计算图标/标签可见性 - this.requestRefreshPointLabelVisibility(); - } - ); - } - - /** 根据 hoveredEntityId 重定位 popup(相机移动后调用,不重建内容) */ - private repositionHoverPopup(): void { - if (!this.viewer || !this.popupElement || !this.hoveredEntityId) return; - const entity = this.viewer.entities.getById(this.hoveredEntityId); - if (!entity || !entity.position) { - this.hidePopup(); - return; - } - const position = entity.position.getValue(this.viewer.clock.currentTime); - if (!position) { - this.hidePopup(); - return; - } - this.updatePopupPosition(position); - } - - /** 批量模式下每帧更新 popup 位置,实现流畅跟随拖拽 */ - private setupBatchPopupPostRenderSync() { - if (!this.viewer || this.batchPostRenderRemover) return; - this.batchPostRenderRemover = this.viewer.scene.postRender.addEventListener( - () => { - if (!this.isBatchPopupMode) return; - this.updateBatchPopupPositions(); - } - ); - } - - /** 停止每帧同步 */ - private clearBatchPopupPostRenderSync() { - if (this.batchPostRenderRemover) { - this.batchPostRenderRemover(); - this.batchPostRenderRemover = null; - } - } - - /** 获取当前相机高度(米) */ - private getCurrentCameraHeight(): number { - if (!this.viewer) return Number.POSITIVE_INFINITY; - const height = this.viewer.camera.positionCartographic.height; - if (!height || !isFinite(height) || height <= 0) { - return Number.POSITIVE_INFINITY; - } - return height; - } - - /** 将相机高度映射为近似 2D zoom 级别(与 getCurrentCesiumZoom 同公式) */ - private getZoomLevelFromCameraHeight(height: number): number { - const zoom = - this.CESIUM_ZOOM_FORMULA_D + - (this.CESIUM_ZOOM_FORMULA_A - this.CESIUM_ZOOM_FORMULA_D) / - (1 + - Math.pow( - height / this.CESIUM_ZOOM_FORMULA_C, - this.CESIUM_ZOOM_FORMULA_B - )) + - 1; - return zoom > -1 ? zoom : 0; - } - - /** 判断实体是否属于 ENG 类型(用于 popup 碰撞优先保留,与 isEntityEng 对齐) */ - private isEngEntity(entity: Cesium.Entity): boolean { - const e = entity as any; - // 优先从实体 properties._sttpMap 直接检查(与 Entity 创建时设置的 _sttpMap 一致) - const sttpFromProps = String(e._sttpMap || '').toUpperCase(); - if (sttpFromProps.startsWith('ENG')) return true; - // 其次从 _rawData 检查 - const rawData = e._rawData as Record | undefined; - if (!rawData) return false; - const sttpMap = getNormalizedPopupSttpMap(rawData).toUpperCase(); - return sttpMap.startsWith('ENG'); - } - - /** 判断实体当前是否可交互(图层可见 + 图例可见 + 区域可见) */ - private isEntityInteractive(entity: Cesium.Entity): boolean { - const e = entity as any; - if (e._layerVisible === false) return false; - if (e._legendVisible === false) return false; - if (e._regionVisible === false) return false; - if (entity.show === false) return false; - return true; - } - - /** 根据实体属性生成 popup HTML,复用 generatePopupHtml */ - private getPopupHtml(rawData: Record): string { - return rawData.popupHtml || generatePopupHtml(rawData) || ''; - } - - /** 关闭单个 hover popup */ - private hidePopup(): void { - this.hoveredEntityId = null; - if (!this.popupElement) return; - this.popupElement.style.display = 'none'; - this.popupElement.innerHTML = ''; - } - - /** 判断 3D 位置是否被地形遮挡(基于射线与 terrain 求交) */ - private isPositionBehindTerrain(position: Cesium.Cartesian3): boolean { - if (!this.viewer) return true; - const scene = this.viewer.scene; - - const windowCoord = Cesium.SceneTransforms.worldToWindowCoordinates( - scene, - position - ); - if (!windowCoord) return true; // 在地球背面 - - const ray = scene.camera.getPickRay(windowCoord); - if (!ray) return false; - - const terrainPos = scene.globe.pick(ray, scene); - if (!terrainPos) return false; // 该方向无地形 - - const cameraPos = scene.camera.positionWC; - const terrainDist = Cesium.Cartesian3.distance(cameraPos, terrainPos); - const entityDist = Cesium.Cartesian3.distance(cameraPos, position); - // 地形交点明显更近 → 实体被遮挡 - return terrainDist + 10 < entityDist; - } - - /** 根据实体世界坐标把 popup 定位到屏幕坐标 */ - private updatePopupPosition(position: Cesium.Cartesian3): void { - if (!this.viewer || !this.popupElement) return; - - // 被地形遮挡时隐藏 popup - if (this.isPositionBehindTerrain(position)) { - this.hidePopup(); - return; - } - - const canvasPosition = Cesium.SceneTransforms.worldToWindowCoordinates( - this.viewer.scene, - position - ); - - if (!canvasPosition) { - this.hidePopup(); - return; - } - - this.popupElement.style.left = `${canvasPosition.x}px`; - this.popupElement.style.top = `${canvasPosition.y}px`; - this.popupElement.style.display = 'block'; - } - - /** 读取实体原始数据并渲染 hover popup */ - private showPopupForEntity(entity: Cesium.Entity): void { - if (!this.viewer || !this.popupElement || !entity.position) return; - - const rawData: Record = { - ...((entity as any)._rawData || {}) - }; - if (!rawData.sttpMap) { - rawData.sttpMap = rawData._sttpMap || rawData.popupSttpMap || ''; - } - const popupHtml = this.getPopupHtml(rawData); - const position = entity.position.getValue(this.viewer.clock.currentTime); - - if (!popupHtml || !position) { - this.hidePopup(); - return; - } - - this.popupElement.innerHTML = popupHtml; - this.hoveredEntityId = entity.id; - this.updatePopupPosition(position); - } - - // ==================== 批量 popup ==================== - - /** 启用批量 popup:所有可见锚点固定显示 popup */ - private enableBatchPopupMode() { - console.log('[Cesium] enableBatchPopupMode'); - this.isBatchPopupMode = true; - this.clearBatchPopups(); - this.hidePopup(); // 清除可能残留的 hover popup - // 先同步执行碰撞检测,确保 _iconCollisionVisible 已正确计算, - // 避免重叠锚点的 popup 在 showBatchPopups 中被短暂创建 - this.refreshPointLabelVisibility(); - // 等所有 chunk 处理完再注册 postRender,避免用不完整列表做碰撞 - this.showBatchPopups().then(() => { - if (this.isBatchPopupMode) { - this.setupBatchPopupPostRenderSync(); - } - }); - } - - /** 退出批量 popup */ - private disableBatchPopupMode() { - console.log('[Cesium] disableBatchPopupMode'); - this.isBatchPopupMode = false; - this.clearBatchPopupPostRenderSync(); - this.clearBatchPopups(); - this.hidePopup(); - } - - /** 收集视口内可交互实体并按 X 坐标排序(ENG 优先) */ - private collectBatchPopupCandidates(): Array<{ - entity: Cesium.Entity; - x: number; - y: number; - popupHtml: string; - }> { - if (!this.viewer) return []; - - const canvas = this.viewer.scene.canvas; - const viewW = canvas.clientWidth; - const viewH = canvas.clientHeight; - const padding = 60; - - const candidates: Array<{ - entity: Cesium.Entity; - x: number; - y: number; - popupHtml: string; - }> = []; - - this.pointLayerRegistry.forEach(entities => { - entities.forEach(entity => { - if (!this.isEntityInteractive(entity)) return; - // 图标被碰撞/密度隐藏的锚点不展示 popup(没有可见图标,popup 无意义且干扰碰撞) - if ((entity as any)._iconCollisionVisible === false) return; - const position = entity.position?.getValue( - this.viewer!.clock.currentTime - ); - if (!position) return; - - const cp = Cesium.SceneTransforms.worldToWindowCoordinates( - this.viewer!.scene, - position - ); - if (!cp) return; - - if ( - cp.x < -padding || - cp.y < -padding || - cp.x > viewW + padding || - cp.y > viewH + padding - ) - return; - - // 被地形遮挡的不展示 popup - if (this.isPositionBehindTerrain(position)) return; - - const rawData: Record = { - ...((entity as any)._rawData || {}) - }; - if (!rawData.sttpMap) { - rawData.sttpMap = rawData._sttpMap || rawData.popupSttpMap || ''; - } - - let popupHtml = shouldPreferEng2Popup(rawData) - ? generatePopupHtml(rawData, { forceEng2: true }) || - rawData.popupHtml || - generatePopupHtml(rawData) - : this.getPopupHtml(rawData); - - if (!popupHtml) { - const title = rawData.titleName || rawData.stnm || rawData.ennm || ''; - if (!title) return; - popupHtml = `
${title}
`; - } - - candidates.push({ entity, x: cp.x, y: cp.y, popupHtml }); - }); - }); - - // 按 X 坐标排序,X 接近时(5px 容差,3D 投影浮点误差)ENG 优先 - candidates.sort((a, b) => { - const dx = a.x - b.x; - if (Math.abs(dx) > 5) return dx; - const aEng = this.isEngEntity(a.entity) ? 0 : 1; - const bEng = this.isEngEntity(b.entity) ? 0 : 1; - if (aEng !== bEng) return aEng - bEng; - return dx; - }); - - return candidates; - } - - /** 在指定容器中分帧构建 popup DOM(避免卡顿),完成后 resolve */ - private buildBatchPopupsInContainer( - container: HTMLDivElement, - candidates: Array<{ - entity: Cesium.Entity; - x: number; - y: number; - popupHtml: string; - }> - ): Promise { - return new Promise(resolve => { - let index = 0; - const CHUNK_SIZE = 80; - const placedPopups: Array<{ - left: number; - right: number; - top: number; - bottom: number; - entity: Cesium.Entity; - element: HTMLDivElement; - }> = []; - - const processChunk = () => { - if (!this.isBatchPopupMode) return; - - const end = Math.min(index + CHUNK_SIZE, candidates.length); - for (let i = index; i < end; i++) { - const { entity, x, y, popupHtml } = candidates[i]; - - const popupEl = document.createElement('div'); - popupEl.className = - this.popupElement?.className || 'map-popup-container'; - popupEl.innerHTML = popupHtml; - popupEl.style.position = 'absolute'; - popupEl.style.display = 'block'; - popupEl.style.transform = 'translate(-50%, calc(-100% - 10px))'; - popupEl.style.pointerEvents = 'none'; - - // 先隐藏测量尺寸 - popupEl.style.visibility = 'hidden'; - popupEl.style.left = `${x}px`; - popupEl.style.top = `${y}px`; - container.appendChild(popupEl); - - const rect = popupEl.getBoundingClientRect(); - const pw = rect.width || 120; - const ph = rect.height || 40; - - const popupRect = { - left: x - pw / 2, - right: x + pw / 2, - top: y - 10 - ph, - bottom: y - 10 - }; - - const isCurrentEng = this.isEngEntity(entity); - let collidedIdx = -1; - - for (let k = 0; k < placedPopups.length; k++) { - if (this.checkPopupCollision(popupRect, placedPopups[k])) { - collidedIdx = k; - break; - } - } - - if (collidedIdx === -1) { - // 无碰撞:正常放置 - popupEl.style.visibility = 'visible'; - popupEl.style.left = `${x}px`; - popupEl.style.top = `${y}px`; - this.batchPopupItems.push({ - entity, - element: popupEl, - popupWidth: pw, - popupHeight: ph - }); - placedPopups.push({ ...popupRect, entity, element: popupEl }); - } else { - const collided = placedPopups[collidedIdx]; - const isCollidedEng = this.isEngEntity(collided.entity); - - if (isCurrentEng && !isCollidedEng) { - // ENG 碰撞普通 popup:移除普通,展示 ENG - collided.element.remove(); - this.batchPopupItems = this.batchPopupItems.filter( - item => item.element !== collided.element - ); - popupEl.style.visibility = 'visible'; - popupEl.style.left = `${x}px`; - popupEl.style.top = `${y}px`; - this.batchPopupItems.push({ - entity, - element: popupEl, - popupWidth: pw, - popupHeight: ph - }); - placedPopups[collidedIdx] = { - ...popupRect, - entity, - element: popupEl - }; - } else if (!isCurrentEng && isCollidedEng) { - // 普通碰撞 ENG:隐藏当前普通 popup,保留 ENG - container.removeChild(popupEl); - } else { - // 同类型碰撞:后来的让路 - container.removeChild(popupEl); - } - } - } - - index = end; - if (index < candidates.length && this.isBatchPopupMode) { - requestAnimationFrame(processChunk); - } else { - resolve(); - } - }; - - requestAnimationFrame(processChunk); - }); - } - - /** 创建批量 popup 容器并构建所有可见 popup */ - private showBatchPopups(): Promise { - if (!this.viewer || !this.containerElement) return Promise.resolve(); - - const batchContainer = document.createElement('div'); - batchContainer.className = 'batch-popup-container'; - batchContainer.style.cssText = ` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - z-index: 1000; - `; - batchContainer.id = 'batch-popup-container'; - this.batchPopupContainer = batchContainer; - this.containerElement.appendChild(batchContainer); - - const candidates = this.collectBatchPopupCandidates(); - return this.buildBatchPopupsInContainer(batchContainer, candidates); - } - - /** 每一帧都在更新,不需要持久化,但清理时一并重置 */ - private popupCollisionFrames = new WeakMap(); - - /** 每帧更新批量 popup 屏幕位置 + 碰撞检测(拖拽/缩放时流畅跟随) */ - private updateBatchPopupPositions() { - if (!this.viewer || !this.batchPopupContainer) return; - if (this.batchPopupItems.length === 0) return; - - const canvas = this.viewer.scene.canvas; - const viewW = canvas.clientWidth; - const viewH = canvas.clientHeight; - const padding = 48; - - // 第一遍:更新位置、收集可见项的屏幕矩形(用缓存宽高,不依赖 DOM 测量) - const visibleRects: { - item: (typeof this.batchPopupItems)[number]; - x: number; - y: number; - left: number; - right: number; - top: number; - bottom: number; - }[] = []; - - for (let i = 0; i < this.batchPopupItems.length; i++) { - const item = this.batchPopupItems[i]; - const { entity, element, popupWidth, popupHeight } = item; - - // 先恢复为 block,碰撞检测再做最终决定 - element.style.display = 'block'; - - if (!this.isEntityInteractive(entity)) { - element.style.display = 'none'; - continue; - } - - // 图标被碰撞隐藏的锚点不展示 popup - if ((entity as any)._iconCollisionVisible === false) { - element.style.display = 'none'; - continue; - } - - const position = entity.position?.getValue( - this.viewer!.clock.currentTime - ); - if (!position) { - element.style.display = 'none'; - continue; - } - - const cp = Cesium.SceneTransforms.worldToWindowCoordinates( - this.viewer!.scene, - position - ); - if (!cp) { - element.style.display = 'none'; - continue; - } - - if ( - cp.x < -padding || - cp.y < -padding || - cp.x > viewW + padding || - cp.y > viewH + padding - ) { - element.style.display = 'none'; - continue; - } - - const x = cp.x; - const y = cp.y; - element.style.left = `${x}px`; - element.style.top = `${y}px`; - - visibleRects.push({ - item, - x, - y, - left: x - popupWidth / 2, - right: x + popupWidth / 2, - top: y - 10 - popupHeight, - bottom: y - 10 - }); - } - - // 第二遍:碰撞检测 — 按 X 排序后右让左,X 接近时(5px 容差)ENG 优先 - visibleRects.sort((a, b) => { - const dx = a.x - b.x; - if (Math.abs(dx) > 5) return dx; - const aEng = this.isEngEntity(a.item.entity) ? 0 : 1; - const bEng = this.isEngEntity(b.item.entity) ? 0 : 1; - if (aEng !== bEng) return aEng - bEng; - return dx; - }); - - const collisionFlags = new Array(visibleRects.length).fill(false); - const instantFlags = new Array(visibleRects.length).fill(false); // ENG 碰撞立即隐藏 - for (let i = 1; i < visibleRects.length; i++) { - const a = visibleRects[i]; - for (let j = 0; j < i; j++) { - if (collisionFlags[j]) continue; - const b = visibleRects[j]; - - if (!this.checkPopupCollision(a, b)) continue; - - const aEng = this.isEngEntity(a.item.entity); - const bEng = this.isEngEntity(b.item.entity); - - if (aEng && !bEng) { - // ENG (a) 碰撞普通 (b):立即隐藏普通,ENG 继续检查是否碰撞其他已放置项 - collisionFlags[j] = true; - instantFlags[j] = true; - } else if (!aEng && bEng) { - // 普通 (a) 碰撞 ENG (b):立即隐藏普通 - collisionFlags[i] = true; - instantFlags[i] = true; - break; - } else { - // 同类型碰撞:后来的让路(经 2 帧滞后防闪烁) - collisionFlags[i] = true; - break; - } - } - } - - // 第三遍:应用显示状态,ENG 碰撞立即隐藏,同类型 2 帧滞后防闪烁 - for (let i = 0; i < visibleRects.length; i++) { - const { element } = visibleRects[i].item; - const hidden = collisionFlags[i]; - - if (hidden && instantFlags[i]) { - // ENG 碰撞:立即隐藏,不经过滞后 - element.style.display = 'none'; - this.popupCollisionFrames.delete(element); - continue; - } - - const prevFrames = this.popupCollisionFrames.get(element) || 0; - if (hidden) { - const count = prevFrames > 0 ? prevFrames + 1 : 1; - this.popupCollisionFrames.set(element, count); - if (count >= 2) { - element.style.display = 'none'; - } - } else { - const count = prevFrames < 0 ? prevFrames - 1 : -1; - this.popupCollisionFrames.set(element, count); - if (count <= -2) { - element.style.display = 'block'; - } - } - } - } - - /** 批量模式下双缓冲重建 popup(图层/图例变化、拖拽/缩放 moveEnd 时调用)。 - * 在新隐藏容器中构建 popup,构建完成后替换旧容器,避免 clear→rebuild 空白帧闪烁。 */ - private _rebuildGeneration = 0; - - private rebuildBatchPopupsIfNeeded() { - if (!this.isBatchPopupMode || !this.containerElement) return; - - console.log( - '[Cesium] rebuildBatchPopups gen=%d', - this._rebuildGeneration + 1 - ); - - // 停止旧容器的 postRender 同步(旧 popup 在新容器就绪前保持显示) - this.clearBatchPopupPostRenderSync(); - - const oldContainer = this.batchPopupContainer; - const generation = ++this._rebuildGeneration; - - // 在隐藏容器中构建新 popup(尚未插入 DOM) - const newContainer = document.createElement('div'); - newContainer.className = 'batch-popup-container'; - newContainer.style.cssText = - 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1000;'; - newContainer.id = 'batch-popup-container'; - - this.batchPopupContainer = newContainer; - this.batchPopupItems = []; - this.popupCollisionFrames = new WeakMap(); - - const candidates = this.collectBatchPopupCandidates(); - this.buildBatchPopupsInContainer(newContainer, candidates).then(() => { - // 如果期间触发了新的重建,丢弃本次结果 - if (generation !== this._rebuildGeneration) { - newContainer.remove(); - return; - } - if (!this.isBatchPopupMode) { - newContainer.remove(); - return; - } - // 双缓冲替换:移除旧容器 → 插入新容器 → 注册 postRender - if (oldContainer) oldContainer.remove(); - this.containerElement!.appendChild(newContainer); - this.setupBatchPopupPostRenderSync(); - }); - } - - /** 清除所有批量 popup */ - private clearBatchPopups() { - this.batchPopupItems = []; - this.popupCollisionFrames = new WeakMap(); - if (this.batchPopupContainer) { - this.batchPopupContainer.remove(); - this.batchPopupContainer = null; - return; - } - const existing = document.getElementById('batch-popup-container'); - if (existing) { - existing.remove(); - } - } - - /** 简单碰撞检测(AABB + padding) */ - private checkPopupCollision( - a: { left: number; right: number; top: number; bottom: number }, - b: { left: number; right: number; top: number; bottom: number } - ): boolean { - const padding = 4; - return !( - a.right + padding < b.left || - a.left - padding > b.right || - a.bottom + padding < b.top || - a.top - padding > b.bottom - ); - } - - // ==================== destroy ==================== - - destroy(): void { - // 取消飞行超时和加载动画 - this.clearFlightFallbackTimer(); - this.hideLoadingOverlay(); - this._ready = false; - this._pendingOSGBItems = []; - - // 批量 popup 和 hover popup - this.isBatchPopupMode = false; - this.clearBatchPopupPostRenderSync(); - this.clearBatchPopups(); - this.hidePopup(); - - // hover 节流 requestAnimationFrame - if (this.hoverRafId !== null) { - cancelAnimationFrame(this.hoverRafId); - this.hoverRafId = null; - } - - // 相机事件监听 - if (this.removePostRenderListener) { - this.removePostRenderListener(); - this.removePostRenderListener = null; - } - if (this.removeCameraChangedListener) { - this.removeCameraChangedListener(); - this.removeCameraChangedListener = null; - } - - // 碰撞检测 rAF / 定时器 - if (this.collisionRefreshFrameId !== null) { - cancelAnimationFrame(this.collisionRefreshFrameId); - this.collisionRefreshFrameId = null; - } - if (this.labelVisibilityDebounceTimerId !== null) { - window.clearTimeout(this.labelVisibilityDebounceTimerId); - this.labelVisibilityDebounceTimerId = null; - } - - // 中止进行中的基地裁切请求 - if (this.clipRequestController) { - this.clipRequestController.abort(); - this.clipRequestController = null; - } - - // 清理遮罩多边形 Entity(需在 viewer.destroy 前清除) - if (this.viewer && !this.viewer.isDestroyed()) { - this.maskPolygonEntities.forEach(entity => { - this.viewer!.entities.remove(entity); - }); - } - this.maskPolygonEntities = []; - - // 清理锚点图层和底图注册表 - this.destroyAllPointLayers(); - this.pointLayerRegistry.clear(); - this.baseLayerRegistry.clear(); - this.baseLayerAliasMap.clear(); - this.imageryBaseLayer = null; - - // 清理 Cesium 事件处理器 - if (this.clickEventHandler) { - this.clickEventHandler.destroy(); - this.clickEventHandler = null; - } - this.popupElement = null; - - // 销毁 Cesium Viewer - if (this.viewer) { - this._rebuildGeneration++; // 中止进行中的双缓冲重建 - this.viewer.camera.cancelFlight(); - this.viewer.destroy(); - this.viewer = null; - } - this.containerElement = null; - - // 重置所有状态变量(确保下次 init 时是干净状态) - this.popupCollisionFrames = new WeakMap(); - this.currentClipGeoJson = null; - this.originalBgColor = null; - this.skyBoxShown = true; - this.skyAtmosphereShown = true; - this.hydropBaseConfig = null; - this.BASEID = ''; - this.hoveredEntityId = null; - - console.log('[Cesium] destroyed'); - } - - private requestRender() { - this.viewer?.scene.requestRender(); - } - - /** - * 标签文本格式化:与 2D formatPointLabelText 完全对齐 - * - 去除括号 ()() - * - 每行最多 12 个字符 - * - 13~24 字符 → 拆成两行 - * - 超过 24 字符 → 第二行末尾用 "..." 截断 - */ - private formatPointLabelText(text: string): string { - const normalizedText = String(text || '') - .replace(/[()()]/g, '') - .trim(); - if (!normalizedText) { - return ''; - } - - const maxLineLength = 12; - if (normalizedText.length <= maxLineLength) { - return normalizedText; - } - - const firstLine = normalizedText.slice(0, maxLineLength); - if (normalizedText.length <= maxLineLength * 2) { - return `${firstLine}\n${normalizedText.slice(maxLineLength)}`; - } - - const secondLine = `${normalizedText.slice( - maxLineLength, - maxLineLength + 9 - )}...`; - return `${firstLine}\n${secondLine}`; - } - - // ==================== Step 2: 基础底图 ==================== - - private getRegistryKeys(layerConfig: any): string[] { - const keys = [layerConfig?.key, layerConfig?.id].filter( - (key): key is string => typeof key === 'string' && key.length > 0 - ); - return Array.from(new Set(keys)); - } - - private resolvePrimaryBaseLayerKey(layerKey: string): string | undefined { - if (!layerKey) return undefined; - if (this.baseLayerRegistry.has(layerKey)) return layerKey; - return this.baseLayerAliasMap.get(layerKey); - } - - private unregisterBaseLayer(layerConfig: any) { - if (!this.viewer) return; - const registryKeys = this.getRegistryKeys(layerConfig); - const uniquePrimaryKeys = new Set(); - - registryKeys.forEach(key => { - const primaryKey = this.resolvePrimaryBaseLayerKey(key) || key; - uniquePrimaryKeys.add(primaryKey); - this.baseLayerAliasMap.delete(key); - }); - - uniquePrimaryKeys.forEach(primaryKey => { - const existingLayer = this.baseLayerRegistry.get(primaryKey); - if (existingLayer) { - this.viewer?.imageryLayers.remove(existingLayer, true); - this.baseLayerRegistry.delete(primaryKey); - } - }); - } - - private registerBaseLayer( - layerConfig: any, - imageryLayer: Cesium.ImageryLayer - ) { - const registryKeys = this.getRegistryKeys(layerConfig); - const primaryKey = layerConfig?.key || layerConfig?.id; - if (!primaryKey) return; - - this.baseLayerRegistry.set(primaryKey, imageryLayer); - registryKeys.forEach(key => { - this.baseLayerAliasMap.set(key, primaryKey); - }); - } - - private getLayerRequestUrl(layerConfig: any): string { - if (layerConfig?.key === 'customBaseLayer') { - return layerConfig?.url_3d; - } - return layerConfig?.url || layerConfig?.url_3d || ''; - } - - private createImageryLayer(layerConfig: any, visible: boolean) { - if (layerConfig?.key === 'hydropBase') return null; - if (!this.viewer) return null; - - const url = this.getLayerRequestUrl(layerConfig); - if (!url) return null; - - let imageryLayer: Cesium.ImageryLayer | null = null; - - // WMTS:与 2D addBaseDataLayer 对齐,从 URL 参数中提取 LAYER / TILEMATRIXSET - // 必须提供 tileMatrixLabels,否则 Cesium 默认用纯数字 tileMatrix 值, - // 而服务器期望的是 "TileMatrixSet:zoom" 格式(如 EPSG:3857_hbb_zrbhq_l13:4) - if (layerConfig?.type === 'wmts') { - const urlParts = url.split('?'); - const baseUrl = urlParts[0]; - const urlParams = new URLSearchParams(urlParts[1] || ''); - const wmtsLayer = urlParams.get('LAYER') || urlParams.get('layer') || ''; - const tileMatrixSetID = - urlParams.get('TILEMATRIXSET') || urlParams.get('TileMatrixSet') || ''; - const maxLevel = 19; - const tileMatrixLabels = Array.from( - { length: maxLevel + 1 }, - (_, i) => `${tileMatrixSetID}:${i}` - ); - - imageryLayer = this.viewer.imageryLayers.addImageryProvider( - new Cesium.WebMapTileServiceImageryProvider({ - url: baseUrl, - layer: wmtsLayer, - style: 'raster', - format: 'image/png', - tileMatrixSetID: tileMatrixSetID, - tileMatrixLabels: tileMatrixLabels, - maximumLevel: maxLevel - }) - ); - } else if ( - layerConfig?.type === 'raster-dem' || - /\{x\}|\{y\}|\{z\}/.test(url) - ) { - imageryLayer = this.viewer.imageryLayers.addImageryProvider( - new Cesium.UrlTemplateImageryProvider({ url }) - ); - } else if (url.includes('MapServer')) { - imageryLayer = this.viewer.imageryLayers.add( - Cesium.ImageryLayer.fromProviderAsync( - Cesium.ArcGisMapServerImageryProvider.fromUrl(url, { - enablePickFeatures: false, - maximumLevel: 19 - }) - ) - ); - } else { - const cleanUrl = url.split('?')[0]; - const wmsLayers = layerConfig?.layers || ''; - imageryLayer = this.viewer.imageryLayers.addImageryProvider( - new Cesium.WebMapServiceImageryProvider({ - url: cleanUrl, - layers: wmsLayers, - parameters: { - service: 'WMS', - transparent: 'true', - format: 'image/png' - } - }) - ); - } - - if (!imageryLayer) return null; - - imageryLayer.show = visible; - if (typeof layerConfig?.rasteropacity === 'number') { - imageryLayer.alpha = layerConfig.rasteropacity; - } - return imageryLayer; - } - - addBaseDataLayer(layerConfig: any, isShow = true): void { - if (!this.viewer || !layerConfig?.key) return; - - // 基地裁切配置存储(与 2D 对齐:layer.type === 'vector' && layer.key === 'hydropBase') - if (layerConfig.type === 'vector' && layerConfig.key === 'hydropBase') { - this.hydropBaseConfig = layerConfig; - return; - } - - if ( - this.IGNORED_BASE_LAYER_KEYS.has(layerConfig.key) || - this.IGNORED_BASE_LAYER_KEYS.has(layerConfig.id) - ) { - return; - } - - if (layerConfig.key === 'BASEMAP-img') { - if (this.imageryBaseLayer) { - this.imageryBaseLayer.show = !!isShow; - } - this.requestRender(); - return; - } - - const imageryLayer = this.createImageryLayer(layerConfig, !!isShow); - if (!imageryLayer) return; - - this.unregisterBaseLayer(layerConfig); - this.registerBaseLayer(layerConfig, imageryLayer); - this.requestRender(); - } - - controlBaseLayerTreeShowAndHidden( - layerType: string, - key: string, - checked: boolean - ): void { - const targetKey = key || layerType; - if (targetKey === 'BASEMAP-img') { - if (this.imageryBaseLayer) { - this.imageryBaseLayer.show = checked; - } - return; - } - - const primaryKey = this.resolvePrimaryBaseLayerKey(targetKey); - if (!primaryKey) return; - - const imageryLayer = this.baseLayerRegistry.get(primaryKey); - if (!imageryLayer) return; - - imageryLayer.show = checked; - this.requestRender(); - } - - hasBaseLayer(layerKey: string): boolean { - if (layerKey === 'BASEMAP-img') return !!this.imageryBaseLayer; - return !!this.resolvePrimaryBaseLayerKey(layerKey); - } - - // ==================== 其他公共方法 ==================== - - flyTopanto(position: number[], zoom: number): void { - if (!this.viewer || !position) return; - const [lng, lat] = position; - const height = 20000000 / Math.pow(2, zoom); - this.viewer.camera.cancelFlight(); - this.viewer.camera.flyTo({ - destination: Cesium.Cartesian3.fromDegrees( - lng, - lat, - height > 100 ? height : 1000 - ), - duration: 1.5 - }); - } - - getCurrentZoom(): number | undefined { - return this.getCurrentCesiumZoom(); - } - - private getCurrentCesiumZoom() { - if (!this.viewer) return undefined; - const height = this.viewer.camera.positionCartographic.height; - if (!height || !isFinite(height) || height <= 0) return undefined; - const zoom = - this.CESIUM_ZOOM_FORMULA_D + - (this.CESIUM_ZOOM_FORMULA_A - this.CESIUM_ZOOM_FORMULA_D) / - (1 + - Math.pow( - Number(height) / this.CESIUM_ZOOM_FORMULA_C, - this.CESIUM_ZOOM_FORMULA_B - )) + - 1; - return zoom > -1 ? zoom : 0; - } - - zoomToggle(type: 'out' | 'in'): void { - if (!this.viewer) return; - const factor = 1.5; - if (type === 'in') this.viewer.camera.zoomIn(factor); - else this.viewer.camera.zoomOut(factor); - } - - mapOutPut(fileName = '3D_截图.png'): void { - if (!this.viewer) { - console.warn('Viewer is not initialized.'); - return; - } - try { - const canvas = this.viewer.canvas; - this.viewer.render(); - const dataURL = canvas.toDataURL('image/png'); - if (dataURL.length < 100) { - console.warn( - 'Canvas data is too small, likely black or empty. Check CORS settings.' - ); - } - const link = document.createElement('a'); - link.href = dataURL; - link.download = fileName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } catch (error) { - console.error('Failed to export map image:', error); - } - } - - initPopupOverlay(popupContainer: HTMLDivElement): void { - this.popupElement = popupContainer; - if (this.popupElement) { - // 清除可能残留的旧样式 - this.popupElement.style.removeProperty('position'); - this.popupElement.style.removeProperty('transform'); - this.popupElement.style.removeProperty('left'); - this.popupElement.style.removeProperty('top'); - this.popupElement.style.display = 'none'; - this.popupElement.style.position = 'absolute'; - this.popupElement.style.transform = 'translate(-50%, calc(-100% - 10px))'; - this.popupElement.style.setProperty( - 'pointer-events', - 'none', - 'important' - ); - } - } - - // ==================== Step 3: 锚点展示 ==================== - - addInitDataLayer( - pointData: any, - layerType: any, - _mdoptions?: MDOptions - ): void { - if (!this.viewer) return; - - let dataArray: any[] = []; - let targetLayerKey = layerType; - - // 与 2D PointLayerManager.addDataLayer 对齐的输入归一化 - if (Array.isArray(pointData)) { - dataArray = pointData; - } else { - dataArray = pointData?.data || []; - targetLayerKey = pointData?.key || layerType; - } - - if (!targetLayerKey) { - console.warn('[Cesium] 缺少图层 Key,无法加载锚点'); - return; - } - - // 先移除旧图层数据 - this.removePointLayer(targetLayerKey); - - const entities: Cesium.Entity[] = []; - - dataArray.forEach((item: any) => { - const { lgtd, lttd, stnm, iconCode, titleName, ennm } = item; - - if (lgtd == null || lttd == null) return; - - const lon = Number(lgtd); - const lat = Number(lttd); - if (isNaN(lon) || isNaN(lat) || !isFinite(lon) || !isFinite(lat)) return; - if (Math.abs(lon) > 180 || Math.abs(lat) > 90) return; - - // 图标:复用现有图标资源路径,与 2D 一致 - const iconUrl = iconCode - ? getIconPath(iconCode) - : getIconPath('default') || ''; - - // 标签文本:格式化(换行截断)+ ylfb 用 ftp,其他用 titleName > stnm > ennm - const rawLabelText = - item.sttpMap === 'ylfb' - ? item.ftp || '' - : titleName || stnm || ennm || ''; - const labelText = this.formatPointLabelText(rawLabelText); - // 行数决定纵向偏移量,与 2D getPointLabelRenderOffsetY 对齐 - const labelLineCount = labelText.includes('\n') ? 2 : labelText ? 1 : 0; - - // Entity ID:与 2D 对齐,使用 _id 优先(_id = sttpMap_stcd),确保同 stcd 不同 sttpMap 的点不互相覆盖 - const rawId = - item._id || item.stcd || `${lon.toFixed(6)},${lat.toFixed(6)}`; - const entityId = `${targetLayerKey}:${rawId}`; - - // 图标缩放:使用 CallbackProperty 动态响应相机缩放变化 - // 调参见顶部 ICON_ 常量 - const dynamicIconScale = new Cesium.CallbackProperty(() => { - const zoom = this.getCurrentCesiumZoom() || 4.5; - return Math.max( - this.ICON_MIN_SCALE, - Math.min( - this.ICON_MAX_SCALE, - this.ICON_BASE_SCALE + (zoom - 4.5) * this.ICON_SCALE_RATE - ) - ); - }, false); - - // 字体大小:与 2D getPointFontSize 对齐 → fontSize = clamp(base * scale, min, max) - // 调参见顶部 LABEL_FONT_ 常量 - const dynamicLabelFont = new Cesium.CallbackProperty(() => { - const zoom = this.getCurrentCesiumZoom() || 4.5; - const scale = Math.max(0.5, Math.min(3.0, 0.7 + (zoom - 4.5) * 0.08)); - const fontSize = Math.round( - Math.max( - this.LABEL_FONT_MIN, - Math.min(this.LABEL_FONT_MAX, this.LABEL_FONT_BASE * scale) - ) - ); - return `${fontSize}px sans-serif`; - }, false); - - // 标签纵向偏移:与 2D getPointLabelRenderOffsetY 对齐 - // 调参见顶部 LABEL_OFFSET_ 常量 - const dynamicLabelOffset = new Cesium.CallbackProperty(() => { - const zoom = this.getCurrentCesiumZoom() || 4.5; - const scale = Math.max(0.5, Math.min(3.0, 0.7 + (zoom - 4.5) * 0.08)); - const offsetY = - labelLineCount > 1 - ? -this.LABEL_OFFSET_MULTI * scale - : -this.LABEL_OFFSET_SINGLE * scale; - return new Cesium.Cartesian2(0, offsetY); - }, false); - - const entity = new Cesium.Entity({ - id: entityId, - position: Cesium.Cartesian3.fromDegrees(lon, lat, 0), - billboard: { - image: iconUrl || undefined, - scale: dynamicIconScale as any, - verticalOrigin: Cesium.VerticalOrigin.CENTER, - horizontalOrigin: Cesium.HorizontalOrigin.CENTER, - scaleByDistance: new Cesium.NearFarScalar(1.0e3, 1.5, 2.0e7, 0.8), - heightReference: Cesium.HeightReference.CLAMP_TO_GROUND - } as any, - label: { - text: labelText || undefined, - font: dynamicLabelFont as any, - fillColor: Cesium.Color.WHITE, - outlineColor: Cesium.Color.BLACK, - outlineWidth: 2, - style: Cesium.LabelStyle.FILL_AND_OUTLINE, - verticalOrigin: Cesium.VerticalOrigin.BOTTOM, - pixelOffset: dynamicLabelOffset as any, - horizontalOrigin: Cesium.HorizontalOrigin.CENTER, - scaleByDistance: new Cesium.NearFarScalar(1.0e3, 1.2, 2.0e7, 0.8), - heightReference: Cesium.HeightReference.CLAMP_TO_GROUND - } as any, - properties: { - ...item, - _layerKey: targetLayerKey, - _iconUrl: iconUrl, - _labelText: labelText, - _legendVisible: true, - _regionVisible: true, - _sttpMap: item.sttpMap - } as any - }); - - this.viewer!.entities.add(entity); - // 存储原始业务数据引用,避免点击时遍历 Cesium Property - (entity as any)._rawData = item; - (entity as any)._labelText = labelText; - // 可见性控制标志(组合计算 entity.show) - (entity as any)._layerVisible = true; - (entity as any)._legendVisible = true; - (entity as any)._regionVisible = true; - // 碰撞检测可见性标志(初始均为可见,碰撞检测会按需更新) - (entity as any)._iconCollisionVisible = true; - (entity as any)._labelCollisionVisible = true; - // 初始应用可见性(确保 entity.show / entity.label.show 被显式设置) - this.applyEntityVisibility(entity); - entities.push(entity); - }); - - this.pointLayerRegistry.set(targetLayerKey, entities); - - // 如果当前有活跃的基地裁切,新图层也需要过滤 - if (this.currentClipGeoJson) { - this.filterPointsByRegionForLayer( - targetLayerKey, - this.extractPolygonCoords(this.currentClipGeoJson) - ); - } - - // 数据加载完成后触发首次碰撞检测(同步执行,避免异步 rAF 导致的首帧闪烁) - this.requestRefreshPointLabelVisibility(true); - } - - mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void { - const entities = this.pointLayerRegistry.get(layerType); - if (!entities) return; - - const visible = checked !== false; - entities.forEach(entity => { - (entity as any)._layerVisible = visible; - this.applyEntityVisibility(entity); - }); - - // 图层切换后立即重算碰撞(同步执行,避免闪烁) - this.requestRefreshPointLabelVisibility(true); - // 批量模式下同步重建 popup - this.rebuildBatchPopupsIfNeeded(); - } - - removePointLayer(layerKey: string): void { - if (!layerKey) return; - - const entities = this.pointLayerRegistry.get(layerKey); - if (!entities) return; - - entities.forEach(entity => { - if (this.viewer && !this.viewer.isDestroyed()) { - this.viewer.entities.remove(entity); - } - }); - this.pointLayerRegistry.delete(layerKey); - } - - hasLayer(layerKey: string): boolean { - return this.pointLayerRegistry.has(layerKey); - } - - private destroyAllPointLayers(): void { - this.pointLayerRegistry.forEach((entities, layerKey) => { - entities.forEach(entity => { - if (this.viewer && !this.viewer.isDestroyed()) { - this.viewer.entities.remove(entity); - } - }); - }); - this.pointLayerRegistry.clear(); - } - - // ==================== Stubs(后续步骤补齐) ==================== - - baseLayerSwitcher(_key: string): void {} - switchView(_type: '2D' | '3D'): void {} - fitBounds(_bounds: any): void {} - lengthCalculate(): void {} - areCalculate(): void {} - removeQueryLayer(): void {} - addTertiarybasinLayer( - _layer: layer, - _fillcolor: any, - _outlineColor: any, - _datas: any - ): void {} - hideTertiarybasinLayer(_layer: layer): void {} - async jdPanelControlShowAndHidden( - _regionId: string, - isAll: boolean - ): Promise { - if (!this.viewer || !this.hydropBaseConfig) { - console.warn('地图未初始化或 hydropBaseConfig 未配置'); - return; - } - - // 取消正在进行的裁切请求 - if (this.clipRequestController) { - this.clipRequestController.abort(); - this.clipRequestController = null; - } - - this.BASEID = _regionId; - - if (!isAll) { - // 取消裁切:清除遮罩多边形,恢复全部锚点可见,平滑飞回全国视图 - this.currentClipGeoJson = null; - this.clearMaskPolygon(); - this.setAllPointsRegionVisible(true); - this.viewer.camera.flyTo({ - destination: Cesium.Cartesian3.fromDegrees( - this.CHINA_CENTER.lng, - this.CHINA_CENTER.lat, - this.CHINA_DEFAULT_HEIGHT - ), - orientation: this.CHINA_VIEW_ORIENTATION, - duration: 1.0 - }); - return; - } - - // 用户从"全部"进入,锚点本来就全可见,无需 API 前先隐藏(避免全量遍历卡顿) - this.clipRequestController = new AbortController(); - const signal = this.clipRequestController.signal; - - try { - const url = - this.hydropBaseConfig.geojson_url + - `&cql_filter=BASEID='${this.BASEID}'`; - - const geoJsonData = await this.fetchGeoJson(url, signal); - - if (signal.aborted) return; - if (this.BASEID !== _regionId) return; - - this.currentClipGeoJson = geoJsonData; - const regionCoords = this.extractPolygonCoords(geoJsonData); - this.filterPointsByRegion(regionCoords); - this.fitViewToGeoJson(regionCoords); - this.createMaskPolygon(regionCoords); - } catch (error: any) { - if (error.name === 'AbortError') return; - this.currentClipGeoJson = null; - console.error('加载裁切数据失败:', error); - this.clearMaskPolygon(); - this.setAllPointsRegionVisible(true); - } finally { - if (this.clipRequestController?.signal === signal) { - this.clipRequestController = null; - } - } - } - mdLayerShowOrHidden( - _layerType: string, - _key?: string, - _baseid?: string, - _checked?: boolean, - _isAll?: boolean - ): void {} - setLegendPointVisible( - layerKey: string, - anchoPointState: string, - checked: boolean - ): void { - const entities = this.pointLayerRegistry.get(layerKey); - if (!entities) return; - - if (!anchoPointState) { - // 第一步:全部隐藏(配合 restoreLayerLegendVisibility 两步走:先全隐藏再按图例逐项恢复) - entities.forEach(entity => { - (entity as any)._legendVisible = false; - this.applyEntityVisibility(entity); - }); - this.requestRefreshPointLabelVisibility(true); - this.rebuildBatchPopupsIfNeeded(); - return; - } - - // 按 anchoPointState 字段匹配并设置可见性 - entities.forEach(entity => { - const rawData = (entity as any)._rawData; - if (rawData && rawData.anchoPointState === anchoPointState) { - (entity as any)._legendVisible = checked; - this.applyEntityVisibility(entity); - } - }); - - // 图例切换后立即重算碰撞(同步执行,避免闪烁) - this.requestRefreshPointLabelVisibility(true); - this.rebuildBatchPopupsIfNeeded(); - } - - // ==================== 基地裁切辅助方法 ==================== - - /** 获取 GeoJSON 数据 */ - private async fetchGeoJson(url: string, signal?: AbortSignal): Promise { - const response = await fetch(url, { signal }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - } - - /** 批量设置全部锚点的 _regionVisible 标志 */ - private setAllPointsRegionVisible(visible: boolean): void { - this.pointLayerRegistry.forEach(entities => { - entities.forEach(entity => { - (entity as any)._regionVisible = visible; - this.applyEntityVisibility(entity); - }); - }); - } - - /** 根据 GeoJSON 边界批量更新锚点的区域显隐状态(射线法) */ - private filterPointsByRegion(regionCoords: number[][][]): void { - if (!regionCoords.length) { - console.warn('无法解析区域边界,显示所有锚点'); - this.setAllPointsRegionVisible(true); - return; - } - - this.pointLayerRegistry.forEach((_entities, layerKey) => { - this.filterPointsByRegionForLayer(layerKey, regionCoords); - }); - } - - /** 对单个图层应用基地裁切过滤 */ - private filterPointsByRegionForLayer( - layerKey: string, - regionCoords: number[][][] - ): void { - const entities = this.pointLayerRegistry.get(layerKey); - if (!entities || !regionCoords.length) return; - - entities.forEach(entity => { - const rawData = (entity as any)._rawData; - if (!rawData) { - (entity as any)._regionVisible = false; - this.applyEntityVisibility(entity); - return; - } - - const lon = Number(rawData.lgtd); - const lat = Number(rawData.lttd); - - if (!isFinite(lon) || !isFinite(lat)) { - (entity as any)._regionVisible = false; - this.applyEntityVisibility(entity); - return; - } - - (entity as any)._regionVisible = this.isPointInPolygon( - lon, - lat, - regionCoords - ); - this.applyEntityVisibility(entity); - }); - } - - /** 根据多边形坐标飞行相机到基地边界 */ - private fitViewToGeoJson(regionCoords: number[][][]): void { - if (!this.viewer) return; - - try { - if (!regionCoords.length) return; - - // 计算所有坐标的经纬度范围 - let minLon = Infinity; - let maxLon = -Infinity; - let minLat = Infinity; - let maxLat = -Infinity; - - regionCoords.forEach(ring => { - ring.forEach(([lon, lat]) => { - if (lon < minLon) minLon = lon; - if (lon > maxLon) maxLon = lon; - if (lat < minLat) minLat = lat; - if (lat > maxLat) maxLat = lat; - }); - }); - - if (!isFinite(minLon)) return; - - const rectangle = Cesium.Rectangle.fromDegrees( - minLon, - minLat, - maxLon, - maxLat - ); - - this.viewer.camera.flyTo({ - destination: rectangle, - duration: 1.0, - complete: () => this.requestRender() - }); - } catch (error) { - console.error('调整3D视野失败:', error); - } - } - - /** 应用基地裁切:globe.clippingPolygons 直接裁切地球渲染 */ - private createMaskPolygon(regionCoords: number[][][]): void { - if (!this.viewer) return; - - // 仅清理旧边框(不重置 clipping/背景色/skyBox,避免 GPU 状态反复重建) - this.maskPolygonEntities.forEach(entity => { - if (!this.viewer!.isDestroyed()) { - this.viewer!.entities.remove(entity); - } - }); - this.maskPolygonEntities = []; - - if (!regionCoords.length) return; - - const cartesianRings = regionCoords.map(ring => - Cesium.Cartesian3.fromDegreesArray( - ring.flatMap(([lon, lat]) => [lon, lat]) - ) - ); - - // 首次进入裁切时保存原始状态 - if (!this.originalBgColor) { - this.originalBgColor = this.viewer.scene.backgroundColor.clone(); - this.skyBoxShown = this.viewer.scene.skyBox?.show ?? true; - this.skyAtmosphereShown = this.viewer.scene.skyAtmosphere?.show ?? true; - } - // 边界外设为白色 - if (this.viewer.scene.skyBox) this.viewer.scene.skyBox.show = false; - if (this.viewer.scene.skyAtmosphere) - this.viewer.scene.skyAtmosphere.show = false; - this.viewer.scene.backgroundColor = Cesium.Color.WHITE; - - // 直接设新裁切(不经过空集合,避免 GPU shader 重复编译) - this.viewer.scene.globe.clippingPolygons = - new Cesium.ClippingPolygonCollection({ - polygons: cartesianRings.map( - positions => new Cesium.ClippingPolygon({ positions }) - ), - inverse: true, - enabled: true - }); - - // 绘制基地边界线(双线视觉效果:紫色 + 浅紫色) - const borderStyles = [ - { width: 3, color: '#6D64DF' }, - { width: 1.5, color: '#CCC9F4' } - ]; - borderStyles.forEach(({ width, color }) => { - cartesianRings.forEach(positions => { - this.maskPolygonEntities.push( - this.viewer!.entities.add({ - polyline: { - positions, - width, - material: Cesium.Color.fromCssColorString(color), - clampToGround: true - } - }) - ); - }); - }); - - this.requestRender(); - this.viewer.scene.screenSpaceCameraController.maximumZoomDistance = 6_400_000; - } - - /** 清除遮罩多边形 */ - private clearMaskPolygon(): void { - if (!this.viewer) return; - - // 解除 globe 裁切 - this.viewer.scene.globe.clippingPolygons = - new Cesium.ClippingPolygonCollection(); - - // 恢复背景色、星空、大气层 - if (this.originalBgColor) { - this.viewer.scene.backgroundColor = this.originalBgColor; - this.originalBgColor = null; - } - if (this.viewer.scene.skyBox) { - this.viewer.scene.skyBox.show = this.skyBoxShown; - } - if (this.viewer.scene.skyAtmosphere) { - this.viewer.scene.skyAtmosphere.show = this.skyAtmosphereShown; - } - - this.viewer.scene.screenSpaceCameraController.maximumZoomDistance = - Infinity; - - this.maskPolygonEntities.forEach(entity => { - if (!this.viewer!.isDestroyed()) { - this.viewer!.entities.remove(entity); - } - }); - this.maskPolygonEntities = []; - } - - /** 从 GeoJSON 中提取多边形环坐标 */ - private extractPolygonCoords(geoJson: any): number[][][] { - if (!geoJson?.features?.length) return []; - - const coords: number[][][] = []; - geoJson.features.forEach((feature: any) => { - const geometry = feature.geometry; - if (!geometry) return; - - if (geometry.type === 'Polygon') { - coords.push(...geometry.coordinates); - } else if (geometry.type === 'MultiPolygon') { - geometry.coordinates.forEach((polygon: number[][][]) => { - coords.push(...polygon); - }); - } - }); - - return coords; - } - - /** 射线法判断点是否在任意一个多边形环内 */ - private isPointInPolygon( - lon: number, - lat: number, - polygons: number[][][] - ): boolean { - for (const polygon of polygons) { - if (this.isPointInRing(lon, lat, polygon)) { - return true; - } - } - return false; - } - - /** 射线法判断点是否在单个闭合环内 */ - private isPointInRing(lon: number, lat: number, ring: number[][]): boolean { - let inside = false; - const n = ring.length; - - for (let i = 0, j = n - 1; i < n; j = i++) { - const xi = ring[i][0]; - const yi = ring[i][1]; - const xj = ring[j][0]; - const yj = ring[j][1]; - - const intersect = - yi > lat !== yj > lat && - lon < ((xj - xi) * (lat - yi)) / (yj - yi) + xi; - - if (intersect) inside = !inside; - } - - return inside; - } - - /** - * 组合计算可见性(3D 解耦模型): - * - entity.show = 基础可见(layer && legend && region),保持 entity 始终"在作用域内" - * - entity.billboard.show = 基础可见 && 图标碰撞通过 - * - entity.label.show = 基础可见 && 标签碰撞通过(图标碰撞不影响标签) - */ - private applyEntityVisibility(entity: Cesium.Entity): void { - const e = entity as any; - const baseVisible = e._layerVisible && e._legendVisible && e._regionVisible; - // entity 整体始终基于 layer/legend/region(不受碰撞影响,保证 popup/click 等交互不受碰撞干扰) - entity.show = baseVisible; - // 图标单独受碰撞控制 - if (entity.billboard) { - (entity.billboard as any).show = - baseVisible && e._iconCollisionVisible !== false; - } - // 标签单独受碰撞控制(不与图标碰撞联动) - if (entity.label) { - (entity.label as any).show = - baseVisible && e._labelCollisionVisible !== false; - } - } - - // ==================== Step 6: 碰撞检测 ==================== - - /** - * 触发碰撞检测刷新(带 rAF 节流,与 2D requestRefreshPointLabelVisibility 对齐) - */ - private requestRefreshPointLabelVisibility( - immediate = false, - debounceMs = 0 - ) { - if (this.labelVisibilityDebounceTimerId !== null) { - window.clearTimeout(this.labelVisibilityDebounceTimerId); - this.labelVisibilityDebounceTimerId = null; - } - - if (immediate) { - if (this.collisionRefreshFrameId !== null) { - cancelAnimationFrame(this.collisionRefreshFrameId); - this.collisionRefreshFrameId = null; - } - this.refreshPointLabelVisibility(); - return; - } - - if (debounceMs > 0) { - this.labelVisibilityDebounceTimerId = window.setTimeout(() => { - this.labelVisibilityDebounceTimerId = null; - this.requestRefreshPointLabelVisibility(); - }, debounceMs); - return; - } - - if (this.collisionRefreshFrameId !== null) return; - - this.collisionRefreshFrameId = window.requestAnimationFrame(() => { - this.collisionRefreshFrameId = null; - this.refreshPointLabelVisibility(); - }); - } - - /** - * 主碰撞检测:密度门控 → 图标碰撞 → 标签碰撞 - * 与 2D refreshPointLabelVisibility 核心算法完全对齐 - */ - private refreshPointLabelVisibility(): void { - if (!this.viewer) return; - - const currentZoom = this.getCurrentCesiumZoom(); - if (currentZoom === undefined || currentZoom === null) return; - - console.log( - `[Cesium Collision] zoom=${currentZoom?.toFixed(2)} layers=${ - this.pointLayerRegistry.size - }` - ); - - // ===== 阶段0:收集视口内所有基础可见实体 ===== - type CollisionCandidate = { - entity: Cesium.Entity; - iconLeft: number; - iconRight: number; - iconTop: number; - iconBottom: number; - hasLabel: boolean; - labelLeft: number; - labelRight: number; - labelTop: number; - labelBottom: number; - engPriority: number; - nearbyPriority: number; - densityPriority: number; - pixelX: number; - pixelY: number; - id: string; - }; - - const candidates: CollisionCandidate[] = []; - const canvas = this.viewer.scene.canvas; - const viewPadding = 120; // 视口外的宽容范围 - - // 孤立点密度网格:用于 shouldBypassDensityGate - const densityPixelGrid = new Map< - string, - Array<{ - entity: Cesium.Entity; - layerKey: string; - pixelX: number; - pixelY: number; - }> - >(); - - const dynamicScale = Math.max( - 0.5, - Math.min(3.0, 0.7 + (currentZoom - 4.5) * 0.08) - ); - const fontSize = Math.max(10, Math.min(24, 12 * dynamicScale)); - - // 第一遍:密度门控 + 构建候选 - this.pointLayerRegistry.forEach((entities, layerKey) => { - entities.forEach(entity => { - const e = entity as any; - - // 基础可见性检查(layer/legend/region) - if ( - e._layerVisible === false || - e._legendVisible === false || - e._regionVisible === false - ) { - e._iconCollisionVisible = false; - e._labelCollisionVisible = false; - this.applyEntityVisibility(entity); - return; - } - - const position = entity.position?.getValue( - this.viewer!.clock.currentTime - ); - if (!position) { - e._iconCollisionVisible = false; - e._labelCollisionVisible = false; - this.applyEntityVisibility(entity); - return; - } - - const screenPos = Cesium.SceneTransforms.worldToWindowCoordinates( - this.viewer!.scene, - position - ); - if (!screenPos) { - e._iconCollisionVisible = false; - e._labelCollisionVisible = false; - this.applyEntityVisibility(entity); - return; - } - - const pixelX = screenPos.x; - const pixelY = screenPos.y; - - // 视口裁剪:太远的实体不参与碰撞,但保留其当前碰撞状态(不重置) - if ( - pixelX < -viewPadding || - pixelY < -viewPadding || - pixelX > canvas.clientWidth + viewPadding || - pixelY > canvas.clientHeight + viewPadding - ) { - return; - } - - // 密度 gating(与 2D getFeatureDensityMinZoom / shouldRenderFeatureByDensity 对齐) - const densityMinZoom = this.getEntityDensityMinZoom(entity); - const bypassDensity = - currentZoom < densityMinZoom && - this.shouldBypassDensityGateForIsolatedEntity( - entity, - pixelX, - pixelY, - layerKey, - densityPixelGrid - ); - const densityVisible = currentZoom >= densityMinZoom || bypassDensity; - - if (!densityVisible) { - e._iconCollisionVisible = false; - e._labelCollisionVisible = false; - this.applyEntityVisibility(entity); - return; - } - - // 构建图标碰撞盒(与 2D 一致:Math.max(14, 24 * dynamicScale)) - const iconCollisionSize = Math.max(14, 24 * dynamicScale); - const iconLeft = pixelX - iconCollisionSize / 2; - const iconRight = pixelX + iconCollisionSize / 2; - const iconTop = pixelY - iconCollisionSize / 2; - const iconBottom = pixelY + iconCollisionSize / 2; - - // 构建标签碰撞盒 - const labelText: string = e._labelText || ''; - const hasLabel = !!labelText; - let labelLeft = 0; - let labelRight = 0; - let labelTop = 0; - let labelBottom = 0; - - if (hasLabel) { - const lines = labelText.split('\n'); - const maxLineLength = Math.max( - ...lines.map((l: string) => l.length), - 1 - ); - const labelLineCount = lines.length; - // 标签碰撞偏移与 2D getPointLabelCollisionOffsetY 对齐 - const labelOffsetY = - labelLineCount > 1 ? -36 * dynamicScale : -28 * dynamicScale; - const estimatedWidth = maxLineLength * fontSize * 0.6 + 16; - const estimatedHeight = labelLineCount * (fontSize + 4) + 8; - const centerY = pixelY + labelOffsetY; - labelLeft = pixelX - estimatedWidth / 2; - labelRight = pixelX + estimatedWidth / 2; - labelTop = centerY - estimatedHeight / 2; - labelBottom = centerY + estimatedHeight / 2; - } - - const entityId = String(entity.id || ''); - const rawData: Record = e._rawData || {}; - - candidates.push({ - entity, - iconLeft, - iconRight, - iconTop, - iconBottom, - hasLabel, - labelLeft, - labelRight, - labelTop, - labelBottom, - engPriority: this.getEntityEngRenderPriority(entity), - nearbyPriority: Number(rawData._nearbyPriority || 9999), - densityPriority: this.getEntityDensityPriority(entity), - pixelX, - pixelY, - id: entityId - }); - }); - }); - - if (candidates.length === 0) return; - - // ===== 阶段1:排序(eng优先级 > 近邻点优先级 > 密度优先级 > Y轴 > ID) ===== - candidates.sort((left, right) => { - if (left.engPriority !== right.engPriority) { - return right.engPriority - left.engPriority; - } - if (left.nearbyPriority !== right.nearbyPriority) { - return left.nearbyPriority - right.nearbyPriority; - } - if (left.densityPriority !== right.densityPriority) { - return left.densityPriority - right.densityPriority; - } - if (left.pixelY !== right.pixelY) { - return left.pixelY - right.pixelY; - } - return left.id.localeCompare(right.id); - }); - - // ===== 阶段2:图标碰撞(AABB + 空间网格加速) ===== - const iconPlacedGrid = new Map< - string, - Array<{ - left: number; - right: number; - top: number; - bottom: number; - }> - >(); - - candidates.forEach(candidate => { - const iconRect = { - left: candidate.iconLeft, - right: candidate.iconRight, - top: candidate.iconTop, - bottom: candidate.iconBottom - }; - - const hasCollision = this.findCollidingRectInGrid( - iconRect, - iconPlacedGrid, - this.COLLISION_GRID_CELL_SIZE, - placed => this.checkIconsAabbCollision(placed, iconRect) - ); - - const iconVisible = !hasCollision; - (candidate.entity as any)._iconCollisionVisible = iconVisible; - - if (iconVisible) { - this.addRectToCollisionGrid( - iconRect, - iconPlacedGrid, - this.COLLISION_GRID_CELL_SIZE - ); - } - }); - - // ===== 阶段3:标签碰撞(先避让图标,再避让标签) ===== - const labelCandidates = [...candidates].sort((left, right) => { - if (left.engPriority !== right.engPriority) - return right.engPriority - left.engPriority; - if (left.nearbyPriority !== right.nearbyPriority) - return left.nearbyPriority - right.nearbyPriority; - if (left.pixelY !== right.pixelY) return left.pixelY - right.pixelY; - if (left.pixelX !== right.pixelX) return left.pixelX - right.pixelX; - return left.id.localeCompare(right.id); - }); - - const labelPlacedGrid = new Map< - string, - Array<{ - left: number; - right: number; - top: number; - bottom: number; - }> - >(); - - labelCandidates.forEach(candidate => { - const e = candidate.entity as any; - - if (!candidate.hasLabel) { - e._labelCollisionVisible = false; - return; - } - - // 3D 解耦:图标被碰撞隐藏不影响标签显示 - // 标签仍会避让已放置的图标(见下方 iconPlacedGrid 检查) - - const labelRect = { - left: candidate.labelLeft, - right: candidate.labelRight, - top: candidate.labelTop, - bottom: candidate.labelBottom - }; - - // 先避让已放置的图标(缩小保护区4px,允许边缘擦过) - const hasIconCollision = this.findCollidingRectInGrid( - labelRect, - iconPlacedGrid, - this.COLLISION_GRID_CELL_SIZE, - iconRect => { - const inset = 4; - const narrowed = { - left: iconRect.left + inset, - right: iconRect.right - inset, - top: iconRect.top + inset, - bottom: iconRect.bottom - inset - }; - return !( - narrowed.right <= labelRect.left || - narrowed.left >= labelRect.right || - narrowed.bottom <= labelRect.top || - narrowed.top >= labelRect.bottom - ); - } - ); - - if (hasIconCollision) { - e._labelCollisionVisible = false; - return; - } - - // 再避让已放置的标签 - const hasLabelCollision = this.findCollidingRectInGrid( - labelRect, - labelPlacedGrid, - this.COLLISION_GRID_CELL_SIZE, - placed => this.checkLabelAabbCollision(placed, labelRect) - ); - - const labelVisible = !hasLabelCollision; - e._labelCollisionVisible = labelVisible; - - if (labelVisible) { - this.addRectToCollisionGrid( - labelRect, - labelPlacedGrid, - this.COLLISION_GRID_CELL_SIZE - ); - } - }); - - // ===== 阶段4:统一应用可见性变化 ===== - let iconVisibleCount = 0; - let labelVisibleCount = 0; - candidates.forEach(candidate => { - const e = candidate.entity as any; - if (e._iconCollisionVisible) iconVisibleCount++; - if (e._labelCollisionVisible) labelVisibleCount++; - this.applyEntityVisibility(candidate.entity); - }); - - console.log( - `[Cesium Collision] 候选=${candidates.length} 图标可见=${iconVisibleCount} 标签可见=${labelVisibleCount}` - ); - } - - // ==================== 碰撞检测辅助:密度门控 ==================== - - /** 从实体原始数据中提取 density 字段(密度分档值,非物理距离) */ - private getEntityDensityValue(entity: Cesium.Entity): number | null { - const rawData = (entity as any)._rawData; - if (!rawData) return null; - const rawDistance = rawData.distance; - if ( - rawDistance === undefined || - rawDistance === null || - rawDistance === '' - ) { - return null; - } - const densityValue = Number(rawDistance); - return Number.isFinite(densityValue) ? densityValue : null; - } - - /** 密度优先级(档位越低越优先,与 2D getFeatureDensityPriority 对齐) */ - private getEntityDensityPriority(entity: Cesium.Entity): number { - const densityValue = this.getEntityDensityValue(entity); - const densityDisplayRules = getNearbyPointDensityDisplayRules(); - if (densityValue === null) { - return densityDisplayRules.length; - } - const matchedRuleIndex = densityDisplayRules.findIndex(rule => { - return densityValue >= rule.minDensityValue; - }); - return matchedRuleIndex >= 0 - ? matchedRuleIndex - : densityDisplayRules.length; - } - - /** 根据密度值获取最低显示缩放级别(与 2D getFeatureDensityMinZoom 对齐) */ - private getEntityDensityMinZoom(entity: Cesium.Entity): number { - const densityValue = this.getEntityDensityValue(entity); - if (densityValue === null) return 0; - const densityDisplayRules = getNearbyPointDensityDisplayRules(); - const matchedRule = densityDisplayRules.find(rule => { - return densityValue >= rule.minDensityValue; - }); - if (matchedRule) return matchedRule.minZoom; - return densityDisplayRules.at(-1)?.minZoom ?? 0; - } - - /** - * 孤立点密度豁免:如果当前视口内 56px 范围内没有其他同图层可见点, - * 则跳过密度门控直接显示(与 2D shouldBypassDensityGateForIsolatedFeature 对齐) - */ - private shouldBypassDensityGateForIsolatedEntity( - entity: Cesium.Entity, - pixelX: number, - pixelY: number, - layerKey: string, - densityPixelGrid: Map< - string, - Array<{ - entity: Cesium.Entity; - layerKey: string; - pixelX: number; - pixelY: number; - }> - > - ): boolean { - // 先登记当前实体到密度网格 - const gridKey = `${layerKey}:${Math.floor( - pixelX / this.DENSITY_ISOLATION_THRESHOLD - )}:${Math.floor(pixelY / this.DENSITY_ISOLATION_THRESHOLD)}`; - const bucket = densityPixelGrid.get(gridKey) || []; - bucket.push({ entity, layerKey, pixelX, pixelY }); - densityPixelGrid.set(gridKey, bucket); - - // 检查周围 3x3 网格单元 - const checkedKeys = new Set(); - for (let dx = -1; dx <= 1; dx++) { - for (let dy = -1; dy <= 1; dy++) { - const neighborKey = `${layerKey}:${ - Math.floor(pixelX / this.DENSITY_ISOLATION_THRESHOLD) + dx - }:${Math.floor(pixelY / this.DENSITY_ISOLATION_THRESHOLD) + dy}`; - if (checkedKeys.has(neighborKey)) continue; - checkedKeys.add(neighborKey); - - const neighborBucket = densityPixelGrid.get(neighborKey); - if (!neighborBucket?.length) continue; - - for (const neighbor of neighborBucket) { - if (neighbor.entity === entity) continue; - const dist = Math.hypot( - neighbor.pixelX - pixelX, - neighbor.pixelY - pixelY - ); - if (dist <= this.DENSITY_ISOLATION_THRESHOLD) { - return false; // 有近邻点,不豁免 - } - } - } - } - - return true; // 孤立点,豁免密度门控 - } - - // ==================== 碰撞检测辅助:eng 优先级 ==================== - - /** 判断是否为 eng 点(与 2D isFeatureEng 对齐) */ - private isEntityEng(entity: Cesium.Entity): boolean { - const e = entity as any; - const rawData: Record = e._rawData || {}; - const layerKey = String(e._layerKey || '').toLowerCase(); - const sttpMap = String( - rawData.sttpMap || rawData._sttpMap || '' - ).toUpperCase(); - const sttpCode = String( - rawData.sttpCode || rawData.sttp || '' - ).toUpperCase(); - - return ( - layerKey.includes('eng_point') || - sttpMap === 'ENG' || - sttpMap === 'ENG2' || - sttpCode === 'ENG' - ); - } - - /** 判断是否为 eng 告警点(与 2D isFeatureEngAlarm 对齐) */ - private isEntityEngAlarm(entity: Cesium.Entity): boolean { - const e = entity as any; - const rawData: Record = e._rawData || {}; - const layerKey = String(e._layerKey || '').toLowerCase(); - const legendState = String(rawData.anchoPointState || '') - .trim() - .toLowerCase(); - - return ( - layerKey.includes('eng_alarm_point') || - layerKey.includes('alarm_range') || - legendState.startsWith('alarm_range_') || - legendState.startsWith('large_eng_built_alarm_range_') || - legendState.startsWith('mid_eng_built_alarm_range_') - ); - } - - /** 获取 eng 渲染优先级:告警=300, eng=200, 普通=100(与 2D 完全一致) */ - private getEntityEngRenderPriority(entity: Cesium.Entity): number { - if (this.isEntityEngAlarm(entity)) return 300; - return this.isEntityEng(entity) ? 200 : 100; - } - - // ==================== 碰撞检测辅助:空间网格与 AABB ==================== - - /** 计算矩形覆盖的碰撞网格 cell keys */ - private getCollisionGridKeys( - rect: { - left: number; - right: number; - top: number; - bottom: number; - }, - cellSize: number - ): string[] { - const startX = Math.floor(rect.left / cellSize); - const endX = Math.floor(rect.right / cellSize); - const startY = Math.floor(rect.top / cellSize); - const endY = Math.floor(rect.bottom / cellSize); - const keys: string[] = []; - - for (let x = startX; x <= endX; x += 1) { - for (let y = startY; y <= endY; y += 1) { - keys.push(`${x}:${y}`); - } - } - - return keys; - } - - /** 将矩形注册到碰撞网格 */ - private addRectToCollisionGrid< - T extends { - left: number; - right: number; - top: number; - bottom: number; - } - >(rect: T, grid: Map, cellSize: number) { - this.getCollisionGridKeys(rect, cellSize).forEach(key => { - const bucket = grid.get(key) || []; - bucket.push(rect); - grid.set(key, bucket); - }); - } - - /** 在碰撞网格中查找与给定矩形碰撞的矩形 */ - private findCollidingRectInGrid< - T extends { - left: number; - right: number; - top: number; - bottom: number; - } - >( - rect: { - left: number; - right: number; - top: number; - bottom: number; - }, - grid: Map, - cellSize: number, - isCollision: (candidate: T) => boolean - ): T | undefined { - for (const key of this.getCollisionGridKeys(rect, cellSize)) { - const bucket = grid.get(key); - if (!bucket?.length) continue; - - for (const candidate of bucket) { - if (isCollision(candidate)) { - return candidate; - } - } - } - - return undefined; - } - - /** 图标碰撞检测:AABB 重叠判定(padding=0,与 2D 图标碰撞一致) */ - private checkIconsAabbCollision( - left: { left: number; right: number; top: number; bottom: number }, - right: { left: number; right: number; top: number; bottom: number } - ): boolean { - return !( - left.right < right.left || - left.left > right.right || - left.bottom < right.top || - left.top > right.bottom - ); - } - - /** 标签碰撞检测:AABB 重叠 + padding=4(与 2D checkLabelCollision 对齐) */ - private checkLabelAabbCollision( - left: { left: number; right: number; top: number; bottom: number }, - right: { left: number; right: number; top: number; bottom: number } - ): boolean { - const padding = this.COLLISION_LABEL_PADDING; - return !( - left.right + padding < right.left || - left.left - padding > right.right || - left.bottom + padding < right.top || - left.top - padding > right.bottom - ); - } - - // ==================== 倾斜摄影(OSGB) ==================== - - /** - * 加载倾斜摄影 3D Tileset 模型 - * 如果 viewer 尚未就绪,暂存到队列中,等 5 秒后批量加载 - */ - addQxsyLayer(item: OSGBItem): void { - if (!this.viewer || !this._ready) { - // 未就绪:放入等待队列 - if (!this._pendingOSGBItems.includes(item)) { - this._pendingOSGBItems.push(item); - } - return; - } - LoadOSGB(this.viewer, item); - } - - /** - * 批量加载所有等待中的倾斜摄影 - */ - private flushPendingOSGB(): void { - if (!this.viewer || this.viewer.isDestroyed()) return; - const items = this._pendingOSGBItems.splice(0); - console.log(`[Cesium] 开始加载 ${items.length} 个待加载的倾斜摄影模型`); - items.forEach(item => LoadOSGB(this.viewer!, item)); - } - - /** - * 移除倾斜摄影模型 - */ - removeQxsyLayer(item: OSGBItem): void { - if (!this.viewer) return; - // 从待加载队列中移除,防止已被移除的模型在 flushPendingOSGB 时又被加载 - this._pendingOSGBItems = this._pendingOSGBItems.filter(i => i !== item); - removeQxsy(this.viewer, item); - } - - /** - * 倾斜摄影显隐切换 - */ - qxsyChangeClick(item: OSGBItem, checked: boolean): void { - if (!this.viewer) return; - osgbChangeClick(this.viewer, item, checked); - } - - /** - * 飞行定位到倾斜摄影模型 - */ - qxsyToPosition(item: OSGBItem): void { - if (!this.viewer) return; - osgbLocation(this.viewer, item); - } -} diff --git a/frontend-sjgl/src/components/gis/map.class.ts b/frontend-sjgl/src/components/gis/map.class.ts deleted file mode 100644 index ea1e450d..00000000 --- a/frontend-sjgl/src/components/gis/map.class.ts +++ /dev/null @@ -1,297 +0,0 @@ -import type { layer, MapInterface } from './map.d'; -// import { MapLeaflet } from "./map.leaflet"; -import { MapOl } from './map.ol'; -import { MapCesium } from './map.cesium'; - -interface MapClassInterface extends MapInterface { - layers: Map; - view: any; -} - -//描点参数 -export type MDOptions = { - isAllowOverlap?: boolean; // 是否允许重叠 - labelType?: number; // 标签类型 - labelminZoom?: number; // 标签最小缩放级别 - labelAltitude?: object; // 标签海拔 - isRemove?: boolean; // 是否移除标签 -}; - -export const mapServerBaseUrl = - localStorage.getItem('gisurl') || 'http://210.72.227.199:18084/'; - -export class MapClass implements MapClassInterface { - layers: Map; - view: any; - private static instance: MapClass; - private service: MapInterface; - - constructor() { - this.layers = new Map(); - this.view = null; - // this.service = new MapLeaflet(); - this.service = new MapOl(); - } - static getInstance(): MapClass { - if (!this.instance) { - this.instance = new MapClass(); - } - return this.instance; - } - // 地图初始化 - init(container: HTMLElement, rectangle?: any): Promise { - return this.service.init(container, rectangle).then(map => { - this.view = map; - return map; - }); - } - // 基地面板控制 - jdPanelControlShowAndHidden(baseid: string, isAll: boolean): void { - this.service.jdPanelControlShowAndHidden(baseid, isAll); - } - - mdLayerShowOrHidden( - layerType: string, - key?: string, - baseid: string, - checked?: boolean, - isAll?: boolean - ): void { - this.service.mdLayerShowOrHidden(layerType, key, baseid, checked, isAll); - } - // 添加基础数据图层 - addBaseDataLayer(layer: any, isShow: boolean): void { - return this.service.addBaseDataLayer(layer, isShow); - } - // 基础图层显示影隐藏方法 - controlBaseLayerTreeShowAndHidden( - layerType: string, - key: string, - checked: boolean - ) { - this.service.controlBaseLayerTreeShowAndHidden(layerType, key, checked); - } - // 图层树控制描点数据显示隐藏方法 - mdLayerTreeShowOrHidden(layerType: string, checked?: boolean) { - this.service.mdLayerTreeShowOrHidden(layerType, checked); - } - // 根据图例控制锚点显示隐藏 - setLegendPointVisible( - layerKey: string, - anchoPointState: string, - checked: boolean - ): void { - this.service.setLegendPointVisible(layerKey, anchoPointState, checked); - } - // 初始化加载描点数据 - addInitDataLayer = (pointData: any[], layerType: any, mdoptions?: any) => { - return this.service.addInitDataLayer(pointData, layerType, mdoptions); - }; - //切换底图 - baseLayerSwitcher(key: string, checked: boolean): void { - this.service.baseLayerSwitcher(key, checked); - } - - // 添加梯级流域图 - addTertiarybasinLayer( - layer: layer, - fillcolor: any, - outlineColor: any, - datas: any - ): void { - this.service.addTertiarybasinLayer(layer, fillcolor, outlineColor, datas); - } - // 移除梯级流域图 - hideTertiarybasinLayer(layer: layer): void { - this.service.hideTertiarybasinLayer?.(layer); - } - // 缩放 - zoomToggle(type: 'out' | 'in') { - this.service.zoomToggle(type); - } - /** - * 长度量算 - */ - lengthCalculate(): void { - this.service.lengthCalculate(); - } - /** - * 面积量算 - */ - areCalculate(): void { - this.service.areCalculate(); - } - /** - * 移除量算结果 - */ - removeQueryLayer(): void { - this.service.removeQueryLayer(); - } - // 地图打印 - mapOutPut() { - this.service.mapOutPut(); - } - // 检查图层是否存在 - hasLayer(layerKey: string): boolean { - return this.service.hasLayer(layerKey); - } - // 检查 GIS/底图图层是否存在 - hasBaseLayer(layerKey: string): boolean { - return this.service.hasBaseLayer(layerKey); - } - // 删除描点图层 - removePointLayer(layerKey: string): void { - this.service.removePointLayer?.(layerKey); - } - // 销毁地图 - destroy(): void { - this.service.destroy(); - } - // 初始化弹窗 - initPopupOverlay(popupContainer: HTMLDivElement): void { - this.service.initPopupOverlay(popupContainer); - } - // 切换地图视图 - switchView(type: '2D' | '3D'): Promise { - // this.service.switchView(type); - return new Promise((resolve, reject) => { - const container = document.getElementById('mapContainer'); - if (!container) { - reject(new Error('Map container not found')); - return; - } - try { - // 1. 销毁当前地图服务 - if (this.service) { - try { - this.service.destroy(); - } catch (e) { - console.warn('Error destroying previous service:', e); - } - this.service = null; - } - this.view = null; - - // 2. 根据类型初始化新的地图服务 - if (type === '2D') { - this.service = new MapOl(); - - this.service - .init(container) - .then(map => { - this.view = map; - resolve(map); - }) - .catch(err => reject(err)); - } else if (type === '3D') { - this.service = new MapCesium(); - - this.service - .init(container) - .then(viewer => { - this.view = viewer; - resolve(viewer); - }) - .catch(err => reject(err)); - } - // const container = this.view._container; - // if (type === '3D' && this.view) { - // if (type === '2D') { - // const rectangle = this.view.camera.computeViewRectangle() - // const heading = this.view.camera.heading - // const height = this.view.camera.positionCartographic.height - // const zoom = altitudeToZoom(height) > 11 ? 11 : altitudeToZoom(height) - // const center = this.service?.getCenterPosition() - // const west = this.transformRadian(rectangle.west) - // const north = this.transformRadian(rectangle.north) - // const east = this.transformRadian(rectangle.east) - // const south = this.transformRadian(rectangle.south) - // const bearing = this.transformRadian(heading) - - // this.service = new MapOl(); - // this.init(document.getElementById('mapContainer')); - // // this.service - // .init(document.getElementById('mapContainer')) - // .then(map => { - // this.view = map; - // resolve(map); - // }); - // } else if (type === '3D') { - // const data: { - // _southWest: { lat: number; lng: number } - // _northEast: { lat: number; lng: number } - // } = this.view.getBounds() - // const bearing = this.view.getBearing() - // const heading = this.transformAngle(bearing) - // //获取相机高度 - // const altitude = zoomToAltitude(this.view.getZoom()) - // //获取地图中心点 - // let lng = 0.5 * (data._ne.lng + data._sw.lng) - // let lat = 0.5 * (data._ne.lat + data._sw.lat) - // let center = { lng, lat } - // this.destroy(); - - // this.service = null; - // this.view = null; - // this.service = new MapCesium() - // this.service.init(container, data, center, altitude, heading).then((viewer) => { - // this.view = viewer - // let removeCallback = viewer.scene.globe.tileLoadProgressEvent.addEventListener((e) => { - // if (e == 0) { - // removeCallback() - // removeCallback = null - // if (viewer && !viewer.isDestroyed()) { - // resolve(viewer) - // } - // } - // }) - // //容错机制,放置地图服务挂掉或者响应非常慢 - // setTimeout(() => { - // if (removeCallback) { - // removeCallback() - // removeCallback = null - // if (viewer && !viewer.isDestroyed()) { - // resolve(viewer) - // } - // } - // }, 5000) - // }) - // } - } catch { - reject(); - } - }); - } - - // 飞行到指定的点 - fitBounds(): void { - // this.service.fitBounds(bounds) - } - - // 飞行到指定的点 - flyTopanto(position: number[], zoom: number): void { - this.service.flyTopanto(position, zoom); - } - - getCurrentZoom(): number | undefined { - return this.service.getCurrentZoom(); - } - - // ==================== 倾斜摄影 ==================== - - addQxsyLayer(item: any): void { - this.service.addQxsyLayer?.(item); - } - - removeQxsyLayer(item: any): void { - this.service.removeQxsyLayer?.(item); - } - - qxsyChangeClick(item: any, checked: boolean): void { - this.service.qxsyChangeClick?.(item, checked); - } - - qxsyToPosition(item: any): void { - this.service.qxsyToPosition?.(item); - } -} diff --git a/frontend-sjgl/src/components/gis/map.d.ts b/frontend-sjgl/src/components/gis/map.d.ts deleted file mode 100644 index 9d04a2dd..00000000 --- a/frontend-sjgl/src/components/gis/map.d.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { MDOptions } from './map.class'; -export type layerType = - | 'markers' - | 'tiledMap' - | 'tiledMapQuery' - | 'geoJson' - | 'arcgisFeature' - | 'dynamicMapLayer' - | 'arcgisMap' - | 'label' - | 'vector'; -export type layerOption = { - opacity?: number; - data?: Array; - zIndex?: number; - clickEvent?: Function; - hoverEvent?: Function; - legendImages?: Array | null | undefined; - geoJsonLegend?: Array | null | undefined; - tiledMapType?: undefined | 'superMap'; -}; - -export interface layer { - id: string; - key: string; - _layer?: any; - type?: layerType; - url?: string; - url_3d?: string; - geojson_url: string; - label?: string; - thumbnail?: string; - visible?: boolean; - option?: layerOption; - tempobj?: any; - layers?: any; - rasteropacity?: any; - imgUrl?: any; - minZoom?: any; - maxZoom?: any; - minHeight?: number; - maxHeight?: number; - /** layer 类型 */ - layerType?: string; - matrixIds_index?: string[]; - tileMatrixSetID?: string; - urlType: string; -} - -export interface MapInterface { - /** - * 地图初始化 - * @param container DOM容器 - * @return any 地图视图 - */ - init( - container: HTMLElement, - rectangle?: any, - center?: any, - altitude?: number, - bearing?: number - ): Promise; - - /** - * 初始化加载描点数据 - * @param pointData - * @param layerType - */ - addInitDataLayer( - pointData: any[], - layerType: any, - mdoptions: MDOptions - ): void; - - /** - * 初始化弹窗 - * @param popupContainer - */ - initPopupOverlay(popupContainer: HTMLDivElement): void; - - /** - * 初始化加载基础图层 - * @param layer - */ - addBaseDataLayer(layer: any, isShow: boolean): void; - - /** - * 切换底图 - * @param layer - */ - baseLayerSwitcher(key: string, checked: boolean): void; - - /** - * 切换2D或者3D视图 - * @param type '2D' | '3D' - */ - switchView(type: '2D' | '3D'): any | null; - - /** - * 缩放 - * out 缩小 - * in 放大 - */ - zoomToggle(type: 'out' | 'in'): void; - - /** - * 检查图层是否存在 - * @param layerKey 图层 key - * @returns 是否存在 - */ - hasLayer(layerKey: string): boolean; - /** - * 检查 GIS/底图图层是否存在 - * @param layerKey 图层 key - * @returns 是否存在 - */ - hasBaseLayer(layerKey: string): boolean; - /** - * 地图输出打印 - */ - mapOutPut(): void; - - //飞行到指定的点 - fitBounds(bbox, bearing): void; - - flyTopanto(positon, zoom): void; - - /** - * 获取当前地图缩放级别语义 - * 2D 直接返回原生 zoom,3D 由相机高度换算后返回 - */ - getCurrentZoom(): number | undefined; - - /** - * 加载倾斜摄影数据 - */ - addQxsyLayer(item: any): void; - - /** - * 移除倾斜摄影数据 - */ - removeQxsyLayer(item: any): void; - - /** - * 倾斜摄影显隐切换 - */ - qxsyChangeClick(item: any, checked: boolean): void; - - /** - * 倾斜摄影定位 - */ - qxsyToPosition(item: any): void; - - /** - * 基地面板控制 - * @param baseid - * @param isAll - */ - jdPanelControlShowAndHidden(baseid: string, isAll: boolean): void; - /** - * 图例和基地面板控制描点数据显示隐藏方法 - * @param layerType - * @param key - * @param checked - * @param isAll - */ - mdLayerShowOrHidden( - layerType: string, - key?: string, - baseid: string, - checked?: boolean, - isAll?: boolean - ): void; - - /** - * 基础图层显示影隐藏方法 - * @param layerType - * @param checked - */ - controlBaseLayerTreeShowAndHidden( - layerType: string, - key: string, - checked: boolean - ): void; - - /** - * 图层树控制描点数据显示隐藏方法 - * @param layerType - * @param checked - */ - mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void; - - /** - * 删除描点图层 - * @param layerKey 图层 key - */ - removePointLayer(layerKey: string): void; - - /** - * 根据 anchoPointState 控制单个图例的锚点显示隐藏 - * @param layerKey 图层 key - * @param anchoPointState 锚点状态(对应图例的 nameEn) - * @param checked 是否显示 - */ - setLegendPointVisible( - layerKey: string, - anchoPointState: string, - checked: boolean - ): void; - - /** - * 长度量算 - */ - lengthCalculate(): void; - /** - * 面积量算 - */ - areCalculate(): void; - - /** - * 移除量算结果 - */ - removeQueryLayer(): void; - /** - * 添加梯级流域图 - * @param layer - * @param fillcolor - */ - addTertiarybasinLayer( - layer: layer, - fillcolor: any, - outlineColor: any, - datas: any - ): void; - - /** - * 移除梯级流域图 - * @param layer - */ - hideTertiarybasinLayer(layer: layer): void; - /** - * 移除地图对象 - */ - destroy(): void; -} diff --git a/frontend-sjgl/src/components/gis/map.leaflet.ts b/frontend-sjgl/src/components/gis/map.leaflet.ts deleted file mode 100644 index e7da68d0..00000000 --- a/frontend-sjgl/src/components/gis/map.leaflet.ts +++ /dev/null @@ -1,440 +0,0 @@ -// import { MapInterface } from './map'; -// import * as L from 'leaflet'; -// import * as esriLeaflet from 'esri-leaflet'; -// import '@/utils/leaflet/leaflet-tilelayer-wmts-src.js'; -// import { mapServerBaseUrl } from './map.class'; -// import '@/utils/leaflet/leaflet.inflatable-markers-group.js'; -// import {MDOptions} from './map.class'; -// import { getIconPath } from "@/utils/index"; -// // @ts-ignore -// import axios from 'axios'; -// // import "@/components/thematicMap/leaflet/leaflet.inflatable-markers-group.js" - -// const tiledMapGroup = L.layerGroup(); -// const chartMapGroup = L.layerGroup(); -// const overlayGroup = L.layerGroup(); -// const CENTER_positionCN: any = [38, 114.17112499999996]; //中心纬经度 中国 -// const basinCenter = { -// DA_HHGLSX: [103.343357, 35.931812], -// FA_CJGLSX: [111.001911, 30.821327] -// }; -// let boundCavansLayer: any = null; -// let ganliulist: any = []; -// export class MapLeaflet implements MapInterface { -// map: any = null; -// htmlMakerLayer: any = []; -// defaultScale = 10; -// minimumZoom = 7; -// setDrawPlug: any = null; -// layermarkers: any = []; -// rainlayerslist: any = []; -// private layerRegistry: Map = new Map(); // ✅ 新增:存储 key -> layer 实例 - -// private currentBaseLayerKey: string | null = null; // ✅ 新增:记录当前激活的底图 Key - -// temperatureMapObj: any = []; -// //地图初始化 -// init(container: HTMLElement, rectangle?: any): Promise { -// try { -// console.log('init初始化container', container); -// var corner1 = L.latLng(55.35715491537772, 140.7821677051657); -// var corner2 = L.latLng(0.975580441812298, 67.56008229483018); -// var bounds = L.latLngBounds(corner2, corner1); -// const map = L.map(container as any, { -// preferCanvas: true, -// zoom: 4.5, -// minZoom: 4.23, -// maxZoom: 22, // 【修改点1】增大最大缩放级别,允许用户继续滚轮放大 -// maxNativeZoom: 12, -// zoomSnap: 0.1, // 【修改点2】让缩放更平滑,不强制对齐整数 -// zoomDelta: 0.5, -// center: CENTER_positionCN, -// attributionControl: false, -// zoomControl: false, -// trackResize: true, -// maxBounds: bounds, -// maxBoundsViscosity: 1.0, -// wheelPxPerZoomLevel: 180, -// }); -// // ✅ 新增:可视化显示 maxBounds 范围 -// // L.rectangle(bounds, {color: "#ff7800", weight: 3, opacity: 0.8, fillOpacity: 0.1}) -// // .addTo(map) -// // .bindPopup("这是 maxBounds 的范围"); -// this.map = map; -// this.map.on('zoomend', (e: any) => { -// console.log('当前缩放级别', e.target.getZoom()); -// }); -// return Promise.resolve(map); -// } catch (e) { -// console.log('测试', e); -// return Promise.reject({}); -// } -// } -// addBaseDataLayer(layer: any): any { -// // The WMTS URL -// console.log(layer); -// switch (layer.type) { -// case 'wmts': -// if (layer.url) { -// console.log('https://211.99.26.225:18085' + layer.url); -// if (layer) { -// var ignLayer = L.tileLayer -// .wmts('https://211.99.26.225:18085/geoserver/gwc/service/wmts', { -// tileMatrixSet: 'EPSG:3857_qgc_qsj_arcgistiles_l13', -// tileSize: 256, //切片大小 -// maxZoom: 13, -// noWrap: true, -// opacity: 0.99, -// minZoom: 4, -// styles:{ -// abc:123 -// }, -// layer: 'qgc_qsj_arcgistiles_l13' -// }) -// .addTo(this.map) -// .bringToBack(); -// const registryKey = layer.key || layer.title; -// if (registryKey) { -// this.layerRegistry.set(registryKey, ignLayer); -// } -// layer._layer = ignLayer; -// layer._layer.layerGroup = overlayGroup; -// } -// } -// return layer; - -// case 'markers': -// this.getRain(layer); -// break; -// default: { -// return; -// } -// } -// } - -// baseLayerSwitcher(key: string) { -// if (!this.map) return; - -// console.log('切换底图 key:', key); - -// // 1. 如果当前已有底图且不是同一个,先移除 -// if (this.currentBaseLayerKey && this.currentBaseLayerKey !== key) { -// const oldLayer = this.layerRegistry.get(this.currentBaseLayerKey); -// if (oldLayer && this.map.hasLayer(oldLayer)) { -// this.map.removeLayer(oldLayer); -// console.log(`已移除旧底图: ${this.currentBaseLayerKey}`); -// } -// } - -// // 2. 检查新底图是否已存在 registry 中 -// let newLayer = this.layerRegistry.get(key); - -// // 3. 如果不存在,则创建并添加到地图和 registry -// if (!newLayer) { -// newLayer = this.createBaseLayerByKey(key); -// if (newLayer) { -// this.layerRegistry.set(key, newLayer); -// newLayer.addTo(this.map); -// // 确保底图在最底层 -// newLayer.bringToBack(); -// } -// } else { -// // 4. 如果已存在,直接添加(如果未添加) -// if (!this.map.hasLayer(newLayer)) { -// newLayer.addTo(this.map); -// newLayer.bringToBack(); -// } -// } - -// // 5. 更新当前激活的 Key -// if (newLayer) { -// this.currentBaseLayerKey = key; -// } -// } - -// /** -// * 根据 Key 创建具体的 Leaflet 图层实例 -// */ -// createBaseLayerByKey(key: string): L.Layer | null { -// console.log(key) -// const tdtToken = 'e90d56e5a09d1767899ad45846b0cefd' //企业版密钥,勿换e650f138c4481cca888cd13094bb9026 -// const mapType = 'w' //c:天地图经纬度底图;w:天地图墨卡托底图 -// const URL_TerTDT = `https://t0.tianditu.gov.cn/ter_${mapType}/wmts?tk=${tdtToken}` //terMap -// switch (key) { -// case 's_province_boundaries': -// // 假设这是之前的 WMTS 矢量图层 -// // 注意:原代码中硬编码了 URL 和 layer 名称,这里需要确保与实际服务对应 -// return L.tileLayer.wmts('https://211.99.26.225:18085/geoserver/gwc/service/wmts', { -// tileMatrixSet: 'EPSG:3857_qgc_qsj_arcgistiles_l13', -// tileSize: 256, -// // maxZoom: 13, -// noWrap: true, -// opacity: 1, -// minZoom: 4, -// zIndex: 1, -// layer: 'qgc_qsj_arcgistiles_l13', // 请确认此 layer 名称是否对应“矢量” -// format: 'image/png' -// }); - -// case 'BASEMAP-white': -// // 地形图示例 (使用 OpenStreetMap 或其他地形服务作为占位,请替换为实际地址) -// return L.tileLayer(`${URL_TerTDT}&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=${mapType}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles`, { -// // maxZoom: 17, -// format: "image/png", -// zIndex: 12, -// attribution: 'Map data: © OpenStreetMap contributors, SRTM | Map style: © OpenTopoMap (CC-BY-SA)' -// }); - -// case 'BASEMAP-img': -// // 影像图示例 (使用 Esri World Imagery 作为占位,请替换为实际地址) -// return L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { -// attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' -// }); - -// default: -// console.warn(`未知的底图 Key: ${key}`); -// return null; -// } -// } - -// controlBaseLayerTreeShowAndHidden(layerType: String, key: String, checked: boolean) { -// // 优先使用 key 查找,如果没有 key 则尝试用 layerType -// // console.log(this.getAllLayers()) -// console.log(key) -// console.log(this.layerRegistry) -// const registryKey:any = key || layerType; -// const layerInstance = this.layerRegistry.get(registryKey); -// if (layerInstance) { -// if (checked) { -// // 显示:如果不在地图上,则添加 -// if (!this.map.hasLayer(layerInstance)) { -// layerInstance.addTo(this.map); -// } -// } else { -// // 隐藏:如果在地图上,则移除 -// if (this.map.hasLayer(layerInstance)) { -// this.map.removeLayer(layerInstance); -// } -// } -// } else { -// console.warn(`未找到标识为 ${registryKey} 的图层实例`); -// } -// } - -// /** -// * 添加初始化数据图层 -// * @param pointLayerObj 图层配置对象 (包含 data, key, checked 等属性) -// * @param layerType 图层类型/Key (如果 pointLayerObj 中有 key,则优先使用 obj.key) -// * @param mdoptions 描点选项配置 -// * @param legendArray 图例映射数据 (用于根据 anchoPointState 匹配图标) -// */ -// addInitDataLayer = (pointLayerObj: any, layerType?: any, mdoptions?: MDOptions, legendArray?: any) => { -// // 1. 参数校验与数据提取 -// if (!this.map || !pointLayerObj) { -// return; -// } - -// let dataArray: any[] = []; -// let targetLayerKey: string = layerType; - -// if (Array.isArray(pointLayerObj)) { -// dataArray = pointLayerObj; -// } else { -// dataArray = pointLayerObj.data || []; -// targetLayerKey = pointLayerObj.key || layerType; -// } - -// if (!targetLayerKey) { -// console.warn('缺少图层 Key,无法加载描点'); -// return; -// } - -// if (dataArray.length === 0) { -// const existingGroup = this.layerRegistry.get(targetLayerKey); -// if (existingGroup && existingGroup instanceof L.LayerGroup) { -// existingGroup.clearLayers(); -// } -// return; -// } - -// console.log(`开始加载图层 [${targetLayerKey}] 的描点,数量: ${dataArray.length}`); - -// // 2. 获取或创建该图层的 LayerGroup -// let layerGroup = this.layerRegistry.get(targetLayerKey); - -// const shouldClear = mdoptions?.isRemove !== false; - -// if (!layerGroup || !(layerGroup instanceof L.LayerGroup)) { -// if (layerGroup && this.map.hasLayer(layerGroup)) { -// this.map.removeLayer(layerGroup); -// } -// layerGroup = L.layerGroup(); -// this.layerRegistry.set(targetLayerKey, layerGroup); -// layerGroup.addTo(this.map); -// } else { -// if (shouldClear) { -// layerGroup.clearLayers(); -// } -// } - -// // 3. 遍历数据生成 Marker -// dataArray.forEach((item: any) => { -// const { lgtd, lttd, stcd, stnm, iconCode, anchoPointState, _id, titleName, ennm } = item; - -// if (lgtd == null || lttd == null) { -// return; -// } - -// // 4. 确定图标样式和文字 -// let iconUrl = ''; -// let iconSize = [15, 15]; - -// // 获取图标 URL -// if (iconCode) { -// iconUrl = getIconPath(iconCode); -// } else if (anchoPointState && legendArray) { -// const legendItem = legendArray[anchoPointState]; -// if (legendItem && legendItem.icon) { -// iconUrl = getIconPath(legendItem.icon); -// if (legendItem.width && legendItem.height) { -// iconSize = [legendItem.width, legendItem.height]; -// } -// } -// } - -// // 如果没有找到图标,使用默认或跳过 -// if (!iconUrl) { -// // 可以选择使用默认图标或者继续 -// iconUrl = getIconPath('default'); // 假设有一个默认图标 -// if(!iconUrl) return; -// } - -// // 5. 创建带文字的 DivIcon -// // 显示的文字优先使用 titleName,其次 ennm,最后 stnm -// const labelText = titleName || ennm || stnm || ''; - -// // 构建 HTML 结构:一个容器包含图片和文字 -// // 注意:这里使用内联样式简单演示,建议在实际项目中提取到 CSS 类中 -// const iconHtml = ` -//
-//
-// ${labelText} -//
-// -//
`; - -// const customIcon = L.divIcon({ -// html: iconHtml, -// className: '', // 重要:设置为空字符串以避免 Leaflet 默认样式干扰 -// iconSize: [iconSize[0], iconSize[1] + 20], // 高度增加以容纳文字 (假设文字高约20px) -// iconAnchor: [iconSize[0] / 2, iconSize[1] + 10], // 锚点设在图片底部中心 -// popupAnchor: [0, -(iconSize[1] + 20)] // 弹窗锚点设在整体顶部 -// }); - -// // 6. 创建 Marker -// const marker = L.marker([lttd, lgtd], { -// icon: customIcon, -// // @ts-ignore -// options: { -// ...item, -// layerKey: targetLayerKey, -// originalEvent: item -// } -// }); - -// // 7. 绑定点击事件 (如果需要) -// marker.on('click', (e: any) => { -// // 触发全局事件,例如: -// // GlobalEvents.get('map_dataLayer_click').set(item); -// console.log('Marker clicked:', item); -// }); - -// // 8. 将 Marker 添加到 LayerGroup -// layerGroup.addLayer(marker); -// }); - -// console.log(`图层 [${targetLayerKey}] 描点加载完成`); -// } -// getRain(data: any) { -// if (data?.geojson) { -// this.removeRainLayer(); -// this.removeLayermarkers(); -// const renderColor = (item: any, colRules: any) => { -// let color = 'rgba(0,0,0,0)'; -// colRules.map((rule: any, index: number) => { -// if ( -// item.properties.hvalue >= rule?.sv && -// item.properties.hvalue < rule?.ev -// ) { -// color = rule.colors; -// } -// if (index === colRules.length - 1) { -// if (item.properties.hvalue >= rule?.ev) { -// color = rule.colors; -// } -// } -// }); -// return color; -// }; - -// data.geojson.features.map((item: any, index: number) => { -// let pushlist = L.geoJSON(item, { -// style: { -// color: renderColor(item, data.colRules), -// weight: 2, -// opacity: 0.8, -// fillOpacity: 0.7 -// // fillColor: "#1D91C0", -// } -// }).addTo(this.map); -// this.layermarkers.push(pushlist); -// }); -// } -// } -// removeLayermarkers = () => { -// // console.log('layermarkers',layermarkers); - -// try { -// if (this.layermarkers.length > 0) { -// if (this.map.hasLayer(this.layermarkers)) { -// this.map.removeLayer(this.layermarkers); -// } else { -// console.log('移除图层失败,应已经移除'); -// } -// } -// // removeAllGeojson(map); -// } catch (e) { -// // message.info('移除图层失败') -// console.log('移除图层失败,是否应移除'); -// } -// }; - -// removeRainLayer(): void { -// console.log('test!删除Layer'); -// const _this = this; -// if (this.rainlayerslist.length > 0) { -// this.rainlayerslist.map((item: any) => { -// _this.map.removeLayer(item); -// }); -// this.rainlayerslist = []; -// } -// } // 缩放 -// zoomToggle(type: 'out' | 'in') { -// if (this.map) { -// if (type === 'out') { -// this.map && this.map.zoomOut(); -// } else { -// this.map && this.map.zoomIn(); -// } -// } -// } -// } diff --git a/frontend-sjgl/src/components/gis/map.ol.ts b/frontend-sjgl/src/components/gis/map.ol.ts deleted file mode 100644 index 61b66db6..00000000 --- a/frontend-sjgl/src/components/gis/map.ol.ts +++ /dev/null @@ -1,2616 +0,0 @@ -import { MapInterface } from './map'; -import OlMap from 'ol/Map'; -import View from 'ol/View'; -import Overlay from 'ol/Overlay'; -import TileLayer from 'ol/layer/Tile'; -import VectorLayer from 'ol/layer/Vector'; -import VectorSource from 'ol/source/Vector'; -import GeoJSON from 'ol/format/GeoJSON'; -import Style from 'ol/style/Style'; -import Fill from 'ol/style/Fill'; -import Stroke from 'ol/style/Stroke'; -import Icon from 'ol/style/Icon'; -import Text from 'ol/style/Text'; -import Circle from 'ol/style/Circle'; -import WMTS from 'ol/source/WMTS'; -import WMTSTileGrid from 'ol/tilegrid/WMTS'; -import { get as getProjection, fromLonLat } from 'ol/proj'; -import { - defaults as defaultInteractions, - Draw, - DoubleClickZoom, - DragPan -} from 'ol/interaction'; -import { getTopLeft, getWidth } from 'ol/extent'; -import MouseWheelZoom from 'ol/interaction/MouseWheelZoom'; -import { servers } from './mapurlManage'; -import { XYZ } from 'ol/source'; -import Feature from 'ol/Feature'; -import LineString from 'ol/geom/LineString'; -import Point from 'ol/geom/Point'; -import { - getLength as getSphericalLength, - getArea as getSphericalArea -} from 'ol/sphere'; -import { unByKey } from 'ol/Observable'; -import { MDOptions } from './map.class'; -import { useModelStore } from '@/store/modules/model'; -import { PointLayerManager } from './ol/point-layer-manager'; -import { PopupManager } from './ol/popup-manager'; -import { RegionMaskManager } from './ol/region-mask-manager'; -import { getNearbyPointDensityDisplayRules } from '@/modules/map/domain/nearby-point-rules'; -import router from '@/router'; - -const modelStore = useModelStore(); -const VITE_APP_MAP_URL = import.meta.env.VITE_APP_MAP_URL; - -// 定义与 leaflet 中相同的常量 -const CENTER_positionCN = [114.17112499999996, 38]; // OpenLayers 使用 [lon, lat] -const MIN_ZOOM = 4.23; -const MAX_ZOOM = 22; -const INITIAL_ZOOM = 4.5; -const BATCH_POPUP_MODE_ZOOM = 15; -const FULL_DISPLAY_NO_COLLISION_ZOOM = Number.POSITIVE_INFINITY; - -// 定义边界 [minX, minY, maxX, maxY] (Web Mercator 坐标) -const BOUNDS_SW = [26.5, -9.99999999999929]; -const BOUNDS_NE = [180.00000000000074, 60.06349386538693]; - -export class MapOl implements MapInterface { - map: OlMap | null = null; - view: View | null = null; - private layerRegistry: Map = new Map(); - private iconLoadState = new Map(); - private pointStyleCache = new Map(); - private baseLayerConfig: any | null = null; - private hydropBaseConfig: any | null = null; - private REGISTRY_KEY = 'customBaseLayer'; - private activeBaseLayerKey = 's_province_boundaries'; - private readonly rasterBaseLayerKeys = ['BASEMAP-white', 'BASEMAP-img']; - private drawInteraction: any = null; - private measureLayer: VectorLayer | null = null; - private measureSource: VectorSource | null = null; - private pointLayerManager: PointLayerManager; - geoJsonData: any = null; - geoJsonData1: any = null; - private BASEID = ''; - private currentClipGeoJson: any | null = null; - private clipRequestController: AbortController | null = null; - private hoveredFeatureId: string | number | null = null; - private popupManager: PopupManager; - private regionMaskManager: RegionMaskManager; - private isBatchPopupMode = false; - private batchPopupRefreshFrameId: number | null = null; - private batchPopupPendingRebuild = false; - private batchPopupPostRenderListenerKey: any = null; - private pointLayerStyleRefreshFrameId: number | null = null; - private labelVisibilityRefreshFrameId: number | null = null; - private labelVisibilityDebounceTimerId: number | null = null; - constructor() { - this.pointLayerManager = new PointLayerManager({ - map: null, - createStyle: feature => this.createPointStyle(feature) - }); - this.popupManager = new PopupManager({ - map: null, - getPointLayers: () => this.pointLayerManager.getLayers() - }); - this.regionMaskManager = new RegionMaskManager({ - map: null, - view: null, - pointLayerManager: this.pointLayerManager, - defaultCenter: CENTER_positionCN as [number, number], - defaultZoom: INITIAL_ZOOM - }); - } - private async loadGeoJsonData(url, signal?: AbortSignal): Promise { - try { - const response = await fetch(url, { signal }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data; - } catch (error: any) { - if (error.name !== 'AbortError') { - console.error('配置加载失败:', error); - } - } - } - //地图初始化 - init(container: HTMLElement): Promise { - try { - const minCoordinate = fromLonLat(BOUNDS_SW); - const maxCoordinate = fromLonLat(BOUNDS_NE); - const extent = [ - minCoordinate[0], - minCoordinate[1], - maxCoordinate[0], - maxCoordinate[1] - ]; - - this.view = new View({ - center: fromLonLat(CENTER_positionCN), // 设置中心点 - zoom: INITIAL_ZOOM, - minZoom: MIN_ZOOM, - maxZoom: MAX_ZOOM, - constrainOnlyCenter: false, // 允许部分视图超出边界,但中心点受限(类似 Leaflet 默认行为) - extent: extent, // 限制视图范围 - smoothExtentConstraint: false // 平滑边界约束 - }); - - const mouseWheelInteraction = new MouseWheelZoom({ - duration: 100, // 缩放动画持续时间 (ms),Leaflet 默认也有动画 - maxDelta: 2, - constrainResolution: false // ✅ 关键:false 允许缩放到非整数级别 (如 4.5, 4.6),实现平滑缩放 - }); - - this.map = new OlMap({ - target: container, - layers: [], - view: this.view, - controls: [], // 对应 Leaflet 的 attributionControl: false, zoomControl: false - interactions: defaultInteractions({ - doubleClickZoom: true, - dragPan: false, - - pinchRotate: false // 通常禁用旋转,除非需要 - }).extend([ - new DragPan({ - kinetic: undefined - }), - mouseWheelInteraction - ]) - }); - this.pointLayerManager.setMap(this.map); - this.popupManager.setMap(this.map); - this.regionMaskManager.setContext(this.map, this.view); - - this.view.on('change:resolution', () => { - this.handleZoomChange(); - }); - this.map.on('click', evt => { - if ( - router.currentRoute.value.path.includes( - 'shengTaiLiuLiangManZuQingKuangJiangJu' - ) - ) { - return; - } - this.popupManager.showPopup(undefined, undefined); - this.popupManager.handleMapClick(evt.pixel, detectedFeature => { - if (detectedFeature.values_.sttpMap == 'ylfb') { - modelStore.ylfbModalVisible = true; - modelStore.params = detectedFeature.values_; - } else { - modelStore.modalVisible = true; - modelStore.params = detectedFeature.values_; - modelStore.title = - detectedFeature.values_.titleName || - detectedFeature.values_.stnm + ' 详情信息'; - } - }); - }); - - this.map.on('pointermove', evt => { - this.popupManager.handlePointerMove(evt.pixel, payload => { - this.hoveredFeatureId = payload.hoveredId; - - const targetElement = this.map?.getTargetElement() as HTMLElement; - if (targetElement) { - targetElement.style.cursor = payload.hoveredId ? 'pointer' : ''; - } - - // 批量 popup 模式下不触发 hover popup - if (!this.isBatchPopupMode) { - this.showPopup(payload.detectedFeature, payload.coordinate); - } - }); - }); - - this.map.on('pointerdrag', () => { - if (this.isBatchPopupMode) { - this.ensureBatchPopupPostRenderSync(); - } - }); - this.map.on('moveend', () => { - this.clearBatchPopupPostRenderSync(); - this.requestRefreshPointLabelVisibility(false, 60); - if (this.isBatchPopupMode) { - this.requestUpdateBatchPopups(false, true); - } - }); - - return Promise.resolve(this.map); - } catch (e) { - console.error('OL Init Error', e); - return Promise.reject(e); - } - } - - /** - * 初始化加载描点数据 - * @param pointData 图层配置对象或数据数组 - * @param layerType 图层类型/Key - * @param mdoptions 描点选项配置 - */ - addInitDataLayer( - pointData: any, - layerType: any, - _mdoptions?: MDOptions - ): void { - this.pointLayerManager.addDataLayer(pointData, layerType); - - if (this.currentClipGeoJson) { - this.regionMaskManager.filterPointsByRegion(this.currentClipGeoJson); - } - - const currentZoom = this.view ? this.view.getZoom() : INITIAL_ZOOM; - this.pointLayerManager.updateNearbyFeatureLayout(currentZoom); - this.requestRefreshPointLabelVisibility(false, 80); - } - /** - * 初始化 Popup Overlay - * @param container 父组件传入的 DOM 容器 - */ - initPopupOverlay(container: HTMLElement) { - this.popupManager.initPopupOverlay(container); - } - /** - * 在指定像素位置检测要素(共享方法) - * @param pixel 像素坐标 - * @returns 检测到的要素和是否命中图标区域 - */ - private detectFeatureAtPixel(pixel: number[]): { - detectedFeature: Feature | undefined; - isHitIcon: boolean; - coordinate?: number[]; - } { - return this.popupManager.detectFeatureAtPixel(pixel); - } - - /** - * 在 pointermove 中调用此方法显示/隐藏 Popup - * @param feature 当前悬停的要素 - * @param coordinate 地理坐标 - */ - showPopup(feature: Feature | undefined, coordinate: number[] | undefined) { - this.popupManager.showPopup(feature, coordinate); - } - - /** - * 监听缩放层级变化,到达指定层级时自动显示可视区域内所有锚点的 popup - */ - private handleZoomChange() { - if (!this.view) return; - - const zoom = this.view.getZoom(); - if (zoom === undefined) return; - - this.pointLayerManager.updateNearbyFeatureLayout(zoom); - this.requestRefreshPointLabelVisibility(); - - if (zoom >= BATCH_POPUP_MODE_ZOOM) { - this.enableBatchPopupMode(); - } else { - this.disableBatchPopupMode(); - } - } - - /** - * 启用批量 popup 模式:自动显示可视区域内所有锚点的 popup - */ - private enableBatchPopupMode() { - this.isBatchPopupMode = true; - this.clearBatchPopupPostRenderSync(); - this.popupManager.clearBatchPopups(); - this.requestUpdateBatchPopups(true, true); - } - - /** - * 禁用批量 popup 模式:恢复鼠标悬停显示 popup - */ - private disableBatchPopupMode() { - this.isBatchPopupMode = false; - this.batchPopupPendingRebuild = false; - this.clearBatchPopupPostRenderSync(); - if (this.batchPopupRefreshFrameId !== null) { - cancelAnimationFrame(this.batchPopupRefreshFrameId); - this.batchPopupRefreshFrameId = null; - } - this.popupManager.clearBatchPopups(); - this.popupManager.showPopup(undefined, undefined); - } - - /** - * 更新批量显示的 popups(拖动地图后调用) - */ - private updateBatchPopups() { - const features = this.pointLayerManager.getFeaturesInViewport(); - // 传入样式检查器,过滤掉 declutter/距离过滤等隐藏的锚点 - this.popupManager.showPopupsForFeatures( - features, - feature => { - const style = this.createPointStyle(feature); - return style !== null; - }, - { - rebuild: this.batchPopupPendingRebuild - } - ); - } - - private ensureBatchPopupPostRenderSync() { - if (!this.map || this.batchPopupPostRenderListenerKey) { - return; - } - - this.batchPopupPostRenderListenerKey = this.map.on('postrender', () => { - if (!this.isBatchPopupMode) { - return; - } - this.popupManager.updateBatchPopupPositions(feature => { - const style = this.createPointStyle(feature); - return style !== null; - }); - }); - } - - private clearBatchPopupPostRenderSync() { - if (this.batchPopupPostRenderListenerKey) { - unByKey(this.batchPopupPostRenderListenerKey); - this.batchPopupPostRenderListenerKey = null; - } - } - /** - * 创建点样式 (模拟 Leaflet 的 DivIcon 效果,支持随缩放动态调整大小) - */ - private createPointStyle(feature: Feature): Style[] | null { - const iconUrl = feature.get('_iconUrl') as string; - const labelText = feature.get('_labelText') as string; - const legendVisible = feature.get('_legendVisible'); - const regionVisible = feature.get('_regionVisible'); - - if (!iconUrl) { - return null; - } - - if (!this.ensureIconReady(iconUrl)) { - return null; - } - - const currentZoom: any = this.view ? this.view.getZoom() : 4.5; - const cachedDensityVisible = feature.get('_densityVisible'); - const densityVisible = - typeof cachedDensityVisible === 'boolean' - ? cachedDensityVisible - : currentZoom >= this.getFeatureDensityMinZoom(feature); - const iconTargetVisible = - legendVisible !== false && - regionVisible !== false && - densityVisible && - this.shouldRenderNearbyFeature(); - - const dynamicScale = this.getDynamicPointScale(currentZoom); - const fontSize = this.getPointFontSize(dynamicScale); - const engPriority = this.getFeatureEngRenderPriority(feature); - const formattedLabelText = this.formatPointLabelText(labelText); - const labelLineCount = formattedLabelText - ? formattedLabelText.split('\n').length - : 1; - const labelOffsetY = this.getPointLabelRenderOffsetY( - dynamicScale, - labelLineCount - ); - const iconCollisionVisible = feature.get('_iconCollisionVisible') !== false; - const labelCollisionVisible = - feature.get('_labelCollisionVisible') === true; - const finalIconVisible = iconTargetVisible && iconCollisionVisible; - const labelTargetVisible = - !this.isBatchPopupMode && - finalIconVisible && - !!formattedLabelText && - labelCollisionVisible; - - if (!finalIconVisible) { - return null; - } - - const styleCacheKey = [ - iconUrl, - dynamicScale.toFixed(2), - fontSize, - engPriority, - labelOffsetY, - labelTargetVisible ? formattedLabelText : '', - labelTargetVisible ? 1 : 0 - ].join('|'); - const cachedStyles = this.pointStyleCache.get(styleCacheKey); - if (cachedStyles) { - return cachedStyles; - } - - const styles: Style[] = [ - new Style({ - zIndex: engPriority, - image: new Icon({ - src: iconUrl, - scale: dynamicScale, - anchor: [0.5, 0.5], - crossOrigin: 'anonymous', - declutterMode: 'none' - }) - }) - ]; - - if (formattedLabelText && labelTargetVisible) { - styles.push( - new Style({ - zIndex: engPriority + 1, - text: new Text({ - text: formattedLabelText, - offsetY: labelOffsetY, - font: `${fontSize}px sans-serif`, - fill: new Fill({ color: '#fff' }), - stroke: new Stroke({ - color: 'rgba(0, 0, 0, 0.9)', - width: 2 - }), - textAlign: 'center', - declutterMode: 'none' - }) - }) - ); - } - - this.pointStyleCache.set(styleCacheKey, styles); - return styles; - } - - private markFeatureVisibilityDirty( - feature: Feature, - key: '_densityVisible' | '_iconCollisionVisible' | '_labelCollisionVisible', - visible: boolean, - dirtyLayerKeys?: Set - ) { - if (feature.get(key) === visible) { - return; - } - - feature.set(key, visible, true); - const layerKey = feature.get('_layerKey'); - if (dirtyLayerKeys && layerKey) { - dirtyLayerKeys.add(String(layerKey)); - } - } - - private flushDirtyPointLayers(dirtyLayerKeys: Set) { - dirtyLayerKeys.forEach(layerKey => { - const layer = this.pointLayerManager.getLayer(layerKey); - layer?.changed(); - }); - } - - private syncFeatureDensityVisible( - feature: Feature, - visible: boolean, - dirtyLayerKeys?: Set - ) { - this.markFeatureVisibilityDirty( - feature, - '_densityVisible', - visible, - dirtyLayerKeys - ); - } - - private getFeaturePixelGridKey( - layerKey: string, - pixelX: number, - pixelY: number, - cellSize: number - ) { - return `${layerKey}:${Math.floor(pixelX / cellSize)}:${Math.floor( - pixelY / cellSize - )}`; - } - - private hasNearbyViewportFeature( - target: { - feature: Feature; - layerKey: string; - pixelX: number; - pixelY: number; - }, - pixelGrid: Map< - string, - Array<{ - feature: Feature; - layerKey: string; - pixelX: number; - pixelY: number; - }> - >, - threshold: number - ): boolean { - const cellX = Math.floor(target.pixelX / threshold); - const cellY = Math.floor(target.pixelY / threshold); - - for (let offsetX = -1; offsetX <= 1; offsetX += 1) { - for (let offsetY = -1; offsetY <= 1; offsetY += 1) { - const bucket = pixelGrid.get( - `${target.layerKey}:${cellX + offsetX}:${cellY + offsetY}` - ); - if (!bucket?.length) { - continue; - } - - for (const candidate of bucket) { - if (candidate.feature === target.feature) { - continue; - } - if ( - Math.hypot( - candidate.pixelX - target.pixelX, - candidate.pixelY - target.pixelY - ) <= threshold - ) { - return true; - } - } - } - } - - return false; - } - - private getCollisionGridKeys( - rect: { left: number; right: number; top: number; bottom: number }, - cellSize: number - ): string[] { - const startX = Math.floor(rect.left / cellSize); - const endX = Math.floor(rect.right / cellSize); - const startY = Math.floor(rect.top / cellSize); - const endY = Math.floor(rect.bottom / cellSize); - const keys: string[] = []; - - for (let x = startX; x <= endX; x += 1) { - for (let y = startY; y <= endY; y += 1) { - keys.push(`${x}:${y}`); - } - } - - return keys; - } - - private addRectToCollisionGrid< - T extends { - left: number; - right: number; - top: number; - bottom: number; - } - >(rect: T, grid: Map, cellSize: number) { - this.getCollisionGridKeys(rect, cellSize).forEach(key => { - const bucket = grid.get(key) || []; - bucket.push(rect); - grid.set(key, bucket); - }); - } - - private findCollidingRectInGrid< - T extends { - left: number; - right: number; - top: number; - bottom: number; - } - >( - rect: { left: number; right: number; top: number; bottom: number }, - grid: Map, - cellSize: number, - isCollision: (candidate: T) => boolean - ): T | undefined { - for (const key of this.getCollisionGridKeys(rect, cellSize)) { - const bucket = grid.get(key); - if (!bucket?.length) { - continue; - } - - for (const candidate of bucket) { - if (isCollision(candidate)) { - return candidate; - } - } - } - - return undefined; - } - - private refreshPointLabelVisibility() { - if (!this.map || !this.view) return; - - const currentZoom = this.view.getZoom(); - if (currentZoom === undefined) return; - - const candidateMap = new Map< - string, - { - feature: Feature; - iconLeft: number; - iconRight: number; - iconTop: number; - iconBottom: number; - hasLabel: boolean; - left: number; - right: number; - top: number; - bottom: number; - priority: number; - engPriority: number; - densityPriority: number; - id: string; - pixelX: number; - pixelY: number; - } - >(); - const candidates: Array<{ - feature: Feature; - iconLeft: number; - iconRight: number; - iconTop: number; - iconBottom: number; - hasLabel: boolean; - left: number; - right: number; - top: number; - bottom: number; - priority: number; - engPriority: number; - densityPriority: number; - id: string; - pixelX: number; - pixelY: number; - }> = []; - - const viewportFeatures = this.pointLayerManager.getFeaturesInViewport(); - const dirtyLayerKeys = new Set(); - const densityIsolationThreshold = 56; - const densityPixelGrid = new Map< - string, - Array<{ - feature: Feature; - layerKey: string; - pixelX: number; - pixelY: number; - }> - >(); - const viewportEntries: Array<{ - feature: Feature; - layerKey: string; - iconUrl: string; - legendVisible: boolean; - regionVisible: boolean; - densityPriority: number; - nearbyVisible: boolean; - labelText: string; - pixelX?: number; - pixelY?: number; - }> = []; - - viewportFeatures.forEach(feature => { - const geometry = feature.getGeometry(); - const pixel = - geometry && geometry.getType() === 'Point' - ? this.map?.getPixelFromCoordinate( - (geometry as Point).getCoordinates() - ) - : null; - const layerKey = String(feature.get('_layerKey') || ''); - const entry = { - feature, - layerKey, - iconUrl: String(feature.get('_iconUrl') || ''), - legendVisible: feature.get('_legendVisible') !== false, - regionVisible: feature.get('_regionVisible') !== false, - densityPriority: this.getFeatureDensityPriority(feature), - nearbyVisible: this.shouldRenderNearbyFeature(), - labelText: this.formatPointLabelText( - feature.get('_labelText') as string - ), - pixelX: pixel?.[0], - pixelY: pixel?.[1] - }; - viewportEntries.push(entry); - - if ( - pixel && - entry.layerKey && - entry.legendVisible && - entry.regionVisible - ) { - const gridKey = this.getFeaturePixelGridKey( - entry.layerKey, - pixel[0], - pixel[1], - densityIsolationThreshold - ); - const bucket = densityPixelGrid.get(gridKey) || []; - bucket.push({ - feature, - layerKey: entry.layerKey, - pixelX: pixel[0], - pixelY: pixel[1] - }); - densityPixelGrid.set(gridKey, bucket); - } - }); - - if (currentZoom >= FULL_DISPLAY_NO_COLLISION_ZOOM) { - viewportEntries.forEach(entry => { - const minZoom = this.getFeatureDensityMinZoom(entry.feature); - const densityVisible = - currentZoom >= minZoom || - (entry.legendVisible && - entry.regionVisible && - !!entry.layerKey && - entry.pixelX !== undefined && - entry.pixelY !== undefined && - !this.hasNearbyViewportFeature( - { - feature: entry.feature, - layerKey: entry.layerKey, - pixelX: entry.pixelX, - pixelY: entry.pixelY - }, - densityPixelGrid, - densityIsolationThreshold - )); - this.syncFeatureDensityVisible( - entry.feature, - densityVisible, - dirtyLayerKeys - ); - const shouldRenderIcon = - !!entry.iconUrl && - this.isIconReady(entry.iconUrl) && - entry.legendVisible && - entry.regionVisible && - densityVisible && - entry.nearbyVisible; - const hasLabel = !!entry.labelText; - - this.syncFeatureIconCollisionVisible( - entry.feature, - shouldRenderIcon, - dirtyLayerKeys - ); - this.syncFeatureLabelCollisionVisible( - entry.feature, - shouldRenderIcon && hasLabel, - dirtyLayerKeys - ); - }); - this.flushDirtyPointLayers(dirtyLayerKeys); - return; - } - - viewportEntries.forEach(entry => { - const minZoom = this.getFeatureDensityMinZoom(entry.feature); - const densityVisible = - currentZoom >= minZoom || - (entry.legendVisible && - entry.regionVisible && - !!entry.layerKey && - entry.pixelX !== undefined && - entry.pixelY !== undefined && - !this.hasNearbyViewportFeature( - { - feature: entry.feature, - layerKey: entry.layerKey, - pixelX: entry.pixelX, - pixelY: entry.pixelY - }, - densityPixelGrid, - densityIsolationThreshold - )); - this.syncFeatureDensityVisible( - entry.feature, - densityVisible, - dirtyLayerKeys - ); - - const shouldRenderIcon = - !!entry.iconUrl && - this.isIconReady(entry.iconUrl) && - entry.legendVisible && - entry.regionVisible && - densityVisible && - entry.nearbyVisible; - - if (!shouldRenderIcon) { - this.syncFeatureIconCollisionVisible( - entry.feature, - false, - dirtyLayerKeys - ); - this.syncFeatureLabelCollisionVisible( - entry.feature, - false, - dirtyLayerKeys - ); - return; - } - - if (entry.pixelX === undefined || entry.pixelY === undefined) { - this.syncFeatureIconCollisionVisible( - entry.feature, - false, - dirtyLayerKeys - ); - this.syncFeatureLabelCollisionVisible( - entry.feature, - false, - dirtyLayerKeys - ); - return; - } - - const dynamicScale = this.getDynamicPointScale(currentZoom); - // 备注:图标碰撞盒适当小于视觉图标尺寸,允许相邻站点轻微贴近显示, - // 避免像“小浪底/三门峡”这类近点被过早裁掉成只剩一个点。 - const iconCollisionSize = Math.max(14, 24 * dynamicScale); - const iconPadding = 0; - const iconLeft = entry.pixelX - iconCollisionSize / 2 - iconPadding; - const iconRight = entry.pixelX + iconCollisionSize / 2 + iconPadding; - const iconTop = entry.pixelY - iconCollisionSize / 2 - iconPadding; - const iconBottom = entry.pixelY + iconCollisionSize / 2 + iconPadding; - const labelText = entry.labelText; - const fontSize = this.getPointFontSize(dynamicScale); - const lines = labelText ? labelText.split('\n') : ['']; - const maxLineLength = Math.max(...lines.map(line => line.length), 1); - const labelLineCount = lines.length; - const labelOffsetY = this.getPointLabelCollisionOffsetY( - dynamicScale, - labelLineCount - ); - const estimatedWidth = maxLineLength * fontSize * 0.6 + 16; - const estimatedHeight = labelLineCount * (fontSize + 4) + 8; - const centerX = entry.pixelX; - const centerY = entry.pixelY + labelOffsetY; - const candidateId = String( - entry.feature.getId?.() || entry.feature.get('stcd') || '' - ); - const dedupeKey = - candidateId || - [ - entry.feature.get('_layerKey') || '', - entry.pixelX.toFixed(2), - entry.pixelY.toFixed(2), - labelText - ].join('|'); - - candidateMap.set(dedupeKey, { - feature: entry.feature, - iconLeft, - iconRight, - iconTop, - iconBottom, - hasLabel: !!labelText, - left: centerX - estimatedWidth / 2, - right: centerX + estimatedWidth / 2, - top: centerY - estimatedHeight / 2, - bottom: centerY + estimatedHeight / 2, - priority: Number(entry.feature.get('_nearbyPriority') || 9999), - engPriority: this.getFeatureEngRenderPriority(entry.feature), - densityPriority: entry.densityPriority, - id: candidateId, - pixelX: entry.pixelX, - pixelY: entry.pixelY - }); - }); - - candidateMap.forEach(candidate => { - candidates.push(candidate); - }); - - candidates.sort((left, right) => { - if (left.engPriority !== right.engPriority) { - return right.engPriority - left.engPriority; - } - if (left.priority !== right.priority) { - return left.priority - right.priority; - } - if (left.densityPriority !== right.densityPriority) { - return left.densityPriority - right.densityPriority; - } - if (left.pixelY !== right.pixelY) { - return left.pixelY - right.pixelY; - } - return left.id.localeCompare(right.id); - }); - - const placedIconRects: Array<{ - featureId: string; - left: number; - right: number; - top: number; - bottom: number; - }> = []; - const placedIconGrid = new Map< - string, - Array<{ - featureId: string; - left: number; - right: number; - top: number; - bottom: number; - }> - >(); - const placedLabelRects: Array<{ - featureId: string; - left: number; - right: number; - top: number; - bottom: number; - }> = []; - const placedLabelGrid = new Map< - string, - Array<{ - featureId: string; - left: number; - right: number; - top: number; - bottom: number; - }> - >(); - const collisionGridCellSize = 96; - - candidates.forEach(candidate => { - const featureId = String( - candidate.feature.getId?.() || candidate.feature.get('stcd') || '' - ); - const iconRect = { - left: candidate.iconLeft, - right: candidate.iconRight, - top: candidate.iconTop, - bottom: candidate.iconBottom - }; - const iconHasCollision = !!this.findCollidingRectInGrid( - iconRect, - placedIconGrid, - collisionGridCellSize, - rect => this.checkLabelCollision(rect, iconRect) - ); - const iconVisible = !iconHasCollision; - this.syncFeatureIconCollisionVisible( - candidate.feature, - iconVisible, - dirtyLayerKeys - ); - - if (iconVisible) { - placedIconRects.push({ - featureId, - left: iconRect.left, - right: iconRect.right, - top: iconRect.top, - bottom: iconRect.bottom - }); - this.addRectToCollisionGrid( - placedIconRects[placedIconRects.length - 1], - placedIconGrid, - collisionGridCellSize - ); - } - }); - - const labelCandidates = [...candidates].sort((left, right) => { - if (left.engPriority !== right.engPriority) { - return right.engPriority - left.engPriority; - } - if (left.priority !== right.priority) { - return left.priority - right.priority; - } - if (left.pixelY !== right.pixelY) { - return left.pixelY - right.pixelY; - } - if (left.pixelX !== right.pixelX) { - return left.pixelX - right.pixelX; - } - return left.id.localeCompare(right.id); - }); - - labelCandidates.forEach(candidate => { - const featureId = String( - candidate.feature.getId?.() || candidate.feature.get('stcd') || '' - ); - const iconVisible = - candidate.feature.get('_iconCollisionVisible') !== false; - if (!candidate.hasLabel) { - this.syncFeatureLabelCollisionVisible( - candidate.feature, - false, - dirtyLayerKeys - ); - return; - } - if (!iconVisible) { - this.syncFeatureLabelCollisionVisible( - candidate.feature, - false, - dirtyLayerKeys - ); - return; - } - - const collidingIconRect = this.findCollidingRectInGrid( - candidate, - placedIconGrid, - collisionGridCellSize, - rect => - rect.featureId !== featureId && - this.checkLabelCollisionWithIcon(rect, candidate) - ); - const hasCollisionWithIcon = !!collidingIconRect; - const collidingLabelRect = this.findCollidingRectInGrid( - candidate, - placedLabelGrid, - collisionGridCellSize, - rect => this.checkLabelCollision(rect, candidate) - ); - const hasCollisionWithLabel = !!collidingLabelRect; - const visible = !hasCollisionWithIcon && !hasCollisionWithLabel; - - this.syncFeatureLabelCollisionVisible( - candidate.feature, - visible, - dirtyLayerKeys - ); - - if (visible) { - placedLabelRects.push({ - featureId, - left: candidate.left, - right: candidate.right, - top: candidate.top, - bottom: candidate.bottom - }); - this.addRectToCollisionGrid( - placedLabelRects[placedLabelRects.length - 1], - placedLabelGrid, - collisionGridCellSize - ); - } - }); - this.flushDirtyPointLayers(dirtyLayerKeys); - } - - private requestRefreshPointLabelVisibility( - immediate = false, - debounceMs = 0 - ) { - if (this.labelVisibilityDebounceTimerId !== null) { - window.clearTimeout(this.labelVisibilityDebounceTimerId); - this.labelVisibilityDebounceTimerId = null; - } - - if (immediate) { - if (this.labelVisibilityRefreshFrameId !== null) { - cancelAnimationFrame(this.labelVisibilityRefreshFrameId); - this.labelVisibilityRefreshFrameId = null; - } - this.refreshPointLabelVisibility(); - return; - } - - if (debounceMs > 0) { - this.labelVisibilityDebounceTimerId = window.setTimeout(() => { - this.labelVisibilityDebounceTimerId = null; - this.requestRefreshPointLabelVisibility(); - }, debounceMs); - return; - } - - if (this.labelVisibilityRefreshFrameId !== null) { - return; - } - - this.labelVisibilityRefreshFrameId = window.requestAnimationFrame(() => { - this.labelVisibilityRefreshFrameId = null; - this.refreshPointLabelVisibility(); - }); - } - - private syncFeatureIconCollisionVisible( - feature: Feature, - visible: boolean, - dirtyLayerKeys?: Set - ) { - this.markFeatureVisibilityDirty( - feature, - '_iconCollisionVisible', - visible, - dirtyLayerKeys - ); - } - - private syncFeatureLabelCollisionVisible( - feature: Feature, - visible: boolean, - dirtyLayerKeys?: Set - ) { - this.markFeatureVisibilityDirty( - feature, - '_labelCollisionVisible', - visible, - dirtyLayerKeys - ); - } - - private isIconReady(iconUrl: string): boolean { - return this.iconLoadState.get(iconUrl) === 'loaded'; - } - - private ensureIconReady(iconUrl: string): boolean { - if (!iconUrl) { - return false; - } - - const status = this.iconLoadState.get(iconUrl); - if (status === 'loaded') { - return true; - } - if (status === 'loading' || status === 'error') { - return false; - } - - this.iconLoadState.set(iconUrl, 'loading'); - const image = new Image(); - image.crossOrigin = 'anonymous'; - image.onload = () => { - this.iconLoadState.set(iconUrl, 'loaded'); - this.requestRefreshPointLayerStyles(); - }; - image.onerror = () => { - this.iconLoadState.set(iconUrl, 'error'); - this.requestRefreshPointLayerStyles(); - }; - image.src = iconUrl; - - if (image.complete && image.naturalWidth > 0) { - this.iconLoadState.set(iconUrl, 'loaded'); - return true; - } - - return false; - } - - private requestRefreshPointLayerStyles() { - if (this.pointLayerStyleRefreshFrameId !== null) { - return; - } - - this.pointLayerStyleRefreshFrameId = window.requestAnimationFrame(() => { - this.pointLayerStyleRefreshFrameId = null; - this.refreshPointLayerStyles(); - }); - } - - private refreshPointLayerStyles() { - this.pointLayerManager.forEachLayer(layer => { - layer.changed(); - }); - this.requestRefreshPointLabelVisibility(); - if (this.isBatchPopupMode) { - this.requestUpdateBatchPopups(); - } - } - - private requestUpdateBatchPopups(immediate = false, rebuild = true) { - if (!this.isBatchPopupMode) { - return; - } - this.batchPopupPendingRebuild = this.batchPopupPendingRebuild || rebuild; - - if (immediate) { - if (this.batchPopupRefreshFrameId !== null) { - cancelAnimationFrame(this.batchPopupRefreshFrameId); - this.batchPopupRefreshFrameId = null; - } - this.updateBatchPopups(); - this.batchPopupPendingRebuild = false; - return; - } - - if (this.batchPopupRefreshFrameId !== null) { - return; - } - - this.batchPopupRefreshFrameId = window.requestAnimationFrame(() => { - this.batchPopupRefreshFrameId = null; - if (!this.isBatchPopupMode) { - return; - } - this.updateBatchPopups(); - this.batchPopupPendingRebuild = false; - }); - } - - private checkLabelCollision( - left: { left: number; right: number; top: number; bottom: number }, - right: { left: number; right: number; top: number; bottom: number } - ) { - const padding = 4; - return !( - left.right + padding < right.left || - left.left - padding > right.right || - left.bottom + padding < right.top || - left.top - padding > right.bottom - ); - } - - private checkLabelCollisionWithIcon( - iconRect: { left: number; right: number; top: number; bottom: number }, - labelRect: { left: number; right: number; top: number; bottom: number } - ) { - // 标签避让图标时仅保留图标中心更小的保护区,并允许边缘接触不算碰撞, - // 避免像“三峡/黄龙滩”这类仅在边界轻微擦到时把文字过度压掉。 - const inset = 4; - const padding = 0; - const narrowedIconRect = { - left: iconRect.left + inset, - right: iconRect.right - inset, - top: iconRect.top + inset, - bottom: iconRect.bottom - inset - }; - - return !( - narrowedIconRect.right + padding <= labelRect.left || - narrowedIconRect.left - padding >= labelRect.right || - narrowedIconRect.bottom + padding <= labelRect.top || - narrowedIconRect.top - padding >= labelRect.bottom - ); - } - - /** - * 点位标注最多显示两行,每行 12 个字符;超出部分在第二行末尾显示省略号。 - */ - private formatPointLabelText(text: string): string { - const normalizedText = String(text || '') - .replace(/[()()]/g, '') - .trim(); - if (!normalizedText) { - return ''; - } - - const maxLineLength = 12; - if (normalizedText.length <= maxLineLength) { - return normalizedText; - } - - const firstLine = normalizedText.slice(0, maxLineLength); - if (normalizedText.length <= maxLineLength * 2) { - return `${firstLine}\n${normalizedText.slice(maxLineLength)}`; - } - - const secondLine = `${normalizedText.slice( - maxLineLength, - maxLineLength + 9 - )}...`; - return `${firstLine}\n${secondLine}`; - } - - /** - * 根据缩放级别计算锚点图标缩放比例,统一复用,减少样式函数中的重复判断。 - */ - private getDynamicPointScale(currentZoom: number): number { - let dynamicScale = 0.7 + (currentZoom - 4.5) * 0.08; - dynamicScale = Math.max(0.5, Math.min(3.0, dynamicScale)); - return dynamicScale; - } - - /** - * 根据图标缩放比例统一计算文字字号,避免样式函数中散落重复计算。 - */ - private getPointFontSize(dynamicScale: number): number { - return Math.max(10, Math.min(24, 12 * dynamicScale)); - } - - /** - * 统一计算标签渲染时的纵向偏移,保持视觉位置与原效果一致。 - */ - private getPointLabelRenderOffsetY( - dynamicScale: number, - labelLineCount: number - ): number { - return labelLineCount > 1 ? -30 * dynamicScale : -22 * dynamicScale; - } - - /** - * 统一计算标签碰撞时的纵向偏移。 - * 这里允许比视觉位置略高一点,避免相邻点图标过早压掉主标签。 - */ - private getPointLabelCollisionOffsetY( - dynamicScale: number, - labelLineCount: number - ): number { - return labelLineCount > 1 ? -36 * dynamicScale : -28 * dynamicScale; - } - - /** - * distance 字段表示点位密度分档,不是物理距离。 - * 这里仅用它决定点位从哪个缩放级别开始参与显示候选。 - */ - private getFeatureDensityValue(feature: Feature): number | null { - const rawDistance = feature.get('distance'); - if ( - rawDistance === undefined || - rawDistance === null || - rawDistance === '' - ) { - return null; - } - const densityValue = Number(rawDistance); - return Number.isFinite(densityValue) ? densityValue : null; - } - - private getFeatureDensityPriority(feature: Feature): number { - const densityValue = this.getFeatureDensityValue(feature); - const densityDisplayRules = getNearbyPointDensityDisplayRules(); - if (densityValue === null) { - return densityDisplayRules.length; - } - - const matchedRuleIndex = densityDisplayRules.findIndex(rule => { - return densityValue >= rule.minDensityValue; - }); - return matchedRuleIndex >= 0 - ? matchedRuleIndex - : densityDisplayRules.length; - } - - private getFeatureDensityMinZoom(feature: Feature): number { - const densityValue = this.getFeatureDensityValue(feature); - if (densityValue === null) { - return 0; - } - - const densityDisplayRules = getNearbyPointDensityDisplayRules(); - const matchedRule = densityDisplayRules.find(rule => { - return densityValue >= rule.minDensityValue; - }); - if (matchedRule) { - return matchedRule.minZoom; - } - - return densityDisplayRules.at(-1)?.minZoom ?? 0; - } - - private isFeatureEng(feature: Feature): boolean { - const layerKey = String(feature.get('_layerKey') || '').toLowerCase(); - const sttpMap = String( - feature.get('_sttpMap') || feature.get('sttpMap') || '' - ).toUpperCase(); - const sttpCode = String( - feature.get('sttpCode') || feature.get('sttp') || '' - ).toUpperCase(); - - return ( - layerKey.includes('eng_point') || - sttpMap === 'ENG' || - sttpMap === 'ENG2' || - sttpCode === 'ENG' - ); - } - - private isFeatureEngAlarm(feature: Feature): boolean { - const layerKey = String( - feature.get('_layerKey') || feature.get('type') || '' - ).toLowerCase(); - const legendState = String(feature.get('anchoPointState') || '') - .trim() - .toLowerCase(); - - return ( - layerKey.includes('eng_alarm_point') || - layerKey.includes('alarm_range') || - legendState.startsWith('alarm_range_') || - legendState.startsWith('large_eng_built_alarm_range_') || - legendState.startsWith('mid_eng_built_alarm_range_') - ); - } - - private getFeatureEngRenderPriority(feature: Feature): number { - if (this.isFeatureEngAlarm(feature)) { - return 300; - } - return this.isFeatureEng(feature) ? 200 : 100; - } - - /** - * 如果点位在当前视图中没有近邻点,则允许跳过静态密度门槛直接参与显示。 - * 这样像“戈兰滩”这类 distance 档位偏密、但当前周围实际很空的点也能出现。 - */ - private shouldBypassDensityGateForIsolatedFeature(feature: Feature): boolean { - if (!this.map) { - return false; - } - - const layerKey = String(feature.get('_layerKey') || ''); - const geometry = feature.getGeometry(); - if (!geometry || geometry.getType() !== 'Point') { - return false; - } - - const featurePixel = this.map.getPixelFromCoordinate( - (geometry as Point).getCoordinates() - ); - if (!featurePixel) { - return false; - } - - const nearbyPixelThreshold = 56; - const viewportFeatures = this.pointLayerManager.getFeaturesInViewport(); - - return !viewportFeatures.some(otherFeature => { - if (otherFeature === feature) { - return false; - } - if (otherFeature.get('_legendVisible') === false) { - return false; - } - if (otherFeature.get('_regionVisible') === false) { - return false; - } - if (String(otherFeature.get('_layerKey') || '') !== layerKey) { - return false; - } - - const otherGeometry = otherFeature.getGeometry(); - if (!otherGeometry || otherGeometry.getType() !== 'Point') { - return false; - } - - const otherPixel = this.map?.getPixelFromCoordinate( - (otherGeometry as Point).getCoordinates() - ); - if (!otherPixel) { - return false; - } - - return ( - Math.hypot( - otherPixel[0] - featurePixel[0], - otherPixel[1] - featurePixel[1] - ) <= nearbyPixelThreshold - ); - }); - } - - private shouldRenderFeatureByDensity( - feature: Feature, - currentZoom: number - ): boolean { - const minZoom = this.getFeatureDensityMinZoom(feature); - const bypassDensityGate = - currentZoom < minZoom && - this.shouldBypassDensityGateForIsolatedFeature(feature); - return currentZoom >= minZoom || bypassDensityGate; - } - - /** - * 近邻点统一参与原位堆叠,具体谁显示在顶层交给碰撞排序和图层优先级控制。 - */ - private shouldRenderNearbyFeature(): boolean { - return true; - } - /** - * 初始化加载基础图层 - * @param layer 图层配置对象 - */ - addBaseDataLayer(layer: any, isShow: boolean, isCache = false): void { - if (!this.map) return; - - // 备注:同 key 的底图重复添加前先移除旧实例,避免注册表覆盖后旧图层仍残留在地图上。 - const existingLayer = layer?.key - ? this.layerRegistry.get(layer.key as string) - : null; - if (existingLayer) { - this.map.removeLayer(existingLayer); - this.layerRegistry.delete(layer.key); - } - - if (layer.type === 'wmts') { - if (!layer.url) return; - const url = !isCache ? layer.url : layer.url_3d; - const urlParams = new URLSearchParams(url.split('?')[1]); - if (layer.key === this.REGISTRY_KEY) { - this.baseLayerConfig = layer; - } - const layerName: string = urlParams.get('LAYER') || ''; - const matrixSetName: any = urlParams.get('TILEMATRIXSET'); - - const projection = getProjection('EPSG:3857'); - if (!projection) { - console.error('无法获取 EPSG:3857 投影'); - return; - } - const projectionExtent = projection.getExtent(); - - const size = getWidth(projectionExtent) / 256; - const maxZoom = 13; - - const resolutions = new Array(maxZoom + 1); - const matrixIds = new Array(maxZoom + 1); - - for (let z = 0; z <= maxZoom; ++z) { - resolutions[z] = size / Math.pow(2, z); - matrixIds[z] = `${matrixSetName}:${z}`; - } - - const wmtsLayer = new TileLayer({ - source: new WMTS({ - url: layer.url.split('?')[0], - layer: layerName, - matrixSet: matrixSetName, - format: 'image/png', - projection: projection, - tileGrid: new WMTSTileGrid({ - origin: getTopLeft(projectionExtent), - resolutions: resolutions, - matrixIds: matrixIds - }), - style: 'default', - wrapX: true, - crossOrigin: 'anonymous' - }) - }); - - if (layer.key === this.REGISTRY_KEY) { - !isCache ? wmtsLayer.setZIndex(-100) : wmtsLayer.setZIndex(-99); - } else { - wmtsLayer.setZIndex(-100); - } - wmtsLayer.type = 'layer'; - wmtsLayer.setVisible(isShow); - this.layerRegistry.set(layer.key, wmtsLayer); - this.map.addLayer(wmtsLayer); - layer._layer = wmtsLayer; - } else if (layer.type == 'raster-dem') { - const tileLayer = new TileLayer({ - source: new XYZ({ - url: layer.url, - wrapX: true, - crossOrigin: 'anonymous' - }) - }); - tileLayer.setZIndex(-100); - tileLayer.setVisible(isShow); - tileLayer.type = 'layer'; - this.layerRegistry.set(layer.key, tileLayer); - this.map.addLayer(tileLayer); - } else if (layer.type === 'vector') { - if (layer.key === 'hydropBase') { - this.hydropBaseConfig = layer; - } - } - } - /** - * 根据 key 设置底图图层显隐 - * @param layerKey 图层 key - * @param visible 是否显示 - */ - private setBaseLayerVisible(layerKey: string, visible: boolean): void { - const layer = this.layerRegistry.get(layerKey); - if (layer) { - layer.setVisible(visible); - } - } - /** - * 同步当前激活底图源的显隐,保证树勾选和下方图源切换使用同一套可见性规则 - * @param checked 是否显示当前激活底图 - */ - private syncActiveBaseLayerVisibility(checked: boolean): void { - this.setBaseLayerVisible(this.REGISTRY_KEY, checked); - - this.rasterBaseLayerKeys.forEach(key => { - const shouldShow = - checked && - this.activeBaseLayerKey !== 's_province_boundaries' && - key === this.activeBaseLayerKey; - this.setBaseLayerVisible(key, shouldShow); - }); - } - /** - * 获取当前应应用基地裁切的底图图层集合 - * @returns 当前可见底图图层列表 - */ - private getCurrentMaskTargetLayers(): TileLayer[] { - const layers: TileLayer[] = []; - const baseLayer = this.layerRegistry.get(this.REGISTRY_KEY); - - if (baseLayer instanceof TileLayer) { - layers.push(baseLayer); - } - - if (this.activeBaseLayerKey !== 's_province_boundaries') { - const activeRasterLayer = this.layerRegistry.get(this.activeBaseLayerKey); - if ( - activeRasterLayer instanceof TileLayer && - activeRasterLayer !== baseLayer - ) { - layers.push(activeRasterLayer); - } - } - - return layers; - } - /** - * 重新把当前基地裁切绑定到最新底图实例,保证切换图源后边界裁切持续生效 - */ - private reapplyCurrentBaseMask(): void { - if (!this.currentClipGeoJson) { - return; - } - - const targetLayers = this.getCurrentMaskTargetLayers(); - if (targetLayers.length === 0) { - return; - } - - this.regionMaskManager.applyMapMaskToLayers( - targetLayers, - this.currentClipGeoJson - ); - } - enableNortheastMask(): void { - const baseLayer = this.layerRegistry.get(this.REGISTRY_KEY); - - if (!this.geoJsonData1) { - console.warn('东北边界数据尚未加载'); - return; - } - - if (baseLayer && baseLayer instanceof TileLayer) { - this.regionMaskManager.applyMapMask(baseLayer, this.geoJsonData1); - } else { - console.warn('未找到全量底图图层或图层类型错误'); - } - } - /** - * 控制特定区域图层的显示(互斥显示) - * @param regionId 区域ID (例如 "hebei", "13", "01" 等,需与图层 Key 或属性对应) - * @param isAll 是否显示所有 (true: 显示所有图层; false: 仅显示匹配 regionId 的图层,隐藏其他) - */ - async jdPanelControlShowAndHidden( - _regionId: string, - isAll: boolean - ): Promise { - if (!this.map || !this.hydropBaseConfig) { - console.warn('地图未初始化或 hydropBaseConfig 未配置'); - return; - } - - if (this.clipRequestController) { - this.clipRequestController.abort(); - this.clipRequestController = null; - } - - this.BASEID = _regionId; - - if (!isAll) { - this.currentClipGeoJson = null; - this.regionMaskManager.clearMapMask(); - this.regionMaskManager.showAllPoints(); - return; - } - - const baseLayers = this.getCurrentMaskTargetLayers(); - if (baseLayers.length === 0) { - console.warn('未找到底图图层,无法应用遮罩'); - return; - } - - this.regionMaskManager.hideAllPoints(); - this.clipRequestController = new AbortController(); - const signal = this.clipRequestController.signal; - - try { - const url = - this.hydropBaseConfig.geojson_url + - `&cql_filter=BASEID='${this.BASEID}'`; - - const geoJsonData = await this.loadGeoJsonData(url, signal); - - if (signal.aborted) { - return; - } - - if (this.BASEID !== _regionId) { - return; - } - - this.currentClipGeoJson = geoJsonData; - this.regionMaskManager.filterPointsByRegion(geoJsonData); - this.regionMaskManager.fitViewToGeoJson(geoJsonData); - this.regionMaskManager.applyMapMaskToLayers(baseLayers, geoJsonData); - } catch (error: any) { - if (error.name === 'AbortError') { - return; - } - this.currentClipGeoJson = null; - console.error('加载裁切数据失败:', error); - this.regionMaskManager.clearMapMask(); - this.regionMaskManager.showAllPoints(); - } finally { - if (this.clipRequestController?.signal === signal) { - this.clipRequestController = null; - } - } - } - /** - * 基础图层显示影隐藏方法 - * @param layerType 图层类型 (备用,优先使用 key) - * @param key 图层唯一标识 Key - * @param checked true 为显示,false 为隐藏 - */ - controlBaseLayerTreeShowAndHidden( - layerType: string, - key: string, - checked: boolean - ): void { - if (!this.map) return; - const registryKey = key || layerType; - - if (!registryKey) { - console.warn( - 'controlBaseLayerTreeShowAndHidden: 缺少有效的 Key 或 LayerType' - ); - return; - } - if (registryKey === this.REGISTRY_KEY) { - this.syncActiveBaseLayerVisibility(checked); - return; - } - const layerInstance = this.layerRegistry.get(registryKey as string); - if (layerInstance) { - layerInstance.setVisible(checked); - // 图层显隐变化时刷新 batch popup - if (this.isBatchPopupMode) { - this.requestUpdateBatchPopups(true); - } - } else { - console.warn( - `未找到标识为 [${registryKey}] 的图层实例,当前注册表 keys:`, - Array.from(this.layerRegistry.keys()) - ); - } - } - mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void { - this.pointLayerManager.setLayerVisible(layerType, checked); - this.requestRefreshPointLabelVisibility(true); - // 图层显隐变化时刷新 batch popup - if (this.isBatchPopupMode) { - this.requestUpdateBatchPopups(true); - } - } - - hasLayer(layerType: string): boolean { - return this.pointLayerManager.hasLayer(layerType as string); - } - - hasBaseLayer(layerKey: string): boolean { - return this.layerRegistry.has(layerKey); - } - - /** - * 缩放地图 - * @param type 'out' 缩小, 'in' 放大 - */ - zoomToggle(type: 'out' | 'in'): void { - if (!this.map || !this.view) return; - - const currentZoom = this.view.getZoom(); - if (currentZoom === undefined) return; - - // 定义缩放步长,对应 Leaflet 的 zoomDelta - const zoomDelta = 1; - - let targetZoom = currentZoom; - - if (type === 'in') { - targetZoom = currentZoom + zoomDelta; - // 限制最大缩放级别 - if (targetZoom > MAX_ZOOM) { - targetZoom = MAX_ZOOM; - } - } else { - targetZoom = currentZoom - zoomDelta; - // 限制最小缩放级别 - if (targetZoom < MIN_ZOOM) { - targetZoom = MIN_ZOOM; - } - } - - // 使用 animate 实现平滑缩放动画 - this.view.animate({ - zoom: targetZoom, - duration: 250, // 动画持续时间 (ms),与滚轮缩放保持一致 - easing: t => t // 线性缓动,也可以引入 ol/easing 使用更复杂的曲线 - }); - } - - /** - * 切换底图 - * @param key 业务标识 Key (如 's_province_boundaries', 'BASEMAP-white', 'BASEMAP-img') - */ - baseLayerSwitcher(key: string, checked: boolean): void { - if (!this.map || !this.view) return; - this.activeBaseLayerKey = key; - const oldLayer = this.layerRegistry.get(this.REGISTRY_KEY); - if (oldLayer) { - this.map.removeLayer(oldLayer); - this.layerRegistry.delete(this.REGISTRY_KEY); - } - - if (key == 's_province_boundaries') { - this.addBaseDataLayer(this.baseLayerConfig, checked); - this.hideBaseMapLayers(this.rasterBaseLayerKeys); - } else { - this.addBaseDataLayer(this.baseLayerConfig, checked, true); - this.rasterBaseLayerKeys.forEach(layerKey => { - if (layerKey === key) { - this.addBaseDataLayer(servers.BaseLayer[layerKey], checked); - } else { - this.setBaseLayerVisible(layerKey, false); - } - }); - } - - this.reapplyCurrentBaseMask(); - } - /** - * 隐藏指定的底图图层 - * @param layerKeys 要隐藏的图层 key 数组 - */ - hideBaseMapLayers(layerKeys: string[]): void { - layerKeys.forEach(key => { - const layer = this.layerRegistry.get(key); - if (layer) { - layer.setVisible(false); - } - }); - } - - /** - * 显示指定的底图图层 - * @param layerKeys 要显示的图层 key 数组 - */ - showBaseMapLayers(layerKeys: string[]): void { - layerKeys.forEach(key => { - const layer = this.layerRegistry.get(key); - if (layer) { - layer.setVisible(true); - } - }); - } - - /** - * 添加梯级流域图 (Vector Layer) - 使用本地 GeoJSON 数据 - * @param layer 图层配置对象 - * @param fillcolor 填充颜色 - * @param outlineColor 边框颜色 - * @param datas 需要显示的 RVCD 列表 (例如: ['SJLY25', ...]),如果为空则显示所有或根据业务逻辑处理 - */ - addTertiarybasinLayer( - layer: any, - fillcolor: any, - outlineColor: any, - datas: any - ): void { - if (!this.map || !this.view) return; - - // 1. 检查是否已存在该图层,已存在时直接切换为显示并刷新样式 - const existingLayer = this.layerRegistry.get(layer.key); - - // 2. 创建样式函数 - const visibleStyle = new Style({ - fill: new Fill({ color: fillcolor }), - stroke: new Stroke({ color: outlineColor, width: 1 }) - }); - - const hiddenStyle = new Style({ - fill: new Fill({ color: 'rgba(0,0,0,0)' }), - stroke: new Stroke({ color: 'rgba(0,0,0,0)' }) - }); - - if (existingLayer) { - const allowedIds = Array.isArray(datas) ? datas : []; - const existingFeatures = - existingLayer.getSource?.()?.getFeatures?.() ?? []; - - existingFeatures.forEach(feature => { - const rvcd = feature.get('RVCD'); - const isVisible = - allowedIds.length > 0 ? rvcd && allowedIds.includes(rvcd) : true; - - feature.setStyle(isVisible ? visibleStyle : hiddenStyle); - }); - - existingLayer.setVisible(layer.visible !== false); - return; - } - - // 3. 创建矢量源 (VectorSource),并通过 layer.geojson_url 加载数据 - const vectorSource = new VectorSource(); - - if (layer?.geojson_url) { - this.loadGeoJsonData(VITE_APP_MAP_URL + layer.geojson_url).then( - geoJsonData => { - if (!geoJsonData) return; - - const features = new GeoJSON().readFeatures(geoJsonData, { - featureProjection: 'EPSG:3857' // 确保转换到地图使用的投影 - }); - - vectorSource.addFeatures(features); - - // 4. 数据过滤逻辑 - const allowedIds = Array.isArray(datas) ? datas : []; - - features.forEach(feature => { - // 获取属性中的 RVCD - const rvcd = feature.get('RVCD'); - - let isVisible = false; - - // 逻辑:如果 datas 为空,通常意味着显示所有(或者都不显示,视具体需求而定) - // 这里假设:datas 有值时严格过滤;datas 为空时显示所有 - if (allowedIds.length > 0) { - isVisible = rvcd && allowedIds.includes(rvcd); - } else { - isVisible = true; - } - - // 应用样式 - feature.setStyle(isVisible ? visibleStyle : hiddenStyle); - }); - } - ); - } - - // 5. 创建矢量图层 (VectorLayer) - const vectorLayer = new VectorLayer({ - source: vectorSource, - style: null, // 样式已应用在 Feature 上,这里设为 null 或保留默认 - zIndex: 100, - visible: layer.visible !== false - }); - - // 6. 注册并添加到地图 - this.layerRegistry.set(layer.key, vectorLayer); - this.map.addLayer(vectorLayer); - } - /** - * 隐藏梯级流域图 - * @param layer 图层配置对象 (需包含 key 属性) - */ - hideTertiarybasinLayer(layer: any): void { - if (!this.map || !layer || !layer.key) { - console.warn('hideTertiarybasinLayer: 无效的图层或地图未初始化'); - return; - } - // 1. 从注册表中获取图层实例 - const layerInstance = this.layerRegistry.get(layer.key); - - if (layerInstance) { - layerInstance.setVisible(false); - } else { - console.warn(`未找到标识为 [${layer.key}] 的梯级流域图层实例`); - } - } - /** - * 删除描点图层 - * @param layerKey 图层 key - */ - removePointLayer(layerKey: string): void { - this.pointLayerManager.removeLayer(layerKey); - } - /** - * 地图打印/导出 - * 将当前地图视图导出为 PNG 图片,背景强制为白色 - */ - mapOutPut(): void { - if (!this.map) { - console.warn('地图未初始化,无法打印'); - return; - } - const mapElement = this.map.getTargetElement(); - const canvas = mapElement.querySelector('canvas'); - - if (!canvas) { - console.error('未找到地图 Canvas 元素,无法导出'); - return; - } - - try { - const width = canvas.width; - const height = canvas.height; - - const offscreenCanvas = document.createElement('canvas'); - offscreenCanvas.width = width; - offscreenCanvas.height = height; - const ctx = offscreenCanvas.getContext('2d'); - - if (!ctx) { - throw new Error('无法获取 Canvas 上下文'); - } - - ctx.fillStyle = '#FFFFFF'; - ctx.fillRect(0, 0, width, height); - - ctx.drawImage(canvas, 0, 0); - - const dataURL = offscreenCanvas.toDataURL('image/png'); - - const link = document.createElement('a'); - link.download = `map_export_${new Date().getTime()}.png`; - link.href = dataURL; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } catch (e) { - console.error('地图导出失败', e); - alert('地图导出失败,请检查控制台错误信息。'); - } - } - - /** - * 切换双击缩放交互的状态 - */ - private toggleDoubleClickZoom(active: boolean): void { - if (!this.map) return; - this.map.getInteractions().forEach(interaction => { - if (interaction instanceof DoubleClickZoom) { - interaction.setActive(active); - } - }); - } - - /** - * 创建并返回测量 Tooltip Overlay - */ - private createMeasureTooltipOverlay(): Overlay { - const element = document.createElement('div'); - element.className = 'measure-tooltip tooltip-measure'; - Object.assign(element.style, { - background: 'rgba(0, 0, 0, 0.7)', - color: 'white', - padding: '4px 8px', - borderRadius: '4px', - fontSize: '12px', - whiteSpace: 'nowrap' - }); - - return new Overlay({ - element, - offset: [0, -15], - positioning: 'bottom-center' - }); - } - /** - * 通用测量启动方法 - * @param geomType 'LineString' | 'Polygon' - * @param drawStyle 绘制中的样式函数 - * @param finishedStyle 完成后的样式 - * @param onResult 计算结果的回调 (feature, resultText) - */ - private startMeasurement( - geomType: 'LineString' | 'Polygon', - drawStyle: any, - finishedStyle: Style, - onResult: (feature: Feature, text: string) => void - ): void { - if (!this.map || !this.view) return; - - // 1. 初始化图层 - if (!this.measureLayer) { - this.measureSource = new VectorSource(); - this.measureLayer = new VectorLayer({ - source: this.measureSource, - zIndex: 1000 - }); - this.map.addLayer(this.measureLayer); - } - - // 2. 清理旧交互 - if (this.drawInteraction) { - this.map.removeInteraction(this.drawInteraction); - } - - // 禁用双击缩放 - this.toggleDoubleClickZoom(false); - - // 3. 创建绘制交互 - this.drawInteraction = new Draw({ - source: this.measureSource!, - type: geomType, - style: drawStyle - }); - - this.map.addInteraction(this.drawInteraction); - - let changeListenerKey: any; - let currentTooltip: Overlay | null = null; - - // 4. 监听绘制开始 - this.drawInteraction.on('drawstart', (evt: any) => { - const feature = evt.feature; - const geom = feature.getGeometry(); - - // 创建动态 Tooltip - currentTooltip = this.createMeasureTooltipOverlay(); - this.map?.addOverlay(currentTooltip); - - // 监听几何变化更新 Tooltip - changeListenerKey = geom!.on('change', () => { - if (currentTooltip && currentTooltip.getElement() && geom) { - // 根据类型计算临时值 - let tempText = ''; - if (geomType === 'LineString') { - tempText = this.formatLength(geom as LineString); - } else { - tempText = this.formatArea( - geom as import('ol/geom/Polygon').default - ); - } - - currentTooltip.getElement()!.innerHTML = tempText; - - // 对于多边形,Tooltip 跟随鼠标最后一个点;对于线,也是最后一个点 - const lastCoord = (geom as any).getLastCoordinate(); - if (lastCoord) currentTooltip.setPosition(lastCoord); - } - }); - }); - - // 5. 监听绘制结束 - this.drawInteraction.on('drawend', (evt: any) => { - const feature = evt.feature; - const geom = feature.getGeometry(); - - // ✅ 修复:检查几何体是否有效 - // 对于 Polygon,如果坐标点数不足(例如只点了两下),geom 可能是无效的或面积为0 - if (!geom || geom.getCoordinates().length === 0) { - console.warn('无效的几何体,已忽略'); - this.measureSource?.removeFeature(feature); - if (currentTooltip) this.map?.removeOverlay(currentTooltip); - if (changeListenerKey) unByKey(changeListenerKey); - return; - } - - // 清理监听和临时 Tooltip - if (changeListenerKey) unByKey(changeListenerKey); - if (currentTooltip) this.map?.removeOverlay(currentTooltip); - - // ✅ 修复:强制设置样式,确保即使默认样式有问题也能显示 - feature.setStyle(finishedStyle); - - // 计算最终结果并回调 - let resultText = ''; - let isValidResult = true; - - if (geomType === 'LineString') { - resultText = this.formatLength(geom as LineString); - } else { - // 对于多边形,再次检查面积是否过小或无效 - const polyGeom = geom as import('ol/geom/Polygon').default; - const area = getSphericalArea(polyGeom); - if (area < 0.0001) { - // 极小面积视为无效绘制 - isValidResult = false; - console.warn('面积过小,已忽略'); - this.measureSource?.removeFeature(feature); - } else { - resultText = this.formatArea(polyGeom); - } - } - - // 只有结果有效才添加标签 - if (isValidResult) { - onResult(feature, resultText); - } - - // 清理交互 - this.map?.removeInteraction(this.drawInteraction); - this.drawInteraction = null; - - // 恢复双击缩放 - setTimeout(() => { - this.toggleDoubleClickZoom(true); - }, 0); - }); - } - /** - * 长度量算 - */ - lengthCalculate(): void { - this.startMeasurement( - 'LineString', - (feature: any) => { - // 绘制中只显示线,隐藏点 - return feature.getGeometry()?.getType() === 'LineString' - ? new Style({ - stroke: new Stroke({ - color: '#ffcc33', - lineDash: [10, 10], - width: 2 - }) - }) - : []; - }, - new Style({ - stroke: new Stroke({ color: '#9AD8E7', width: 3 }) // 加粗一点 - }), - (feature, text) => { - this.addFixedLabel(feature, text); - } - ); - } - - /** - * 面积量算 - */ - areCalculate(): void { - this.startMeasurement( - 'Polygon', - (feature: any) => { - // 绘制中显示线和填充 - return feature.getGeometry()?.getType() === 'Polygon' - ? new Style({ - stroke: new Stroke({ - color: '#ffcc33', - lineDash: [10, 10], - width: 2 - }), - fill: new Fill({ color: 'rgba(255, 204, 51, 0.3)' }) // 提高透明度以便看清 - }) - : []; - }, - new Style({ - stroke: new Stroke({ color: '#9AD8E7', width: 3 }), // 加粗边框 - fill: new Fill({ color: 'rgba(154, 216, 231, 0.4)' }) // 提高填充可见度 - }), - (feature, text) => { - this.addFixedLabel(feature, text); - } - ); - } - /** - * 移除量算结果 - */ - removeQueryLayer(): void { - // 1. 清除矢量数据 (线条) - if (this.measureSource) { - this.measureSource.clear(); - } - - // 2. 清除所有相关 Overlay (Label) - if (this.map) { - // ✅ 修复:先获取所有 Overlay 的副本,避免在遍历过程中修改原数组导致漏删 - const overlaysToRemove: Overlay[] = []; - - this.map.getOverlays().forEach(overlay => { - const el = overlay.getElement(); - // 通过类名判断是否为我们创建的测量标签 - if ( - el && - (el.classList.contains('measure-tooltip') || - el.classList.contains('measure-label-container')) - ) { - overlaysToRemove.push(overlay); - } - }); - - // 统一移除 - overlaysToRemove.forEach(overlay => { - this.map?.removeOverlay(overlay); - }); - - // 确保恢复双击缩放 - this.toggleDoubleClickZoom(true); - } - - // 3. 清除交互 - if (this.drawInteraction) { - this.map?.removeInteraction(this.drawInteraction); - this.drawInteraction = null; - } - } - - /** - * 添加固定的 Label (带删除按钮) - */ - private addFixedLabel(feature: Feature, lengthText: string): void { - if (!this.map) return; - - const container = document.createElement('div'); - container.className = 'measure-label-container'; - - // 使用 Object.assign 批量设置样式,代码更整洁 - Object.assign(container.style, { - position: 'absolute', - border: '1px solid #000', - padding: '4px 8px', - borderRadius: '4px', - fontSize: '12px', - color: '#000', - cursor: 'default', - whiteSpace: 'nowrap', - display: 'flex', - alignItems: 'center', - zIndex: '1001', - pointerEvents: 'auto' // 确保可以点击 - }); - - const textSpan = document.createElement('span'); - textSpan.innerText = lengthText; - - const closeBtn = document.createElement('span'); - closeBtn.innerHTML = '×'; - Object.assign(closeBtn.style, { - fontWeight: 'bold', - marginLeft: '5px', - cursor: 'pointer', - fontSize: '14px', - lineHeight: '1' - }); - - const geom: any = feature.getGeometry(); - let center: any; - - // ✅ 修复:根据几何类型选择标签位置 - if (geom?.getType() === 'Polygon') { - // 多边形使用内部点,确保标签在面内 - center = (geom as import('ol/geom/Polygon').default) - .getInteriorPoint() - .getCoordinates(); - } else { - // 线使用中点 - center = (geom as LineString).getCoordinateAt(0.5); - } - - const overlay = new Overlay({ - element: container, - positioning: 'top-center', - offset: [0, -10], - position: center - }); - - closeBtn.onclick = e => { - e.stopPropagation(); - this.measureSource?.removeFeature(feature); - this.map?.removeOverlay(overlay); - }; - - container.appendChild(textSpan); - container.appendChild(closeBtn); - this.map.addOverlay(overlay); - } - /** - * 格式化长度输出 (米) - */ - private formatLength(line: LineString): string { - return '长度:' + getSphericalLength(line).toFixed(3) + 'm'; - } - /** - * 格式化面积输出 (km²) - */ - private formatArea(polygon: import('ol/geom/Polygon').default): string { - // 获取球面面积 (平方米) - const areaSqMeters = getSphericalArea(polygon); - // 转换为平方公里 - const areaSqKm = areaSqMeters / 1000000; - return '面积:' + areaSqKm.toFixed(3) + 'km²'; - } - /** - * 图例和基地面板控制描点数据显示隐藏方法 - * @param layerType 图层类型 - * @param key 图层标识符(锚点状态 nameEn) - * @param baseid 基地ID - * @param checked 是否选中(显示/隐藏) - * @param isAll 是否全部操作 - */ - mdLayerShowOrHidden( - layerType: string, - key?: string, - baseid: string, - checked?: boolean, - isAll?: boolean - ): void { - void baseid; - void isAll; - this.pointLayerManager.setLegendVisibleByField( - layerType, - key || '', - !!checked - ); - // 图例变化时刷新 batch popup - if (this.isBatchPopupMode) { - this.requestUpdateBatchPopups(true); - } - } - - /** - * 根据 anchoPointState 控制单个图例的锚点显示隐藏 - * @param layerKey 图层 key - * @param anchoPointState 锚点状态(对应图例的 nameEn) - * @param checked 是否显示 - */ - setLegendPointVisible( - layerKey: string, - anchoPointState: string, - checked: boolean - ): void { - this.pointLayerManager.setLegendVisibleByField( - layerKey, - anchoPointState, - checked - ); - this.requestRefreshPointLabelVisibility(true); - // 图例变化时刷新 batch popup - if (this.isBatchPopupMode) { - this.requestUpdateBatchPopups(true); - } - } - - switchView(_type): void {} - fitBounds(): void {} - /** - * 飞行定位到指定经纬度和缩放级别 - * @param position [经度, 纬度] (EPSG:4326) - * @param zoom 缩放级别 - */ - flyTopanto(position: number[], zoom: number): void { - if (!this.map || !this.view) { - console.warn('地图未初始化,无法执行飞行定位'); - return; - } - - // 1. 将经纬度 [lon, lat] 转换为地图投影坐标 (EPSG:3857) - const targetCenter = fromLonLat(position); - - // 2. 执行动画 - // duration: 动画持续时间,单位毫秒,例如 1000ms (1秒) - this.view.animate( - { - center: targetCenter, - duration: 1000 - }, - { - zoom: zoom, - duration: 1000 - } - ); - } - - getCurrentZoom(): number | undefined { - return this.view?.getZoom(); - } - - /** - * 生成带完整圆边框的上半圆 Canvas - * @param radius 半径(像素) - * @param color 填充颜色 - * @returns HTMLCanvasElement - */ - private createSemiCircleCanvas( - radius: number, - color: string - ): HTMLCanvasElement { - const size = radius * 2 + 2; - const canvas = document.createElement('canvas'); - canvas.width = size; - canvas.height = size; - const ctx = canvas.getContext('2d')!; - ctx.clearRect(0, 0, size, size); - - const centerX = size / 2; - const centerY = size / 2; - - // 1. 绘制下半圆填充(给定颜色)— 圆弧从 π 到 2π - ctx.beginPath(); - ctx.arc(centerX, centerY, radius, 0, Math.PI, false); - ctx.closePath(); - ctx.fillStyle = color; - ctx.fill(); - - // 2. 绘制上半圆填充(白色) - ctx.beginPath(); - ctx.arc(centerX, centerY, radius, Math.PI, 2 * Math.PI, false); - ctx.closePath(); - ctx.fillStyle = '#FFFFFF'; // 白色 - ctx.fill(); - - // 3. 绘制完整圆边框 - ctx.beginPath(); - ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); - ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)'; - ctx.lineWidth = 1.5; - ctx.stroke(); - - return canvas; - } - - /** - * 移除地图对象,释放资源 - */ - destroy(): void { - if (this.pointLayerStyleRefreshFrameId !== null) { - cancelAnimationFrame(this.pointLayerStyleRefreshFrameId); - this.pointLayerStyleRefreshFrameId = null; - } - if (this.labelVisibilityRefreshFrameId !== null) { - cancelAnimationFrame(this.labelVisibilityRefreshFrameId); - this.labelVisibilityRefreshFrameId = null; - } - if (this.labelVisibilityDebounceTimerId !== null) { - window.clearTimeout(this.labelVisibilityDebounceTimerId); - this.labelVisibilityDebounceTimerId = null; - } - this.clearBatchPopupPostRenderSync(); - this.removeQueryLayer(); - this.regionMaskManager.destroy(); - - const clearLayer = (layer: any) => { - if (layer && layer.getSource) { - const source = layer.getSource(); - if (source && source.clear) { - source.clear(); - } - } - if (this.map && this.map.getLayers().getArray().includes(layer)) { - this.map.removeLayer(layer); - } - if (layer.setMap) layer.setMap(null); - }; - - if (this.layerRegistry) { - this.layerRegistry.forEach(clearLayer); - this.layerRegistry.clear(); - } - - this.popupManager.destroy(); - this.pointLayerManager.destroy(); - - if (this.map) { - this.map.getInteractions().clear(); - this.map.getOverlays().clear(); - this.map.setTarget(undefined); - this.map.dispose(); - this.map = null; - } - this.popupManager.setMap(null); - this.pointLayerManager.setMap(null); - this.regionMaskManager.setContext(null, null); - - if (this.view) { - this.view.dispose(); - this.view = null; - } - - this.baseLayerConfig = null; - this.measureSource = null; - this.measureLayer = null; - this.drawInteraction = null; - this.geoJsonData = null; - this.geoJsonData1 = null; - } - - // 倾斜摄影方法仅3D支持,2D为空实现 - addQxsyLayer(_item: any): void {} - removeQxsyLayer(_item: any): void {} - qxsyChangeClick(_item: any, _checked: boolean): void {} - qxsyToPosition(_item: any): void {} -} diff --git a/frontend-sjgl/src/components/gis/mapurlManage.ts b/frontend-sjgl/src/components/gis/mapurlManage.ts deleted file mode 100644 index 88161502..00000000 --- a/frontend-sjgl/src/components/gis/mapurlManage.ts +++ /dev/null @@ -1,759 +0,0 @@ -/** - * @author: yjw - * @date: 2022-11-17 - * @description: 后期建议本文件内容做到系统配置中 - */ - -// import { MemoryCache } from '@zebras/qgc-share/cache/MemoryCache' -// import { Session } from '@zebras/qgc-share/service/Session' - -export interface MapBaseUrlItem { - code: string; - name: string; - url: string; -} - -export interface ServerConfigItem { - id?: string; - key?: string; - urlType: 'gisurl' | 'online'; - url?: string; - url_3d?: string; - layers?: string; - rasteropacity?: number; - _layer?: string; - imgUrl?: string; - visible?: boolean; - visibility?: boolean; //?是否可以删除 - minZoom?: number; - maxZoom?: number; - fillOpacity?: number; - lineWidth?: number; - [name: string]: any; -} - -const tdtToken = 'e90d56e5a09d1767899ad45846b0cefd'; //企业版密钥,勿换e650f138c4481cca888cd13094bb9026 -const mapType = 'w'; //c:天地图经纬度底图;w:天地图墨卡托底图 -const URL_TerTDT = `https://t0.tianditu.gov.cn/ter_${mapType}/wmts?tk=${tdtToken}`; //terMap -// const URL_TerLabelTDT = `http://t0.tianditu.gov.cn/cta_${mapType}/wmts?tk=${tdtToken}` //terLabel -// const URL_VecTDT = `http://t0.tianditu.com/vec_${mapType}/wmts?tk=${tdtToken}` //vecMap -// const URL_VecLableTDT = `http://t0.tianditu.com/cva_${mapType}/wmts?tk=${tdtToken}` //vecLabel -// const URL_ImgTDT = `http://t0.tianditu.com/img_${mapType}/wmts?tk=${tdtToken}` //imgMap -// const URL_ImgLableTDT = `http://t0.tianditu.com/cia_${mapType}/wmts?tk=${tdtToken}` - -// const znyMapUrl = "http://10.219.26.6:8050/" //中南院的测试地图环境 -// const djjtMapUrl = "http://210.72.227.199:18084/" //中南院的测试地图环境 -//TODO:以后改成在系统管理里面配置地图可视化范围 -export const location_map_maxlon = 126.8267; -export const location_map_maxlat = 38.5769; -export const location_map_minlon = 73.7443; -export const location_map_minlat = 16.32; -// todo: 以后从接口获取数据 -export const servers: Record | Record = { - // 省(自治区)界 - province_boundaries: { - id: 'province_boundaries', - key: 'province_boundaries', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:provinceline&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:provinceline&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:provinceline', - rasteropacity: 1, - minZoom: 0, - maxZoom: 21, - type: 'wms', - layerType: 'line', - lineWidth: 2, - visible: true - }, - //国界 - chinaBoundary: { - id: 'chinaBoundary', - key: 'chinaBoundary', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Achina@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:china&maxFeatures=50&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:china', - _layer: 'china', - layerType: 'line', - minZoom: 0, - maxZoom: 16, - minHeight: 0, - maxHeight: 80000000, - rasteropacity: 1, - type: 'vector_Boundary', - paint: { - 'line-color': '#AAA4C5', - 'line-width': 3, - 'line-opacity': 0.9 - } - }, - //九段线 - nineSegmentLine: { - id: 'nineSegmentLine', - key: 'nineSegmentLine', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Azh_island1@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:zh_island1&maxFeatures=50&outputFormat=application%2Fjson&token= - `, - layers: 'qgc:zh_island1', - _layer: 'zh_island1', - layerType: 'line', - minZoom: 0, - maxZoom: 16, - minHeight: 0, - maxHeight: 80000000, - rasteropacity: 1, - type: 'vector_Boundary', - paint: { - 'line-color': '#AAA4C5', - 'line-width': 3, - 'line-opacity': 0.9 - } - }, - // 基础图层 - BaseLayer: { - 'BASEMAP-img': { - key: 'BASEMAP-img', - type: 'raster-dem', - urlType: 'online', - url: - 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' + - `&token=`, - url_3d: - 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' + - `&token=`, - _layer: 'img', - visible: false, - maxzoom: 20 - }, - 'BASEMAP-white': { - key: 'BASEMAP-white', - urlType: 'online', - type: 'raster-dem', - url: `${URL_TerTDT}&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=${mapType}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles&token= - `, - url_3d: URL_TerTDT + `&token=`, - _layer: 'ter', - visible: false, - maxzoom: 14 - } - }, - // 区|县 - county: { - id: 'county', - key: 'county', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Acounty@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:county&outputFormat=application%2Fjson&token=`, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:county', - _layer: 'county', - rasteropacity: 0.5, - imgUrl: '/zfile/qgc/icons/map-xzXian.png', - minZoom: 8, - maxZoom: 16, - minHeight: 0, - maxHeight: 800000, - type: 'vector', - layerType: 'symbol', - visible: true - }, - //县注记 - county_lable: { - id: 'county_lable', - key: 'county_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:countyAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:countyAno&outputFormat=application%2Fjso&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:countyAno', - _layer: 'countyAno', - rasteropacity: 1, - minZoom: 8, - maxZoom: 16, - minHeight: 0, - maxHeight: 800000, - type: 'wms', - layerType: 'symbol', - visible: true - }, - - // 市 - city: { - id: 'city', - key: 'city', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Acity@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:city&outputFormat=application%2Fjson&token=`, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:city', - _layer: 'city', - rasteropacity: 0.5, - imgUrl: '/zfile/qgc/icons/map-xzShi.png', - minZoom: 5, - maxZoom: 8, - minHeight: 800000, - maxHeight: 1200000, - type: 'vector', - layerType: 'symbol', - visible: false - }, - //市注记 - city_lable: { - id: 'city_lable', - key: 'city_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:cityAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:cityAno&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:cityAno', - _layer: 'cityAno', - rasteropacity: 1, - minZoom: 5, - maxZoom: 8, - minHeight: 800000, - maxHeight: 1200000, - type: 'wms', - layerType: 'symbol', - visible: true - }, - // 省 - provincial_capital: { - id: 'provincial_capital', - key: 'provincial_capital', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Aprovincecity@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:provincecity&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:provincecity', - _layer: 'provincecity', - rasteropacity: 0.5, - imgUrl: '/zfile/qgc/icons/map-xzSheng.png', - visible: true, - minZoom: 0, - maxZoom: 5, - minHeight: 1200000, - maxHeight: 99999999999, - type: 'vector', - layerType: 'symbol' - }, - //省注记 - province_lable: { - id: 'province_lable', - key: 'province_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:provincecityAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:provincecityAno&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:provincecityAno', - _layer: 'provinceAno', - rasteropacity: 0.5, - minZoom: 0, - maxZoom: 5, - minHeight: 1200000, - maxHeight: 99999999999, - type: 'wms', - layerType: 'symbol', - visible: true - }, - baseImageLayerUrl: { - id: 'BaseLayer', - key: 'BaseLayer', - urlType: 'online', - url: 'http://t{s}.tianditu.gov.cn/img_w/wmts?tk=88735d6d9bbd5930f1b1c8b8df72e762&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}' - }, - WhiteBaseLayerUrl: { - id: 'BaseLayer', - key: 'BaseLayer', - urlType: 'online', - url: `https://t0.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=e6372a5333c4bac9b9ef6097453c3cd6&token= - ` - }, - - /** - * 电站基地 - */ - hydropBase: { - id: 'hydropBase', - key: 'hydropBase', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3AstationEra1117@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:stationEra1117&maxFeatures=50&outputFormat=application/json&token= - `, - url_3d: '/geoserver/qgc/wms', - _layer: 'stationEra1117', - layers: 'qgc:stationEra1117', - rasteropacity: 0.5, - visible: true, - minZoom: 0, - maxZoom: 20, - type: 'vector', - layerType: 'fill', - paint: { - 'fill-color': '#f0dcda', - 'fill-opacity': 0.8, - 'fill-outline-color': '#dd7065' - } - }, - /** - * 电站基地配置 - */ - powerBaseStationLayerWFSUrl: { - id: 's_hydropBase_wfs', - key: 's_hydropBase_wfs', - urlType: 'gisurl', - url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:stationEra1117&maxFeatures=50&outputFormat=application/json&token= - `, - _layer: 'stationEra1117', - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:stationEra1117', - rasteropacity: 0.5, - visible: true, - minZoom: 0, - maxZoom: 20 - }, - - /** - * 电站基地配置 LM项目 - */ - powerBaseStationLayerWFSUrlOfLM: { - id: 's_hydropBase_wfs', - key: 's_hydropBase_wfs', - urlType: 'gisurl', - url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:stationEraLM&maxFeatures=50&outputFormat=application/json&token= - `, - _layer: 'stationEraLM', - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:stationEraLM', - rasteropacity: 0.5, - visible: true, - minZoom: 0, - maxZoom: 20 - }, - /** - * 河流连通性分析中河流水系配置 - */ - YLJ_LCJ_LayerWFSUrl: { - id: 'river_riverEvaTemp', - key: 'river_riverEvaTemp', - urlType: 'gisurl', - url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:ylj_lcj&maxFeatures=50&outputFormat=application%2Fjson&token= - `, - _layer: 'ylj_lcj', - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:ylj_lcj', - rasteropacity: 0.5, - visible: true, - minZoom: 0, - maxZoom: 20 - }, - - /** - * 水质水温渐变线配置 - */ - LCJ_WaterQuality_line: { - id: 'LCJ_WaterQuality_line', - key: 'LCJ_WaterQuality_line', - urlType: 'gisurl', - url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:lcj_waterquality_line&maxFeatures=50&outputFormat=application%2Fjson&token= - `, - _layer: 'lcj_waterquality_line', - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:lcj_waterquality_line', - rasteropacity: 0.5, - visible: true, - minZoom: 0, - maxZoom: 20 - }, - - /** - * 全国乡镇名称 - */ - NationalTownships: { - id: 'NationalTownships', - key: 'NationalTownships', - layerType: 'symbol', - isShowLabel: true, - urlType: 'gisurl', - url: `/geoserver/gwc/service/tms/1.0.0/qgc%3ANationalTownships@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf?token=`, - _layer: 'NationalTownships', - layers: 'qgc:NationalTownships', - visible: true, - minZoom: 10.5, - maxZoom: 18, - isOnlyAno: true, - type: 'vector', - layout_text: { - 'text-field': ['get', 'NAME'], - 'text-font': ['Open Sans Bold'], - 'text-size': 16, - 'text-anchor': 'top', - 'text-padding': 0, - 'text-max-width': 100, - 'text-ignore-placement': false, - 'text-allow-overlap': false, - visibility: 'visible' - }, - paint_text: { - 'text-color': '#404040', - 'text-halo-color': '#fff', - 'text-halo-width': 2 - } - }, - - /** - * 一级流域 - */ - heliu1: { - id: 'heliu1', - key: 'heliu1', - urlType: 'gisurl', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river1_line&maxFeatures=50&outputFormat=application/json&token= - `, - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Ariver1_line@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - _layer: 'river1_line', - layers: 'qgc:river1_line', - rasteropacity: 0.5, - visible: true, - minZoom: 0, - maxZoom: 18, - minHeight: 0, - maxHeight: 99999999999, - type: 'vector', - layerType: 'line', - paint: { - 'line-color': '#00A1E9', - 'line-width': 4, - 'line-opacity': 0.9 - } - }, - //一级河流注记 - heliu1_lable: { - id: 'heliu1_lable', - key: 'heliu1_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:river1_lineAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river1_lineAno&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:river1_lineAno', - _layer: 'river1_lineAno', - rasteropacity: 1, - minZoom: 0, - maxZoom: 18, - minHeight: 0, - maxHeight: 99999999999, - type: 'wms', - layerType: 'symbol', - visible: true - }, - - /** - * 二级流域 - */ - heliu2: { - id: 'heliu2', - key: 'heliu2', - urlType: 'gisurl', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river2_line&maxFeatures=50&outputFormat=application/json&token= - `, - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Ariver2_line@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - _layer: 'river2_line', - layers: 'qgc:river2_line', - lineWidth: 2, - rasteropacity: 0.5, - visible: false, - minZoom: 4, - maxZoom: 18, - minHeight: 0, - maxHeight: 1200000, - type: 'vector', - layerType: 'line', - paint: { - 'line-color': '#00A1E9', - 'line-width': 3, - 'line-opacity': 0.9 - } - }, - //二级河流注记 - heliu2_lable: { - id: 'heliu2_lable', - key: 'heliu2_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:river2_lineAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river2_lineAno&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:river2_lineAno', - _layer: 'river2_lineAno', - rasteropacity: 1, - minZoom: 4, - maxZoom: 18, - minHeight: 0, - maxHeight: 1200000, - type: 'wms', - layerType: 'symbol', - visible: true - }, - /** - * 三级流域 - */ - heliu3: { - id: 'heliu3', - key: 'heliu3', - urlType: 'gisurl', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river3_line&maxFeatures=50&outputFormat=application/json&token= - `, - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Ariver3_line@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - _layer: 'river3_line', - layers: 'qgc:river3_line', - lineWidth: 1, - rasteropacity: 0.5, - visible: false, - minZoom: 6, - maxZoom: 18, - minHeight: 0, - maxHeight: 800000, - type: 'vector', - layerType: 'line', - paint: { - 'line-color': '#00A1E9', - 'line-width': 2, - 'line-opacity': 0.9 - } - }, - //三级河流注记 - heliu3_lable: { - id: 'heliu3_lable', - key: 'heliu3_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:river3_lineAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river3_lineAno&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:river3_lineAno', - _layer: 'river3_lineAno', - rasteropacity: 1, - minZoom: 6, - maxZoom: 18, - minHeight: 0, - maxHeight: 800000, - type: 'wms', - layerType: 'symbol', - visible: true - }, - /** - * 四级流域 - */ - heliu4: { - id: 'heliu4', - key: 'heliu4', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Ariver4_line@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river4_line&maxFeatures=8000&outputFormat=application%2Fjson&token= - `, - _layer: 'river4_line', - layers: 'qgc:river4_line', - visible: false, - minZoom: 8, - maxZoom: 18, - minHeight: 0, - maxHeight: 800000, - rasteropacity: 1, - type: 'vector', - layerType: 'line', - paint: { - 'line-color': '#00A1E9', - 'line-width': 1, - 'line-opacity': 0.9 - } - }, - //四级河流注记 - heliu4_lable: { - id: 'heliu4_lable', - key: 'heliu4_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:river4_lineAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river4_lineAno&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:river4_lineAno', - _layer: 'river4_lineAno', - rasteropacity: 1, - minZoom: 8, - maxZoom: 18, - minHeight: 0, - maxHeight: 800000, - type: 'wms', - layerType: 'symbol', - - visible: true - }, - /** - * 五级流域 - 水质等级图层 - */ - heliu5: { - id: 'heliu5', - key: 'heliu5', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Ariver5_line@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river5_line&maxFeatures=8000&outputFormat=application%2Fjson&token= - `, - _layer: 'river5_line', - layers: 'qgc:river5_line', - visible: false, - minZoom: 8, - maxZoom: 18, - minHeight: 0, - maxHeight: 800000, - rasteropacity: 1, - type: 'vector', - layerType: 'line', - paint: { - 'line-color': '#00A1E9', - 'line-width': 1, - 'line-opacity': 0.9 - } - }, - //五级河流注记 - heliu5_lable: { - id: 'heliu5_lable', - key: 'heliu5_lable', - urlType: 'gisurl', - // url: this.gisServers.gisurl + '/geoserver/qgc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&STYLES&LAYERS=qgc%3Aprovinceline&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&WIDTH=768&HEIGHT=620&bbox={bbox-epsg-3857}', - url: '/geoserver/qgc/wms?service=WMS&version=1.1.0&request=GetMap&layers=qgc:river5_lineAno&styles=&bbox={bbox-epsg-3857}&width=768&height=620&srs=EPSG:3857&format=image%2Fpng&TRANSPARENT=true', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:river5_lineAno&outputFormat=application%2Fjson&token= - `, - url_3d: '/geoserver/qgc/wms', - layers: 'qgc:river5_lineAno', - _layer: 'river5_lineAno', - rasteropacity: 1, - minZoom: 8, - maxZoom: 18, - minHeight: 0, - maxHeight: 800000, - type: 'wms', - layerType: 'symbol', - visible: true - }, - //流域梯级图 - Tertiarybasin: { - id: 'Tertiarybasin', - key: 'Tertiarybasin', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Anew_LiuYuThrLev@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:new_LiuYuThrLev&outputFormat=application%2Fjson&token= - `, - _layer: 'new_LiuYuThrLev', - layers: 'qgc:new_LiuYuThrLev', - visible: true, - minZoom: 1, - maxZoom: 18, - minHeight: 0, - maxHeight: 800000, - rasteropacity: 0.8, - type: 'vector', - layerType: 'fill' - }, - // //鱼类栖息地 - // fishQxd: { - // id: 'fishQxd', - // key: 'fishQxd', - // urlType: 'gisurl', - // url: '/geoserver/gwc/service/tms/1.0.0/qgc%3Afish_qxd@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - // url_3d: '/geoserver/qgc/wms', - // geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:fish_qxd&outputFormat=application%2Fjson&token= - // `, - // _layer: 'fish_qxd', - // layers: 'qgc:fish_qxd', - // visible: true, - // minZoom: 0, - // maxZoom: 18, - // minHeight: 0, - // maxHeight: 80000000, - // rasteropacity: 0.9, - // type: 'vector', - // layerType: 'line', - // lineWidth: 2, - // paint: { - // 'line-color': '#A21C83', - // 'line-width': 4, - // 'line-opacity': 1 - // } - // }, - /** - * 水功能分区 - */ - WaterFunctionPartition: { - id: 'WaterFunctionPartition', - key: 'WaterFunctionPartition', - urlType: 'gisurl', - url: '/geoserver/gwc/service/tms/1.0.0/qgc%3AGNQ@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf', - url_3d: '/geoserver/qgc/wms', - geojson_url: `/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:GNQ&outputFormat=application%2Fjson&token=`, - _layer: 'GNQ', - layers: 'qgc:GNQ', - visible: true, - minZoom: 0, - maxZoom: 18, - minHeight: 0, - maxHeight: 8000000, - rasteropacity: 0.9, - type: 'vector', - layerType: 'line', - lineWidth: 2, - paint: { - 'line-color': '#A21C83', - 'line-width': 3, - 'line-opacity': 0 - } - } -}; - -/** - * 获取Gis图层配置 - * @param key gis服务key - * @returns - */ -// export const getConfig = (key: string) => { -// const mapBaseUrls: Record = MemoryCache.get('mapBaseUrls') || {} -// if (Object.prototype.hasOwnProperty.call(servers, key)) { -// const r = { ...servers[key] } -// const { urlType } = r -// const baseUrlObj = mapBaseUrls[urlType as string] -// if (baseUrlObj) { -// r.url = baseUrlObj.url + r.url -// r.url_3d = baseUrlObj.url + r.url_3d -// if (r.geojson_url) { -// r.geojson_url = baseUrlObj.url + r.geojson_url -// } -// } -// return r -// } -// console.warn(`The gis config which key is ${key} not exist!`) -// return null -// } diff --git a/frontend-sjgl/src/components/gis/ol/point-layer-manager.ts b/frontend-sjgl/src/components/gis/ol/point-layer-manager.ts deleted file mode 100644 index 87e29dd7..00000000 --- a/frontend-sjgl/src/components/gis/ol/point-layer-manager.ts +++ /dev/null @@ -1,391 +0,0 @@ -import Feature from 'ol/Feature'; -import Point from 'ol/geom/Point'; -import OlMap from 'ol/Map'; -import VectorLayer from 'ol/layer/Vector'; -import VectorSource from 'ol/source/Vector'; -import { fromLonLat } from 'ol/proj'; -import { getIconPath } from '@/utils/index'; -type PointLayerStyleFactory = (feature: Feature) => any; - -type PointLayerManagerOptions = { - map: OlMap | null; - createStyle: PointLayerStyleFactory; -}; - -export class PointLayerManager { - private map: OlMap | null; - private createStyle: PointLayerStyleFactory; - private layerRegistry: Map> = new Map(); - private layerFeatureIndexes: Map< - string, - Map> - > = new Map(); - - constructor(options: PointLayerManagerOptions) { - this.map = options.map; - this.createStyle = options.createStyle; - } - - // 备注:同步更新地图实例,供地图初始化后把真实 map 注入点图层管理器。 - setMap(map: OlMap | null) { - this.map = map; - } - - // 备注:获取当前屏幕可视区域内的所有点要素(仅包含图层可见且图例可见的要素)。 - getFeaturesInViewport(): Feature[] { - if (!this.map) return []; - - const extent = this.map.getView().calculateExtent(this.map.getSize()); - if (!extent) return []; - - const [minX, minY, maxX, maxY] = extent; - const result: Feature[] = []; - - this.forEachFeature((feature, layer) => { - // 跳过图层本身不可见的要素 - if (!layer.getVisible()) return; - - const geom = feature.getGeometry(); - if (!geom || geom.getType() !== 'Point') return; - - const coords = (geom as Point).getCoordinates(); - const [x, y] = coords; - - if (x >= minX && x <= maxX && y >= minY && y <= maxY) { - result.push(feature); - } - }); - - return result; - } - - // 备注:统一返回当前点图层注册表,供外层做遍历或清理。 - getRegistry() { - return this.layerRegistry; - } - - // 备注:按图层 key 获取已注册的点图层实例。 - getLayer(layerKey: string) { - return this.layerRegistry.get(layerKey); - } - - // 备注:返回当前所有点图层实例,供命中检测和批量刷新使用。 - getLayers(): VectorLayer[] { - return Array.from(this.layerRegistry.values()); - } - - // 备注:按当前缩放级别统一刷新近邻点展开坐标,保证抽吸后的点击和 popup 与视觉位置一致。 - updateNearbyFeatureLayout(currentZoom?: number): void { - if (!this.map) return; - - if (currentZoom === undefined || !Number.isFinite(currentZoom)) { - return; - } - - this.forEachFeature(feature => { - this.resetFeatureToBaseCoordinate(feature); - }); - } - - // 备注:判断指定图层 key 的点图层是否已存在。 - hasLayer(layerKey: string): boolean { - return this.layerRegistry.has(layerKey); - } - - // 备注:按需获取或创建点图层实例,并自动完成注册与挂载。 - ensureLayer(layerKey: string, layerType?: string) { - if (!this.map || !layerKey) return null; - - let vectorLayer = this.layerRegistry.get(layerKey); - if (vectorLayer) { - return vectorLayer; - } - - const vectorSource = new VectorSource(); - vectorLayer = new VectorLayer({ - source: vectorSource, - zIndex: 100, - declutter: true, - style: feature => this.createStyle(feature as Feature) - }); - (vectorLayer as any).type = layerType || layerKey; - this.layerRegistry.set(layerKey, vectorLayer); - this.map.addLayer(vectorLayer); - return vectorLayer; - } - - // 备注:把后端点位数组转换为 OpenLayers Feature,并写入指定点图层。 - addDataLayer(pointData: any, layerType: string): void { - if (!this.map) return; - - let dataArray: any[] = []; - let targetLayerKey = layerType; - - if (Array.isArray(pointData)) { - dataArray = pointData; - } else { - dataArray = pointData?.data || []; - targetLayerKey = pointData?.key || layerType; - } - - if (!targetLayerKey) { - console.warn('缺少图层 Key,无法加载描点'); - return; - } - - const vectorLayer = this.ensureLayer(targetLayerKey, layerType); - if (!vectorLayer) return; - - const vectorSource = vectorLayer.getSource(); - if (!vectorSource) { - return; - } - - vectorSource.clear(); - this.resetLayerFeatureIndexes(targetLayerKey); - - if (dataArray.length === 0) { - return; - } - - const features: Feature[] = []; - - dataArray.forEach((item: any) => { - const feature = this.createFeature(item, targetLayerKey); - if (feature) { - this.indexFeature(targetLayerKey, feature); - features.push(feature); - } - }); - - if (features.length > 0) { - vectorSource.addFeatures(features); - } - } - - // 备注:统一控制整个点图层显隐,供图层树勾选联动使用。 - setLayerVisible(layerKey: string, visible?: boolean): void { - const vectorLayer = this.layerRegistry.get(layerKey); - if (vectorLayer && vectorLayer.getVisible() !== visible) { - vectorLayer.setVisible(visible); - vectorLayer.changed(); - } - } - - // 备注:按图层 key 删除点图层并清空其 source 数据。 - removeLayer(layerKey: string): void { - if (!this.map || !layerKey) { - console.warn('removePointLayer: 无效的图层 key 或地图未初始化'); - return; - } - - const vectorLayer = this.layerRegistry.get(layerKey); - if (!vectorLayer) { - console.warn(`未找到标识为 [${layerKey}] 的描点图层实例`); - return; - } - - const source = vectorLayer.getSource(); - if (source) { - source.clear(); - } - this.map.removeLayer(vectorLayer); - this.layerRegistry.delete(layerKey); - this.layerFeatureIndexes.delete(layerKey); - } - - // 备注:按图层遍历所有点图层实例,供 hover 刷新和区域裁切复用。 - forEachLayer( - callback: (layer: VectorLayer, layerKey: string) => void - ): void { - this.layerRegistry.forEach((layer, layerKey) => { - callback(layer, layerKey); - }); - } - - // 备注:遍历所有点要素,供区域裁切或批量状态更新使用。 - forEachFeature( - callback: ( - feature: Feature, - layer: VectorLayer, - layerKey: string - ) => void - ): void { - this.forEachLayer((layer, layerKey) => { - const source = layer.getSource(); - if (!source) return; - source.getFeatures().forEach(feature => { - callback(feature as Feature, layer, layerKey); - }); - }); - } - - // 备注:按图例字段值统一控制要素显隐,供图例和容量筛选联动复用。 - setLegendVisibleByField( - layerKey: string, - matchValue: string, - checked: boolean, - fieldKey = 'anchoPointState' - ): void { - const vectorLayer = this.layerRegistry.get(layerKey); - if (!vectorLayer) return; - - const source = vectorLayer.getSource(); - if (!source) return; - - if (!matchValue) { - source.getFeatures().forEach(feature => { - if (feature.get('_legendVisible') !== false) { - feature.set('_legendVisible', false); - feature.changed(); - } - }); - vectorLayer.changed(); - return; - } - - const targetFeatures = this.getIndexedFeatures( - layerKey, - fieldKey, - matchValue - ); - if (targetFeatures.length === 0) return; - - targetFeatures.forEach(feature => { - if (feature.get('_legendVisible') !== checked) { - feature.set('_legendVisible', checked); - feature.changed(); - } - }); - vectorLayer.changed(); - } - - // 备注:统一销毁所有点图层并清空注册表,供地图销毁阶段复用。 - destroy(): void { - this.forEachLayer(layer => { - const source = layer.getSource(); - if (source) { - source.clear(); - } - if (this.map && this.map.getLayers().getArray().includes(layer)) { - this.map.removeLayer(layer); - } - if ((layer as any).setMap) { - (layer as any).setMap(null); - } - }); - this.layerRegistry.clear(); - this.layerFeatureIndexes.clear(); - } - - // 备注:统一把业务点位数据转换为 Feature,并补齐样式依赖字段。 - private createFeature(item: any, targetLayerKey: string): Feature | null { - const { lgtd, lttd, stnm, iconCode, titleName, ennm } = item; - - if (lgtd == null || lttd == null) { - return null; - } - - const lon = Number(lgtd); - const lat = Number(lttd); - if (isNaN(lon) || isNaN(lat) || !isFinite(lon) || !isFinite(lat)) { - return null; - } - if (Math.abs(lon) > 180 || Math.abs(lat) > 90) { - return null; - } - - const coord = fromLonLat([lon, lat]); - if (!isFinite(coord[0]) || !isFinite(coord[1])) { - return null; - } - let iconUrl = iconCode ? getIconPath(iconCode) : ''; - if (!iconUrl) { - iconUrl = getIconPath('default') || ''; - } - - const feature = new Feature({ - geometry: new Point(coord), - ...item, - _layerKey: targetLayerKey - }); - const rawFeatureId = - item.stcd || item._id || `${lon.toFixed(6)},${lat.toFixed(6)}`; - feature.setId(`${targetLayerKey}:${rawFeatureId}`); - feature.set('_iconUrl', iconUrl); - feature.set( - '_labelText', - item.sttpMap === 'ylfb' ? item.ftp || '' : titleName || stnm || ennm || '' - ); - feature.set('_baseCoordinates', coord); - feature.set('_legendVisible', true); - feature.set('_regionVisible', true); - feature.set('_sttpMap', item.sttpMap); - if (item.popupHtml) { - feature.set('popupHtml', item.popupHtml); - } - - return feature; - } - - // 备注:重置单个图层的字段索引,供重新灌入数据或删除图层时复用。 - private resetLayerFeatureIndexes(layerKey: string): void { - this.layerFeatureIndexes.set(layerKey, new Map()); - } - - // 备注:按字段值给要素建立索引,供图例切换时快速命中目标锚点集合。 - private indexFeature(layerKey: string, feature: Feature): void { - const fieldIndexMap = - this.layerFeatureIndexes.get(layerKey) || - new Map>(); - - const legendFieldKey = 'anchoPointState'; - const legendFieldValue = feature.get(legendFieldKey); - if (legendFieldValue != null) { - const valueMap = - fieldIndexMap.get(legendFieldKey) || new Map(); - const normalizedValue = String(legendFieldValue); - const targetFeatures = valueMap.get(normalizedValue) || []; - targetFeatures.push(feature); - valueMap.set(normalizedValue, targetFeatures); - fieldIndexMap.set(legendFieldKey, valueMap); - } - - this.layerFeatureIndexes.set(layerKey, fieldIndexMap); - } - - // 备注:根据图层 key、字段名和值快速返回目标要素集合,避免图例切换时整层遍历。 - private getIndexedFeatures( - layerKey: string, - fieldKey: string, - matchValue: string - ): Feature[] { - const fieldIndexMap = this.layerFeatureIndexes.get(layerKey); - if (!fieldIndexMap) return []; - - const valueMap = fieldIndexMap.get(fieldKey); - if (!valueMap) return []; - return valueMap.get(String(matchValue)) || []; - } - - // 备注:先恢复要素原始坐标,再按缩放阶段决定是否做展开偏移,避免多次缩放后偏移累计。 - private resetFeatureToBaseCoordinate(feature: Feature): void { - const baseCoordinates = feature.get('_baseCoordinates') as - | number[] - | undefined; - const geometry = feature.getGeometry(); - if ( - !baseCoordinates || - baseCoordinates.length < 2 || - !geometry || - geometry.getType() !== 'Point' - ) { - return; - } - - (geometry as Point).setCoordinates([ - baseCoordinates[0], - baseCoordinates[1] - ]); - } -} diff --git a/frontend-sjgl/src/components/gis/ol/popup-manager.ts b/frontend-sjgl/src/components/gis/ol/popup-manager.ts deleted file mode 100644 index 929d8dfb..00000000 --- a/frontend-sjgl/src/components/gis/ol/popup-manager.ts +++ /dev/null @@ -1,530 +0,0 @@ -import Feature from 'ol/Feature'; -import Point from 'ol/geom/Point'; -import OlMap from 'ol/Map'; -import Overlay from 'ol/Overlay'; -import VectorLayer from 'ol/layer/Vector'; -import VectorSource from 'ol/source/Vector'; -import { - generatePopupHtml, - shouldPreferEng2Popup -} from '@/utils/popupHtmlGenerator'; - -type PopupManagerOptions = { - map: OlMap | null; - getPointLayers: () => VectorLayer[]; -}; - -type PopupHitResult = { - detectedFeature: Feature | undefined; - isHitIcon: boolean; - coordinate?: number[]; -}; - -type PointerMoveChangePayload = { - hoveredId: string | number | null; - detectedFeature: Feature | undefined; - coordinate?: number[]; -}; - -export class PopupManager { - private map: OlMap | null; - private popupOverlay: Overlay | null = null; - private popupElement: HTMLElement | null = null; - private batchPopupContainer: HTMLDivElement | null = null; - private batchPopupItems: Array<{ - feature: Feature; - element: HTMLDivElement; - }> = []; - private lastHoveredId: string | number | null = null; - private animationFrameId: number | null = null; - private getPointLayers: () => VectorLayer[]; - private popupMouseEnterHandler: (() => void) | null = null; - private popupMouseMoveHandler: (() => void) | null = null; - private hoverChangeHandler: - | ((payload: PointerMoveChangePayload) => void) - | undefined; - - constructor(options: PopupManagerOptions) { - this.map = options.map; - this.getPointLayers = options.getPointLayers; - } - - // 备注:同步地图实例,供地图初始化和销毁后更新 Popup 管理器上下文。 - setMap(map: OlMap | null) { - this.map = map; - } - - // 备注:初始化 Popup Overlay 并挂载到地图实例。 - initPopupOverlay(container: HTMLElement) { - this.popupElement = container; - this.popupElement.style.display = 'none'; - this.popupElement.style.removeProperty('position'); - this.popupElement.style.removeProperty('transform'); - this.popupElement.style.removeProperty('left'); - this.popupElement.style.removeProperty('top'); - this.popupElement.style.setProperty('pointer-events', 'none', 'important'); - this.popupMouseEnterHandler = () => { - this.forceHidePopup(); - }; - this.popupMouseMoveHandler = () => { - this.forceHidePopup(); - }; - this.popupElement.addEventListener( - 'mouseenter', - this.popupMouseEnterHandler - ); - this.popupElement.addEventListener('mousemove', this.popupMouseMoveHandler); - this.popupOverlay = new Overlay({ - element: this.popupElement, - positioning: 'bottom-center', - offset: [0, -10], - stopEvent: false, - autoPan: false - }); - - if (this.map) { - this.map.addOverlay(this.popupOverlay); - } - } - - // 备注:统一处理地图点击命中检测,命中图标时通过回调把要素抛给外层。 - handleMapClick( - pixel: number[], - onHit?: (feature: Feature, coordinate?: number[]) => void - ) { - const { detectedFeature, isHitIcon, coordinate } = - this.detectFeatureAtPixel(pixel); - if (detectedFeature && isHitIcon) { - onHit?.(detectedFeature, coordinate); - } - } - - // 备注:统一处理鼠标悬停命中检测与节流,只有 hover 目标变化时才通知外层刷新。 - handlePointerMove( - pixel: number[], - onHoverChange?: (payload: PointerMoveChangePayload) => void - ) { - if (!this.map) return; - this.hoverChangeHandler = onHoverChange; - - const zoom = this.map.getView().getZoom(); - if (zoom !== undefined && zoom < 4.7) { - return; - } - - if (this.animationFrameId) { - cancelAnimationFrame(this.animationFrameId); - } - - this.animationFrameId = requestAnimationFrame(() => { - const { detectedFeature, isHitIcon, coordinate } = - this.detectFeatureAtPixel(pixel); - const hoveredId = - detectedFeature && isHitIcon ? (detectedFeature.getId() as any) : null; - const nextFeature = hoveredId ? detectedFeature : undefined; - const nextCoordinate = hoveredId ? coordinate : undefined; - - if (hoveredId !== this.lastHoveredId) { - this.emitHoverChange({ - hoveredId, - detectedFeature: nextFeature, - coordinate: nextCoordinate - }); - } - }); - } - - // 备注:在指定像素位置检测点图层要素,并判断是否命中图标区域。 - detectFeatureAtPixel(pixel: number[]): PopupHitResult { - let detectedFeature: Feature | undefined = undefined; - let isHitIcon = false; - let coordinate: number[] | undefined; - const pointLayers = this.getPointLayers(); - - if (this.isPixelInsidePopup(pixel)) { - return { - detectedFeature: undefined, - isHitIcon: false, - coordinate: undefined - }; - } - - this.map?.forEachFeatureAtPixel( - pixel, - (feature, layer) => { - if (pointLayers.includes(layer as VectorLayer)) { - detectedFeature = feature as Feature; - const geom = feature.getGeometry(); - if (geom && geom.getType() === 'Point') { - coordinate = (geom as Point).getCoordinates(); - const iconPixel = this.map?.getPixelFromCoordinate(coordinate); - if (iconPixel) { - const dx = pixel[0] - iconPixel[0]; - const dy = pixel[1] - iconPixel[1]; - if (this.isPixelInsideIconArea(dx, dy)) { - isHitIcon = true; - } - } - } - - return true; - } - return false; - }, - { hitTolerance: 0 } - ); - - return { detectedFeature, isHitIcon, coordinate }; - } - - // 备注:仅把图标本体区域作为 hover 命中范围,避免上方文字标签被误判为图标命中。 - private isPixelInsideIconArea(dx: number, dy: number): boolean { - if (!this.map) return false; - - const zoom = this.map.getView().getZoom() ?? 4.5; - let dynamicScale = 0.7 + (zoom - 4.5) * 0.08; - dynamicScale = Math.max(0.5, Math.min(3.0, dynamicScale)); - - const halfWidth = 12 * dynamicScale; - const topReach = 8 * dynamicScale; - const bottomReach = 12 * dynamicScale; - - return ( - dx >= -halfWidth && - dx <= halfWidth && - dy >= -topReach && - dy <= bottomReach - ); - } - - // 备注:根据当前悬停要素统一显示或隐藏 Popup 内容。 - showPopup(feature: Feature | undefined, coordinate: number[] | undefined) { - if (!this.popupOverlay || !this.popupElement) return; - - if (feature && coordinate) { - const props = feature.getProperties(); - const popupHtml = this.getPopupHtml(props); - - if (popupHtml) { - this.popupElement.innerHTML = popupHtml; - this.popupOverlay.setPosition(coordinate); - this.popupElement.style.display = 'block'; - return; - } - } - - this.popupOverlay.setPosition(undefined); - this.popupElement.style.display = 'none'; - } - - private getPopupHtml(props: Record) { - return props.popupHtml || generatePopupHtml(props); - } - - // 备注:批量显示多个要素的 Popup,使用绝对定位的 div 而非 Overlay。 - showPopupsForFeatures( - features: Feature[], - styleChecker?: (feature: Feature) => boolean, - options?: { - rebuild?: boolean; - } - ) { - if (!this.map || !this.popupElement) return; - const shouldRebuild = options?.rebuild !== false; - - // 过滤:只显示图例可见的要素 - const visibleFeatures = features.filter( - f => f.get('_legendVisible') !== false - ); - - if (visibleFeatures.length === 0) { - this.clearBatchPopups(); - return; - } - - if (!shouldRebuild && this.batchPopupItems.length > 0) { - this.updateBatchPopupPositions(styleChecker); - return; - } - - this.clearBatchPopups(); - - const mapElement = this.map.getTargetElement(); - const batchContainer = document.createElement('div'); - batchContainer.className = 'batch-popup-container'; - batchContainer.style.cssText = ` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - z-index: 1000; - `; - batchContainer.id = 'batch-popup-container'; - this.batchPopupContainer = batchContainer; - - // 先添加容器到 DOM,以便后续元素能正确测量尺寸 - mapElement.appendChild(batchContainer); - - // 用于碰撞检测的已放置 popup 列表 - const placedPopups: Array<{ - left: number; - right: number; - top: number; - bottom: number; - }> = []; - visibleFeatures.forEach(feature => { - const geom = feature.getGeometry(); - if (!geom || geom.getType() !== 'Point') return; - - const coords = (geom as Point).getCoordinates(); - const pixel = this.map?.getPixelFromCoordinate(coords); - if (!pixel) return; - - // 检查样式是否隐藏(declutter、距离过滤等) - if (styleChecker && !styleChecker(feature)) { - return; - } - - const props = feature.getProperties(); - const popupHtml = shouldPreferEng2Popup(props) - ? generatePopupHtml(props, { forceEng2: true }) || - props.popupHtml || - generatePopupHtml(props) - : this.getPopupHtml(props); - if (!popupHtml) return; - - // 创建与原始 popupElement 完全相同的样式 - const popupEl = document.createElement('div'); - popupEl.className = this.popupElement.className; - popupEl.innerHTML = popupHtml; - // 不设置自定义 cssText,只设置定位(对应原始 popup 的 positioning: 'bottom-center', offset: [0, -10]) - popupEl.style.position = 'absolute'; - popupEl.style.display = 'block'; - popupEl.style.transform = 'translate(-50%, -100%)'; // bottom-center 对齐 - popupEl.style.pointerEvents = 'none'; - - // 临时设置 visibility: hidden 来测量尺寸 - popupEl.style.visibility = 'hidden'; - popupEl.style.left = `${pixel[0]}px`; - popupEl.style.top = `${pixel[1] - 10}px`; - batchContainer.appendChild(popupEl); - - // 获取 popup 尺寸 - const rect = popupEl.getBoundingClientRect(); - const popupWidth = rect.width; - const popupHeight = rect.height; - - // 计算 popup 的位置(对应原始 positioning: 'bottom-center', offset: [0, -10]) - const popupX = pixel[0]; - const popupY = pixel[1] - 10; // bottom-center 对齐 + 10px 偏移 - const popupRect = { - left: popupX - popupWidth / 2, - right: popupX + popupWidth / 2, - top: popupY - popupHeight, - bottom: popupY - }; - - // 碰撞检测 - let hasCollision = false; - for (const placed of placedPopups) { - if (this.checkCollision(popupRect, placed)) { - hasCollision = true; - break; - } - } - - if (!hasCollision) { - // 无碰撞,显示 popup - popupEl.style.visibility = 'visible'; - popupEl.style.left = `${popupX}px`; - popupEl.style.top = `${popupY}px`; - this.batchPopupItems.push({ - feature, - element: popupEl - }); - - placedPopups.push({ - left: popupRect.left, - right: popupRect.right, - top: popupRect.top, - bottom: popupRect.bottom - }); - } else { - // 有碰撞,移除该 popup - batchContainer.removeChild(popupEl); - } - }); - } - - updateBatchPopupPositions(styleChecker?: (feature: Feature) => boolean) { - if ( - !this.map || - !this.batchPopupContainer || - this.batchPopupItems.length === 0 - ) { - return; - } - - const mapSize = this.map.getSize(); - const viewportWidth = mapSize?.[0] ?? 0; - const viewportHeight = mapSize?.[1] ?? 0; - const viewportPadding = 48; - - this.batchPopupItems.forEach(({ feature, element }) => { - if (feature.get('_legendVisible') === false) { - element.style.display = 'none'; - return; - } - - if (styleChecker && !styleChecker(feature)) { - element.style.display = 'none'; - return; - } - - const geom = feature.getGeometry(); - if (!geom || geom.getType() !== 'Point') { - element.style.display = 'none'; - return; - } - - const coords = (geom as Point).getCoordinates(); - const pixel = this.map?.getPixelFromCoordinate(coords); - if (!pixel) { - element.style.display = 'none'; - return; - } - - const popupX = pixel[0]; - const popupY = pixel[1] - 10; - if ( - popupX < -viewportPadding || - popupY < -viewportPadding || - popupX > viewportWidth + viewportPadding || - popupY > viewportHeight + viewportPadding - ) { - element.style.display = 'none'; - return; - } - - element.style.left = `${popupX}px`; - element.style.top = `${popupY}px`; - element.style.display = 'block'; - }); - } - - // 备注:清除批量显示的 popups。 - clearBatchPopups() { - this.batchPopupItems = []; - if (this.batchPopupContainer) { - this.batchPopupContainer.remove(); - this.batchPopupContainer = null; - return; - } - - const existing = document.getElementById('batch-popup-container'); - if (existing) { - existing.remove(); - } - } - - // 备注:检测两个 popup 矩形是否碰撞。 - private checkCollision( - rect1: { left: number; right: number; top: number; bottom: number }, - rect2: { left: number; right: number; top: number; bottom: number } - ): boolean { - const padding = 4; - return !( - rect1.right + padding < rect2.left || - rect1.left - padding > rect2.right || - rect1.bottom + padding < rect2.top || - rect1.top - padding > rect2.bottom - ); - } - - // 备注:统一重置 hover 和 Popup 状态,供地图销毁和切换时复用。 - reset() { - if (this.animationFrameId) { - cancelAnimationFrame(this.animationFrameId); - this.animationFrameId = null; - } - this.emitHoverChange({ - hoveredId: null, - detectedFeature: undefined, - coordinate: undefined - }); - } - - // 备注:在弹窗层意外接管鼠标时,通过统一 hover 回调链路立即清空当前悬停状态。 - private forceHidePopup() { - this.emitHoverChange({ - hoveredId: null, - detectedFeature: undefined, - coordinate: undefined - }); - } - - // 备注:统一派发 hover 变化,确保地图侧的 cursor、Popup 和图层刷新走同一条处理链路。 - private emitHoverChange(payload: PointerMoveChangePayload) { - this.lastHoveredId = payload.hoveredId; - this.hoverChangeHandler?.(payload); - if (!this.hoverChangeHandler) { - this.showPopup(payload.detectedFeature, payload.coordinate); - } - } - - // 备注:当鼠标进入当前 Popup 可见区域时,禁止再把该区域重新识别为图标 hover。 - private isPixelInsidePopup(pixel: number[]): boolean { - if (!this.map || !this.popupElement) { - return false; - } - - if (this.popupElement.style.display === 'none') { - return false; - } - - const popupRect = this.popupElement.getBoundingClientRect(); - if (popupRect.width <= 0 || popupRect.height <= 0) { - return false; - } - - const mapRect = this.map.getTargetElement().getBoundingClientRect(); - const clientX = mapRect.left + pixel[0]; - const clientY = mapRect.top + pixel[1]; - - return ( - clientX >= popupRect.left && - clientX <= popupRect.right && - clientY >= popupRect.top && - clientY <= popupRect.bottom - ); - } - - // 备注:销毁 Popup 管理器内部状态和 Overlay 引用。 - destroy() { - this.clearBatchPopups(); - this.reset(); - if (this.popupElement && this.popupMouseEnterHandler) { - this.popupElement.removeEventListener( - 'mouseenter', - this.popupMouseEnterHandler - ); - } - if (this.popupElement && this.popupMouseMoveHandler) { - this.popupElement.removeEventListener( - 'mousemove', - this.popupMouseMoveHandler - ); - } - if (this.map && this.popupOverlay) { - this.map.removeOverlay(this.popupOverlay); - } - this.popupMouseEnterHandler = null; - this.popupMouseMoveHandler = null; - this.hoverChangeHandler = undefined; - this.popupOverlay = null; - this.popupElement = null; - this.map = null; - } -} diff --git a/frontend-sjgl/src/components/gis/ol/region-mask-manager.ts b/frontend-sjgl/src/components/gis/ol/region-mask-manager.ts deleted file mode 100644 index 10989037..00000000 --- a/frontend-sjgl/src/components/gis/ol/region-mask-manager.ts +++ /dev/null @@ -1,341 +0,0 @@ -import OlMap from 'ol/Map'; -import View from 'ol/View'; -import TileLayer from 'ol/layer/Tile'; -import GeoJSON from 'ol/format/GeoJSON'; -import VectorSource from 'ol/source/Vector'; -import { fromLonLat } from 'ol/proj'; -import { PointLayerManager } from './point-layer-manager'; - -type RegionMaskManagerOptions = { - map: OlMap | null; - view: View | null; - pointLayerManager: PointLayerManager; - defaultCenter: [number, number]; - defaultZoom: number; -}; - -export class RegionMaskManager { - private map: OlMap | null; - private view: View | null; - private pointLayerManager: PointLayerManager; - private defaultCenter: [number, number]; - private defaultZoom: number; - private maskBindings: Array<{ - layer: TileLayer; - prerender: (event: any) => void; - postrender: (event: any) => void; - }> = []; - - constructor(options: RegionMaskManagerOptions) { - this.map = options.map; - this.view = options.view; - this.pointLayerManager = options.pointLayerManager; - this.defaultCenter = options.defaultCenter; - this.defaultZoom = options.defaultZoom; - } - - // 备注:同步地图和视图上下文,供地图初始化、销毁和重建后复用区域过滤能力。 - setContext(map: OlMap | null, view: View | null) { - this.map = map; - this.view = view; - } - - // 备注:统一隐藏所有点位的区域显示状态,供基地裁切前的预处理复用。 - hideAllPoints(): void { - this.pointLayerManager.forEachFeature(feature => { - feature.set('_regionVisible', false); - feature.changed(); - }); - } - - // 备注:统一恢复所有点位的区域显示状态,供取消裁切或异常兜底时复用。 - showAllPoints(): void { - this.pointLayerManager.forEachFeature(feature => { - feature.set('_regionVisible', true); - feature.changed(); - }); - } - - // 备注:根据 GeoJSON 边界批量更新点位的区域显隐状态。 - filterPointsByRegion(geoJson: any): void { - const regionCoords = this.extractPolygonCoords(geoJson); - if (!regionCoords.length) { - console.warn('无法解析区域边界,显示所有锚点'); - this.showAllPoints(); - return; - } - - this.pointLayerManager.forEachFeature(feature => { - const props = feature.getProperties(); - const lon = Number(props.lgtd); - const lat = Number(props.lttd); - - if (!isFinite(lon) || !isFinite(lat)) { - feature.set('_regionVisible', false); - feature.changed(); - return; - } - - feature.set('_regionVisible', this.isPointInPolygon(lon, lat, regionCoords)); - feature.changed(); - }); - } - - // 备注:根据 GeoJSON 范围拟合地图视野,供基地切换后快速聚焦到目标区域。 - fitViewToGeoJson(geoJson: any): void { - if (!this.map || !this.view) return; - - try { - const features = new GeoJSON().readFeatures(geoJson, { - dataProjection: 'EPSG:4326', - featureProjection: 'EPSG:3857' - }); - - if (!features.length) return; - - const source = new VectorSource({ - features - }); - const extent = source.getExtent(); - - if (extent[0] === Infinity || extent[0] === -Infinity) { - console.warn('无法计算有效的地图包围盒'); - return; - } - - this.view.fit(extent, { - padding: [100, 200, 50, 50], - duration: 1000, - maxZoom: 13 - }); - } catch (error) { - console.error('调整地图视野失败:', error); - } - } - - // 备注:把 GeoJSON 边界绑定到底图 prerender/postrender 事件,实现区域裁切遮罩。 - applyMapMask(rasterLayer: TileLayer, clipGeoJson: any): void { - this.applyMapMaskToLayers(rasterLayer ? [rasterLayer] : [], clipGeoJson); - } - - // 备注:把 GeoJSON 边界同时绑定到多个底图图层,保证多底图叠加场景下裁切边界保持一致。 - applyMapMaskToLayers(rasterLayers: TileLayer[] = [], clipGeoJson: any): void { - if (!clipGeoJson) return; - - const targetLayers = rasterLayers.filter(Boolean); - if (targetLayers.length === 0) return; - - const features = new GeoJSON().readFeatures(clipGeoJson, { - dataProjection: 'EPSG:4326', - featureProjection: 'EPSG:3857' - }); - - if (!features.length) { - console.warn('裁切数据为空,无法应用遮罩'); - return; - } - - this.clearMaskEvents(); - - targetLayers.forEach(rasterLayer => { - const maskPrerender = (event: any) => { - const context = event.context; - const frameState = event.frameState; - - if (!context || !frameState || !this.map) return; - - context.save(); - context.setTransform(1, 0, 0, 1, 0, 0); - context.beginPath(); - - let hasValidPath = false; - - for (const feature of features) { - const geometry = feature.getGeometry(); - if (!geometry) continue; - - if (geometry.getType() === 'Polygon') { - const rings = (geometry as any).getCoordinates() as number[][][]; - for (const ring of rings) { - if (this.drawRingToContext(context, ring, frameState)) { - hasValidPath = true; - } - } - } else if (geometry.getType() === 'MultiPolygon') { - const polygons = (geometry as any).getCoordinates() as number[][][][]; - for (const polygonRings of polygons) { - for (const ring of polygonRings) { - if (this.drawRingToContext(context, ring, frameState)) { - hasValidPath = true; - } - } - } - } - } - - if (!hasValidPath) { - console.warn('未能生成有效的裁切路径'); - return; - } - - context.save(); - context.lineWidth = 6; - context.strokeStyle = '#6D64DF'; - context.stroke(); - context.clip(); - context.strokeStyle = '#CCC9F4'; - context.lineWidth = 5; - context.stroke(); - context.clip(); - }; - - const maskPostrender = (event: any) => { - if (event.context) { - event.context.restore(); - event.context.restore(); - } - }; - - this.maskBindings.push({ - layer: rasterLayer, - prerender: maskPrerender, - postrender: maskPostrender - }); - - rasterLayer.on('prerender', maskPrerender); - rasterLayer.on('postrender', maskPostrender); - rasterLayer.changed(); - }); - } - - // 备注:清除当前底图遮罩并按需恢复默认视野,供切换基地和异常兜底复用。 - clearMapMask(resetView = true): void { - const maskedLayers = this.maskBindings.map(binding => binding.layer); - this.clearMaskEvents(); - - maskedLayers.forEach(layer => layer.changed()); - - if (resetView && this.view) { - this.view.animate({ - center: fromLonLat(this.defaultCenter), - zoom: this.defaultZoom, - duration: 1000 - }); - } - } - - // 备注:统一销毁区域过滤相关引用和事件,供地图销毁阶段复用。 - destroy(): void { - this.clearMapMask(false); - this.map = null; - this.view = null; - } - - // 备注:从 GeoJSON 中提取可用于点在面判断的多边形环坐标。 - private extractPolygonCoords(geoJson: any): number[][][] { - if (!geoJson?.features?.length) { - return []; - } - - const coords: number[][][] = []; - geoJson.features.forEach((feature: any) => { - const geometry = feature.geometry; - if (!geometry) return; - - if (geometry.type === 'Polygon') { - coords.push(...geometry.coordinates); - } else if (geometry.type === 'MultiPolygon') { - geometry.coordinates.forEach((polygon: number[][][]) => { - coords.push(...polygon); - }); - } - }); - - return coords; - } - - // 备注:使用射线法判断点是否落在任意一个区域环内。 - private isPointInPolygon( - lon: number, - lat: number, - polygons: number[][][] - ): boolean { - for (const polygon of polygons) { - if (this.isPointInRing(lon, lat, polygon)) { - return true; - } - } - return false; - } - - // 备注:使用射线法判断点是否落在单个闭合环内。 - private isPointInRing(lon: number, lat: number, ring: number[][]): boolean { - let inside = false; - const ringLength = ring.length; - - for (let i = 0, j = ringLength - 1; i < ringLength; j = i++) { - const xi = ring[i][0]; - const yi = ring[i][1]; - const xj = ring[j][0]; - const yj = ring[j][1]; - - const intersect = - yi > lat !== yj > lat && - lon < ((xj - xi) * (lat - yi)) / (yj - yi) + xi; - - if (intersect) { - inside = !inside; - } - } - - return inside; - } - - // 备注:把单个面环转换为当前帧 Canvas 可裁切的路径。 - private drawRingToContext( - context: CanvasRenderingContext2D, - ring: number[][], - frameState: any - ): boolean { - if (!ring || ring.length < 3) return false; - - const pixelRatio = frameState.pixelRatio || window.devicePixelRatio || 1; - let moved = false; - - for (let i = 0; i < ring.length; i++) { - const coord = ring[i]; - if (!coord || coord.length < 2 || typeof coord[0] !== 'number') { - continue; - } - - const cssPixel = this.map?.getPixelFromCoordinate(coord as [number, number]); - if (!cssPixel) continue; - - const canvasX = cssPixel[0] * pixelRatio; - const canvasY = cssPixel[1] * pixelRatio; - - if (!moved) { - context.moveTo(canvasX, canvasY); - moved = true; - } else { - context.lineTo(canvasX, canvasY); - } - } - - if (moved) { - context.closePath(); - return true; - } - - return false; - } - - // 备注:解除旧底图上的遮罩事件,避免重复绑定或残留旧裁切效果。 - private clearMaskEvents(): void { - this.maskBindings.forEach(binding => { - binding.layer.un('prerender', binding.prerender); - binding.layer.un('postrender', binding.postrender); - }); - this.maskBindings = []; - } -} diff --git a/frontend-sjgl/src/components/gis/osgbUtils.ts b/frontend-sjgl/src/components/gis/osgbUtils.ts deleted file mode 100644 index 5f4abea8..00000000 --- a/frontend-sjgl/src/components/gis/osgbUtils.ts +++ /dev/null @@ -1,747 +0,0 @@ -/** - * 倾斜摄影(OSGB)工具函数 - * 适配自旧项目 osgbTool.ts,核心逻辑保持一致 - * - * 使用字段: url, boundary, location, height, accuracy - */ -import * as turf from '@turf/turf'; -import * as Cesium from 'cesium'; -import { getToken } from '@/utils/auth'; - -// ==================== 类型定义 ==================== - -export interface OSGBLocation { - destination: number[]; // [x, y, z] 或 [lng, lat, height] - orientation: { - heading: number; - pitch: number; - roll: number; - }; - center: number[]; -} - -export interface OSGBItem { - /** 3D Tileset 服务地址 */ - url: string; - /** GeoJSON Polygon 边界(用于裁切) */ - boundary?: any; - /** 定位信息 */ - location?: OSGBLocation | string; - /** 模型高度偏移 */ - height?: number; - /** 屏幕空间误差 (默认 16) */ - accuracy?: number; - /** 内部状态 - 不要手动设置 */ - osgbtitles?: any; - cartesian3?: any; - clippingPlanes?: any; - eventListener?: any; - _loading?: boolean; - _loadId?: number; - /** 用户手动关闭开关标志:为 true 时不自动加载/显示,只做显隐切换 */ - _switchOff?: boolean; -} - -// ==================== 内部状态(每个 OSGB 实例独立) ==================== - -interface OSGBInstanceState { - destination: Cesium.Cartesian3 | null; - orientation: any; - timelookat: any; - time: number; - num: number; - witetime: number; - timeout: any; -} - -const instanceStates = new WeakMap(); - -function getState(obj: OSGBItem): OSGBInstanceState { - let state = instanceStates.get(obj); - if (!state) { - state = { - destination: null, - orientation: null, - timelookat: null, - time: 0, - num: 0, - witetime: 0, - timeout: undefined - }; - instanceStates.set(obj, state); - } - return state; -} - -function clearState(obj: OSGBItem) { - const state = instanceStates.get(obj); - if (state) { - clearTimeout(state.timeout); - clearInterval(state.timelookat); - } - instanceStates.delete(obj); -} - -// ==================== 3D Tileset 场景管理 ==================== - -/** - * 将 tileset 添加到场景 - */ -const addOSGB = (viewer: Cesium.Viewer, tileset: Cesium.Cesium3DTileset) => { - if (!viewer || viewer.isDestroyed()) return; - viewer.scene.primitives.add(tileset); -}; - -/** - * 从场景移除 tileset(PrimitiveCollection.remove 默认 destroy=true,会销毁 tileset) - */ -const removeOSGB = (viewer: Cesium.Viewer, tileset: Cesium.Cesium3DTileset) => { - if (!viewer || viewer.isDestroyed()) return; - viewer.scene.primitives.remove(tileset); -}; - -// ==================== 裁切相关 ==================== - -/** - * 规范化 GeoJSON 输入,提取纯 Geometry - * 兼容 Feature / FeatureCollection / Geometry - */ -const normalizeGeometry = (geo: any): any => { - if (!geo) return null; - // 已经是 Geometry 类型(有 type 且 coordinates) - if (geo.type && geo.coordinates) return geo; - // Feature → 提取 geometry - if (geo.type === 'Feature' && geo.geometry) return geo.geometry; - // FeatureCollection → 取第一个 feature 的 geometry - if (geo.type === 'FeatureCollection' && geo.features?.length) { - return normalizeGeometry(geo.features[0]); - } - return null; -}; - -const get_verts_core = (poly: any): Cesium.Cartesian3[] => { - const verts_carte3: Cesium.Cartesian3[] = []; - if (!poly) return verts_carte3; - - try { - // 规范化输入:兼容 Feature / FeatureCollection / Geometry - const geom = normalizeGeometry(poly); - if (!geom) return verts_carte3; - - let verts = turf.coordAll(geom); - const clockwiseRing = turf.booleanClockwise(turf.lineString(verts)); - if (clockwiseRing) { - verts = verts.reverse(); - } - for (let i = 0; i < verts.length - 1; i++) { - const vert = verts[i]; - verts_carte3.push(Cesium.Cartesian3.fromDegrees(vert[0], vert[1])); - } - } catch (e) { - console.warn('get_verts_core 解析 boundary 失败:', e); - } - return verts_carte3; -}; - -const get_clipping_planes = ( - points: Cesium.Cartesian3[] -): Cesium.ClippingPlane[] => { - const pointsLength = points.length; - const clippingPlanes: Cesium.ClippingPlane[] = []; - - for (let i = 0; i < pointsLength; ++i) { - const nextIndex = (i + 1) % pointsLength; - let midpoint = Cesium.Cartesian3.add( - points[i], - points[nextIndex], - new Cesium.Cartesian3() - ); - midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint); - - const up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3()); - let right = Cesium.Cartesian3.subtract( - points[nextIndex], - midpoint, - new Cesium.Cartesian3() - ); - right = Cesium.Cartesian3.normalize(right, right); - - let normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3()); - normal = Cesium.Cartesian3.normalize(normal, normal); - - const originCenteredPlane = new Cesium.Plane(normal, 0.0); - const distance = Cesium.Plane.getPointDistance( - originCenteredPlane, - midpoint - ); - - clippingPlanes.push(new Cesium.ClippingPlane(normal, distance)); - } - - return clippingPlanes; -}; - -const clipping_terrain = ( - viewer: Cesium.Viewer, - clippingPlanes: Cesium.ClippingPlane[] -) => { - const globe = viewer.scene.globe; - globe.clippingPlanes = new Cesium.ClippingPlaneCollection({ - planes: clippingPlanes, - edgeWidth: 0, - edgeColor: Cesium.Color.GREEN, - enabled: true - }); - globe.backFaceCulling = true; - globe.showSkirts = true; -}; - -const clipping_3dtiles = ( - clippingPlanes: Cesium.ClippingPlane[], - tileset: Cesium.Cesium3DTileset -) => { - // Cesium 1.141: root.transform 是内部属性,安全访问 - const rootTransform = (tileset as any)?.root?.transform; - if (!rootTransform) return; - const invtrans = Cesium.Matrix4.inverse(rootTransform, new Cesium.Matrix4()); - for (let i = 0; i < clippingPlanes.length; i++) { - clippingPlanes[i] = Cesium.Plane.transform(clippingPlanes[i], invtrans); - } - tileset.clippingPlanes = new Cesium.ClippingPlaneCollection({ - planes: clippingPlanes, - unionClippingRegions: true, - edgeWidth: 0, - edgeColor: Cesium.Color.RED, - enabled: false - }); -}; - -const Clip3DTile = (boundary: any, tileset: Cesium.Cesium3DTileset) => { - try { - const geom = normalizeGeometry(boundary); - if (!geom) return; - const buffered = turf.buffer(geom, 10, { units: 'meters' }); - const verts_core = get_verts_core(buffered).reverse(); - if (!verts_core.length) return; - const planes = get_clipping_planes(verts_core); - clipping_3dtiles(planes, tileset); - } catch (e) { - console.warn('Clip3DTile 裁切失败:', e); - } -}; - -const ClipTerrain = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => { - // Cesium 1.141: 安全访问 clippingPlanes 内部属性 _planes - const existingPlanes = (viewer.scene.globe.clippingPlanes as any)?._planes; - if ( - !osgbObj.clippingPlanes || - (existingPlanes && - !compareClippingPlanes(osgbObj.clippingPlanes, existingPlanes)) - ) { - const verts_core = get_verts_core(osgbObj.boundary); - if (!verts_core.length) return; - const planes = get_clipping_planes(verts_core); - clipping_terrain(viewer, planes); - osgbObj.clippingPlanes = planes; - } -}; - -const removeClip = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => { - if (osgbObj.clippingPlanes) { - removeClippingPlanes(viewer); - osgbObj.clippingPlanes = undefined; - } -}; - -const removeClippingPlanes = (viewer: Cesium.Viewer) => { - if (!Cesium.defined(viewer) || viewer.isDestroyed()) { - return; - } - if (Cesium.defined(viewer.scene.globe.clippingPlanes)) { - viewer.scene.globe.clippingPlanes.removeAll(); - } -}; - -const compareClippingPlanes = (planes1: any, planes2: any): boolean => { - let result = true; - if (planes1.length === planes2?.length) { - for (let index = 0; index < planes1.length; index++) { - if (planes1[index]._distance !== planes2[index]._distance) { - result = false; - break; - } - } - } else { - result = false; - } - return result; -}; - -const getMinDistanceOfOSGB = (viewer: Cesium.Viewer): number => { - let result = Infinity; - const length = viewer.scene.primitives.length; - for (let i = 0; i < length; i++) { - const p = viewer.scene.primitives.get(i); - // Cesium 1.141: _url 和 _distanceToCamera 是内部属性,安全访问 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((p as any)?._url) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const d = (p as any)?.root?._distanceToCamera; - if (d != null && d < result) result = d; - } - } - return result; -}; - -// ==================== 动态显隐 ==================== - -const dynamicSetVisible = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => { - if (!Cesium.defined(viewer) || viewer.isDestroyed()) return; - if (!Cesium.defined(osgbObj?.cartesian3)) return; - - let cameraPosition: Cesium.Cartesian3 | undefined; - if (viewer.trackedEntity) { - cameraPosition = viewer.trackedEntity.position?.getValue( - viewer.clock.currentTime - ); - } - if (!cameraPosition) { - cameraPosition = viewer.camera.position.clone(); - } - - const distance = Cesium.Cartesian3.distance( - osgbObj.cartesian3, - cameraPosition - ); - - if (distance < 12000) { - // 用户手动关闭了开关 → 不自动加载 - if (osgbObj._switchOff) return; - - // 匹配旧代码逻辑:osgbObj.osgbtitles 同步设置为 nonClassificationTileset 防重入 - // 新代码用 _loading + _loadId 实现同样的效果(因为 fromUrl 是异步的) - if (!osgbObj.osgbtitles && !osgbObj._loading) { - osgbObj._loading = true; - osgbObj._loadId = (osgbObj._loadId || 0) + 1; - const loadId = osgbObj._loadId; - - const url = osgbObj.url.includes('?') - ? osgbObj.url + `&token=${getToken()}` - : osgbObj.url + `?token=${getToken()}`; - Cesium.Cesium3DTileset.fromUrl(url, { - maximumScreenSpaceError: Number(osgbObj?.accuracy) || 16, - show: true, - maximumMemoryUsage: 128 - }) - .then((tileset: Cesium.Cesium3DTileset) => { - // 竞态保护:版本号变了(removeQxsy 被调用或新一轮加载开始)→ 销毁退出 - if ( - osgbObj._loadId !== loadId || - !Cesium.defined(viewer) || - viewer.isDestroyed() - ) { - tileset.destroy(); - return; - } - osgbObj._loading = false; - if (!Cesium.defined(tileset)) return; - tileset.maximumScreenSpaceError = 32; - - // height 向上偏移:模型几何自带高程,height 只是微调(如 44m) - if (osgbObj.height != 0) { - const bs = Cesium.Cartographic.fromCartesian( - tileset.boundingSphere.center - ); - const surface = Cesium.Cartesian3.fromRadians( - bs.longitude, - bs.latitude, - 0.0 - ); - const offset = Cesium.Cartesian3.fromRadians( - bs.longitude, - bs.latitude, - osgbObj.height - ); - const translation = Cesium.Cartesian3.subtract( - offset, - surface, - new Cesium.Cartesian3() - ); - tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); - } - - addOSGB(viewer, tileset); - osgbObj.osgbtitles = tileset; - }) - .catch((e: Error) => { - osgbObj._loading = false; - console.log('dynamicSetVisible 加载倾斜摄影报错', e); - }); - } - } else if (distance > 13000) { - // 匹配旧代码逻辑:镜头拉远 → 销毁 tileset 释放内存 - if (osgbObj.osgbtitles) { - removeOSGB(viewer, osgbObj.osgbtitles); - osgbObj.osgbtitles = undefined; - } - // 不重置 _switchOff,开关状态由用户手动控制,不受距离影响 - } -}; - -// ==================== 地形动态裁切 ==================== - -const dynamicClipTerrain = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => { - if (!osgbObj?.osgbtitles) { - removeClip(viewer, osgbObj); - return; - } - - // Cesium 1.141: root 和 _distanceToCamera 是内部属性,安全访问 - const root = (osgbObj.osgbtitles as any)?.root; - if (!Cesium.defined(root)) { - removeClip(viewer, osgbObj); - if (osgbObj.osgbtitles?.clippingPlanes?.enabled) { - osgbObj.osgbtitles.clippingPlanes.enabled = false; - } - return; - } - - if (!osgbObj?.osgbtitles?.show) { - removeClip(viewer, osgbObj); - if (osgbObj.osgbtitles?.clippingPlanes?.enabled) { - osgbObj.osgbtitles.clippingPlanes.enabled = false; - } - return; - } - - const distance = root._distanceToCamera; - const tilesLoaded = osgbObj.osgbtitles.tilesLoaded; - if (osgbObj.boundary && tilesLoaded) { - if (distance != null && getMinDistanceOfOSGB(viewer) === distance) { - ClipTerrain(viewer, osgbObj); - if ( - osgbObj.osgbtitles?.clippingPlanes && - !osgbObj.osgbtitles.clippingPlanes.enabled - ) { - osgbObj.osgbtitles.clippingPlanes.enabled = true; - } - } else { - removeClip(viewer, osgbObj); - if (osgbObj.osgbtitles?.clippingPlanes?.enabled) { - osgbObj.osgbtitles.clippingPlanes.enabled = false; - } - } - } -}; - -// ==================== 公开 API ==================== - -/** - * 加载倾斜摄影 3D Tileset 模型 - * - * Cesium 1.141 中必须使用 Cesium3DTileset.fromUrl() 静态方法, - * new Cesium3DTileset({url}) 不会触发网络请求。 - */ -export const LoadOSGB = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => { - if (!osgbObj.url) return; - - // 已加载则跳过 - if (osgbObj.osgbtitles && !osgbObj.osgbtitles.isDestroyed?.()) return; - - // 正在加载中也跳过(防止重复调用 fromUrl) - if (osgbObj._loading) return; - osgbObj._loading = true; - - // 自增版本号:每次 LoadOSGB 都 +1,.then() 里靠版本号判断是否已失效 - osgbObj._loadId = (osgbObj._loadId || 0) + 1; - const loadId = osgbObj._loadId; - - const url = osgbObj.url.includes('?') - ? osgbObj.url + `&token=${getToken()}` - : osgbObj.url + `?token=${getToken()}`; - - Cesium.Cesium3DTileset.fromUrl(url, { - maximumScreenSpaceError: Number(osgbObj?.accuracy) || 16, - show: true, - maximumMemoryUsage: 128 - }) - .then((tileset: Cesium.Cesium3DTileset) => { - // 竞态保护:版本号变了 → 销毁退出 - if ( - osgbObj._loadId !== loadId || - !Cesium.defined(viewer) || - viewer.isDestroyed() - ) { - tileset.destroy(); - return; - } - if (!Cesium.defined(tileset)) return; - - osgbObj._loading = false; - tileset.maximumScreenSpaceError = 32; - - // height 向上偏移:模型几何自带高程,height 只是微调(如 44m) - if (osgbObj.height != 0) { - const bs = Cesium.Cartographic.fromCartesian( - tileset.boundingSphere.center - ); - const surface = Cesium.Cartesian3.fromRadians( - bs.longitude, - bs.latitude, - 0.0 - ); - const offset = Cesium.Cartesian3.fromRadians( - bs.longitude, - bs.latitude, - osgbObj.height - ); - const translation = Cesium.Cartesian3.subtract( - offset, - surface, - new Cesium.Cartesian3() - ); - tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); - } - - // 加入场景(匹配旧代码 readyPromise 中 addOSGB) - addOSGB(viewer, tileset); - osgbObj.osgbtitles = tileset; - - // 始终设置 cartesian3,保证 dynamicSetVisible 能计算距离 - osgbObj.cartesian3 = tileset.boundingSphere.center.clone(); - - // boundary 裁切(匹配旧代码 readyPromise 中处理) - if (osgbObj.boundary) { - if (typeof osgbObj.boundary === 'string') { - osgbObj.boundary = JSON.parse(osgbObj.boundary); - } - Clip3DTile(osgbObj.boundary, tileset); - } - - // 注册 postRender 动态显隐(300ms 节流,匹配旧代码 throttle) - let lastTime = 0; - osgbObj.eventListener = viewer.scene.postRender.addEventListener(() => { - if (!Cesium.defined(viewer) || viewer.isDestroyed()) return; - const now = performance.now(); - if (now - lastTime < 300) return; - lastTime = now; - - if (!getState(osgbObj).timelookat) { - dynamicSetVisible(viewer, osgbObj); - } - dynamicClipTerrain(viewer, osgbObj); - }); - }) - .catch((e: Error) => { - osgbObj._loading = false; - console.log('加载倾斜摄影报错', e); - }); -}; - -/** - * 卸载倾斜摄影模型(完全移除) - * 仅在切换 2D 等场景调用,平时开关节用 osgbChangeClick 做显隐 - * 匹配旧代码 removeqxsy(osgbTool.ts:233-244):不清理 cartesian3 - */ -export const removeQxsy = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => { - // 递增版本号:使所有进行中的 fromUrl().then() 失效 - osgbObj._loadId = (osgbObj._loadId || 0) + 1; - removeClippingPlanes(viewer); - osgbObj.clippingPlanes = undefined; - osgbObj._loading = false; - osgbObj._switchOff = undefined; - - if (typeof osgbObj.eventListener === 'function') { - osgbObj.eventListener(); - osgbObj.eventListener = undefined; - } - - if (osgbObj.osgbtitles) { - removeOSGB(viewer, osgbObj.osgbtitles); - osgbObj.osgbtitles = undefined; - } - // 注意:不清理 cartesian3,匹配旧代码行为 -}; - -/** - * 倾斜摄影显隐切换 - * - 开关状态全局记录,不受距离影响 - * - 12km 以内:执行实际的显隐/加载操作 - * - 12km 以外:仅记录状态,不做 tileset 操作(tileset 不存在或已销毁) - * - 仅切换 2D 时才通过 removeQxsy 真正销毁 - */ -export const osgbChangeClick = ( - viewer: Cesium.Viewer, - osgbObj: OSGBItem, - checked: boolean -) => { - // 先记录开关状态(无论距离远近) - osgbObj._switchOff = !checked; - - // 12km 以外:只记状态不操作 tileset(此时 tileset 可能不存在或已销毁) - if (osgbObj.cartesian3) { - const cameraPosition = viewer.camera.position.clone(); - const distance = Cesium.Cartesian3.distance( - osgbObj.cartesian3, - cameraPosition - ); - if (distance >= 12000) return; - } - - if (checked) { - // 开关 ON:已有未销毁的 tileset → 直接显示;否则加载 - if (osgbObj.osgbtitles && !osgbObj.osgbtitles.isDestroyed?.()) { - osgbObj.osgbtitles.show = true; - } else { - LoadOSGB(viewer, osgbObj); - } - } else { - // 开关 OFF:隐藏 tileset,移除 terrain 裁切 - if (osgbObj.osgbtitles && !osgbObj.osgbtitles.isDestroyed?.()) { - osgbObj.osgbtitles.show = false; - } - removeClip(viewer, osgbObj); - } -}; - -/** - * 解析 location 字段为结构化定位信息 - */ -const parseLocation = (location: any): OSGBLocation | null => { - if (!location) return null; - - // 如果已经是对象,直接用 - if (typeof location === 'object' && location.destination) { - return location as OSGBLocation; - } - - // 如果是字符串,尝试解析 - if (typeof location === 'string') { - try { - const jsonString = location.replace(/\n/g, ''); - const startIndex = jsonString.indexOf('{'); - const endIndex = jsonString.lastIndexOf('}'); - if (startIndex === -1 || endIndex === -1) return null; - const extractedData = jsonString.substring(startIndex, endIndex + 1); - const parsed = eval('(' + extractedData + ')'); - return { - destination: parsed.destination, - orientation: parsed.orientation, - center: parsed.center - }; - } catch { - return null; - } - } - - return null; -}; - -/** - * 飞行定位到倾斜摄影模型 - * 包含 flyTo + 到达后旋转动画 - */ -export const osgbLocation = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => { - const state = getState(osgbObj); - - clearTimeout(state.timeout); - state.num = 0; - state.time = 0; - state.witetime = 0; - - viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - - let center = Cesium.Cartesian3.fromDegrees( - 100.271729604817, - 35.3107902811941, - 2800 - ); - let heading = Cesium.Math.toRadians(0.0); - const pitch = Cesium.Math.toRadians(-30.0); - const range = 2000.0; - - const locationInfor = parseLocation(osgbObj.location); - - if (!locationInfor) { - console.warn('该模型定位坐标未配置!'); - return; - } - - // 判断是经纬度还是笛卡尔坐标 - const destCoords = locationInfor.destination; - if (destCoords[0] <= 180 && destCoords[0] >= -180) { - state.destination = Cesium.Cartesian3.fromDegrees( - destCoords[0], - destCoords[1], - destCoords[2] - ); - } else { - state.destination = new Cesium.Cartesian3( - destCoords[0], - destCoords[1], - destCoords[2] - ); - } - - state.orientation = locationInfor.orientation; - center = Cesium.Cartesian3.fromDegrees( - locationInfor.center[0], - locationInfor.center[1], - locationInfor.center[2] - ); - - if (!state.destination || !state.orientation) return; - - viewer.camera.cancelFlight(); - viewer.camera.flyTo({ - destination: state.destination, - orientation: state.orientation, - complete: () => { - state.timeout = setTimeout(() => { - rotate(); - }, 2000); - } - }); - - const rotate = () => { - clearInterval(state.timelookat); - state.timelookat = setInterval(() => { - if (!Cesium.defined(viewer) || viewer.isDestroyed()) { - return; - } - state.witetime += 0.7; - if (state.witetime > 180 && state.witetime < 360) { - state.time += 0.7; - heading = Cesium.Math.toRadians(state.time); - viewer.camera.lookAt( - center, - new Cesium.HeadingPitchRange(heading, pitch, range) - ); - } else if (state.witetime > 360) { - viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - clearInterval(state.timelookat); - state.timelookat = null; - viewer.camera.flyTo({ - destination: state.destination!, - orientation: state.orientation, - duration: 2 - }); - return; - } - }, 10); - }; - - // 点击 body 两次退出旋转 - const body = document.getElementsByTagName('body')[0]; - body.onclick = function () { - if (viewer && !viewer.isDestroyed() && viewer.camera) { - state.num = state.num + 1; - if (state.num > 1) { - viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - clearInterval(state.timelookat); - clearTimeout(state.timeout); - state.timelookat = null; - } - } - }; -}; diff --git a/frontend-sjgl/src/layout/components/Sidebar/index.vue b/frontend-sjgl/src/layout/components/Sidebar/index.vue index ac637235..5d5bc0cd 100644 --- a/frontend-sjgl/src/layout/components/Sidebar/index.vue +++ b/frontend-sjgl/src/layout/components/Sidebar/index.vue @@ -2,9 +2,7 @@ import { ref, onBeforeMount, onMounted } from 'vue'; import { useRoute, useRouter } from 'vue-router'; -import { useMapStore } from '@/store/modules/map'; import { usePermissionStore } from '@/store/modules/permission'; -const mapStore = useMapStore(); const permissionStore = usePermissionStore(); const menus: any = ref([]); // 主菜单列表 const subMenus: any = ref([]); // 子菜单列表 @@ -14,15 +12,6 @@ const subActiveKey = ref(''); // 子菜单选中项 const route = useRoute(); const router = useRouter(); -const buildPageKeyFromPath = (path?: string) => { - const normalizedPath = path || ''; - const parts = normalizedPath.split('/').filter(Boolean); - if (parts.length >= 2) { - return `${parts[0]}_${parts[1]}`; - } - return normalizedPath || '__route-change__'; -}; - // 主菜单切换 const handleTabChange = (key: string) => { let data = menus.value.find((item: any) => item.id === key); @@ -33,9 +22,6 @@ const handleTabChange = (key: string) => { let subData = subMenus.value.find( (item: any) => item.id === subActiveKey.value ); - mapStore.markPendingPageNavigation( - buildPageKeyFromPath(subData?.path || subData?.opturl) - ); router.push(subData.path || subData.opturl); } else { subActiveKey.value = ''; @@ -45,9 +31,6 @@ const handleTabChange = (key: string) => { const handleSubTabChange = (key: string) => { subActiveKey.value = key; let data = subMenus.value.find((item: any) => item.id === key); - mapStore.markPendingPageNavigation( - buildPageKeyFromPath(data?.path || data?.opturl) - ); router.push(data.path || data.opturl); }; diff --git a/frontend-sjgl/src/main.ts b/frontend-sjgl/src/main.ts index 8968d2b1..4eca3cd3 100644 --- a/frontend-sjgl/src/main.ts +++ b/frontend-sjgl/src/main.ts @@ -13,9 +13,7 @@ import 'ant-design-vue/dist/reset.css'; // Ant Design 全局样式重置 import dayjs from 'dayjs'; // ant 中文语言 import 'dayjs/locale/zh-cn'; import 'virtual:svg-icons-register'; -// 3d地图 -import * as Cesium from 'cesium'; -import 'cesium/Build/Cesium/Widgets/widgets.css'; + // 国际化 import i18n from '@/lang/index'; diff --git a/frontend-sjgl/src/modules/jidiSelectorMod.vue b/frontend-sjgl/src/modules/jidiSelectorMod.vue deleted file mode 100644 index 7ec74a20..00000000 --- a/frontend-sjgl/src/modules/jidiSelectorMod.vue +++ /dev/null @@ -1,110 +0,0 @@ - - - - diff --git a/frontend-sjgl/src/modules/map/application/map-orchestrator.ts b/frontend-sjgl/src/modules/map/application/map-orchestrator.ts deleted file mode 100644 index 4c0ff19e..00000000 --- a/frontend-sjgl/src/modules/map/application/map-orchestrator.ts +++ /dev/null @@ -1,806 +0,0 @@ -import { watch, type WatchStopHandle, ref } from 'vue'; -import { useRoute } from 'vue-router'; -import { unByKey } from 'ol/Observable'; -import dayjs from 'dayjs'; -import { MapClass } from '@/components/gis/map.class'; -import { getMapConfig, layerConfig2Flat } from '@/components/gis/gisUtils'; -import { useMapConfigStore } from '@/modules/map/stores/map-config.store'; -import { useMapDataStore } from '@/modules/map/stores/map-data.store'; -import { useMapViewStore } from '@/modules/map/stores/map-view.store'; -import { useMapStore } from '@/store/modules/map'; -import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; - -type InitializeOptions = { - container: HTMLElement; - popupContainer?: HTMLDivElement | null; - pageKey: string; -}; - -type LoadPageOptions = { - pageKey: string; - isInitialLoad?: boolean; -}; - -type ApplyLayerSelectionOptions = { - checkedKeys: string[]; - triggerKey?: string; - checked?: boolean; -}; -const SYSTEM_ID = 'qgc'; -const HYDRO_DYNAMIC_LAYER_KEYS = [ - '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' -]; -const ENG_POINT_LAYER_KEY = 'eng_point'; -const ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5; -const CESIUM_ZOOM_SYNC_DEBOUNCE_MS = 80; -const LARGE_ENG_LEGEND_PREFIX = 'large_eng_'; -const MID_ENG_LEGEND_PREFIX = 'mid_eng_'; - -export const useMapOrchestrator = () => { - const route = useRoute(); - const mapClass = MapClass.getInstance(); - const mapStore = useMapStore(); - const mapConfigStore = useMapConfigStore(); - const mapDataStore = useMapDataStore(); - const mapViewStore = useMapViewStore(); - const jidiSelectEventStore = useJidiSelectEventStore(); - let activePageLoadRequestId = 0; - let removeZoomListener: (() => void) | null = null; - let stopBaseSelectionWatch: WatchStopHandle | null = null; - const initializedBaseLayerKeys = new Set(); - const baseSelectionDebounceTimer = ref | null>( - null - ); - const zoomSyncDebounceTimer = ref | null>(null); - let latestHydroMenuGetter: (() => boolean) | null = null; - let currentMapType: '2D' | '3D' = '2D'; - let hydroMenuDefaultCheckedKeys = new Set(); - let hydroDynamicLayerSyncTask: { - visible: boolean; - promise: Promise; - } | null = null; - - // 备注:统一获取当前地图视图对象,避免在多个组件里重复兼容 2D/3D view 访问方式。 - const getCurrentView = () => { - return mapClass.view?.getView ? mapClass.view.getView() : mapClass.view; - }; - - // 备注:统一获取当前地图缩放级别,供页面切换和缩放监听复用。 - const getCurrentZoom = (): number | undefined => { - return mapClass.getCurrentZoom(); - }; - - const clearZoomSyncDebounceTimer = () => { - if (zoomSyncDebounceTimer.value) { - clearTimeout(zoomSyncDebounceTimer.value); - zoomSyncDebounceTimer.value = null; - } - }; - - // 备注:统一把当前页面中的 GIS 基础图层初始化到地图实例,避免在图层树组件里做配置解析和加层编排。 - const ensureBaseLayersInitialized = (configs: any[] = []) => { - const flatLayerConfigs = layerConfig2Flat(configs); - - flatLayerConfigs.forEach((item: any) => { - if (item.type !== 'GISMap' || !item.key) { - return; - } - - if (!item.config && item.paramJson) { - try { - const jsonObj = JSON.parse(item.paramJson); - item.config = getMapConfig(jsonObj); - } catch { - item.config = null; - } - } - - if (!item.config) { - return; - } - - if (item.key === 'customBaseLayer') { - const serializableBaseLayerConfig = { - key: item.config?.key, - id: item.config?.id, - name: item.config?.name, - type: item.config?.type, - url: item.config?.url, - url_3d: item.config?.url_3d, - layers: item.config?.layers, - rasteropacity: item.config?.rasteropacity - }; - sessionStorage.setItem( - 'customBaseLayer', - JSON.stringify(serializableBaseLayerConfig) - ); - } - - if (!initializedBaseLayerKeys.has(item.key)) { - mapClass.addBaseDataLayer(item.config, item.checked === 1); - initializedBaseLayerKeys.add(item.key); - } - - mapClass.controlBaseLayerTreeShowAndHidden( - item.key, - item.config?.id || item.key, - item.checked === 1 - ); - }); - }; - - // 备注:统一加载当前页面所需的图层配置、图例配置以及首批锚点数据。 - const loadPage = async ({ - pageKey, - isInitialLoad = false - }: LoadPageOptions) => { - let backgroundLoadStarted = false; - const pageLoadRequestId = ++activePageLoadRequestId; - - try { - mapDataStore.setLoading(true); - - const hasGlobalLegendConfig = - Array.isArray(mapConfigStore.legendConfigOriginal) && - mapConfigStore.legendConfigOriginal.length > 0; - const shouldLoadGlobalLegend = isInitialLoad && !hasGlobalLegendConfig; - const previousPageKey = mapConfigStore.lastLoadOptions?.pageKey || ''; - const previousCheckedKeys = mapViewStore.getCheckedLayerKeys(); - const moduleId = (route.meta?.moduleId as string) || ''; - const loadOptions = { - systemId: SYSTEM_ID, - moduleId, - pageKey, - description: 'true' - }; - const layerConfigPromise = - mapConfigStore.loadPageLayerConfig(loadOptions); - const legendConfigPromise = mapConfigStore.loadPageLegendConfig( - moduleId, - { - includeGlobal: shouldLoadGlobalLegend - } - ); - - // 同时等待两个 promise - const [{ layerConfig }, { legendOriginal, pageLegend }] = - await Promise.all([layerConfigPromise, legendConfigPromise]); - - if (pageLoadRequestId !== activePageLoadRequestId) { - return; - } - - // 先设置图层数据(更新 checkedLayerKeys),再设置图例数据 - if (layerConfig.length > 0) { - hydroMenuDefaultCheckedKeys = new Set( - mapConfigStore.extractCheckedLayerKeys(layerConfig) - ); - mapStore.setLayerData(layerConfig); - ensureBaseLayersInitialized(layerConfig); - - let checkedKeys = mapViewStore.getCheckedLayerKeys(); - if (previousPageKey === pageKey && previousCheckedKeys.length > 0) { - const getAllLayerKeys = (items: any[]): string[] => { - const keys: string[] = []; - const walk = (nodes: any[]) => { - nodes.forEach(item => { - if (item?.key) { - keys.push(item.key); - } - if (item?.children?.length > 0) { - walk(item.children); - } - }); - }; - walk(items); - return keys; - }; - const currentLayerKeys = new Set(getAllLayerKeys(layerConfig)); - const runtimeCheckedKeys = previousCheckedKeys.filter(key => - currentLayerKeys.has(key) - ); - if (runtimeCheckedKeys.length > 0) { - mapViewStore.setCheckedLayerKeys(runtimeCheckedKeys); - checkedKeys = runtimeCheckedKeys; - } - } - - // 设置图例数据(此时 checkedLayerKeys 已正确设置) - if (legendOriginal.length > 0) { - mapStore.setLegendData(legendOriginal, pageLegend); - } - - const activePageToken = mapStore.activatePageContext( - pageKey, - layerConfig - ); - await mapStore.updateLayerData(checkedKeys, true); - mapStore.setSelectedLegendData(); - const backgroundLoadPromise = isInitialLoad - ? mapStore.loadAllLayerData(layerConfig, checkedKeys, { - pageToken: activePageToken, - skipSessionCheck: true, - pageKey - }) - : mapStore.loadCurrentPageLayerData(layerConfig, checkedKeys, { - pageToken: activePageToken, - skipSessionCheck: true - }); - - void backgroundLoadPromise.catch(error => { - console.error(`页面锚点后台加载失败 [${pageKey}]`, error); - }); - backgroundLoadStarted = true; - } - } finally { - if ( - !backgroundLoadStarted && - pageLoadRequestId === activePageLoadRequestId - ) { - mapDataStore.setLoading(false); - } - } - }; - - // 备注:统一完成地图实例初始化和弹窗挂载,确保后续事件监听可以在图层数据加载前生效。 - const initializeMapShell = async ({ - container, - popupContainer - }: InitializeOptions) => { - await mapClass.init(container); - - if (popupContainer) { - const popupHost = container.parentElement; - if (popupHost && popupContainer.parentElement !== popupHost) { - popupHost.appendChild(popupContainer); - } - popupContainer.style.display = 'none'; - mapClass.initPopupOverlay(popupContainer); - } - }; - - const replayCurrentMapState = async ( - options: Pick & { - getIsHydroMenu: () => boolean; - } - ) => { - initializedBaseLayerKeys.clear(); - - const currentZoom = getCurrentZoom(); - if (currentZoom !== undefined) { - mapViewStore.setCurrentZoomLevel(currentZoom); - } - - if (options.popupContainer) { - const container = document.getElementById('mapContainer'); - const popupHost = container?.parentElement || null; - if (popupHost && options.popupContainer.parentElement !== popupHost) { - popupHost.appendChild(options.popupContainer); - } - options.popupContainer.style.display = 'none'; - mapClass.initPopupOverlay(options.popupContainer); - } - - ensureBaseLayersInitialized(mapStore.layerData || []); - - const checkedKeys = mapViewStore.getCheckedLayerKeys(); - await mapStore.updateLayerData(checkedKeys, true); - mapStore.setSelectedLegendData(); - await syncZoomSensitiveState({ - isHydroMenu: options.getIsHydroMenu(), - refreshEngPoint: true - }); - - const selectedBaseId = mapViewStore.selectedBaseId; - mapClass.jdPanelControlShowAndHidden( - selectedBaseId || 'all', - !!selectedBaseId - ); - - bindZoomListener(options.getIsHydroMenu); - }; - - // 备注:统一完成首屏页面配置加载,供挂载阶段和后续页面重载复用。 - const initialize = async ({ - container, - popupContainer, - pageKey - }: InitializeOptions) => { - await initializeMapShell({ - container, - popupContainer, - pageKey - }); - await loadPage({ pageKey, isInitialLoad: true }); - }; - - // 备注:统一绑定地图缩放监听,让页面组件只负责触发初始化,不再直接处理缩放联动细节。 - const bindZoomListener = (getIsHydroMenu: () => boolean) => { - if (removeZoomListener) { - removeZoomListener(); - removeZoomListener = null; - } - clearZoomSyncDebounceTimer(); - - const currentZoom = getCurrentZoom(); - if (currentZoom !== undefined) { - mapViewStore.setCurrentZoomLevel(currentZoom); - } - - const emitZoomChange = async () => { - const zoom = getCurrentZoom(); - if (zoom === undefined) return; - - await handleZoomLevelChange( - mapViewStore.currentZoomLevel, - zoom, - getIsHydroMenu() - ); - }; - - if (currentMapType === '3D') { - const removeCesiumListener = - mapClass.view?.camera?.changed?.addEventListener(() => { - clearZoomSyncDebounceTimer(); - zoomSyncDebounceTimer.value = setTimeout(() => { - zoomSyncDebounceTimer.value = null; - void emitZoomChange(); - }, CESIUM_ZOOM_SYNC_DEBOUNCE_MS); - }); - - if (typeof removeCesiumListener === 'function') { - removeZoomListener = () => { - clearZoomSyncDebounceTimer(); - removeCesiumListener(); - }; - } - return; - } - - const view = getCurrentView(); - if (!view?.on) return; - - const zoomListenerKey = view.on('change:resolution', () => { - void emitZoomChange(); - }); - removeZoomListener = () => { - unByKey(zoomListenerKey); - }; - }; - - // 备注:统一绑定基地切换事件,让页面组件不再直接 watch 外部基地选择源。 - const bindBaseSelection = () => { - if (stopBaseSelectionWatch) { - stopBaseSelectionWatch(); - stopBaseSelectionWatch = null; - } - - stopBaseSelectionWatch = watch( - () => jidiSelectEventStore.selectedItem?.wbsCode, - newWbsCode => { - if (!newWbsCode) { - changeBaseId(''); - return; - } - - if (baseSelectionDebounceTimer.value) { - clearTimeout(baseSelectionDebounceTimer.value); - } - - baseSelectionDebounceTimer.value = setTimeout(() => { - changeBaseId(newWbsCode); - baseSelectionDebounceTimer.value = null; - }, 100); - }, - { immediate: true } - ); - }; - - // 备注:统一挂载地图页面生命周期,初始化后自动接管缩放监听和基地切换监听。 - const mountView = async ( - options: InitializeOptions & { - getIsHydroMenu: () => boolean; - } - ) => { - currentMapType = '2D'; - latestHydroMenuGetter = options.getIsHydroMenu; - await initializeMapShell(options); - bindBaseSelection(); - bindZoomListener(options.getIsHydroMenu); - await loadPage({ pageKey: options.pageKey, isInitialLoad: true }); - await syncZoomSensitiveState({ - isHydroMenu: options.getIsHydroMenu(), - refreshEngPoint: true - }); - }; - - const switchMapType = async ( - type: '2D' | '3D', - options: Pick & { - getIsHydroMenu?: () => boolean; - } = {} - ) => { - const hydroMenuGetter = - options.getIsHydroMenu || latestHydroMenuGetter || (() => false); - latestHydroMenuGetter = hydroMenuGetter; - currentMapType = type; - - await mapClass.switchView(type); - await replayCurrentMapState({ - popupContainer: options.popupContainer, - getIsHydroMenu: hydroMenuGetter - }); - }; - - // 备注:统一处理菜单切换后的页面配置重载,供页面 watch 直接复用。 - const reloadPage = async (pageKey: string) => { - if (!pageKey) return; - await loadPage({ pageKey, isInitialLoad: false }); - }; - - // 备注:菜单切换前只隐藏旧页面当前可见点图层,底图保持连续显示,避免切换时闪烁。 - const hideCurrentVisibleLayers = () => { - const currentCheckedKeys = mapViewStore.getCheckedLayerKeys(); - - currentCheckedKeys.forEach(layerKey => { - const layerItem = mapStore.findLayerByKey(mapStore.layerData, layerKey); - if (!layerItem?.key) { - return; - } - - if (layerItem.type === 'pointMap') { - if (mapClass.hasLayer(layerItem.key)) { - mapClass.mdLayerTreeShowOrHidden(layerItem.key, false); - } - } - }); - }; - - // 备注:统一处理图层树勾选结果归一化和地图联动,供图层树组件复用。 - const applyLayerSelection = async ({ - checkedKeys, - triggerKey, - checked - }: ApplyLayerSelectionOptions) => { - const normalizedKeys = mapStore.normalizeCheckedLayerKeys( - checkedKeys, - triggerKey, - checked - ); - await mapStore.updateLayerData(normalizedKeys); - return normalizedKeys; - }; - - // 备注:统一确保指定动态图层完成数据预加载,避免缩放切换时重复在页面组件里拼装加载逻辑。 - const ensureDynamicLayersLoaded = async (layerKeys: string[] = []) => { - const layersToLoad = layerKeys - .map(layerKey => mapStore.findLayerByKey(mapStore.layerData, layerKey)) - .filter( - (layer: any) => layer?.url && !mapDataStore.hasPointLayerData(layer.key) - ); - - if (layersToLoad.length === 0) { - return; - } - - await Promise.all(layersToLoad.map(layer => mapStore.loadLayerData(layer))); - }; - - // 备注:统一控制水电开发菜单下的动态图层增删,避免页面组件直接操作加载和勾选细节。 - const syncHydroDynamicLayers = async (visible: boolean) => { - if ( - hydroDynamicLayerSyncTask && - hydroDynamicLayerSyncTask.visible === visible - ) { - return hydroDynamicLayerSyncTask.promise; - } - - const task = (async () => { - const currentCheckedKeys = mapViewStore.getCheckedLayerKeys(); - - if (visible) { - const layersToAdd = HYDRO_DYNAMIC_LAYER_KEYS.filter( - key => !currentCheckedKeys.includes(key) - ); - - if (layersToAdd.length === 0) { - return currentCheckedKeys; - } - - await ensureDynamicLayersLoaded(layersToAdd); - return applyLayerSelection({ - checkedKeys: [...currentCheckedKeys, ...layersToAdd] - }); - } - - const nextCheckedKeys = currentCheckedKeys.filter(key => { - if (!HYDRO_DYNAMIC_LAYER_KEYS.includes(key)) { - return true; - } - return hydroMenuDefaultCheckedKeys.has(key); - }); - - if (nextCheckedKeys.length === currentCheckedKeys.length) { - return currentCheckedKeys; - } - - return applyLayerSelection({ - checkedKeys: nextCheckedKeys - }); - })().finally(() => { - if (hydroDynamicLayerSyncTask?.promise === task) { - hydroDynamicLayerSyncTask = null; - } - }); - - hydroDynamicLayerSyncTask = { - visible, - promise: task - }; - return task; - }; - - // 备注:统一按当前缩放对齐首页菜单的中型站过滤和 12 级动态图层状态。 - const syncZoomSensitiveState = async ({ - isHydroMenu, - refreshEngPoint = false - }: { - isHydroMenu: boolean; - refreshEngPoint?: boolean; - }) => { - const currentZoom = getCurrentZoom(); - if (currentZoom !== undefined) { - mapViewStore.setCurrentZoomLevel(currentZoom); - } - - if ( - isHydroMenu && - refreshEngPoint && - mapViewStore.getCheckedLayerKeys().includes(ENG_POINT_LAYER_KEY) - ) { - mapStore.refreshPointLayerDisplayData(ENG_POINT_LAYER_KEY); - } - - await syncHydroDynamicLayers( - !!isHydroMenu && currentZoom !== undefined && currentZoom >= 12 - ); - }; - - // 备注:统一处理地图缩放联动,收口水电开发菜单下的动态图层阈值切换逻辑。 - const handleZoomLevelChange = async ( - previousZoom: number, - currentZoom: number, - isHydroMenu: boolean - ) => { - mapViewStore.setCurrentZoomLevel(currentZoom); - - const crossedEngPointMediumThreshold = - (previousZoom <= ENG_POINT_MEDIUM_VISIBLE_ZOOM && - currentZoom > ENG_POINT_MEDIUM_VISIBLE_ZOOM) || - (previousZoom > ENG_POINT_MEDIUM_VISIBLE_ZOOM && - currentZoom <= ENG_POINT_MEDIUM_VISIBLE_ZOOM); - - if ( - isHydroMenu && - crossedEngPointMediumThreshold && - mapViewStore.getCheckedLayerKeys().includes(ENG_POINT_LAYER_KEY) - ) { - mapStore.refreshPointLayerDisplayData(ENG_POINT_LAYER_KEY); - } - - if (!isHydroMenu) { - return; - } - - const oldZoomLevel = Math.floor(previousZoom); - const newZoomLevel = Math.floor(currentZoom); - - if (oldZoomLevel < 12 && newZoomLevel >= 12) { - await syncHydroDynamicLayers(true); - return; - } - - if (oldZoomLevel >= 12 && newZoomLevel < 12) { - await syncHydroDynamicLayers(false); - } - }; - - // 备注:统一处理菜单切换后的页面重载和动态图层收口,减少页面组件里的分支编排。 - const handlePageChange = (pageKey: string, isHydroMenu: boolean) => { - if (!pageKey) return Promise.resolve(); - - const previousPageKey = mapConfigStore.lastLoadOptions?.pageKey || ''; - if (previousPageKey && previousPageKey !== pageKey) { - hideCurrentVisibleLayers(); - mapViewStore.setCheckedLayerKeys([]); - mapStore.setSelectedLegendData(); - } - - return reloadPage(pageKey).then(async () => { - await syncZoomSensitiveState({ - isHydroMenu, - refreshEngPoint: true - }); - }); - }; - - // 备注:统一处理单个图层切换命令,供后续其他入口复用。 - const toggleLayer = async (layerKey: string, checked?: boolean) => { - const currentKeys = mapViewStore.getCheckedLayerKeys(); - const currentSet = new Set(currentKeys); - const nextChecked = - checked === undefined ? !currentSet.has(layerKey) : checked; - - if (nextChecked) { - currentSet.add(layerKey); - } else { - currentSet.delete(layerKey); - } - - return applyLayerSelection({ - checkedKeys: Array.from(currentSet), - triggerKey: layerKey, - checked: nextChecked - }); - }; - - // 备注:统一处理单个图例切换命令,供图例组件复用。 - const toggleLegend = (nameEn: string, checked?: number) => { - if (!nameEn) return; - console.log('toggleLegend', nameEn, checked); - console.log(mapViewStore.getLegendChecked(nameEn)); - const nextChecked = - checked === undefined - ? mapViewStore.getLegendChecked(nameEn) === 1 - ? 0 - : 1 - : checked; - console.log(nextChecked); - mapStore.updateLegendChecked(nameEn, nextChecked); - }; - - // 备注:统一处理批量图例切换命令,供容量筛选和分组图例复用。 - const toggleLegendBatch = (nameEns: string[] = [], checked: number) => { - const targetNameEns = nameEns.filter(Boolean); - if (targetNameEns.length === 0) return; - mapStore.updateLegendCheckedBatch(targetNameEns, checked); - }; - - // 备注:统一处理基地切换命令,同时驱动地图基地裁切联动。 - const changeBaseId = (baseId: string) => { - const nextBaseId = !baseId || baseId === 'all' ? '' : baseId; - mapViewStore.setSelectedBaseId(nextBaseId); - mapClass.jdPanelControlShowAndHidden( - baseId || 'all', - baseId !== 'all' && !!baseId - ); - }; - - // 备注:统一处理时间范围变更,并触发相关图层数据重载。 - const changeTimeRange = async (range: [any, any]) => { - mapViewStore.setSearchTimeRange(range); - await mapStore.reloadBySearchTimeRange(); - }; - - // 备注:统一处理装机容量筛选,让筛选器切换时同步更新 eng_point 图例运行态和点位显示。 - const changeEngPointCapacity = (capacityType?: string | null) => { - const legendItems = mapStore.getLegendItemsByLayerCode(ENG_POINT_LAYER_KEY); - if (!legendItems.length) return; - - const allEngLegendNames = legendItems - .map((item: any) => item?.nameEn) - .filter(Boolean); - - if (!allEngLegendNames.length) return; - - const normalizedCapacityType = capacityType || 'all'; - let activePrefixes = [LARGE_ENG_LEGEND_PREFIX, MID_ENG_LEGEND_PREFIX]; - - if (normalizedCapacityType === 'large_eng_built') { - activePrefixes = [LARGE_ENG_LEGEND_PREFIX]; - } else if (normalizedCapacityType === 'mid_eng_built') { - activePrefixes = [MID_ENG_LEGEND_PREFIX]; - } - - const visibleLegendNames = allEngLegendNames.filter((nameEn: string) => - activePrefixes.some(prefix => nameEn.startsWith(prefix)) - ); - const hiddenLegendNames = allEngLegendNames.filter( - (nameEn: string) => !visibleLegendNames.includes(nameEn) - ); - - mapStore.updateLegendCheckedBatch(hiddenLegendNames, 0); - mapStore.updateLegendCheckedBatch(visibleLegendNames, 1); - mapStore.refreshPointLayerDisplayData(ENG_POINT_LAYER_KEY); - }; - - // 备注:统一重置地图筛选表单依赖的时间范围,供筛选组件在切页时复位默认输入。 - const resetFilterState = () => { - mapViewStore.setSearchTimeRange([dayjs().subtract(1, 'M'), dayjs()]); - }; - - // 备注:统一处理搜索定位,按点位编码从缓存数据中查找并飞行到目标位置。 - const focusPoint = ( - pointId: string, - zoom = 15, - fallbackPoints: any[] = [] - ) => { - if (!pointId) return; - const pointMatcher = (item: any) => { - return item.stcd === pointId || item._id === pointId; - }; - const targetPoint = - mapDataStore.pointData.find(pointMatcher) || - fallbackPoints.find(pointMatcher); - if (!targetPoint?.lgtd || !targetPoint?.lttd) return; - - const legendState = String(targetPoint?.anchoPointState || ''); - if (legendState && mapViewStore.getLegendChecked(legendState) !== 1) { - mapStore.updateLegendChecked(legendState, 1); - } - - const layerKey = String(targetPoint?.layerKey || ''); - if (layerKey && mapClass.hasLayer(layerKey)) { - mapClass.mdLayerTreeShowOrHidden(layerKey, true); - } - - mapClass.flyTopanto([targetPoint.lgtd, targetPoint.lttd], zoom); - }; - - // 备注:统一释放地图页面生命周期里注册的监听,避免页面卸载后残留联动。 - const unmountView = () => { - if (removeZoomListener) { - removeZoomListener(); - removeZoomListener = null; - } - clearZoomSyncDebounceTimer(); - - if (stopBaseSelectionWatch) { - stopBaseSelectionWatch(); - } - - if (baseSelectionDebounceTimer.value) { - clearTimeout(baseSelectionDebounceTimer.value); - baseSelectionDebounceTimer.value = null; - } - - latestHydroMenuGetter = null; - currentMapType = '2D'; - initializedBaseLayerKeys.clear(); - hydroMenuDefaultCheckedKeys.clear(); - }; - - return { - initialize, - mountView, - switchMapType, - unmountView, - loadPage, - reloadPage, - applyLayerSelection, - ensureDynamicLayersLoaded, - syncHydroDynamicLayers, - getCurrentZoom, - handleZoomLevelChange, - handlePageChange, - toggleLayer, - toggleLegend, - toggleLegendBatch, - changeBaseId, - changeTimeRange, - changeEngPointCapacity, - resetFilterState, - focusPoint - }; -}; diff --git a/frontend-sjgl/src/modules/map/domain/legend-deriver.ts b/frontend-sjgl/src/modules/map/domain/legend-deriver.ts deleted file mode 100644 index c550f926..00000000 --- a/frontend-sjgl/src/modules/map/domain/legend-deriver.ts +++ /dev/null @@ -1,147 +0,0 @@ -type BuildLegendTreeOptions = { - items?: any[]; - selectedLayerCodes?: Set; - getLegendChecked: (nameEn?: string) => number; - normalizeLegendNameEn: (nameEn?: string) => string; -}; - -// 备注:统一把 checked 值规范为 0/1,避免运行态和原始配置态混用不同类型。 -export const normalizeLegendCheckedValue = ( - checked: any -): number | undefined => { - if (checked === null || checked === undefined || checked === '') { - return undefined; - } - - const numericChecked = Number(checked); - if (Number.isNaN(numericChecked)) { - return undefined; - } - - return numericChecked === 1 ? 1 : 0; -}; - -// 备注:收集图例树的叶子节点,供初始化图例运行态 checked 索引使用。 -export const collectLegendLeaves = (items: any[] = []): any[] => { - const leaves: any[] = []; - items.forEach(item => { - if (item?.childrenList?.length > 0) { - leaves.push(...collectLegendLeaves(item.childrenList)); - } else { - leaves.push(item); - } - }); - return leaves; -}; - -// 备注:根据原始图例配置生成图例运行态 checked 映射,避免后续重复递归取默认值。 -export const buildLegendCheckedState = ( - sourceData: any[] = [], - normalizeLegendNameEn: (nameEn?: string) => string -): Record => { - const nextState: Record = {}; - - collectLegendLeaves(sourceData).forEach(item => { - const normalizedNameEn = normalizeLegendNameEn(item?.nameEn); - if (!normalizedNameEn) return; - - const fallbackChecked = normalizeLegendCheckedValue(item?.checked) ?? 0; - nextState[normalizedNameEn] = fallbackChecked; - }); - - return nextState; -}; - -// 备注:按当前选中图层和图例运行态生成可展示的图例树结构。 -export const buildLegendTree = ({ - items = [], - selectedLayerCodes, - getLegendChecked, - normalizeLegendNameEn -}: BuildLegendTreeOptions): any[] => { - const buildLegendNode = (item: any): any | null => { - if (item?.name === '地图') { - return null; - } - - if (item?.childrenList?.length > 0) { - const children = item.childrenList - .map((child: any) => buildLegendNode(child)) - .filter(Boolean); - - if (selectedLayerCodes && children.length === 0) { - return null; - } - - return { - ...item, - checked: children.some((child: any) => child.checked === 1) ? 1 : 0, - childrenList: children - }; - } - - if (item?.ifShow === 0) { - return null; - } - - if ( - selectedLayerCodes && - (!item?.layerCode || !selectedLayerCodes.has(item.layerCode)) - ) { - return null; - } - - const normalizedNameEn = normalizeLegendNameEn(item?.nameEn); - return { - ...item, - nameEn: normalizedNameEn, - checked: getLegendChecked(normalizedNameEn) - }; - }; - - return items.map(item => buildLegendNode(item)).filter(Boolean); -}; - -// 备注:按名称从图例树中提取指定分组,供环保设施补充规则复用。 -export const pickLegendGroupByName = ( - items: any[] = [], - groupName: string -): any[] => { - return items.filter(item => item?.name === groupName); -}; - -// 备注:统一应用环保设施图例补充规则,避免在多个位置重复拼装同一组图例。 -export const applyEnvFacilityLegendRule = ( - legendItems: any[] = [], - layerKeys: string[] = [], - legendSource: any[] = [], - buildLegendItems: (items: any[]) => any[] -): any[] => { - const triggerKeys = [ - 'fp_point', - 'eq_point', - 'fb_point', - 'vp_point', - 'va_point', - 'sg_point', - 'dw_point' - ]; - const hasTriggerLayer = triggerKeys.some(key => layerKeys.includes(key)); - const withoutEnvFacility = legendItems.filter( - (item: any) => item.name !== '环保设施' - ); - - if (!hasTriggerLayer) { - return withoutEnvFacility; - } - - const envFacilityLegend = buildLegendItems( - pickLegendGroupByName(legendSource, '环保设施') - )[0]; - - if (!envFacilityLegend) { - return withoutEnvFacility; - } - - return [...withoutEnvFacility, envFacilityLegend]; -}; diff --git a/frontend-sjgl/src/modules/map/domain/map-layer-rules.ts b/frontend-sjgl/src/modules/map/domain/map-layer-rules.ts deleted file mode 100644 index bd1ab375..00000000 --- a/frontend-sjgl/src/modules/map/domain/map-layer-rules.ts +++ /dev/null @@ -1,80 +0,0 @@ -type ApplyLayerMutualExclusionRulesOptions = { - rawKeys?: string[]; - previousKeys?: string[]; - triggerKey?: string; - checked?: boolean; - getLayerBranchKeys?: (rootKey: string) => string[]; -}; - -// 备注:统一按当前业务规则收口图层互斥关系,返回归一化后的勾选结果。 -export const applyLayerMutualExclusionRules = ({ - rawKeys = [], - previousKeys = [], - triggerKey, - checked, - getLayerBranchKeys -}: ApplyLayerMutualExclusionRulesOptions): string[] => { - let normalizedKeys = Array.from(new Set(rawKeys.filter(Boolean))); - - const hasAny = (keys: string[]) => - keys.length > 0 && keys.some(key => normalizedKeys.includes(key)); - const hadAny = (keys: string[]) => - keys.length > 0 && keys.some(key => previousKeys.includes(key)); - const removeKeys = (keys: string[]) => { - if (keys.length === 0) return; - normalizedKeys = normalizedKeys.filter(key => !keys.includes(key)); - }; - - // 备注:统一处理两组图层的互斥关系,并优先保留当前触发的一组。 - const resolveConflict = ( - primaryKeys: string[], - secondaryKeys: string[], - preferredGroup: 'primary' | 'secondary' = 'primary' - ) => { - if (!hasAny(primaryKeys) || !hasAny(secondaryKeys)) { - return; - } - - if (triggerKey && checked !== false) { - if (primaryKeys.includes(triggerKey)) { - removeKeys(secondaryKeys); - return; - } - if (secondaryKeys.includes(triggerKey)) { - removeKeys(primaryKeys); - return; - } - } - - const primaryWasChecked = hadAny(primaryKeys); - const secondaryWasChecked = hadAny(secondaryKeys); - - if (primaryWasChecked && !secondaryWasChecked) { - removeKeys(secondaryKeys); - return; - } - if (!primaryWasChecked && secondaryWasChecked) { - removeKeys(primaryKeys); - return; - } - - if (preferredGroup === 'primary') { - removeKeys(secondaryKeys); - } else { - removeKeys(primaryKeys); - } - }; - - resolveConflict(['rare_fish_point'], ['fish_along_point'], 'primary'); - resolveConflict( - ['stinfo', 'stinfo_video_point'], - ['stinfo_ai_video_point'], - 'secondary' - ); - - const facilityKeys = getLayerBranchKeys?.('facilities') || []; - const facilityBuiltKeys = getLayerBranchKeys?.('facilities_built') || []; - resolveConflict(facilityKeys, facilityBuiltKeys, 'primary'); - - return normalizedKeys; -}; diff --git a/frontend-sjgl/src/modules/map/domain/nearby-point-rules.ts b/frontend-sjgl/src/modules/map/domain/nearby-point-rules.ts deleted file mode 100644 index 77d99e51..00000000 --- a/frontend-sjgl/src/modules/map/domain/nearby-point-rules.ts +++ /dev/null @@ -1,418 +0,0 @@ -export type NearbyPointDisplayMode = 'default' | 'replace' | 'expand'; - -export type NearbyPointAutoRule = { - distanceThresholdMeters: number; - replaceAtZoom: number; - expandAtZoom: number; - expandOffsetPx: number; - angleStepDeg: number; - minGroupSize: number; -}; - -export type NearbyPointLayoutRule = { - ringSize: number; - fanAngleForTwoDeg: number; - fanAngleForFourDeg: number; - fanAngleForManyDeg: number; - ringGapPx: number; - ringGapFactor: number; -}; - -export type NearbyPointDensityDisplayRule = { - minDensityValue: number; - minZoom: number; -}; - -export type NearbyPointConfig = { - autoRule: NearbyPointAutoRule; - layoutRule: NearbyPointLayoutRule; - densityDisplayRules: NearbyPointDensityDisplayRule[]; -}; - -export type NearbyPointRuntimeMeta = { - groupId: string; - displayName: string; - groupSize: number; - priority: number; - showZoomMin: number | null; - showZoomMax: number | null; - replaceAtZoom: number; - expandAtZoom: number; - expandOffsetPx: number; - expandAngleDeg: number; - displayMode: NearbyPointDisplayMode; -}; - -// 备注:近邻点统一调参入口。 -// 当前参数偏向“更容易成组、稍早进入替换与展开、展开位移更明显”, -// 方便抽吸效果在常用浏览层级更早被感知。 -export const DEFAULT_NEARBY_POINT_AUTO_RULE: NearbyPointAutoRule = { - distanceThresholdMeters: 9000, - replaceAtZoom: 10.5, - expandAtZoom: 12.2, - expandOffsetPx: 48, - angleStepDeg: 55, - minGroupSize: 2 -}; - -// 备注:展开布局统一调参入口。这里控制单圈数量、扇形角度和外圈扩散节奏。 -export const DEFAULT_NEARBY_POINT_LAYOUT_RULE: NearbyPointLayoutRule = { - ringSize: 5, - fanAngleForTwoDeg: 110, - fanAngleForFourDeg: 150, - fanAngleForManyDeg: 180, - ringGapPx: 22, - ringGapFactor: 0.85 -}; - -// 备注:密度分档显示规则。distance 越大表示越稀疏,越早参与显示候选。 -export const DEFAULT_NEARBY_POINT_DENSITY_DISPLAY_RULES: NearbyPointDensityDisplayRule[] = - [ - { - minDensityValue: 1500000, - minZoom: 0 - }, - { - minDensityValue: 800000, - minZoom: 0 - }, - { - minDensityValue: 400000, - minZoom: 0 - }, - { - minDensityValue: 50000, - minZoom: 2.0 - }, - { - minDensityValue: 0, - minZoom: 4.0 - } - ]; - -export const DEFAULT_NEARBY_POINT_CONFIG: NearbyPointConfig = { - autoRule: DEFAULT_NEARBY_POINT_AUTO_RULE, - layoutRule: DEFAULT_NEARBY_POINT_LAYOUT_RULE, - densityDisplayRules: DEFAULT_NEARBY_POINT_DENSITY_DISPLAY_RULES -}; - -export const getNearbyPointConfig = (): NearbyPointConfig => { - return DEFAULT_NEARBY_POINT_CONFIG; -}; - -export const getNearbyPointAutoRule = (): NearbyPointAutoRule => { - return getNearbyPointConfig().autoRule; -}; - -export const getNearbyPointLayoutRule = (): NearbyPointLayoutRule => { - return getNearbyPointConfig().layoutRule; -}; - -export const getNearbyPointDensityDisplayRules = - (): NearbyPointDensityDisplayRule[] => { - return getNearbyPointConfig().densityDisplayRules; - }; - -type GroupCandidate = { - index: number; - point: Record; - lon: number; - lat: number; - layerKey: string; -}; - -const isAlarmRangeLegendState = (legendState: string): boolean => { - return ( - legendState.startsWith('alarm_range_') || - legendState.startsWith('large_eng_built_alarm_range_') || - legendState.startsWith('mid_eng_built_alarm_range_') - ); -}; - -const getPointRenderPriority = (point: Record): number => { - const layerKey = String(point.layerKey || point.type || '').toLowerCase(); - const legendState = String(point.anchoPointState || '').trim().toLowerCase(); - - if ( - layerKey.includes('eng_alarm_point') || - layerKey.includes('alarm_range') || - isAlarmRangeLegendState(legendState) - ) { - return 300; - } - - if ( - layerKey.includes('eng_point') || - legendState.startsWith('large_eng_') || - legendState.startsWith('mid_eng_') - ) { - return 200; - } - - return 100; -}; - -const normalizeText = (value: unknown): string => { - return String(value ?? '') - .trim() - .replace(/[()()]/g, '') - .toLowerCase(); -}; - -const toFiniteNumber = (value: unknown): number | null => { - if (value === '' || value === null || value === undefined) { - return null; - } - const numericValue = Number(value); - return Number.isFinite(numericValue) ? numericValue : null; -}; - -const haversineDistanceMeters = ( - lon1: number, - lat1: number, - lon2: number, - lat2: number -): number => { - const toRad = (degree: number) => (degree * Math.PI) / 180; - const earthRadius = 6371000; - const dLat = toRad(lat2 - lat1); - const dLon = toRad(lon2 - lon1); - const lat1Rad = toRad(lat1); - const lat2Rad = toRad(lat2); - - const a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1Rad) * - Math.cos(lat2Rad) * - Math.sin(dLon / 2) * - Math.sin(dLon / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return earthRadius * c; -}; - -const clearNearbyPointRuntimeMeta = (point: Record) => { - delete point._nearbyRule; - delete point._nearbyGroupId; - delete point._nearbyDisplayName; - delete point._nearbyGroupSize; - delete point._nearbyPriority; - delete point._nearbyShowZoomMin; - delete point._nearbyShowZoomMax; - delete point._nearbyReplaceAtZoom; - delete point._nearbyExpandAtZoom; - delete point._nearbyExpandOffsetPx; - delete point._nearbyExpandAngleDeg; - delete point._nearbyDisplayMode; - delete point._nearbyIsPrimary; -}; - -const getPointDisplayName = (point: Record): string => { - return ( - point.titleName || point.stnm || point.ennm || point.stcd || point._id || '' - ); -}; - -const getPointStableIdentity = ( - point: Record, - index: number -): string => { - return normalizeText( - point.stcd || - point._id || - point.titleName || - point.stnm || - point.ennm || - `point_${index}` - ); -}; - -const getGroupSortKey = (candidate: GroupCandidate): string => { - return [ - getPointStableIdentity(candidate.point, candidate.index), - normalizeText(candidate.point.layerKey), - String(candidate.index).padStart(6, '0') - ].join('|'); -}; - -const compareGroupCandidates = ( - left: GroupCandidate, - right: GroupCandidate -): number => { - const leftRenderPriority = getPointRenderPriority(left.point); - const rightRenderPriority = getPointRenderPriority(right.point); - if (leftRenderPriority !== rightRenderPriority) { - return rightRenderPriority - leftRenderPriority; - } - - const leftDensityValue = toFiniteNumber(left.point.distance); - const rightDensityValue = toFiniteNumber(right.point.distance); - - if ( - leftDensityValue !== null && - rightDensityValue !== null && - leftDensityValue !== rightDensityValue - ) { - return rightDensityValue - leftDensityValue; - } - - if (leftDensityValue !== null || rightDensityValue !== null) { - return leftDensityValue !== null ? -1 : 1; - } - - return getGroupSortKey(left).localeCompare(getGroupSortKey(right)); -}; - -const buildRuntimeMeta = ( - groupId: string, - groupSize: number, - priority: number, - displayName: string, - rule: NearbyPointAutoRule -): NearbyPointRuntimeMeta => { - return { - groupId, - displayName, - groupSize, - priority, - showZoomMin: null, - showZoomMax: null, - replaceAtZoom: rule.replaceAtZoom, - expandAtZoom: rule.expandAtZoom, - expandOffsetPx: rule.expandOffsetPx, - expandAngleDeg: 0, - displayMode: 'default' - }; -}; - -const buildGroupCandidates = ( - points: Record[] = [] -): GroupCandidate[] => { - const candidates: GroupCandidate[] = []; - - points.forEach((point, index) => { - const lon = toFiniteNumber(point.lgtd); - const lat = toFiniteNumber(point.lttd); - if (lon === null || lat === null) { - clearNearbyPointRuntimeMeta(point); - return; - } - if (Math.abs(lon) > 180 || Math.abs(lat) > 90) { - clearNearbyPointRuntimeMeta(point); - return; - } - - candidates.push({ - index, - point, - lon, - lat, - layerKey: String(point.layerKey || '') - }); - }); - - return candidates; -}; - -const buildNearbyGroups = ( - candidates: GroupCandidate[], - rule: NearbyPointAutoRule -): GroupCandidate[][] => { - const result: GroupCandidate[][] = []; - const sortedCandidates = [...candidates].sort(compareGroupCandidates); - const consumedIndexes = new Set(); - - sortedCandidates.forEach((seedCandidate, seedIndex) => { - if (consumedIndexes.has(seedIndex)) { - return; - } - - const groupItems: GroupCandidate[] = [seedCandidate]; - const groupIndexes = [seedIndex]; - - for ( - let candidateIndex = seedIndex + 1; - candidateIndex < sortedCandidates.length; - candidateIndex += 1 - ) { - if (consumedIndexes.has(candidateIndex)) { - continue; - } - - const currentCandidate = sortedCandidates[candidateIndex]; - const distanceMeters = haversineDistanceMeters( - seedCandidate.lon, - seedCandidate.lat, - currentCandidate.lon, - currentCandidate.lat - ); - - if (distanceMeters <= rule.distanceThresholdMeters) { - groupItems.push(currentCandidate); - groupIndexes.push(candidateIndex); - } - } - - if (groupItems.length >= rule.minGroupSize) { - groupIndexes.forEach(index => consumedIndexes.add(index)); - result.push(groupItems); - } - }); - - return result; -}; - -// 备注:统一按空间距离自动识别近邻点组,不按名称或点类型分规则。 -export const attachNearbyPointRuntimeMeta = ( - points: Record[] = [], - rule: NearbyPointAutoRule = getNearbyPointAutoRule() -): Record[] => { - if (!Array.isArray(points) || points.length === 0) { - return []; - } - - points.forEach(point => clearNearbyPointRuntimeMeta(point)); - - const candidates = buildGroupCandidates(points); - if (candidates.length < rule.minGroupSize) { - return points; - } - const groupedCandidates = buildNearbyGroups(candidates, rule); - - let groupSeed = 0; - groupedCandidates.forEach(groupItems => { - if (groupItems.length < rule.minGroupSize) { - return; - } - - const sortedItems = [...groupItems].sort(compareGroupCandidates); - groupSeed += 1; - const groupId = `nearby-group-${groupSeed}`; - - sortedItems.forEach((candidate, orderIndex) => { - const priority = orderIndex + 1; - const runtimeMeta = buildRuntimeMeta( - groupId, - sortedItems.length, - priority, - getPointDisplayName(candidate.point), - rule - ); - - candidate.point._nearbyRule = runtimeMeta; - candidate.point._nearbyGroupId = runtimeMeta.groupId; - candidate.point._nearbyDisplayName = runtimeMeta.displayName; - candidate.point._nearbyGroupSize = runtimeMeta.groupSize; - candidate.point._nearbyPriority = runtimeMeta.priority; - candidate.point._nearbyShowZoomMin = runtimeMeta.showZoomMin; - candidate.point._nearbyShowZoomMax = runtimeMeta.showZoomMax; - candidate.point._nearbyReplaceAtZoom = runtimeMeta.replaceAtZoom; - candidate.point._nearbyExpandAtZoom = runtimeMeta.expandAtZoom; - candidate.point._nearbyExpandOffsetPx = runtimeMeta.expandOffsetPx; - candidate.point._nearbyExpandAngleDeg = runtimeMeta.expandAngleDeg; - candidate.point._nearbyDisplayMode = runtimeMeta.displayMode; - candidate.point._nearbyIsPrimary = runtimeMeta.priority === 1; - }); - }); - - return points; -}; diff --git a/frontend-sjgl/src/modules/map/stores/map-config.store.ts b/frontend-sjgl/src/modules/map/stores/map-config.store.ts deleted file mode 100644 index 248d5a00..00000000 --- a/frontend-sjgl/src/modules/map/stores/map-config.store.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { defineStore } from 'pinia'; -import { ref } from 'vue'; -import { getMapList, getModuleMapLegendList } from '@/api/map'; - -type MapConfigLoadOptions = { - systemId: string; - moduleId: string; - pageKey?: string; - description?: string; -}; - -type LegendConfigLoadOptions = { - includeGlobal?: boolean; -}; - -const cloneConfigData = (data: T): T => { - return JSON.parse(JSON.stringify(data)); -}; - -export const useMapConfigStore = defineStore('map-config', () => { - const layerConfigTree = ref([]); - const layerConfigByKey = ref>({}); - const legendConfigOriginal = ref([]); - const legendConfigByNameEn = ref>({}); - const legendConfigByLayerCode = ref>({}); - const pageLegendConfig = ref([]); - const configLoading = ref(false); - const legendLoading = ref(false); - const lastLoadOptions = ref(null); - - const normalizeLegendNameEn = (nameEn?: string): string => { - if (!nameEn) return ''; - return nameEn; - }; - - // 备注:重建图层配置索引,后续按 key 读取图层配置时不再全量递归。 - const rebuildLayerConfigIndex = (items: any[] = []) => { - const nextIndex: Record = {}; - - const walk = (nodes: any[] = []) => { - nodes.forEach(item => { - if (item?.key) { - nextIndex[item.key] = item; - } - if (item?.children?.length > 0) { - walk(item.children); - } - }); - }; - - walk(items); - layerConfigByKey.value = nextIndex; - }; - - // 备注:从图层配置中提取默认勾选项,作为页面初始化时的基础勾选状态。 - const extractCheckedLayerKeys = (items: any[] = []): string[] => { - const keys: string[] = []; - - const walk = (nodes: any[] = []) => { - nodes.forEach(item => { - if (Number(item?.checked) === 1 && item?.key) { - keys.push(item.key); - } - if (item?.children?.length > 0) { - walk(item.children); - } - }); - }; - - walk(items); - return keys; - }; - - // 备注:重建图例配置索引,分别支持按 nameEn 和 layerCode 查询图例配置。 - const rebuildLegendConfigIndexes = (items: any[] = []) => { - const nextNameEnMap: Record = {}; - const nextLayerCodeMap: Record = {}; - - const walk = (nodes: any[] = []) => { - nodes.forEach(item => { - if (item?.childrenList?.length > 0) { - walk(item.childrenList); - return; - } - - const normalizedNameEn = normalizeLegendNameEn(item?.nameEn); - if (normalizedNameEn) { - nextNameEnMap[normalizedNameEn] = item; - } - - if (item?.layerCode) { - if (!nextLayerCodeMap[item.layerCode]) { - nextLayerCodeMap[item.layerCode] = []; - } - nextLayerCodeMap[item.layerCode].push(item); - } - }); - }; - - walk(items); - legendConfigByNameEn.value = nextNameEnMap; - legendConfigByLayerCode.value = nextLayerCodeMap; - }; - - // 备注:设置图层树配置,并同步初始化图层索引与默认勾选状态。 - const setLayerConfigTree = (data: any[] = []) => { - const nextData = cloneConfigData(data); - layerConfigTree.value = nextData; - rebuildLayerConfigIndex(nextData); - }; - - // 备注:设置全量原始图例配置,并同步构建图例索引。 - const setLegendConfigOriginal = (data: any[] = []) => { - const nextData = cloneConfigData(data); - legendConfigOriginal.value = nextData; - rebuildLegendConfigIndexes(nextData); - }; - - // 备注:设置页面级图例配置,仅保留后端返回的页面图例结构,不在这里做运行态加工。 - const setPageLegendConfig = (data: any[] = []) => { - pageLegendConfig.value = cloneConfigData(data); - }; - - // 备注:按图层 key 获取图层配置,供后续编排层和运行态 store 复用。 - const getLayerConfigByKey = (layerKey: string) => { - return layerConfigByKey.value[layerKey]; - }; - - // 备注:按图层 layerCode 获取原始图例项列表,供图层勾选后派生图例使用。 - const getLegendConfigByLayerCode = (layerCode: string): any[] => { - return legendConfigByLayerCode.value[layerCode] || []; - }; - - // 备注:按图例 nameEn 获取原始图例项,供运行态读取默认 checked 配置。 - const getLegendConfigByNameEn = (nameEn: string) => { - const normalizedNameEn = normalizeLegendNameEn(nameEn); - return legendConfigByNameEn.value[normalizedNameEn]; - }; - - // 备注:统一清理配置 store,便于菜单切换或地图销毁后重置配置状态。 - const clearConfigState = () => { - layerConfigTree.value = []; - layerConfigByKey.value = {}; - legendConfigOriginal.value = []; - legendConfigByNameEn.value = {}; - legendConfigByLayerCode.value = {}; - pageLegendConfig.value = []; - configLoading.value = false; - legendLoading.value = false; - lastLoadOptions.value = null; - }; - - const loadPageLayerConfig = async (options: MapConfigLoadOptions) => { - configLoading.value = true; - lastLoadOptions.value = { ...options }; - - try { - const layerRes = await getMapList({ - systemId: options.systemId, - moduleId: options.moduleId, - description: options.description ?? 'true' - }); - - const layerConfig = layerRes?.data?.mapLayerVos || []; - setLayerConfigTree(layerConfig); - return { - layerConfig - }; - } finally { - configLoading.value = false; - } - }; - - const loadPageLegendConfig = async ( - pageKey?: string, - options: LegendConfigLoadOptions = {} - ) => { - legendLoading.value = true; - - try { - const includeGlobal = options.includeGlobal !== false; - - let legendOriginal = legendConfigOriginal.value || []; - let pageLegend: any[] = []; - - if (includeGlobal) { - const [legendAllRes, legendPageRes] = await Promise.all([ - getModuleMapLegendList(), - getModuleMapLegendList(pageKey ? { moduleId: pageKey } : undefined) - ]); - - legendOriginal = legendAllRes?.data || []; - pageLegend = legendPageRes?.data || []; - } else { - const legendPageRes = await getModuleMapLegendList( - pageKey ? { moduleId: pageKey } : undefined - ); - pageLegend = legendPageRes?.data || []; - } - - setLegendConfigOriginal(legendOriginal); - setPageLegendConfig(pageLegend); - - return { - legendOriginal, - pageLegend - }; - } finally { - legendLoading.value = false; - } - }; - - // 备注:加载页面地图配置,只负责请求和保存配置数据,不处理锚点或地图渲染逻辑。 - const loadPageMapConfig = async (options: MapConfigLoadOptions) => { - const [{ layerConfig }, { legendOriginal, pageLegend }] = await Promise.all( - [loadPageLayerConfig(options), loadPageLegendConfig(options.pageKey)] - ); - - return { - layerConfig, - legendOriginal, - pageLegend - }; - }; - - return { - layerConfigTree, - layerConfigByKey, - legendConfigOriginal, - legendConfigByNameEn, - legendConfigByLayerCode, - pageLegendConfig, - configLoading, - legendLoading, - lastLoadOptions, - normalizeLegendNameEn, - rebuildLayerConfigIndex, - extractCheckedLayerKeys, - rebuildLegendConfigIndexes, - setLayerConfigTree, - setLegendConfigOriginal, - setPageLegendConfig, - getLayerConfigByKey, - getLegendConfigByLayerCode, - getLegendConfigByNameEn, - clearConfigState, - loadPageLayerConfig, - loadPageLegendConfig, - loadPageMapConfig - }; -}); diff --git a/frontend-sjgl/src/modules/map/stores/map-data.store.ts b/frontend-sjgl/src/modules/map/stores/map-data.store.ts deleted file mode 100644 index 1ec9658b..00000000 --- a/frontend-sjgl/src/modules/map/stores/map-data.store.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { defineStore } from 'pinia'; -import { ref } from 'vue'; - -type PointLayerCacheItem = { - checked: boolean; - data: any[]; - cacheKey?: string; -}; - -type LayerLoadStatus = { - loading: boolean; - loaded: boolean; - error: string | null; -}; - -export const useMapDataStore = defineStore('map-data', () => { - const pointData = ref([]); - const pointDataCache = ref>({}); - const layerLoadState = ref>({}); - const loading = ref(true); - - // 备注:统一设置地图模块整体 loading 状态,供图例和筛选组件复用。 - const setLoading = (value: boolean) => { - loading.value = value; - }; - - // 备注:整体清空锚点合并数据,通常用于菜单切换或重新初始化。 - const clearPointData = () => { - pointData.value = []; - }; - - // 备注:直接覆盖锚点合并数据,供地图加载完成后统一回写。 - const setPointData = (data: any[] = []) => { - pointData.value = data; - }; - - // 备注:读取指定图层缓存,供地图渲染逻辑优先命中缓存数据。 - const getPointLayerCache = (layerKey: string) => { - return pointDataCache.value[layerKey]; - }; - - // 备注:判断单个图层缓存是否与当前请求条件匹配,避免旧筛选结果直接复用。 - const hasValidPointLayerCache = (layerKey: string, cacheKey: string) => { - const cache = getPointLayerCache(layerKey); - if (!cache || !cacheKey) return false; - return cache.cacheKey === cacheKey && cache.data.length > 0; - }; - - // 备注:读取单个图层的缓存数据数组,未命中时返回空数组,简化外层判空逻辑。 - const getPointLayerData = (layerKey: string): any[] => { - return pointDataCache.value[layerKey]?.data || []; - }; - - // 备注:判断单个图层缓存是否已具备可用数据,供加载和显隐逻辑快速分支。 - const hasPointLayerData = (layerKey: string): boolean => { - return getPointLayerData(layerKey).length > 0; - }; - - // 备注:写入单个图层的锚点缓存,并保留当前是否勾选的状态。 - const setPointLayerCache = (layerKey: string, cache: PointLayerCacheItem) => { - if (!layerKey) return; - pointDataCache.value[layerKey] = cache; - }; - - // 备注:只更新缓存中的勾选状态,避免重复覆盖整份锚点数据。 - const setPointLayerCacheChecked = (layerKey: string, checked: boolean) => { - const cache = pointDataCache.value[layerKey]; - if (!cache) return; - setPointLayerCache(layerKey, { - ...cache, - checked - }); - }; - - // 备注:删除指定图层缓存,供时间筛选重载和图层强制刷新使用。 - const removePointLayerCache = (layerKey: string) => { - if (!pointDataCache.value[layerKey]) return; - const nextCache = { ...pointDataCache.value }; - delete nextCache[layerKey]; - pointDataCache.value = nextCache; - }; - - // 备注:清空全部图层缓存,供页面切换或地图销毁时复用。 - const clearPointLayerCache = () => { - pointDataCache.value = {}; - }; - - // 备注:根据当前缓存重建合并锚点数据,避免在多个地方手写拼装逻辑。 - const rebuildPointDataFromCache = (layerKeys: string[] = []) => { - const targetLayerKeys = - layerKeys.length > 0 ? layerKeys : Object.keys(pointDataCache.value); - const allPointData: any[] = []; - - targetLayerKeys.forEach(layerKey => { - const cache = pointDataCache.value[layerKey]; - if (!cache?.data?.length) return; - - cache.data.forEach((point: any) => { - if (point?.layerKey !== layerKey) { - point.layerKey = layerKey; - } - allPointData.push(point); - }); - }); - - pointData.value = allPointData; - return allPointData; - }; - - // 备注:统一初始化单个图层的加载状态,避免外层重复判空。 - const ensureLayerLoadState = (layerKey: string): LayerLoadStatus => { - if (!layerLoadState.value[layerKey]) { - layerLoadState.value[layerKey] = { - loading: false, - loaded: false, - error: null - }; - } - return layerLoadState.value[layerKey]; - }; - - // 备注:标记单个图层进入加载中状态。 - const setLayerLoading = (layerKey: string) => { - const current = ensureLayerLoadState(layerKey); - layerLoadState.value[layerKey] = { - ...current, - loading: true, - error: null - }; - }; - - // 备注:标记单个图层加载成功,供后续判断是否已完成首次加载。 - const setLayerLoaded = (layerKey: string) => { - const current = ensureLayerLoadState(layerKey); - layerLoadState.value[layerKey] = { - ...current, - loading: false, - loaded: true, - error: null - }; - }; - - // 备注:标记单个图层加载失败,并记录错误信息。 - const setLayerLoadError = (layerKey: string, error: unknown) => { - const current = ensureLayerLoadState(layerKey); - layerLoadState.value[layerKey] = { - ...current, - loading: false, - loaded: false, - error: error instanceof Error ? error.message : String(error) - }; - }; - - // 备注:清理指定图层的加载状态,供重载场景复用。 - const clearLayerLoadState = (layerKey: string) => { - if (!layerLoadState.value[layerKey]) return; - delete layerLoadState.value[layerKey]; - }; - - // 备注:整体重置数据缓存 store,供菜单切换或地图销毁时调用。 - const resetDataState = () => { - pointData.value = []; - pointDataCache.value = {}; - layerLoadState.value = {}; - loading.value = true; - }; - - return { - pointData, - pointDataCache, - layerLoadState, - loading, - setLoading, - clearPointData, - setPointData, - getPointLayerCache, - hasValidPointLayerCache, - getPointLayerData, - hasPointLayerData, - setPointLayerCache, - setPointLayerCacheChecked, - removePointLayerCache, - clearPointLayerCache, - rebuildPointDataFromCache, - ensureLayerLoadState, - setLayerLoading, - setLayerLoaded, - setLayerLoadError, - clearLayerLoadState, - resetDataState - }; -}); diff --git a/frontend-sjgl/src/modules/map/stores/map-view.store.ts b/frontend-sjgl/src/modules/map/stores/map-view.store.ts deleted file mode 100644 index c0c80452..00000000 --- a/frontend-sjgl/src/modules/map/stores/map-view.store.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { defineStore } from 'pinia'; -import { ref } from 'vue'; -import dayjs from 'dayjs'; - -export const useMapViewStore = defineStore('map-view', () => { - const checkedLayerKeys = ref([]); - const legendCheckedState = ref>({}); - const searchTimeRange = ref<[any, any]>([dayjs().subtract(1, 'M'), dayjs()]); - const selectedBaseId = ref(''); - const currentZoomLevel = ref(4.5); - - // 备注:统一写入当前选中的图层 key,始终保持去重后的结果。 - const setCheckedLayerKeys = (keys: string[] = []) => { - checkedLayerKeys.value = Array.from(new Set(keys.filter(Boolean))); - }; - - // 备注:获取当前选中的图层 key,供图层树、动态图层和筛选联动复用。 - const getCheckedLayerKeys = (): string[] => { - return checkedLayerKeys.value; - }; - - // 备注:覆盖写入完整图例运行态勾选状态,适合图例初始化和整批替换。 - const setLegendCheckedState = (state: Record = {}) => { - legendCheckedState.value = { ...state }; - }; - - // 备注:写入单个图例运行态勾选状态,供点击图例项时复用。 - const setLegendChecked = (nameEn: string, checked: number) => { - if (!nameEn) return; - legendCheckedState.value = { - ...legendCheckedState.value, - [nameEn]: checked - }; - }; - - // 备注:批量写入多个图例运行态勾选状态,供分组图例和筛选联动复用。 - const setLegendCheckedBatch = ( - stateMap: Record = {} - ) => { - legendCheckedState.value = { - ...legendCheckedState.value, - ...stateMap - }; - }; - - // 备注:获取单个图例当前运行态勾选状态,未命中时默认返回 0。 - const getLegendChecked = (nameEn: string): number => { - return legendCheckedState.value[nameEn] ?? 0; - }; - - // 备注:统一更新当前时间筛选范围,供筛选组件和地图数据重载复用。 - const setSearchTimeRange = (range: [any, any]) => { - searchTimeRange.value = range; - }; - - // 备注:统一更新当前基地 ID,供地图筛选和基地联动复用。 - const setSelectedBaseId = (baseId: string) => { - selectedBaseId.value = baseId || ''; - }; - - // 备注:统一更新当前地图缩放级别,供动态图层联动复用。 - const setCurrentZoomLevel = (zoom: number) => { - currentZoomLevel.value = zoom; - }; - - // 备注:在菜单切换或地图销毁时重置运行态到默认值。 - const resetViewState = () => { - checkedLayerKeys.value = []; - legendCheckedState.value = {}; - searchTimeRange.value = [dayjs().subtract(1, 'M'), dayjs()]; - selectedBaseId.value = ''; - currentZoomLevel.value = 4.5; - }; - - return { - checkedLayerKeys, - legendCheckedState, - searchTimeRange, - selectedBaseId, - currentZoomLevel, - setCheckedLayerKeys, - getCheckedLayerKeys, - setLegendCheckedState, - setLegendChecked, - setLegendCheckedBatch, - getLegendChecked, - setSearchTimeRange, - setSelectedBaseId, - setCurrentZoomLevel, - resetViewState - }; -}); diff --git a/frontend-sjgl/src/store/modules/map.ts b/frontend-sjgl/src/store/modules/map.ts deleted file mode 100644 index 99cbd6c0..00000000 --- a/frontend-sjgl/src/store/modules/map.ts +++ /dev/null @@ -1,1490 +0,0 @@ -import { defineStore, storeToRefs } from 'pinia'; -import { ref } from 'vue'; -import dayjs from 'dayjs'; -import { MapClass } from '@/components/gis/map.class'; -import { getMapConfig } from '@/components/gis/gisUtils'; -import { applyLayerMutualExclusionRules } from '@/modules/map/domain/map-layer-rules'; -import { - applyEnvFacilityLegendRule, - buildLegendCheckedState, - buildLegendTree -} from '@/modules/map/domain/legend-deriver'; -import { attachNearbyPointRuntimeMeta } from '@/modules/map/domain/nearby-point-rules'; -import { useMapConfigStore } from '@/modules/map/stores/map-config.store'; -import { useMapDataStore } from '@/modules/map/stores/map-data.store'; -import { useMapViewStore } from '@/modules/map/stores/map-view.store'; -import request from '@/utils/request'; -const mapClass = MapClass.getInstance(); -const ENG_POINT_LAYER_KEY = 'eng_point'; -const ENG_ALARM_POINT_LAYER_KEY = 'eng_alarm_point'; -const YLFB_POINT_LAYER_KEY = 'ylfb_point'; -const ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5; -const ENG_POINT_LARGE_STATES = [ - 'large_eng_built', - 'large_eng_ubuilt', - 'large_eng_nbuilt' -]; -const ENG_POINT_MEDIUM_STATES = [ - 'mid_eng_built', - 'mid_eng_ubuilt', - 'mid_eng_nbuilt' -]; - -const isEngPointRuntimeItem = (item: any): boolean => { - const runtimeLayerKey = String( - item?.layerKey || item?.type || item?._layerKey || '' - ).toLowerCase(); - const sttpMap = String( - item?._sttpMap || item?.sttpMap || item?.popupSttpMap || '' - ).toUpperCase(); - const sttpCode = String(item?.sttpCode || item?.sttp || '').toUpperCase(); - - if (runtimeLayerKey) { - return runtimeLayerKey.includes(ENG_POINT_LAYER_KEY); - } - - return sttpMap === 'ENG' || sttpMap === 'ENG2' || sttpCode === 'ENG'; -}; - -const normalizeCachePayload = (value: any): any => { - if (Array.isArray(value)) { - return value.map(item => normalizeCachePayload(item)); - } - - if (value && typeof value === 'object') { - return Object.keys(value) - .sort() - .reduce>((acc, key) => { - acc[key] = normalizeCachePayload(value[key]); - return acc; - }, {}); - } - - return value; -}; - -const isCanceledRequestError = (error: unknown) => { - const message = error instanceof Error ? error.message : String(error || ''); - return ( - message.includes('canceled') || - message.includes('aborted') || - message.includes('AbortError') - ); -}; - -const buildFiltersFromParamsObject = ( - paramsObject: Record = {} -) => { - const filtersArray: any[] = []; - - Object.keys(paramsObject).forEach(paramKey => { - const value = paramsObject[paramKey]; - - if (Array.isArray(value)) { - value.forEach((filter: any) => { - filtersArray.push({ - ...filter, - dataType: filter?.dataType || 'string' - }); - }); - return; - } - - filtersArray.push({ - field: paramKey, - operator: 'eq', - dataType: 'string', - value - }); - }); - - return filtersArray; -}; - -const normalizeRequestParams = (rawParams: any) => { - if (!rawParams || !Object.keys(rawParams).length) { - return { logic: 'and', filters: [] }; - } - - if (Array.isArray(rawParams.filters)) { - return { - logic: rawParams.logic || 'and', - filters: rawParams.filters.map((filter: any) => ({ - ...filter, - dataType: filter?.dataType || 'string' - })) - }; - } - - return { - logic: 'and', - filters: buildFiltersFromParamsObject(rawParams) - }; -}; - -const parseAnchorParamJson = (anchorParamJson?: string) => { - const result: { filters: any[]; orders: any[] | null } = { - filters: [], - orders: null - }; - - if (!anchorParamJson) return result; - - try { - const parsed = JSON.parse(anchorParamJson); - if (!parsed || typeof parsed !== 'object') return result; - - // 解析 params -> filters(兼容旧格式:如果没有 params 字段,整个对象即为 params) - const paramsObj = parsed.params || parsed; - if ( - paramsObj && - typeof paramsObj === 'object' && - !Array.isArray(paramsObj) - ) { - result.filters = buildFiltersFromParamsObject(paramsObj); - } - - // 解析 orders -> sort 数组 - if (parsed.orders) { - let ordersObj: Record = {}; - if (typeof parsed.orders === 'string') { - ordersObj = JSON.parse(parsed.orders); - } else if (typeof parsed.orders === 'object') { - ordersObj = parsed.orders; - } - if (ordersObj && typeof ordersObj === 'object') { - result.orders = Object.entries(ordersObj).map(([field, dir]) => ({ - field, - dir - })); - } - } - - return result; - } catch (error) { - console.error('解析 anchorParamJson 失败:', error); - return result; - } -}; - -export const useMapStore = defineStore('map', () => { - const mapConfigStore = useMapConfigStore(); - const mapDataStore = useMapDataStore(); - const mapViewStore = useMapViewStore(); - const { - getLayerConfigByKey, - getLegendConfigByLayerCode, - getLegendConfigByNameEn - } = mapConfigStore; - const { hasPointLayerData, getPointLayerData } = mapDataStore; - const { - layerConfigTree: layerData, - legendConfigOriginal: legendDataOriginal, - pageLegendConfig - } = storeToRefs(mapConfigStore); - const { checkedLayerKeys, legendCheckedState, searchTimeRange } = - storeToRefs(mapViewStore); - const { pointData, pointDataCache, layerLoadState, loading } = - storeToRefs(mapDataStore); - - // 图例数据(经过筛选和排序的) - const legendData = ref([]); - // 选中的图例数据 - const legendDataSelected = ref([]); - const inFlightRequestMap = new Map< - string, - { - promise: Promise; - controller: AbortController; - layerKey: string; - sessionId: number; - } - >(); - const activeLayerRequestKeyMap = new Map(); - const backgroundPointLayerCache = new Map< - string, - { - checked: boolean; - data: any[]; - cacheKey?: string; - } - >(); - let loadSessionSeed = 0; - let activePageRenderToken = 0; - let activePageKey = ''; - let activePageLayerKeys = new Set(); - let activeLoadingPageToken: number | null = null; - let pendingPageNavigationKey = ''; - - const normalizeLegendNameEn = mapConfigStore.normalizeLegendNameEn; - - const filterPointLayerDataForDisplay = ( - layerKey: string, - sourceData: any[] = [], - currentZoom: number = mapViewStore.currentZoomLevel - ): any[] => { - if (layerKey !== ENG_POINT_LAYER_KEY || !Array.isArray(sourceData)) { - return sourceData; - } - - const allowMedium = currentZoom > ENG_POINT_MEDIUM_VISIBLE_ZOOM; - return sourceData.filter((item: any) => { - if (!isEngPointRuntimeItem(item)) { - return true; - } - - const state = String(item?.anchoPointState || ''); - if (ENG_POINT_LARGE_STATES.includes(state)) { - return true; - } - if (ENG_POINT_MEDIUM_STATES.includes(state)) { - return allowMedium; - } - return true; - }); - }; - - const refreshPointLayerDisplayData = (layerKey: string) => { - if (!layerKey) return; - - const layerItem = findLayerByKey(layerData.value, layerKey); - syncBackgroundCacheToReactiveStore(layerKey); - const rawData = - Array.isArray(layerItem?.data) && layerItem.data.length > 0 - ? layerItem.data - : getEffectivePointLayerData(layerKey); - const displayData = filterPointLayerDataForDisplay(layerKey, rawData); - const shouldRestoreVisible = - layerItem?.checked === 1 || checkedLayerKeys.value.includes(layerKey); - - mapClass.addInitDataLayer(displayData, layerKey); - if (!shouldRestoreVisible) { - mapClass.mdLayerTreeShowOrHidden(layerKey, false); - return; - } - restoreLayerLegendVisibility(layerKey); - mapClass.mdLayerTreeShowOrHidden(layerKey, true); - }; - - const getLegendItemsByLayerCode = (layerCode: string): any[] => { - return getLegendConfigByLayerCode(layerCode); - }; - - const getLegendChecked = (nameEn?: string): number => { - const normalizedNameEn = normalizeLegendNameEn(nameEn); - if (!normalizedNameEn) return 0; - return mapViewStore.getLegendChecked(normalizedNameEn); - }; - - const restoreLayerLegendVisibility = (layerKey: string): boolean => { - const layerLegendItems = getLegendItemsByLayerCode(layerKey); - if ( - legendDataOriginal.value.length === 0 || - layerLegendItems.length === 0 - ) { - return false; - } - - mapClass.setLegendPointVisible(layerKey, '', false); - layerLegendItems.forEach((legendItem: any) => { - if (getLegendChecked(legendItem.nameEn) === 1) { - applyLegendItemVisibility(legendItem, 1); - } - }); - - return true; - }; - - const buildRuntimeLegendTree = ( - items: any[] = [], - selectedLayerCodes?: Set - ): any[] => { - return buildLegendTree({ - items, - selectedLayerCodes, - getLegendChecked, - normalizeLegendNameEn - }); - }; - - const deriveSelectedLegendData = (layerKeys: string[]): any[] => { - const selectedLayerCodes = new Set( - layerKeys.filter( - key => - key && - key !== '-' && - key !== 'customBaseLayer' && - key !== 'powerBaseStation' - ) - ); - - let derivedLegend = buildRuntimeLegendTree( - legendDataOriginal.value, - selectedLayerCodes - ); - derivedLegend = applyEnvFacilityLegendRule( - derivedLegend, - Array.from(selectedLayerCodes), - legendDataOriginal.value, - items => buildRuntimeLegendTree(items) - ); - return sortByOrderIndex(derivedLegend); - }; - - const rebuildLegendRuntimeData = ( - layerKeys: string[] = checkedLayerKeys.value - ) => { - legendData.value = sortByOrderIndex( - buildRuntimeLegendTree(legendDataOriginal.value) - ); - legendDataSelected.value = deriveSelectedLegendData(layerKeys); - }; - - const getLayerBranchKeys = (rootKey: string): string[] => { - const rootLayer = getLayerConfigByKey(rootKey); - if (!rootLayer) return []; - - const branchKeys: string[] = []; - const walk = (node: any) => { - if (node?.key) { - branchKeys.push(node.key); - } - if (node?.children?.length > 0) { - node.children.forEach((child: any) => walk(child)); - } - }; - - walk(rootLayer); - return branchKeys; - }; - - const normalizeCheckedLayerKeys = ( - rawKeys: string[] = [], - triggerKey?: string, - isChecked?: boolean - ): string[] => { - return applyLayerMutualExclusionRules({ - rawKeys, - previousKeys: checkedLayerKeys.value, - triggerKey, - checked: isChecked, - getLayerBranchKeys - }); - }; - - const getRuntimeCheckedLayerKeys = (): string[] => { - return mapViewStore.getCheckedLayerKeys(); - }; - - const shouldSyncMergedPointData = (layerKey: string, pageToken?: number) => { - if (!layerKey) return false; - return ( - shouldApplyToActivePage(layerKey, pageToken) || - getRuntimeCheckedLayerKeys().includes(layerKey) - ); - }; - - const getEffectivePointLayerCache = (layerKey: string) => { - const reactiveCache = mapDataStore.getPointLayerCache(layerKey); - if (reactiveCache) { - return reactiveCache; - } - return backgroundPointLayerCache.get(layerKey); - }; - - const getEffectivePointLayerData = (layerKey: string): any[] => { - const reactiveCache = mapDataStore.getPointLayerCache(layerKey); - if (reactiveCache?.data) { - return reactiveCache.data; - } - return getEffectivePointLayerCache(layerKey)?.data || []; - }; - - const hasEffectivePointLayerCache = (layerKey: string, cacheKey: string) => { - const cache = getEffectivePointLayerCache(layerKey); - if (!cache || !cacheKey) return false; - return cache.cacheKey === cacheKey && Array.isArray(cache.data); - }; - - const setBackgroundPointLayerCache = ( - layerKey: string, - cache: { - checked: boolean; - data: any[]; - cacheKey?: string; - } - ) => { - if (!layerKey) return; - backgroundPointLayerCache.set(layerKey, cache); - }; - - const syncBackgroundCacheToReactiveStore = (layerKey: string) => { - const cache = backgroundPointLayerCache.get(layerKey); - if (!cache) return; - mapDataStore.setPointLayerCache(layerKey, cache); - backgroundPointLayerCache.delete(layerKey); - }; - - const syncPointDataForFilter = (layerKeys: string[] = []) => { - mapDataStore.rebuildPointDataFromCache(layerKeys); - }; - - const normalizePointLayerItems = (list: any[] = []) => { - return list - .map((item: any) => { - const iconType = item.anchoPointState; - const legendConfig = getLegendConfigByNameEn(iconType); - return { - ...item, - iconCode: item.iconCode || legendConfig?.icon || '', - code: item.code || legendConfig?.code || '', - tm: - item.sttpMap === 'WQ_ALARM' - ? item?.warnDataList?.[0]?.tm - : item?.tm, - _id: - item._id || `${item.sttpMap || iconType || 'point'}_${item.stcd}`, - layerKey: item.layerKey || YLFB_POINT_LAYER_KEY - }; - }) - .filter( - (item: any) => - (item.iconCode || item.code == 'colorLayer') && - !(item?.baseId == 'all' && item?.anchoPointState?.endsWith('_nbuilt')) - ); - }; - - const ensureGISLayerConfig = (layerItem: any) => { - if (!layerItem) return null; - - if (!layerItem.config && layerItem.paramJson) { - try { - const jsonObj = JSON.parse(layerItem.paramJson); - layerItem.config = getMapConfig(jsonObj); - } catch { - layerItem.config = null; - } - } - - if (!layerItem.config) { - return null; - } - - if (layerItem.config.key !== layerItem.key) { - layerItem.config = { - ...layerItem.config, - key: layerItem.key - }; - } - - return layerItem.config; - }; - - /** - * 设置图层数据 - */ - const setLayerData = (data: any[]) => { - mapConfigStore.setLayerConfigTree(data); - const nextCheckedLayerKeys = mapConfigStore.extractCheckedLayerKeys(data); - mapViewStore.setCheckedLayerKeys(nextCheckedLayerKeys); - rebuildLegendRuntimeData(nextCheckedLayerKeys); - }; - - /** - * 按照 orderIndex 排序图例数据(递归) - */ - const sortByOrderIndex = (items: any[]): any[] => { - return items - .map(item => { - if (item.childrenList && item.childrenList.length > 0) { - return { - ...item, - childrenList: sortByOrderIndex(item.childrenList) - }; - } - return item; - }) - .sort((a, b) => (a.orderIndex || 0) - (b.orderIndex || 0)); - }; - - /** - * 启动新的图层加载会话,供需要严格按最新查询条件收口的场景复用。 - */ - const startLoadSession = () => { - loadSessionSeed += 1; - return loadSessionSeed; - }; - - /** - * 判断当前请求是否仍属于最新加载会话,避免旧请求结果回写到最新页面。 - */ - const isCurrentLoadSession = (sessionId: number) => { - return loadSessionSeed === sessionId; - }; - - const activatePageContext = (pageKey: string, items: any[] = []) => { - activePageKey = pageKey; - pendingPageNavigationKey = ''; - activePageRenderToken += 1; - activePageLayerKeys = new Set(getAllLayerKeys(items)); - return activePageRenderToken; - }; - - const markPendingPageNavigation = (targetPageKey: string) => { - pendingPageNavigationKey = targetPageKey || '__route-change__'; - }; - - const hasPendingPageNavigationAwayFrom = (pageKey: string) => { - return !!pendingPageNavigationKey && pendingPageNavigationKey !== pageKey; - }; - - const shouldApplyToActivePage = (layerKey: string, pageToken?: number) => { - if (!layerKey) return false; - if (activePageKey && hasPendingPageNavigationAwayFrom(activePageKey)) { - return false; - } - if (pageToken === undefined) return true; - return ( - activePageRenderToken === pageToken && activePageLayerKeys.has(layerKey) - ); - }; - - const isActivePageToken = (pageToken?: number) => { - if (pageToken === undefined) return true; - return activePageRenderToken === pageToken; - }; - - const beginPageLoading = (pageToken?: number) => { - if (typeof pageToken === 'number') { - activeLoadingPageToken = pageToken; - } - mapDataStore.setLoading(true); - }; - - const finishPageLoading = (pageToken?: number) => { - if (typeof pageToken !== 'number') { - mapDataStore.setLoading(false); - return; - } - - if ( - activeLoadingPageToken === pageToken && - activePageRenderToken === pageToken - ) { - activeLoadingPageToken = null; - mapDataStore.setLoading(false); - } - }; - - /** - * 设置图例数据(过滤掉 name == '地图' 的项,ifShow == 0 时不显示) - * @param data - 全部图例数据 - * @param selectedData - 当前页面选中的图例数据(用于标记选中状态) - */ - const setLegendData = (data: any[], selectedData: any[] = []) => { - mapConfigStore.setLegendConfigOriginal(data); - mapConfigStore.setPageLegendConfig(selectedData); - mapViewStore.setLegendCheckedState( - buildLegendCheckedState(legendDataOriginal.value, normalizeLegendNameEn) - ); - rebuildLegendRuntimeData(checkedLayerKeys.value); - }; - - /** - * 设置选中的图例数据(在图层加载完成后调用) - */ - const setSelectedLegendData = () => { - rebuildLegendRuntimeData(checkedLayerKeys.value); - }; - - /** - * 设置描点数据 - */ - const setPointData = (data: any[]) => { - mapDataStore.setPointData(data); - }; - - const replacePointLayerData = ( - layerKey: string, - data: any[] = [], - visible = true - ) => { - if (!layerKey) return; - - const layerItem = findLayerByKey(layerData.value, layerKey); - const normalizedData = normalizePointLayerItems( - data.map((item: any) => ({ - ...item, - layerKey - })) - ); - - mapDataStore.setPointLayerCache(layerKey, { - checked: visible, - data: normalizedData, - cacheKey: `custom:${layerKey}:${normalizedData.length}` - }); - mapDataStore.rebuildPointDataFromCache(); - - if (layerItem) { - layerItem.data = normalizedData; - layerItem.checked = visible ? 1 : 0; - } - - if (!visible || normalizedData.length === 0) { - if (mapClass.hasLayer(layerKey)) { - mapClass.mdLayerTreeShowOrHidden(layerKey, false); - } - return; - } - - refreshPointLayerDisplayData(layerKey); - }; - - const applyLoadedPointLayerToMap = ( - layer: any, - layerKey: string, - list: any[] = [], - pageToken?: number - ) => { - if (!Array.isArray(list)) { - return; - } - - layer.data = list; - const isLayerChecked = layer.checked === 1; - - if (list.length === 0 || !shouldApplyToActivePage(layerKey, pageToken)) { - return; - } - - const displayData = filterPointLayerDataForDisplay(layerKey, list); - mapClass.addInitDataLayer(displayData, layerKey); - if (isLayerChecked) { - restoreLayerLegendVisibility(layerKey); - } - mapClass.mdLayerTreeShowOrHidden(layerKey, isLayerChecked); - }; - - /** - * 图例数据转对象 - */ - const legendData2Obj = (data: any[]): Record => { - const _tempData: Record = {}; - const f = (_data: any[]) => { - _data.forEach(item => { - if (item?.childrenList && item.childrenList?.length > 0) { - f(item.childrenList); - } else { - const normalizedNameEn = normalizeLegendNameEn(item.nameEn); - if (normalizedNameEn) { - _tempData[normalizedNameEn] = item; - } - } - }); - }; - f(data); - return _tempData; - }; - - /** - * 处理单个 pointMap 图层的显示/隐藏 - * 数据已在初始化时通过 loadLayerData 加载并存入 layer.data - */ - const handlePointMapLayer = async (layer: any, isChecked: boolean) => { - const { key } = layer; - - // 如果没有勾选,隐藏图层 - if (!isChecked) { - if (mapClass.hasLayer(key)) { - mapClass.mdLayerTreeShowOrHidden(key, false); - } - if (mapDataStore.getPointLayerCache(key)) { - mapDataStore.setPointLayerCacheChecked(key, false); - } - if (layer) { - layer.checked = 0; - } - return; - } - - // 使用初始化时已加载的数据(存入 layer.data) - const cachedData = - Array.isArray(layer?.data) && layer.data.length > 0 - ? layer.data - : getPointLayerData(key); - if (cachedData && cachedData.length > 0) { - layer.checked = 1; - const displayData = filterPointLayerDataForDisplay(key, cachedData); - mapClass.addInitDataLayer(displayData, key); - restoreLayerLegendVisibility(key); - if (mapDataStore.getPointLayerCache(key)) { - mapDataStore.setPointLayerCacheChecked(key, true); - } - } else { - console.warn(`图层 ${key} 没有缓存数据`); - } - }; - - /** - * 更新图层数据(联动图例和描点) - * @param checkKeys - 选中的图层 key 列表 - * @param isInit - 是否是初始化阶段(初始化时遍历所有图层) - */ - const updateLayerData = async (checkKeys: string[], isInit = false) => { - if (!isInit) { - checkKeys = normalizeCheckedLayerKeys(checkKeys); - } - - mapViewStore.setCheckedLayerKeys(checkKeys); - - // 收集变化的图层 key(新增选中 / 取消选中) - const newlyChecked: string[] = []; - const newlyUnchecked: string[] = []; - - // 更新图层的 checked 状态,并记录变化 - const updateCheckedStatus = (items: any[]) => { - items.forEach(item => { - if (item.key) { - const wasChecked = item.checked === 1; - const isChecked = checkKeys.includes(item.key); - item.checked = isChecked ? 1 : 0; - if (wasChecked !== isChecked) { - if (isChecked) { - newlyChecked.push(item.key); - } else { - newlyUnchecked.push(item.key); - } - } - } - - if (item.children && item.children.length > 0) { - updateCheckedStatus(item.children); - } - }); - }; - updateCheckedStatus(layerData.value); - - rebuildLegendRuntimeData(checkKeys); - - // 控制锚点显示隐藏:初始化时处理全部,否则只处理变化的图层 - const keysToProcess = isInit - ? getAllLayerKeys(layerData.value) - : [...newlyChecked, ...newlyUnchecked]; - - for (const key of keysToProcess) { - const layerItem = findLayerByKey(layerData.value, key); - if (layerItem && layerItem.type === 'pointMap' && layerItem.url) { - const shouldBeVisible = checkKeys.includes(key); - await handlePointMapLayer(layerItem, shouldBeVisible); - if (shouldBeVisible) { - restoreLayerLegendVisibility(key); - mapClass.mdLayerTreeShowOrHidden(key, true); - } - } else if (layerItem && layerItem.type === 'GISMap') { - // 处理 GISMap 类型的图层:勾选时若地图实例中不存在,则先重新加载再控制显隐。 - if (key && key !== '-' && key !== 'powerBaseStation') { - const shouldBeVisible = checkKeys.includes(key); - const layerConfig = ensureGISLayerConfig(layerItem); - if ( - shouldBeVisible && - layerConfig && - !mapClass.hasBaseLayer(layerItem.key) - ) { - mapClass.addBaseDataLayer(layerConfig, true); - } - mapClass.controlBaseLayerTreeShowAndHidden( - key, - layerItem.key, - shouldBeVisible - ); - } - } - } - }; - - /** - * 加载所有包含URL的图层数据 - * @param items 图层数据 - * @param checkedKeys 选中的图层 keys(用于优先加载) - */ - const loadAllLayerData = async ( - items: any[], - checkedKeys: string[] = [], - options: { - pageToken?: number; - skipSessionCheck?: boolean; - pageKey?: string; - } = {} - ) => { - beginPageLoading(options.pageToken); - const currentSessionId = options.skipSessionCheck - ? undefined - : startLoadSession(); - const checkedTasks: Array<() => Promise> = []; - const uncheckedTasks: Array<() => Promise> = []; - const processedKeys = new Set(); - const debugStart = Date.now(); - - const processItems = (itemList: any[]) => { - itemList.forEach(item => { - if ( - (item.type === 'pointMap' || item.url) && - item.key && - item.url && - !processedKeys.has(item.key) - ) { - processedKeys.add(item.key); - const task = () => - loadLayerData(item, { - sessionId: currentSessionId, - pageToken: options.pageToken, - skipSessionCheck: options.skipSessionCheck, - pageKey: options.pageKey - }); - if (checkedKeys.includes(item.key)) { - checkedTasks.push(task); - } else { - uncheckedTasks.push(task); - } - } - - if (item.children && item.children.length > 0) { - processItems(item.children); - } - }); - }; - - processItems(items); - - try { - // 首页初始化:所有图层接口一次性并发下发,避免分批串行等待。 - const loadResults = await Promise.allSettled( - [...checkedTasks, ...uncheckedTasks].map(task => task()) - ); - const failedResults = loadResults.filter( - result => result.status === 'rejected' - ); - - if (failedResults.length > 0) { - console.warn( - '部分图层数据加载失败,但不会阻断其他图层:', - failedResults - ); - } - - if ( - (typeof currentSessionId === 'number' && - !isCurrentLoadSession(currentSessionId)) || - !isActivePageToken(options.pageToken) - ) { - return; - } - - if ( - options.pageKey && - hasPendingPageNavigationAwayFrom(options.pageKey) - ) { - return; - } - - // 确保所有图层的数据都已经缓存到 pointDataCache 和 layer.data - const allLayerKeys = getAllLayerKeys(items); - for (const key of allLayerKeys) { - const layer = findLayerByKey(items, key); - if (layer && getEffectivePointLayerData(key).length > 0) { - syncBackgroundCacheToReactiveStore(key); - layer.data = getEffectivePointLayerData(key); - } - } - - const allPointData = mapDataStore.rebuildPointDataFromCache(allLayerKeys); - attachNearbyPointRuntimeMeta(allPointData); - const runtimeCheckedKeys = getRuntimeCheckedLayerKeys(); - const treeCheckedKeys = mapConfigStore.extractCheckedLayerKeys( - layerData.value.length > 0 ? layerData.value : items - ); - const finalCheckedKeys = - runtimeCheckedKeys.length > 0 ? runtimeCheckedKeys : treeCheckedKeys; - - // 备注:近邻点元数据需要基于全量点位统一识别,识别完成后同步刷新各图层 Feature。 - for (const key of allLayerKeys) { - const layer = findLayerByKey(items, key); - if (!layer || !hasPointLayerData(key)) { - continue; - } - - const layerPoints = getPointLayerData(key); - const displayData = filterPointLayerDataForDisplay(key, layerPoints); - const cacheChecked = mapDataStore.getPointLayerCache(key)?.checked; - const shouldRestoreVisible = - typeof cacheChecked === 'boolean' - ? cacheChecked - : layer.checked === 1 || finalCheckedKeys.includes(key); - layer.data = layerPoints; - mapClass.addInitDataLayer(displayData, key); - if (shouldRestoreVisible) { - restoreLayerLegendVisibility(key); - } - mapClass.mdLayerTreeShowOrHidden(key, shouldRestoreVisible); - } - - // 设置选中的图例数据(确保图例在所有锚点加载完成后才显示) - setSelectedLegendData(); - - // 备注:初始化 loading 期间用户可能已经手动改过图层勾选,这里必须以最新运行态收尾, - // 不能再回放 load 启动瞬间的默认 checked 快照。 - await updateLayerData(finalCheckedKeys, true); - } finally { - const shouldFinishLoading = - (typeof currentSessionId !== 'number' || - isCurrentLoadSession(currentSessionId)) && - (options.pageToken === undefined || - activePageRenderToken === options.pageToken); - if (shouldFinishLoading) { - finishPageLoading(options.pageToken); - } - } - }; - - const loadCurrentPageLayerData = async ( - items: any[], - checkedKeys: string[] = [], - options: { - pageToken?: number; - skipSessionCheck?: boolean; - } = {} - ) => { - const checkedLayerSet = new Set(checkedKeys); - const tasks: Array<() => Promise> = []; - const processedKeys = new Set(); - - const processItems = (itemList: any[]) => { - itemList.forEach(item => { - if ( - item.type === 'pointMap' && - item.key && - item.url && - checkedLayerSet.has(item.key) && - !processedKeys.has(item.key) - ) { - processedKeys.add(item.key); - tasks.push(() => - loadLayerData(item, { - sessionId: options.skipSessionCheck ? undefined : loadSessionSeed, - pageToken: options.pageToken, - skipSessionCheck: options.skipSessionCheck ?? true - }) - ); - } - - if (item.children && item.children.length > 0) { - processItems(item.children); - } - }); - }; - - processItems(items); - - if (tasks.length === 0) { - finishPageLoading(options.pageToken); - return; - } - - beginPageLoading(options.pageToken); - - try { - // 菜单切换:仅当前页的勾选图层一次性并发下发,旧请求在后台继续运行。 - await Promise.allSettled(tasks.map(task => task())); - } finally { - if (isActivePageToken(options.pageToken)) { - finishPageLoading(options.pageToken); - } - } - }; - - /** - * 根据 key 查找图层 - */ - const findLayerByKey = (items: any[], key: string): any | null => { - void items; - if (!key) return null; - return getLayerConfigByKey(key) || null; - }; - - /** - * 获取所有图层 keys - */ - const getAllLayerKeys = (data: any[]): string[] => { - const keys: string[] = []; - const f = (arr: any[] = []) => { - arr.forEach((item: any) => { - if (item.key) { - keys.push(item.key); - } - if (item.children && item.children.length > 0) { - f(item.children); - } - }); - }; - f(data); - return keys; - }; - - const getRuntimeLegendNames = ( - layerKey: string, - nameEn: string - ): string[] => { - const normalizedNameEn = normalizeLegendNameEn(nameEn); - if (!normalizedNameEn) { - return []; - } - - if (normalizedNameEn.includes('alarm_range_')) { - const candidates: string[] = []; - if (layerKey === ENG_ALARM_POINT_LAYER_KEY) { - candidates.push(`mid_eng_built_${normalizedNameEn}`); - candidates.push(`large_eng_built_${normalizedNameEn}`); - } else { - candidates.push(`large_eng_built_${normalizedNameEn}`); - candidates.push(`mid_eng_built_${normalizedNameEn}`); - } - return candidates; - } - - return [normalizedNameEn]; - }; - - const syncLegendCheckedInTree = ( - items: any[], - nameEn: string, - checked: number - ) => { - items.forEach((item: any) => { - if (item.nameEn === nameEn) { - item.checked = checked; - } - if (item.childrenList && item.childrenList.length > 0) { - syncLegendCheckedInTree(item.childrenList, nameEn, checked); - } - }); - }; - - const applyLegendItemVisibility = (legendItem: any, checked: number) => { - if (!legendItem?.nameEn || !legendItem?.layerCode) { - return; - } - - const layerKey = legendItem.layerCode; - const layerItem = getLayerConfigByKey(layerKey); - const shouldBeVisible = checked === 1; - const runtimeLegendNames = getRuntimeLegendNames( - layerKey, - legendItem.nameEn - ); - - if (layerItem?.type === 'GISMap') { - mapClass.controlBaseLayerTreeShowAndHidden( - layerKey, - layerItem.config?.id || layerKey, - shouldBeVisible - ); - return; - } - - runtimeLegendNames.forEach(runtimeLegendName => { - mapClass.setLegendPointVisible( - layerKey, - runtimeLegendName, - shouldBeVisible - ); - }); - }; - - /** - * 更新图例选中状态 - */ - const updateLegendChecked = (nameEn: string, checked: number) => { - const normalizedNameEn = normalizeLegendNameEn(nameEn); - const legendItem = getLegendConfigByNameEn(normalizedNameEn); - if (!legendItem) { - return; - } - - mapViewStore.setLegendChecked(normalizedNameEn, checked); - syncLegendCheckedInTree(legendData.value, normalizedNameEn, checked); - syncLegendCheckedInTree( - legendDataSelected.value, - normalizedNameEn, - checked - ); - rebuildLegendRuntimeData(checkedLayerKeys.value); - applyLegendItemVisibility(legendItem, checked); - }; - - const updateLegendCheckedBatch = (nameEns: string[], checked: number) => { - const nextState = { ...legendCheckedState.value }; - const changedLegendItems: any[] = []; - - nameEns.forEach(nameEn => { - const normalizedNameEn = normalizeLegendNameEn(nameEn); - const legendItem = getLegendConfigByNameEn(normalizedNameEn); - if (!legendItem) return; - - nextState[normalizedNameEn] = checked; - changedLegendItems.push(legendItem); - }); - - mapViewStore.setLegendCheckedState(nextState); - rebuildLegendRuntimeData(checkedLayerKeys.value); - changedLegendItems.forEach(legendItem => - applyLegendItemVisibility(legendItem, checked) - ); - }; - - /** - * 生成请求标识符,用于避免重复请求 - */ - const getRequestIdentifier = (url: string, params: any): string => { - const paramString = JSON.stringify(normalizeCachePayload(params)); - return `${url}?${encodeURIComponent(paramString)}`; - }; - - /** - * 加载单个图层数据 - */ - const loadLayerData = async ( - layer: any, - options: { - sessionId?: number; - pageToken?: number; - skipSessionCheck?: boolean; - pageKey?: string; - } = {} - ) => { - const { - sessionId = loadSessionSeed, - pageToken, - skipSessionCheck = false, - pageKey - } = options; - const { key, url, params = {}, paramJson, anchorParamJson } = layer; - - // 没有URL,跳过 - if (!url) { - return []; - } - const requestUrl = url; - let requestParams: any = params; - let requestOrders: any = null; - - const layerKey = layer?.key || ''; - - const ylfbKeys = ['ylfb_point']; - const timeRangeLayerKeys = ['ef_point']; - - const yearTime = dayjs().subtract(1, 'years'); - - requestParams = normalizeRequestParams(requestParams); - - const anchorParamResult = parseAnchorParamJson(anchorParamJson); - if (anchorParamResult.filters.length) { - requestParams.filters.push(...anchorParamResult.filters); - } - if (anchorParamResult.orders) { - requestOrders = anchorParamResult.orders; - } - - if (timeRangeLayerKeys.includes(layerKey)) { - requestParams.filters.push({ - field: 'tm', - operator: 'gte', - dataType: 'date', - value: dayjs(searchTimeRange.value[0]).format('YYYY-MM-DD HH:mm:ss') - }); - requestParams.filters.push({ - field: 'tm', - operator: 'lte', - dataType: 'date', - value: dayjs(searchTimeRange.value[1]).format('YYYY-MM-DD 23:59:59') - }); - } - if (ylfbKeys.includes(layerKey)) { - if (yearTime) { - requestParams.filters.push({ - field: 'startTime', - operator: 'eq', - dataType: 'date', - value: dayjs(yearTime).startOf('year').format('YYYY-MM-DD 00:00:00') - }); - requestParams.filters.push({ - field: 'endTime', - operator: 'eq', - dataType: 'date', - value: dayjs(yearTime).endOf('year').format('YYYY-MM-DD 23:59:59') - }); - } - } - - if (!requestOrders && layer?.orders) { - try { - const ordersObj = JSON.parse(layer.orders); - const ordersArray = Object.entries(ordersObj).map(([field, dir]) => ({ - field, - dir - })); - requestOrders = ordersArray; - } catch (e) { - console.error('解析 orders 失败:', e); - } - } - - if (!Object.keys(requestParams).length && paramJson) { - try { - const jsonObj = JSON.parse(paramJson); - if (jsonObj.params) { - requestParams = jsonObj.params; - } - } catch (e) { - console.error('解析 paramJson 失败:', e); - } - } - const requestData: any = { - filter: requestParams - }; - if (requestOrders) { - requestData.sort = requestOrders; - } - - const requestIdentifier = getRequestIdentifier(requestUrl, { - layerKey: key, - requestData - }); - - if (hasEffectivePointLayerCache(key, requestIdentifier)) { - syncBackgroundCacheToReactiveStore(key); - const cachedList = getEffectivePointLayerData(key); - layer.data = cachedList; - if (shouldSyncMergedPointData(key, pageToken)) { - syncPointDataForFilter(getRuntimeCheckedLayerKeys()); - } - applyLoadedPointLayerToMap(layer, key, cachedList, pageToken); - return cachedList; - } - - activeLayerRequestKeyMap.set(key, requestIdentifier); - const existingRequest = inFlightRequestMap.get(requestIdentifier); - if (existingRequest) { - mapDataStore.setLayerLoading(key); - return existingRequest.promise.then(list => { - if (Array.isArray(list) && list.length > 0) { - if (shouldSyncMergedPointData(key, pageToken)) { - syncPointDataForFilter(getRuntimeCheckedLayerKeys()); - } - applyLoadedPointLayerToMap(layer, key, list, pageToken); - } - return list; - }); - } - - mapDataStore.setLayerLoading(key); - - const controller = new AbortController(); - - const requestPromise = (async () => { - try { - const response = await request({ - url: requestUrl, - method: 'post', - data: requestData, - signal: controller.signal - }); - const resData = response?.data || response?.data?.data || []; - - if ( - controller.signal.aborted || - (!skipSessionCheck && !isCurrentLoadSession(sessionId)) - ) { - return []; - } - - let list: any[] = []; - if (resData) { - if (Array.isArray(resData)) { - list = resData; - } else if (Array.isArray(resData.data)) { - list = resData.data; - } else if (resData.data && Array.isArray(resData.data.data)) { - list = resData.data.data; - } else if ( - resData.data && - resData.data.data && - Array.isArray(resData.data.data.data) - ) { - list = resData.data.data.data; - } - } - - list = normalizePointLayerItems( - list.map((item: any) => ({ - ...item, - layerKey: item.layerKey || key - })) - ); - - if (activeLayerRequestKeyMap.get(key) !== requestIdentifier) { - return []; - } - - const isLayerChecked = layer.checked === 1; - const cachePayload = { - checked: isLayerChecked, - data: list, - cacheKey: requestIdentifier - }; - const shouldDeferToBackgroundCache = - !!pageKey && hasPendingPageNavigationAwayFrom(pageKey); - - if (shouldDeferToBackgroundCache) { - setBackgroundPointLayerCache(key, cachePayload); - layer.data = list; - return list; - } - - mapDataStore.setPointLayerCache(key, cachePayload); - if (shouldSyncMergedPointData(key, pageToken)) { - syncPointDataForFilter(getRuntimeCheckedLayerKeys()); - } - applyLoadedPointLayerToMap(layer, key, list, pageToken); - - mapDataStore.setLayerLoaded(key); - return list; - } catch (error) { - if ( - controller.signal.aborted || - isCanceledRequestError(error) || - (!skipSessionCheck && !isCurrentLoadSession(sessionId)) || - activeLayerRequestKeyMap.get(key) !== requestIdentifier - ) { - return []; - } - - console.error(`加载描点数据失败 [${key}]:`, error); - mapDataStore.setLayerLoadError(key, error); - throw error; - } finally { - const activeRequest = inFlightRequestMap.get(requestIdentifier); - if (activeRequest?.controller === controller) { - inFlightRequestMap.delete(requestIdentifier); - } - if (activeLayerRequestKeyMap.get(key) === requestIdentifier) { - activeLayerRequestKeyMap.delete(key); - } - } - })(); - - inFlightRequestMap.set(requestIdentifier, { - promise: requestPromise, - controller, - layerKey: key, - sessionId - }); - - return requestPromise; - }; - - /** - * 获取选中的图层 key - */ - const getCheckedKeys = (): string[] => { - return mapViewStore.getCheckedLayerKeys(); - }; - - /** - * 根据当前搜索时间范围重新加载描点数据 - */ - const reloadBySearchTimeRange = async () => { - mapDataStore.setLoading(true); - const currentSessionId = startLoadSession(); - - // 只处理和 searchTimeRange 相关的图层 - const timeRangeLayerKeys = ['ef_point']; - - try { - for (const key of timeRangeLayerKeys) { - // 如果描点图层存在于地图中,先删除旧图层 - mapClass.removePointLayer(key); - - // 清空缓存 - if (mapDataStore.getPointLayerCache(key)) { - mapDataStore.removePointLayerCache(key); - mapDataStore.clearLayerLoadState(key); - } - } - - // 重新加载需要时间搜索的图层数据 - if (layerData.value.length > 0) { - const currentCheckedKeys = getCheckedKeys(); - - for (const key of timeRangeLayerKeys) { - const layerItem = findLayerByKey(layerData.value, key); - if (layerItem && currentCheckedKeys.includes(key)) { - await loadLayerData(layerItem, { sessionId: currentSessionId }); - } - } - - if (!isCurrentLoadSession(currentSessionId)) { - return; - } - - const allPointData = mapDataStore.rebuildPointDataFromCache(); - // 更新地图锚点显示 - await updateLayerData(currentCheckedKeys, false); - } - } finally { - mapDataStore.setLoading(false); - } - }; - - /** - * 更新搜索时间范围并重新加载描点数据 - */ - const updateSearchTimeRange = async (newRange: [any, any]) => { - mapViewStore.setSearchTimeRange(newRange); - await reloadBySearchTimeRange(); - }; - - return { - layerData, - setLayerData, - loadAllLayerData, - loadCurrentPageLayerData, - loadLayerData, - setSelectedLegendData, - legendData, - setLegendData, - legendDataSelected, - pointData, - setPointData, - checkedLayerKeys, - updateLayerData, - updateLegendChecked, - updateLegendCheckedBatch, - replacePointLayerData, - getCheckedKeys, - legendData2Obj, - loading, - pointDataCache, - layerLoadState, - findLayerByKey, - getLegendItemsByLayerCode, - getLayerBranchKeys, - normalizeCheckedLayerKeys, - refreshPointLayerDisplayData, - activatePageContext, - markPendingPageNavigation, - searchTimeRange, - reloadBySearchTimeRange, - updateSearchTimeRange - }; -}); diff --git a/frontend-sjgl/src/store/modules/shuJuTianBao.ts b/frontend-sjgl/src/store/modules/shuJuTianBao.ts index d8f967eb..416ec6a0 100644 --- a/frontend-sjgl/src/store/modules/shuJuTianBao.ts +++ b/frontend-sjgl/src/store/modules/shuJuTianBao.ts @@ -31,6 +31,7 @@ export const useShuJuTianBaoStore = defineStore('shuJuTianBao', () => { const list = [...res.data]; // 直接赋值给 ref,触发响应式更新 baseOption.value = list; + // debugger } } catch (error) { console.error('获取水电基地列表失败:', error); diff --git a/frontend-sjgl/src/utils/GisUrlList.ts b/frontend-sjgl/src/utils/GisUrlList.ts deleted file mode 100644 index 84aec487..00000000 --- a/frontend-sjgl/src/utils/GisUrlList.ts +++ /dev/null @@ -1,5158 +0,0 @@ -import dayjs from 'dayjs'; -// const page =location.href.split('page='); -// const zaiJianParams = page?.[1]==="zaiJianDianZhanHuanBaoGongZuo"?{bldsttCcode:1}:{} -// const zaiJianParams2 = page?.[1]==="zaiJianDianZhanHuanBaoGongZuo"?{BLDSTT_CCODE:1}:{} -// import getUrl from '@zebras/qgc-share/utils/isQGCrul' -export const urlList = [ - { - url: '/wmp-eng-server/eng/point/GetKendoListCust', - title: '常规水电✅️', - params: { - anchoPointState: [{ field: 'anchoPointState', operator: 'isnotnull' }] - }, - orders: - '{"baseId":"asc","rvcdStepSort":"asc","siteStepSort":"asc","ennm":"asc"}' - }, - { - url: '/wmp-env-server/sw/getFacilityPointList/GetKendoListCust', - title: '低温水减缓设施✅️', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-eng-server/eng/eq/interval/GetKendoListCust', - title: '生态流量达标率✅️', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] }, - orders: - '{"baseId":"asc","rvcdStepSort":"asc","siteStepSort":"asc","ennm":"asc"}' - }, - // { - // url: '/wmp-env-server/env/wq/anchorPoint/reach/GetKendoListCust', - // title: '实际水质', - // params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - // }, - // { - // url: '/wmp-env-server/env/wq/anchorPoint/reach/GetKendoListCust', - // title: '目标水质', - // params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - // }, - // { - // url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - // title: '自建站✅️', - // params: { - // dtinType: '0', - // ...(window.__lyConfigs?.baseId === "07" ? { sttpCode: "WQ" } : {}), - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - // }, - // orders: '{"siteStepSort":"asc"}' - // }, - { - url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - title: '国家站✅️', - params: { dtinType: '1', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - title: '人工站✅️', - params: { dtinType: '2', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - title: '栖息地水质站❓不知道', - params: { - fhstcd: [{ field: 'fhstcd', operator: 'isnotnull' }], - fhFlag: '1', - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] - } - }, - // { url: getUrl('/wmp-env-server/env/fp/point/GetKendoListCust'), title: '过鱼设施', params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - { - url: '/wmp-eng-server/eng/eq/eqds/GetKendoListCust', - title: '生态流量泄放设施✅️', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/fb/point/GetKendoListCust', - title: '鱼类增殖站✅️', - params: { sttp: 'FB', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fhvap/GetKendoListCust', - title: '珍稀植物园✅️', - params: { sttpCode: 'VP', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fhvap/GetKendoListCust', - title: '动物救助站✅️', - params: { sttpCode: 'VA', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/fb/point/GetKendoListCust', - title: '人工产卵场✅️', - params: { sttp: 'SG', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/sw/getEngTempPointList/GetKendoListCust', - title: '水温监测断面✅️', - keyType: 'wt_point', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/sw/getEngTempPointList/GetKendoListCust', - title: '自建水温站❓', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], flag: '0' } - }, - { - url: '/wmp-env-server/sw/getEngTempPointList/GetKendoListCust', - title: '人工水温站❓', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], flag: '2' } - }, - { - url: '/wmp-env-server/sw/getEngTempPointList/GetKendoListCust', - title: '栖息地水温站❓', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - fhstcd: [{ field: 'fhstcd', operator: 'isnotnull' }], - fhFlag: '1' - } - }, - { - url: '/wmp-env-server/env/fhvap/GetKendoListCust', - title: '栖息地✅️', - params: { sttpCode: 'FH', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - - { - url: '/wmp-env-server/env/fhvap/fhPoint/GetKendoListCust', - title: '水质监测站✅️', - params: { sttpCode: 'WQ', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fhvap/fhPoint/GetKendoListCust', - title: '水温监测站✅️', - params: { - sttpCode: 'WTRV', - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] - } - }, - { - url: '/wmp-env-server/env/fhvap/fhPoint/GetKendoListCust', - title: '流量监测站✅️', - params: { sttpCode: 'ZQ', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fhvap/fhPoint/GetKendoListCust', - title: '视频监控✅️', - params: { sttpCode: 'VD', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/vd/aiPoint/GetKendoListCust', - title: 'AI视频监控站 缺失!', - params: { sttpCode: 'AIVD' } - }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '水电站监控视频', keyType: "video_fbfm_point", params: { sttp: 'VD_FBFM', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '生态流量监测断面视频', keyType: "video_eqs_point", params: { sttp: 'VD_EQS', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '水质站运行视频', keyType: "video_wq_point", params: { sttp: 'VD_WQ', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '过鱼设施视频', keyType: "video_fp_point", params: { sttp: 'VD_FP', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '鱼类增殖站视频', keyType: "video_fb_point", params: { sttp: 'VD_FB', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '栖息地视频', keyType: "video_fh_point", params: { sttp: 'VD_FH', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '低温水减缓设施视频', keyType: "video_dw_point", params: { sttp: 'VD_DW', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '动物救助站视频', keyType: "video_va_point", params: { sttp: 'VD_VA', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - // { url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', title: '珍稀植物园视频', keyType: "video_vp_point", params: { sttp: 'VD_VP', hbrvcd: window.__lyConfigs.wbsCode, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - - // { - // url: getUrl('/wmp-env-server/env/we/point/GetKendoListCust'), - // title: '水生生态调查断面 ❌️数量不对 真实是338', - // params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] }, - // orders: '{"siteStepSort":"asc"}' - // }, - // { - // url: getUrl('/wmp-env-server/env/we/fishPoint/GetKendoListCust'), - // title: '鱼类分布 ✅️', - // params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - // }, - { - url: '/wmp-eng-server/eng/alarmPoint/GetKendoListCust', - title: '水电站告警情况❌️ 数据不对 真实数据是663', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - // { - // url: '/wmp-env-server/fb/point/discharge/GetKendoListCust', - // title: '水电站', - // params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - // }, - { - url: '/wmp-env-server/env/fh/zqpoint/GetKendoListCust', - title: '国家水文站✅️', - params: { dtinType: 1, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fh/zqpoint/GetKendoListCust', - title: '自建水文站✅️', - params: { dtinType: 0, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - title: '增殖站水质站', - params: { - dtinType: 0, - sttpCode: 'WQFB', - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] - } - }, - { - url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', - title: '视频监控站✅️', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - // { - // url: '/wmp-env-server/env/wb/point/GetKendoListCust', - // title: '气象站', - // params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - // }, - // { - // url: '/wmp-env-server/sw/getEngTempPointList/getLastData', - // title: '水温站点', - // params: { - // logic: 'and', - // filters: [] - // } - // }, - { - url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - title: '水质站点', - params: { - logic: 'and', - filters: [ - { - field: 'lgtd', - operator: 'isnotnull', - dataType: 'string' - }, - { - field: 'orderIndex', - operator: 'isnotnull', - dataType: 'string' - } - ] - }, - orders: '{"orderIndex":"asc"}' - }, - // { - // url: '/wmp-env-server/env/warn/stcd/point/GetKendoListCust', - // title: '水质告警', - // params: { - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - // sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'WQ' }] - // } - // }, - // { - // url: '/wmp-env-server/env/warn/stcd/point/GetKendoListCust', - // title: '水温告警', - // params: { - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - // sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'WTRV' }] - // } - // }, - // { - // url: '/wmp-env-server/env/warn/stcd/point/GetKendoListCust', - // title: '水位告警', - // params: { - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - // sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'ENG' }] - // } - // }, - // { - // url: '/wmp-env-server/env/warn/stcd/operatePoint/GetKendoListCust', - // title: '环保设施告警', - // params: { - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - // yr: dayjs().format('YYYY') - // } - // }, - { - url: '/wmp-env-server/env/fp/point/built/GetKendoListCust', - title: '在建过鱼设施-地图锚点 ✅️', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - bldsttCcode: [{ field: 'bldsttCcode', operator: 'eq', value: '1' }] - } - }, - { - url: '/wmp-env-server/env/fb/point/built/GetKendoListCust', - title: '在建鱼类增殖站 缺失!', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'FB' }], - bldsttCcode: [{ field: 'bldsttCcode', operator: 'eq', value: '1' }] - } - }, - { - url: '/wmp-env-server/env/fb/point/built/GetKendoListCust', - title: '在建人工产卵场 缺失!', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'SG' }], - bldsttCcode: [{ field: 'bldsttCcode', operator: 'eq', value: '1' }] - } - }, - { - url: '/wmp-env-server/env/fhvap/built/GetKendoListCust', - title: '在建珍稀植物园✅️', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'VP' }], - bldsttCcode: [{ field: 'bldsttCcode', operator: 'eq', value: '1' }] - } - }, - { - url: '/wmp-env-server/env/fhvap/built/GetKendoListCust', - title: '在建动物救助站✅️', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'VA' }], - bldsttCcode: [{ field: 'bldsttCcode', operator: 'eq', value: '1' }] - } - }, - // { - // url: '/wmp-env-server/eng/eq/eqds/built/GetKendoListCust', - // title: '在建生态流量泄放设施', - // params: { - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - // BLDSTT_CCODE: [{ field: 'BLDSTT_CCODE', operator: 'eq', value: '1' }] - // } - // }, - // { - // url: '/wmp-env-server/env/we/fishList/point/GetKendoList', - // title: '鱼类沿程', - // params: { - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] - // } - // }, - { - url: '/wmp-env-server/env/wva/point/GetKendoListCust', - title: '野生动物监测✅️', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'WVA' }] - } - }, - // { - // url: '/wmp-env-server/env/we/fishList/point/GetNativeRareFish', - // title: '土著珍稀鱼类', - // params: { - // rare: '1', - // specOrigin: '1', - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] - // } - // }, - // { url: '/wmp-env-server/te/tet/point/GetTerrestrialAnimal', title: '陆生动物分布', params: { baseId: window?.__lyConfigs?.baseId, lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - { - url: '/wmp-env-server/sdFprdR/point/getFprdPointList', - title: '鱼类调查装置 对', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - } -]; - -export const lmGisUrl = [ - { - url: '/wmp-swqx-server/swqx/mm/pptnr/point/GetKendoListCust', - title: '气象站', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-swqx-server/st/point/GetKendoList', - title: '水文站', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'ST' }] - } - }, - { - url: '/wmp-swqx-server/st/point/GetKendoList', - title: '水电站', - params: { - lgtd: [{ field: 'lgtd', operator: 'isnotnull' }], - sttpCode: [{ field: 'sttpCode', operator: 'eq', value: 'ENG' }] - } - } -]; -export const fbGisUrl = [ - { - url: '/wmp-env-server/env/fh/zqpoint/GetKendoListCust', - title: '流量站', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fh/fdPoint/GetKendoListCust', - title: '捕鱼装置', - params: { logic: 'and', filters: [] } - }, - { - url: '/wmp-env-server/sw/getEngTempPointList/GetKendoListCust', - title: '水温监测断面', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - // { - // url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - // title: '自建站', - // params: { - // dtinType: '0', - // ...(window.__lyConfigs?.baseId === "07" ? { sttpCode: "WQ" } : {}), - // lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] - // }, - // orders: '{"siteStepSort":"asc"}' - // }, - { - url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - title: '国家站', - params: { dtinType: '1', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/wq/anchorPoint/GetKendoListCust', - title: '人工站', - params: { dtinType: '2', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fhvap/GetKendoListCust', - title: '栖息地', - params: { sttpCode: 'FH', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fh/vdpoint/GetKendoListCust', - title: '视频监控站', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - // { url: getUrl('/wmp-env-server/env/fp/point/GetKendoListCust'), title: '过鱼设施', params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } }, - { - url: '/wmp-env-server/sw/getFacilityPointList/GetKendoListCust', - title: '低温水减缓设施', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/fb/point/GetKendoListCust', - title: '鱼类增殖站', - params: { sttp: 'FB', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/env/fhvap/GetKendoListCust', - title: '珍稀植物园', - params: { sttpCode: 'VP', lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - }, - { - url: '/wmp-env-server/fb/point/discharge/GetKendoListCust', - title: '水电站', - params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] } - } - // { - // url: getUrl('/wmp-env-server/env/we/point/GetKendoListCust'), - // title: '水生生态调查断面', - // params: { lgtd: [{ field: 'lgtd', operator: 'isnotnull' }] }, - // orders: '{"siteStepSort":"asc"}' - // }, -]; - -export const soltData = [ - { - fieldName: 'MZGJDDZSL', - name: '闽浙赣基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'GHDZSL', - name: '规划', - nameEn: '', - color: '#96B0B5', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SJNFDL', - name: '年发电量', - nameEn: '', - color: '', - unit: '亿kw·h', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSWZCBSSJ', - name: '未正常报送数据', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'ARS', - name: '砷', - nameEn: 'ARS', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.00006', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SYMJ', - name: '水域面积', - nameEn: '', - color: '', - unit: 'km2', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSWZCYX', - name: '未正常运行', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'WTMP', - name: '水温', - nameEn: '', - color: '#01B5B9', - unit: '℃', - theme: '', - decpos: '6', - mn: '0', - mx: '25', - norm: '18', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'DBJDDZSL', - name: '东北基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSDDSJYQ', - name: '达到设计要求', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'YJDZZJRL', - name: '已建', - nameEn: '', - color: '#78C300', - unit: '万kW', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'OIL', - name: '石油类', - nameEn: 'OIL', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.01', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'FCG', - name: '粪大肠菌群', - nameEn: 'FCG', - color: '', - unit: '个/L', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'Ⅰ', - name: 'I', - nameEn: '', - color: '#13ADE6', - unit: '', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'DOX', - name: '溶解氧', - nameEn: 'DO', - color: '#9A60B4', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '1', - mx: '15', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'HHSYJDDZSL', - name: '黄河上游基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'DDHJDDZSL', - name: 'ddh基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSZCYX', - name: '正常运行', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'ZN', - name: '锌', - nameEn: 'ZN', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.05', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'NPJJDDZSL', - name: '南盘江&红水河基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'STLLNONE', - name: '无数据', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'LAS', - name: '阴离子表面活性剂', - nameEn: 'LAS', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.05', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'Ⅳ', - name: 'IV', - nameEn: '', - color: '#FF8800', - unit: '', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'BXSW', - name: '坝下水位', - nameEn: '', - color: '', - unit: 'm', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSBDB', - name: '不达标', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CJSYJDDZSL', - name: '长江上游基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: '劣Ⅴ', - name: '劣V', - nameEn: '', - color: '#D9001B', - unit: '', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CKLL', - name: '出库流量', - nameEn: '', - color: '', - unit: 'm3/s', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'TP', - name: '总磷', - nameEn: 'TP', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.01', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'STLLXZ', - name: '生态流量限值', - nameEn: '', - color: '', - unit: 'm3/s', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'FE', - name: '铁', - nameEn: 'FE', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.03', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSYXQT', - name: '其它', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SJZJRL', - name: '装机容量', - nameEn: '', - color: '', - unit: 'MW', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'TN', - name: '总氮', - nameEn: 'TN', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.05', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'TU', - name: '浊度', - nameEn: 'TU', - color: '#91CC75', - unit: 'NTU', - theme: '', - decpos: '1', - mn: '0.1', - mx: '5000', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSSJWJR', - name: '数据未接入', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'NJJDDZSL', - name: '怒江基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'PB', - name: '铅', - nameEn: 'PB', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.01', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'BOD5', - name: '五日生化需氧量', - nameEn: 'BOD?', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '2', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'S2', - name: '硫化物', - nameEn: 'S2', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.004', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'pH', - name: 'pH', - nameEn: 'pH', - color: '#73C1DF', - unit: '', - theme: '', - decpos: '1', - mn: '6', - mx: '10', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'QEC_STLL_90AND95', - name: '90%-95%', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '', - mn: '90', - mx: '95', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SE', - name: '硒', - nameEn: 'SE', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.00025', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CL', - name: '氯化物', - nameEn: 'CL', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.02', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SO4', - name: '硫酸盐', - nameEn: 'SO?', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.4', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSWSJ', - name: '无数据', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'WJJDDZSL', - name: '乌江基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'HHZYJDDZSL', - name: '黄河中游基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'Ⅴ', - name: 'V', - nameEn: '', - color: '#E55555', - unit: '', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'QEC_STLL_XY80', - name: '<80%', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '', - mn: '', - mx: '80', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSAYQYX', - name: '按要求运行', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'STLL', - name: '生态流量', - nameEn: '', - color: '', - unit: 'm3/s', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CN', - name: '氰化物', - nameEn: 'CN', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.002', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'COND', - name: '电导率', - nameEn: 'COND', - color: '#C7C402', - unit: 'uS/cm', - theme: '', - decpos: '1', - mn: '300', - mx: '500', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSXYH', - name: '需优化', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'THRD', - name: '总硬度', - nameEn: 'THRD', - color: '', - unit: 'mmol/L', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSWYX', - name: '未运行', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'BSSW', - name: '坝上水位', - nameEn: '', - color: '', - unit: 'm', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CU', - name: '铜', - nameEn: 'CU', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.001', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'Ⅱ', - name: 'II', - nameEn: '', - color: '#4FC64D', - unit: '', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSWAYQYX', - name: '未按要求运行', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'F', - name: '氟化物', - nameEn: 'F', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.02', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'YLJJDDZSL', - name: 'ylj基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CD', - name: '镉', - nameEn: 'CD', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.001', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CODCR', - name: '化学需氧量', - nameEn: 'CODcr', - color: '#02B2FF', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0', - mx: '50', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'YJDZSL', - name: '已建', - nameEn: '', - color: '#78C300', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'HG', - name: '汞', - nameEn: 'HG', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.00005', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'QTJDDZSL', - name: '其他基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'NH3N', - name: '氨氮', - nameEn: '', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.01', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'NO3', - name: '硝氮/硝酸盐氮', - nameEn: 'NO3', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.02', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CODMN', - name: '高锰酸盐指数', - nameEn: 'CODMN', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.5', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'ZJDZZJRL', - name: '在建', - nameEn: '', - color: '#669ECF', - unit: '万kW', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'MN', - name: '锰', - nameEn: 'MN', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.01', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'JSJJDDZSL', - name: '金沙江基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'STLLWYQ', - name: '无要求', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'QEC_STLL_DY95', - name: '≥95%', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '', - mn: '95', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'XXJDDZSL', - name: '湘西基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSYXDB', - name: '达标', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CLARITY', - name: '透明度', - nameEn: 'CLARITY', - color: '', - unit: 'mm', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'GC', - name: '高程', - nameEn: '', - color: '', - unit: 'm', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CR6', - name: '铬(六价)', - nameEn: 'CR6', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.004', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'GHDZZJRL', - name: '规划', - nameEn: '', - color: '#96B0B5', - unit: '万kW', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'RKLL', - name: '入库流量', - nameEn: '', - color: '', - unit: 'm3/s', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'ZJDZSL', - name: '在建', - nameEn: '', - color: '#669ECF', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'QEC_STLL_80AND90', - name: '80%-90%', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '', - mn: '80', - mx: '90', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CYANO', - name: '蓝绿藻', - nameEn: 'CYANO', - color: '', - unit: '个/L', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'CHLA', - name: '叶绿素a', - nameEn: 'CHLA', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'SSYXZ', - name: '运行中', - nameEn: '', - color: '', - unit: '个', - theme: '', - decpos: '', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'VLPH', - name: '挥发酚', - nameEn: 'VLPH', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.002', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'AL', - name: '铝', - nameEn: 'AL', - color: '', - unit: 'mg/L', - theme: '', - decpos: '1', - mn: '0.00231', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'Ⅲ', - name: 'III', - nameEn: '', - color: '#FFCC00', - unit: '', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'LCJGLJDDZSL', - name: '澜沧江干流基地', - nameEn: '', - color: '', - unit: '座', - theme: '', - decpos: '1', - mn: '', - mx: '', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - }, - { - fieldName: 'DO', - name: '溶解氧', - nameEn: 'DO', - color: '', - unit: 'mg/L', - theme: '', - decpos: '2', - mn: '1', - mx: '15', - norm: '', - enabled: '', - remark: '', - orderIndex: '', - createUser: '', - createTime: null, - updateUser: '', - updateTime: null, - filterContent: '', - tenantId: '', - isDeleted: '', - deleteUser: '', - deleteTime: null, - systemId: '' - } -]; - -// export const dbList = [ -// { -// "name": "PH", -// "key": "ph", -// "pkey": "", -// "fixed": 0, -// "sort": 0, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "溶解氧", -// "key": "dox", -// "pkey": "", -// "fixed": 0, -// "sort": 1, -// "group": "", -// "groupSort": "", -// "description": "mg/L", -// "children": [] -// }, -// { -// "name": "高锰酸盐指数", -// "key": "codmn", -// "pkey": "", -// "fixed": 0, -// "sort": 2, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "化学需氧量", -// "key": "codcr", -// "pkey": "", -// "fixed": 0, -// "sort": 3, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "五日生化需氧量BODS", -// "key": "bod5", -// "pkey": "", -// "fixed": 0, -// "sort": 4, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "氨氮", -// "key": "nh3n", -// "pkey": "", -// "fixed": 0, -// "sort": 5, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "总磷", -// "key": "tp", -// "pkey": "", -// "fixed": 0, -// "sort": 6, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "总氮", -// "key": "tn", -// "pkey": "", -// "fixed": 0, -// "sort": 7, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "铜", -// "key": "cu", -// "pkey": "", -// "fixed": 0, -// "sort": 8, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "锌", -// "key": "zn", -// "pkey": "", -// "fixed": 0, -// "sort": 9, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "氟化物", -// "key": "f", -// "pkey": "", -// "fixed": 0, -// "sort": 10, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "硒", -// "key": "se", -// "pkey": "", -// "fixed": 0, -// "sort": 11, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "砷", -// "key": "ars", -// "pkey": "", -// "fixed": 0, -// "sort": 12, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "汞", -// "key": "hg", -// "pkey": "", -// "fixed": 0, -// "sort": 13, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "镉", -// "key": "cd", -// "pkey": "", -// "fixed": 0, -// "sort": 14, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "铬(六价)", -// "key": "cr6", -// "pkey": "", -// "fixed": 0, -// "sort": 15, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "铅", -// "key": "pb", -// "pkey": "", -// "fixed": 0, -// "sort": 16, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "氰化物", -// "key": "cn", -// "pkey": "", -// "fixed": 0, -// "sort": 17, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "挥发酚", -// "key": "vlph", -// "pkey": "", -// "fixed": 0, -// "sort": 18, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "石油类", -// "key": "oil", -// "pkey": "", -// "fixed": 0, -// "sort": 19, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "阴离子表面活性剂", -// "key": "las", -// "pkey": "", -// "fixed": 0, -// "sort": 20, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "硫化物", -// "key": "s2", -// "pkey": "", -// "fixed": 0, -// "sort": 21, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "粪大肠菌群", -// "key": "fcg", -// "pkey": "", -// "fixed": 0, -// "sort": 22, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "氯化物", -// "key": "cl", -// "pkey": "", -// "fixed": 0, -// "sort": 23, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "硫酸盐", -// "key": "so4", -// "pkey": "", -// "fixed": 0, -// "sort": 24, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "硝酸盐氮", -// "key": "no3", -// "pkey": "", -// "fixed": 0, -// "sort": 25, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "总硬度", -// "key": "thrd", -// "pkey": "", -// "fixed": 0, -// "sort": 26, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "电导率", -// "key": "cond", -// "pkey": "", -// "fixed": 0, -// "sort": 27, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "铁", -// "key": "fe", -// "pkey": "", -// "fixed": 0, -// "sort": 28, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "锰", -// "key": "mn", -// "pkey": "", -// "fixed": 0, -// "sort": 29, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "铝", -// "key": "al", -// "pkey": "", -// "fixed": 0, -// "sort": 30, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "叶绿素a", -// "key": "chla", -// "pkey": "", -// "fixed": 0, -// "sort": 31, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "透明度", -// "key": "clarity", -// "pkey": "", -// "fixed": 0, -// "sort": 32, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// }, -// { -// "name": "浊度", -// "key": "tu", -// "pkey": "", -// "fixed": 0, -// "sort": 33, -// "group": "", -// "groupSort": "", -// "description": "", -// "children": [] -// } -// ] - -export const stcdNameList = [ - { key: '猴', value: '猴子岩' }, - { key: '枕一', value: '枕头坝一级' }, - { key: '枕二', value: '枕头坝二级' }, - { key: '铜', value: '铜街子' }, - { key: '深', value: '深溪沟' }, - { key: '沙一', value: '沙坪一级' }, - { key: '沙二', value: '沙坪二级' }, - { key: '双', value: '双江口' }, - { key: '瀑', value: '瀑布沟' }, - { key: '龚', value: '龚嘴' }, - { key: '大', value: '大岗山' }, - { key: '金', value: '金川' }, - { key: '杨', value: '杨房沟' }, - { key: '桐', value: '桐子林' }, - { key: '孟', value: '孟底沟' }, - { key: '锦一', value: '锦屏一级' }, - { key: '锦二', value: '锦屏二级' }, - { key: '官', value: '官地' }, - { key: '两', value: '两河口' }, - { key: '卡', value: '卡拉' }, - { key: '二', value: '二滩' }, - { key: '里', value: '里底' }, - { key: '托', value: '托巴' }, - { key: '苗', value: '苗尾' }, - { key: '功', value: '功果桥' }, - { key: '湾', value: '小湾' }, - { key: '乌', value: '乌弄龙' }, - { key: '漫', value: '漫湾' }, - { key: '糯', value: '糯扎渡' }, - { key: '景', value: '景洪' }, - { key: '黄', value: '黄登' }, - { key: '华', value: '大华桥' }, - { key: '积', value: '积石峡' }, - { key: '苏', value: '苏只' }, - { key: '李', value: '李家峡' }, - { key: '拉', value: '拉西瓦' }, - { key: '龙', value: '龙羊峡' }, - { key: '班', value: '班多' }, - { key: '公', value: '公伯峡' } -]; - -export const offset1: any = { - sdzYijianL: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - sdzYijianR: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - sdzZaijianL: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - sdzZaijianR: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - wushujvL: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - wushujvR: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - sdzDabiaolv1L: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - sdzDabiaolv1R: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - sdzDabiaolv2L: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - sdzDabiaolv2R: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - sdzDabiaolv3L: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - sdzDabiaolv3R: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - sdzDabiaolv4L: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - sdzDabiaolv4R: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - sdzDabiaolv5L: { - text_x: -10, - text_y: -2.9, - icon_x: -200, - icon_y: -69, - billboard_x: -80, - billboard_y: -15, - labelOffset: [100, 41] - }, - sdzDabiaolv5R: { - text_x: 10, - text_y: -2.9, - icon_x: 200, - icon_y: -69, - billboard_x: 80, - billboard_y: -15, - labelOffset: [306, 41] - }, - szzBudabiaoL: { - text_x: -12, - text_y: -2.9, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -13, - labelOffset: [154, 46] - }, - szzBudabiaoR: { - text_x: 12, - text_y: -2.9, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -13, - labelOffset: [362, 46] - }, - szzDabiaoL: { - text_x: -12, - text_y: -2.9, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -13, - labelOffset: [154, 46] - }, - szzDabiaoR: { - text_x: 12, - text_y: -2.9, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -13, - labelOffset: [362, 46] - }, - szzWudabiaoshujvL: { - text_x: -12, - text_y: -2.9, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -13, - labelOffset: [154, 46] - }, - szzWudabiaoshujvR: { - text_x: 12, - text_y: -2.9, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -13, - labelOffset: [362, 46] - }, - shuishengshengtai: { - text_x: -0.1, - text_y: -3.5, - icon_x: -1, - icon_y: -100, - billboard_x: -1, - billboard_y: -25, - labelOffset: [150, 45] - }, - cezhanL: { - text_x: -12.2, - text_y: -2.9, - icon_x: -260, - icon_y: -68, - billboard_x: -100, - billboard_y: -13, - labelOffset: [154, 48] - }, - cezhanR: { - text_x: 12.2, - text_y: -2.9, - icon_x: 260, - icon_y: -68, - billboard_x: 100, - billboard_y: -13, - labelOffset: [362, 48] - }, - sdzGaojing0L: { - text_x: -11.7, - text_y: -2.8, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -15, - labelOffset: [154, 46] - }, - sdzGaojing0R: { - text_x: 11.7, - text_y: -2.8, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -15, - labelOffset: [362, 46] - }, - sdzGaojing1L: { - text_x: -11.7, - text_y: -2.8, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -15, - labelOffset: [154, 46] - }, - sdzGaojing1R: { - text_x: 11.7, - text_y: -2.8, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -15, - labelOffset: [362, 46] - }, - sdzGaojing2L: { - text_x: -11.7, - text_y: -2.8, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -15, - labelOffset: [154, 46] - }, - sdzGaojing2R: { - text_x: 11.7, - text_y: -2.8, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -15, - labelOffset: [362, 46] - }, - sdzGaojing3L: { - text_x: -11.7, - text_y: -2.8, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -15, - labelOffset: [154, 46] - }, - sdzGaojing3R: { - text_x: 11.7, - text_y: -2.8, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -15, - labelOffset: [362, 46] - }, - wushujvlgL: { - text_x: -11.7, - text_y: -2.8, - icon_x: -255, - icon_y: -68, - billboard_x: -100, - billboard_y: -15, - labelOffset: [154, 46] - }, - wushujvlgR: { - text_x: 11.7, - text_y: -2.8, - icon_x: 255, - icon_y: -68, - billboard_x: 100, - billboard_y: -15, - labelOffset: [362, 46] - }, - gyssL: { - text_x: -12.65, - text_y: -2.9, - icon_x: -275, - icon_y: -68, - billboard_x: -110, - billboard_y: -13, - labelOffset: [165, 41] - }, - gyssR: { - text_x: 12.65, - text_y: -2.9, - icon_x: 275, - icon_y: -68, - billboard_x: 110, - billboard_y: -13, - labelOffset: [385, 41] - } - // qixidiL: { text_x: 12.2, text_y: 2.9, icon_x: 260, icon_y: 68, billboard_x: 100, billboard_y: 13, label_x: 140, label_y: 44 }, - // qixidiR: { text_x: 12.2, text_y: 2.9, icon_x: 260, icon_y: 68, billboard_x: 100, billboard_y: 13, label_x: 140, label_y: 44 }, -}; -export const drawDotImg1: any = { - large_eng_built: { left: 'sdzYijianL', right: 'sdzYijianR' }, - large_eng_ubuilt: { left: 'sdzZaijianL', right: 'sdzZaijianR' }, - large_eng_nbuilt: { left: 'wushujvL', right: 'wushujvR' }, - mid_eng_built: { left: 'sdzYijianL', right: 'sdzYijianR' }, - mid_eng_ubuilt: { left: 'sdzZaijianL', right: 'sdzZaijianR' }, - mid_eng_nbuilt: { left: 'wushujvL', right: 'wushujvR' }, - eef_1_1: { left: 'sdzDabiaolv1L', right: 'sdzDabiaolv1R' }, - eef_1_2: { left: 'sdzDabiaolv2L', right: 'sdzDabiaolv2R' }, - eef_1_3: { left: 'sdzDabiaolv3L', right: 'sdzDabiaolv3R' }, - eef_1_4: { left: 'sdzDabiaolv4L', right: 'sdzDabiaolv4R' }, - eef_1_none: { left: 'sdzDabiaolv5L', right: 'sdzDabiaolv5R' }, - eef_2_1: { left: 'sdzDabiaolv1L', right: 'sdzDabiaolv1R' }, - eef_2_2: { left: 'sdzDabiaolv2L', right: 'sdzDabiaolv2R' }, - eef_2_3: { left: 'sdzDabiaolv3L', right: 'sdzDabiaolv3R' }, - eef_2_4: { left: 'sdzDabiaolv4L', right: 'sdzDabiaolv4R' }, - eef_2_none: { left: 'sdzDabiaolv5L', right: 'sdzDabiaolv5R' }, - WT_1: { left: 'gyssL', right: 'gyssR' }, - WT_2: { left: 'gyssL', right: 'gyssR' }, - stinfo_wtzj_legend: { left: 'gyssL', right: 'gyssR' }, - wq_station_3: { left: 'szzDabiaoL', right: 'szzDabiaoR' }, - wq_station_4: { left: 'szzBudabiaoL', right: 'szzBudabiaoR' }, - wq_station_1: { left: 'szzDabiaoL', right: 'szzDabiaoR' }, - wq_station_2: { left: 'szzBudabiaoL', right: 'szzBudabiaoR' }, - wq_station_5: { left: 'szzDabiaoL', right: 'szzDabiaoR' }, - wq_station_6: { left: 'szzBudabiaoL', right: 'szzBudabiaoR' }, - wq_station_7: { left: 'szzWudabiaoshujvL', right: 'szzWudabiaoshujvR' }, - wq_station_8: { left: 'szzWudabiaoshujvL', right: 'szzWudabiaoshujvR' }, - wq_station_9: { left: 'szzWudabiaoshujvL', right: 'szzWudabiaoshujvR' }, - alarm_range_3: { left: 'sdzGaojing3L', right: 'sdzGaojing3R' }, - alarm_range_2: { left: 'sdzGaojing2L', right: 'sdzGaojing2R' }, - alarm_range_1: { left: 'sdzGaojing1L', right: 'sdzGaojing1R' }, - alarm_range_0: { left: 'sdzGaojing0L', right: 'sdzGaojing0R' }, - WE: { left: 'shuishengshengtai', right: 'shuishengshengtai' }, - qxz: { left: 'cezhanL', right: 'cezhanR' }, - spjk: { left: 'cezhanL', right: 'cezhanR' }, - fbfm_spjk: { left: 'cezhanL', right: 'cezhanR' }, - eqs_spjk: { left: 'cezhanL', right: 'cezhanR' }, - wq_spjk: { left: 'cezhanL', right: 'cezhanR' }, - fp_spjk: { left: 'cezhanL', right: 'cezhanR' }, - fb_spjk: { left: 'cezhanL', right: 'cezhanR' }, - fh_spjk: { left: 'cezhanL', right: 'cezhanR' }, - va_spjk: { left: 'cezhanL', right: 'cezhanR' }, - vp_spjk: { left: 'cezhanL', right: 'cezhanR' }, - dw_spjk: { left: 'cezhanL', right: 'cezhanR' }, - gjllz: { left: 'cezhanL', right: 'cezhanR' }, - zjllz: { left: 'cezhanL', right: 'cezhanR' }, - wt_alarm_1: { left: 'sdzGaojing0L', right: 'sdzGaojing0R' }, - wt_alarm_2: { left: 'sdzGaojing3L', right: 'sdzGaojing3R' }, - wt_alarm_0: { left: 'wushujvlgL', right: 'wushujvlgR' }, - wq_alarm_1: { left: 'sdzGaojing0L', right: 'sdzGaojing0R' }, - wq_alarm_2: { left: 'sdzGaojing3L', right: 'sdzGaojing3R' }, - wq_alarm_0: { left: 'wushujvlgL', right: 'wushujvlgR' }, - rz_alarm_1: { left: 'sdzGaojing0L', right: 'sdzGaojing0R' }, - rz_alarm_2: { left: 'sdzGaojing3L', right: 'sdzGaojing3R' }, - rz_alarm_0: { left: 'wushujvlgL', right: 'wushujvlgR' }, - operat_alarm_1: { left: 'sdzGaojing0L', right: 'sdzGaojing0R' }, - operat_alarm_2: { left: 'sdzGaojing3L', right: 'sdzGaojing3R' }, - operat_alarm_0: { left: 'wushujvlgL', right: 'wushujvlgR' }, - dxsdz_ywc: { left: 'sdzDabiaolv2L', right: 'sdzDabiaolv2R' }, - dxsdz_wwc: { left: 'sdzGaojing3L', right: 'sdzGaojing3R' }, - dxsdz_wtb: { left: 'sdzDabiaolv3L', right: 'sdzDabiaolv3R' }, - zxsdz_ywc: { left: 'sdzDabiaolv2L', right: 'sdzDabiaolv2R' }, - zxsdz_wwc: { left: 'sdzGaojing3L', right: 'sdzGaojing3R' }, - zxsdz_wtb: { left: 'sdzDabiaolv3L', right: 'sdzDabiaolv3R' }, - gy_1: { left: 'gyssL', right: 'gyssR' }, - gy_2: { left: 'gyssL', right: 'gyssR' }, - gy_3: { left: 'gyssL', right: 'gyssR' }, - gy_4: { left: 'gyssL', right: 'gyssR' }, - gy_5: { left: 'gyssL', right: 'gyssR' }, - dws_1: { left: 'gyssL', right: 'gyssR' }, - dws_2: { left: 'gyssL', right: 'gyssR' }, - dws_3: { left: 'gyssL', right: 'gyssR' }, - dws_4: { left: 'gyssL', right: 'gyssR' }, - EQ_7: { left: 'gyssL', right: 'gyssR' }, - EQ_6: { left: 'gyssL', right: 'gyssR' }, - EQ_2: { left: 'gyssL', right: 'gyssR' }, - EQ_3: { left: 'gyssL', right: 'gyssR' }, - EQ_4: { left: 'gyssL', right: 'gyssR' }, - EQ_5: { left: 'gyssL', right: 'gyssR' }, - EQ_1: { left: 'gyssL', right: 'gyssR' }, - FB: { left: 'gyssL', right: 'gyssR' }, - VP: { left: 'gyssL', right: 'gyssR' }, - VA: { left: 'gyssL', right: 'gyssR' }, - SG: { left: 'gyssL', right: 'gyssR' }, - FH: { left: 'gyssL', right: 'gyssR' }, - ylfb: { left: 'gyssL', right: 'gyssR' }, - gy_1_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - gy_2_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - gy_3_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - gy_4_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - gy_5_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - dws_1_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - dws_2_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - dws_3_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - dws_4_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - EQ_6_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - EQ_2_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - EQ_3_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - EQ_4_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - EQ_5_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - EQ_1_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - FB_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - VP_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - VA_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - SG_incomplete: { left: 'wushujvlgL', right: 'wushujvlgR' }, - large_eng_built_alarm_range_0: { - left: 'sdzGaojing0L', - right: 'sdzGaojing0R' - }, - mid_eng_built_alarm_range_0: { left: 'sdzGaojing0L', right: 'sdzGaojing0R' }, - large_eng_built_alarm_range_1: { - left: 'sdzGaojing1L', - right: 'sdzGaojing1R' - }, - mid_eng_built_alarm_range_1: { left: 'sdzGaojing1L', right: 'sdzGaojing1R' }, - large_eng_built_alarm_range_2: { - left: 'sdzGaojing2L', - right: 'sdzGaojing2R' - }, - mid_eng_built_alarm_range_2: { left: 'sdzGaojing2L', right: 'sdzGaojing2L' }, - large_eng_built_alarm_range_3: { - left: 'sdzGaojing3L', - right: 'sdzGaojing3R' - }, - mid_eng_built_alarm_range_3: { left: 'sdzGaojing3L', right: 'sdzGaojing3L' }, - fhwt_legend: { left: 'gyssL', right: 'gyssR' }, - fhwq_legend: { left: 'gyssL', right: 'gyssR' } -}; - -export const offset2: any = { - dxsdzYijianL: { - text_x: -4, - text_y: -0.2, - icon_x: -130, - icon_y: -5, - billboard_x: -50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - dxsdzZaijianL: { - text_x: -4, - text_y: -0.2, - icon_x: -130, - icon_y: -5, - billboard_x: -50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - dxsdzGuihuaL: { - text_x: -4, - text_y: -0.2, - icon_x: -130, - icon_y: -5, - billboard_x: -50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - zxsdzYijianL: { - text_x: -4, - text_y: -0.2, - icon_x: -130, - icon_y: -5, - billboard_x: -50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - zxsdzZaijianL: { - text_x: -4, - text_y: -0.2, - icon_x: -130, - icon_y: -5, - billboard_x: -50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - zxsdzGuihuaL: { - text_x: -4, - text_y: -0.2, - icon_x: -130, - icon_y: -5, - billboard_x: -50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - - dxsdzYijianR: { - text_x: 6.5, - text_y: -0.2, - icon_x: 130, - icon_y: -5, - billboard_x: 50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - dxsdzZaijianR: { - text_x: 6.5, - text_y: -0.2, - icon_x: 130, - icon_y: -5, - billboard_x: 50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - dxsdzGuihuaR: { - text_x: 6.5, - text_y: -0.2, - icon_x: 130, - icon_y: -5, - billboard_x: 50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - zxsdzYijianR: { - text_x: 6.5, - text_y: -0.2, - icon_x: 130, - icon_y: -5, - billboard_x: 50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - zxsdzZaijianR: { - text_x: 6.5, - text_y: -0.2, - icon_x: 130, - icon_y: -5, - billboard_x: 50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - zxsdzGuihuaR: { - text_x: 6.5, - text_y: -0.2, - icon_x: 130, - icon_y: -5, - billboard_x: 50, - billboard_y: 0.01, - labelOffset: [138, 30] - }, - - 'dxsdzDabiaolv-1': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-2': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-3': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-4': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-5': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-1': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-2': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-3': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-4': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-5': { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - gyss: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [176, 43] - }, - szzDabiao: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - szzBudabiao: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - szzWudabiaoshujv: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing0: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing1: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing2: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing3: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - qixidi: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - cezhan: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - wushujvlg: { - text_x: 0, - text_y: -2.9, - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - ylfbL: { - text_x: -8, - text_y: -0.3, - icon_x: -180, - icon_y: -5, - billboard_x: -75, - billboard_y: 15, - labelOffset: [158, 49] - }, - ylfbR: { - text_x: 8.3, - text_y: -0.3, - icon_x: 170, - icon_y: -5, - billboard_x: 70, - billboard_y: 15, - labelOffset: [158, 49] - }, - yeshengdongwu: { - text_x: 7.5, - text_y: -0.3, - icon_x: 170, - icon_y: -5, - billboard_x: 0, - billboard_y: -20, - labelOffset: [116, 46] - }, - 'bg-mapDongwuL': { - text_offset: [-6.8, -0.3], - icon_x: -180, - icon_y: -5, - billboard_x: -75, - billboard_y: 15, - labelOffset: [111, 46] - }, - 'bg-mapDongwuR': { - text_offset: [6.8, -0.3], - icon_x: 170, - icon_y: -5, - billboard_x: 70, - billboard_y: 15, - labelOffset: [161, 46] - } -}; - -export const drawDotImg2: any = { - large_eng_built: { left: 'dxsdzYijianL', right: 'dxsdzYijianR' }, - large_eng_ubuilt: { left: 'dxsdzZaijianL', right: 'dxsdzZaijianR' }, - large_eng_nbuilt: { left: 'dxsdzGuihuaL', right: 'dxsdzGuihuaR' }, - mid_eng_built: { left: 'zxsdzYijianL', right: 'zxsdzYijianR' }, - mid_eng_ubuilt: { left: 'zxsdzZaijianL', right: 'zxsdzZaijianR' }, - mid_eng_nbuilt: { left: 'zxsdzGuihuaL', right: 'zxsdzGuihuaR' }, - eef_1_1: { left: 'dxsdzDabiaolv-1', right: 'dxsdzDabiaolv-1' }, - eef_1_2: { left: 'dxsdzDabiaolv-2', right: 'dxsdzDabiaolv-2' }, - eef_1_3: { left: 'dxsdzDabiaolv-3', right: 'dxsdzDabiaolv-3' }, - eef_1_4: { left: 'dxsdzDabiaolv-4', right: 'dxsdzDabiaolv-4' }, - eef_1_none: { left: 'dxsdzDabiaolv-5', right: 'dxsdzDabiaolv-5' }, - eef_2_1: { left: 'zxsdzDabiaolv-1', right: 'zxsdzDabiaolv-1' }, - eef_2_2: { left: 'zxsdzDabiaolv-2', right: 'zxsdzDabiaolv-2' }, - eef_2_3: { left: 'zxsdzDabiaolv-3', right: 'zxsdzDabiaolv-3' }, - eef_2_4: { left: 'zxsdzDabiaolv-4', right: 'zxsdzDabiaolv-4' }, - eef_2_none: { left: 'zxsdzDabiaolv-5', right: 'zxsdzDabiaolv-5' }, - WT_1: { left: 'gyss', right: 'gyss' }, - WT_2: { left: 'gyss', right: 'gyss' }, - wqfb_legend: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_3: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_4: { left: 'szzBudabiao', right: 'szzBudabiao' }, - wq_station_1: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_2: { left: 'szzBudabiao', right: 'szzBudabiao' }, - wq_station_5: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_6: { left: 'szzBudabiao', right: 'szzBudabiao' }, - wq_station_7: { left: 'szzWudabiaoshujv', right: 'szzWudabiaoshujv' }, - wq_station_8: { left: 'szzWudabiaoshujv', right: 'szzWudabiaoshujv' }, - wq_station_9: { left: 'szzWudabiaoshujv', right: 'szzWudabiaoshujv' }, - alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - WE: { left: 'qixidi', right: 'qixidi' }, - qxz: { left: 'cezhan', right: 'cezhan' }, - spjk: { left: 'cezhan', right: 'cezhan' }, - fbfm_spjk: { left: 'cezhan', right: 'cezhan' }, - eqs_spjk: { left: 'cezhan', right: 'cezhan' }, - wq_spjk: { left: 'cezhan', right: 'cezhan' }, - fp_spjk: { left: 'cezhan', right: 'cezhan' }, - fb_spjk: { left: 'cezhan', right: 'cezhan' }, - fh_spjk: { left: 'cezhan', right: 'cezhan' }, - va_spjk: { left: 'cezhan', right: 'cezhan' }, - vp_spjk: { left: 'cezhan', right: 'cezhan' }, - dw_spjk: { left: 'cezhan', right: 'cezhan' }, - gjllz: { left: 'cezhan', right: 'cezhan' }, - zjllz: { left: 'cezhan', right: 'cezhan' }, - wt_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - wt_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wt_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - wq_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - wq_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wq_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - rz_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - rz_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - rz_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - operat_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - operat_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - operat_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - dxsdz_ywc: { left: 'dxsdzDabiaolv-2', right: 'dxsdzDabiaolv-2' }, - dxsdz_wwc: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - dxsdz_wtb: { left: 'dxsdzDabiaolv-3', right: 'dxsdzDabiaolv-3' }, - zxsdz_ywc: { left: 'zxsdzDabiaolv-2', right: 'zxsdzDabiaolv-2' }, - zxsdz_wwc: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - zxsdz_wtb: { left: 'zxsdzDabiaolv-3', right: 'zxsdzDabiaolv-3' }, - gy_1: { left: 'gyss', right: 'gyss' }, - gy_2: { left: 'gyss', right: 'gyss' }, - gy_3: { left: 'gyss', right: 'gyss' }, - gy_4: { left: 'gyss', right: 'gyss' }, - gy_5: { left: 'gyss', right: 'gyss' }, - dws_1: { left: 'gyss', right: 'gyss' }, - dws_2: { left: 'gyss', right: 'gyss' }, - dws_3: { left: 'gyss', right: 'gyss' }, - dws_4: { left: 'gyss', right: 'gyss' }, - EQ_7: { left: 'gyss', right: 'gyss' }, - EQ_6: { left: 'gyss', right: 'gyss' }, - EQ_2: { left: 'gyss', right: 'gyss' }, - EQ_3: { left: 'gyss', right: 'gyss' }, - EQ_4: { left: 'gyss', right: 'gyss' }, - EQ_5: { left: 'gyss', right: 'gyss' }, - EQ_1: { left: 'gyss', right: 'gyss' }, - FB: { left: 'gyss', right: 'gyss' }, - VP: { left: 'gyss', right: 'gyss' }, - VA: { left: 'gyss', right: 'gyss' }, - SG: { left: 'gyss', right: 'gyss' }, - FH: { left: 'gyss', right: 'gyss' }, - ylfb: { left: 'gyss', right: 'gyss' }, - fish_along: { left: 'ylfbR', right: 'ylfbR' }, - gy_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_5_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_6_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_5_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - FB_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - VP_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - VA_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - SG_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - large_eng_built_alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - mid_eng_built_alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - large_eng_built_alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - mid_eng_built_alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - large_eng_built_alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - mid_eng_built_alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - large_eng_built_alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - mid_eng_built_alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wild_animal_legend: { left: 'yeshengdongwu', right: 'yeshengdongwu' }, - fd_legend: { left: 'gyss', right: 'gyss' }, - stinfo_wtzj_legend: { left: 'gyss', right: 'gyss' }, - fhwt_legend: { left: 'gyss', right: 'gyss' }, - fhwq_legend: { left: 'gyss', right: 'gyss' }, - terrestrial_animals: { left: 'bg-mapDongwuL', right: 'bg-mapDongwuR' } -}; -export const offset3: any = { - dxsdzYijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - dxsdzZaijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - dxsdzGuihuaL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - zxsdzYijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - zxsdzZaijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - zxsdzGuihuaL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - - dxsdzYijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - dxsdzZaijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - dxsdzGuihuaR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - zxsdzYijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - zxsdzZaijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - zxsdzGuihuaR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - - 'dxsdzDabiaolv-1': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-2': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-3': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-4': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-5': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-1': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-2': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-3': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-4': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-5': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - gyss: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [176, 43] - }, - szzDabiao: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - szzBudabiao: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - szzWudabiaoshujv: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing0: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing1: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing2: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing3: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - qixidi: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - cezhan: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - wushujvlg: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - ylfbL: { - text_offset: [-8, -0.3], - icon_x: -180, - icon_y: -5, - billboard_x: -75, - billboard_y: 15, - labelOffset: [158, 49] - }, - ylfbR: { - text_offset: [8.3, -0.3], - icon_x: 170, - icon_y: -5, - billboard_x: 70, - billboard_y: 15, - labelOffset: [158, 49] - }, - yeshengdongwu: { - text_offset: [7.5, -0.3], - icon_x: 170, - icon_y: -5, - billboard_x: 0, - billboard_y: -20, - labelOffset: [116, 46] - }, - 'bg-mapDongwuL': { - text_offset: [-6.8, -0.3], - icon_x: -180, - icon_y: -5, - billboard_x: -75, - billboard_y: 15, - labelOffset: [111, 46] - }, - 'bg-mapDongwuR': { - text_offset: [6.8, -0.3], - icon_x: 170, - icon_y: -5, - billboard_x: 70, - billboard_y: 15, - labelOffset: [161, 46] - } -}; -export const drawDotImg3: any = { - large_eng_built: { left: 'dxsdzYijianL', right: 'dxsdzYijianR' }, - large_eng_ubuilt: { left: 'dxsdzZaijianL', right: 'dxsdzZaijianR' }, - large_eng_nbuilt: { left: 'dxsdzGuihuaL', right: 'dxsdzGuihuaR' }, - mid_eng_built: { left: 'zxsdzYijianL', right: 'zxsdzYijianR' }, - mid_eng_ubuilt: { left: 'zxsdzZaijianL', right: 'zxsdzZaijianR' }, - mid_eng_nbuilt: { left: 'zxsdzGuihuaL', right: 'zxsdzGuihuaR' }, - eef_1_1: { left: 'dxsdzDabiaolv-1', right: 'dxsdzDabiaolv-1' }, - eef_1_2: { left: 'dxsdzDabiaolv-2', right: 'dxsdzDabiaolv-2' }, - eef_1_3: { left: 'dxsdzDabiaolv-3', right: 'dxsdzDabiaolv-3' }, - eef_1_4: { left: 'dxsdzDabiaolv-4', right: 'dxsdzDabiaolv-4' }, - eef_1_none: { left: 'dxsdzDabiaolv-5', right: 'dxsdzDabiaolv-5' }, - eef_2_1: { left: 'zxsdzDabiaolv-1', right: 'zxsdzDabiaolv-1' }, - eef_2_2: { left: 'zxsdzDabiaolv-2', right: 'zxsdzDabiaolv-2' }, - eef_2_3: { left: 'zxsdzDabiaolv-3', right: 'zxsdzDabiaolv-3' }, - eef_2_4: { left: 'zxsdzDabiaolv-4', right: 'zxsdzDabiaolv-4' }, - eef_2_none: { left: 'zxsdzDabiaolv-5', right: 'zxsdzDabiaolv-5' }, - WT_1: { left: 'gyss', right: 'gyss' }, - WT_2: { left: 'gyss', right: 'gyss' }, - wqfb_legend: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_3: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_4: { left: 'szzBudabiao', right: 'szzBudabiao' }, - wq_station_1: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_2: { left: 'szzBudabiao', right: 'szzBudabiao' }, - wq_station_5: { left: 'szzDabiao', right: 'szzDabiao' }, - wq_station_6: { left: 'szzBudabiao', right: 'szzBudabiao' }, - wq_station_7: { left: 'szzWudabiaoshujv', right: 'szzWudabiaoshujv' }, - wq_station_8: { left: 'szzWudabiaoshujv', right: 'szzWudabiaoshujv' }, - wq_station_9: { left: 'szzWudabiaoshujv', right: 'szzWudabiaoshujv' }, - alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - WE: { left: 'qixidi', right: 'qixidi' }, - rare_fish: { left: 'qixidi', right: 'qixidi' }, - qxz: { left: 'cezhan', right: 'cezhan' }, - spjk: { left: 'cezhan', right: 'cezhan' }, - fbfm_spjk: { left: 'blueL', right: 'blueR' }, - eqs_spjk: { left: 'blueL', right: 'blueR' }, - wq_spjk: { left: 'blueL', right: 'blueR' }, - fp_spjk: { left: 'blueL', right: 'blueR' }, - fb_spjk: { left: 'blueL', right: 'blueR' }, - fh_spjk: { left: 'blueL', right: 'blueR' }, - va_spjk: { left: 'blueL', right: 'blueR' }, - vp_spjk: { left: 'blueL', right: 'blueR' }, - dw_spjk: { left: 'blueL', right: 'blueR' }, - gjllz: { left: 'cezhan', right: 'cezhan' }, - zjllz: { left: 'cezhan', right: 'cezhan' }, - wt_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - wt_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wt_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - wq_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - wq_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wq_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - rz_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - rz_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - rz_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - operat_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - operat_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - operat_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - dxsdz_ywc: { left: 'dxsdzDabiaolv-2', right: 'dxsdzDabiaolv-2' }, - dxsdz_wwc: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - dxsdz_wtb: { left: 'dxsdzDabiaolv-3', right: 'dxsdzDabiaolv-3' }, - zxsdz_ywc: { left: 'zxsdzDabiaolv-2', right: 'zxsdzDabiaolv-2' }, - zxsdz_wwc: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - zxsdz_wtb: { left: 'zxsdzDabiaolv-3', right: 'zxsdzDabiaolv-3' }, - gy_1: { left: 'gyss', right: 'gyss' }, - gy_2: { left: 'gyss', right: 'gyss' }, - gy_3: { left: 'gyss', right: 'gyss' }, - gy_4: { left: 'gyss', right: 'gyss' }, - gy_5: { left: 'gyss', right: 'gyss' }, - dws_1: { left: 'gyss', right: 'gyss' }, - dws_2: { left: 'gyss', right: 'gyss' }, - dws_3: { left: 'gyss', right: 'gyss' }, - dws_4: { left: 'gyss', right: 'gyss' }, - EQ_7: { left: 'gyss', right: 'gyss' }, - EQ_6: { left: 'gyss', right: 'gyss' }, - EQ_2: { left: 'gyss', right: 'gyss' }, - EQ_3: { left: 'gyss', right: 'gyss' }, - EQ_4: { left: 'gyss', right: 'gyss' }, - EQ_5: { left: 'gyss', right: 'gyss' }, - EQ_1: { left: 'gyss', right: 'gyss' }, - FB: { left: 'gyss', right: 'gyss' }, - VP: { left: 'gyss', right: 'gyss' }, - VA: { left: 'gyss', right: 'gyss' }, - SG: { left: 'gyss', right: 'gyss' }, - FH: { left: 'gyss', right: 'gyss' }, - ylfb: { left: 'gyss', right: 'gyss' }, - fish_along: { left: 'ylfbR', right: 'ylfbR' }, - gy_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_5_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_6_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_5_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - FB_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - VP_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - VA_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - SG_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - large_eng_built_alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - mid_eng_built_alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - large_eng_built_alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - mid_eng_built_alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - large_eng_built_alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - mid_eng_built_alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - large_eng_built_alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - mid_eng_built_alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wild_animal_legend: { left: 'yeshengdongwu', right: 'yeshengdongwu' }, - fd_legend: { left: 'gyss', right: 'gyss' }, - stinfo_wtzj_legend: { left: 'gyss', right: 'gyss' }, - fhwt_legend: { left: 'gyss', right: 'gyss' }, - fhwq_legend: { left: 'gyss', right: 'gyss' }, - terrestrial_animals: { left: 'bg-mapDongwuL', right: 'bg-mapDongwuR' } -}; - -export const offset5: any = { - dxsdzYijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - dxsdzZaijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - dxsdzGuihuaL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - zxsdzYijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - zxsdzZaijianL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - zxsdzGuihuaL: { - text_offset: [-0.7, 0.3], - text_offset2: [-1.0, 0.3], - icon_x: -130, - icon_y: -5, - billboard_x: -57, - billboard_y: 15, - labelOffset: [140, 30] - }, - - dxsdzYijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - dxsdzZaijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - dxsdzGuihuaR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - zxsdzYijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - zxsdzZaijianR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - zxsdzGuihuaR: { - text_offset: [0.7, -0.5], - text_offset2: [1.0, -0.5], - icon_x: 130, - icon_y: -5, - billboard_x: 57, - billboard_y: 8, - labelOffset: [70, 30] - }, - - 'dxsdzDabiaolv-1': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-2': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-3': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-4': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'dxsdzDabiaolv-5': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-1': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-2': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-3': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-4': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - 'zxsdzDabiaolv-5': { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [138, 30] - }, - gyss: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [176, 43] - }, - szzDabiao: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - szzBudabiao: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - szzWudabiaoshujv: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing0: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing1: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing2: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - sdzGaojing3: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - qixidi: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - cezhan: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - wushujvlg: { - text_offset: [0, -2.9], - icon_x: 0, - icon_y: -69, - billboard_x: 0, - billboard_y: -20, - labelOffset: [158, 49] - }, - ylfbL: { - text_offset: [-8, -0.3], - icon_x: -180, - icon_y: -5, - billboard_x: -75, - billboard_y: 15, - labelOffset: [158, 49] - }, - ylfbR: { - text_offset: [8.3, -0.3], - icon_x: 170, - icon_y: -5, - billboard_x: 70, - billboard_y: 15, - labelOffset: [158, 49] - }, - yeshengdongwu: { - text_offset: [7.5, -0.3], - icon_x: 170, - icon_y: -5, - billboard_x: 0, - billboard_y: -20, - labelOffset: [116, 46] - }, - 'bg-mapDongwuL': { - text_offset: [-6.8, -0.3], - icon_x: -180, - icon_y: -5, - billboard_x: -75, - billboard_y: 15, - labelOffset: [111, 46] - }, - 'bg-mapDongwuR': { - text_offset: [6.8, -0.3], - icon_x: 170, - icon_y: -5, - billboard_x: 70, - billboard_y: 15, - labelOffset: [161, 46] - }, - dabiaoL: { - text_offset: [-2.6, -0.9], - text_offset2: [-3.2, -0.9], - icon_offset2: [-60, -20], - icon_x: -44, - icon_y: -20, - billboard_x: -6, - billboard_y: -16, - labelOffset: [60, 16] - }, - dabiaoR: { - text_offset: [2.6, -0.9], - text_offset2: [3.2, -0.9], - icon_offset2: [60, -20], - icon_x: 44, - icon_y: -20, - billboard_x: 6, - billboard_y: -16, - labelOffset: [60, 16] - }, - budabiaoL: { - text_offset: [-2.6, -0.9], - text_offset2: [-3.2, -0.9], - icon_offset2: [-60, -20], - icon_x: -44, - icon_y: -20, - billboard_x: -6, - billboard_y: -16, - labelOffset: [60, 16] - }, - budabiaoR: { - text_offset: [2.6, -0.9], - text_offset2: [3.2, -0.9], - icon_offset2: [60, -20], - icon_x: 44, - icon_y: -20, - billboard_x: 6, - billboard_y: -16, - labelOffset: [60, 16] - }, - wushujvL: { - text_offset: [-2.6, -0.9], - text_offset2: [-3.2, -0.9], - icon_offset2: [-60, -20], - icon_x: -44, - icon_y: -20, - billboard_x: -6, - billboard_y: -16, - labelOffset: [60, 16] - }, - wushujvR: { - text_offset: [2.6, -0.9], - text_offset2: [3.2, -0.9], - icon_offset2: [60, -20], - icon_x: 44, - icon_y: -20, - billboard_x: 6, - billboard_y: -16, - labelOffset: [60, 16] - }, - greenL: { - text_offset: [-2.6, -0.9], - text_offset2: [-3.2, -0.9], - icon_offset2: [-60, -20], - icon_x: -44, - icon_y: -20, - billboard_x: -6, - billboard_y: -18, - labelOffset: [60, 16] - }, - greenR: { - text_offset: [2.6, -0.9], - text_offset2: [3.2, -0.9], - icon_offset2: [60, -20], - icon_x: 44, - icon_y: -20, - billboard_x: 6, - billboard_y: -18, - labelOffset: [60, 16] - }, - blueL: { - text_offset: [-2.6, -0.9], - text_offset2: [-3.2, -0.9], - icon_offset2: [-60, -20], - icon_x: -44, - icon_y: -20, - billboard_x: -6, - billboard_y: -16, - labelOffset: [60, 16] - }, - blueR: { - text_offset: [2.6, -0.9], - text_offset2: [3.2, -0.9], - icon_offset2: [60, -20], - icon_x: 44, - icon_y: -20, - billboard_x: 6, - billboard_y: -16, - labelOffset: [60, 16] - } -}; - -export const drawDotImg5: any = { - large_eng_built: { left: 'dxsdzYijianL', right: 'dxsdzYijianR' }, - large_eng_ubuilt: { left: 'dxsdzZaijianL', right: 'dxsdzZaijianR' }, - large_eng_nbuilt: { left: 'dxsdzGuihuaL', right: 'dxsdzGuihuaR' }, - mid_eng_built: { left: 'zxsdzYijianL', right: 'zxsdzYijianR' }, - mid_eng_ubuilt: { left: 'zxsdzZaijianL', right: 'zxsdzZaijianR' }, - mid_eng_nbuilt: { left: 'zxsdzGuihuaL', right: 'zxsdzGuihuaR' }, - eef_1_1: { left: 'dxsdzDabiaolv-1', right: 'dxsdzDabiaolv-1' }, - eef_1_2: { left: 'dxsdzDabiaolv-2', right: 'dxsdzDabiaolv-2' }, - eef_1_3: { left: 'dxsdzDabiaolv-3', right: 'dxsdzDabiaolv-3' }, - eef_1_4: { left: 'dxsdzDabiaolv-4', right: 'dxsdzDabiaolv-4' }, - eef_1_none: { left: 'dxsdzDabiaolv-5', right: 'dxsdzDabiaolv-5' }, - eef_2_1: { left: 'zxsdzDabiaolv-1', right: 'zxsdzDabiaolv-1' }, - eef_2_2: { left: 'zxsdzDabiaolv-2', right: 'zxsdzDabiaolv-2' }, - eef_2_3: { left: 'zxsdzDabiaolv-3', right: 'zxsdzDabiaolv-3' }, - eef_2_4: { left: 'zxsdzDabiaolv-4', right: 'zxsdzDabiaolv-4' }, - eef_2_none: { left: 'zxsdzDabiaolv-5', right: 'zxsdzDabiaolv-5' }, - WT_1: { left: 'greenL', right: 'greenR' }, - WT_2: { left: 'greenL', right: 'greenR' }, - wqfb_legend: { left: 'dabiaoL', right: 'dabiaoR' }, - wq_station_3: { left: 'dabiaoL', right: 'dabiaoR' }, - wq_station_4: { left: 'budabiaoL', right: 'budabiaoR' }, - wq_station_1: { left: 'dabiaoL', right: 'dabiaoR' }, - wq_station_2: { left: 'budabiaoL', right: 'budabiaoR' }, - wq_station_5: { left: 'dabiaoL', right: 'dabiaoR' }, - wq_station_6: { left: 'budabiaoL', right: 'budabiaoR' }, - wq_station_7: { left: 'wushujvL', right: 'wushujvR' }, - wq_station_8: { left: 'wushujvL', right: 'wushujvR' }, - wq_station_9: { left: 'wushujvL', right: 'wushujvR' }, - alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - WE: { left: 'greenL', right: 'greenR' }, - rare_fish: { left: 'greenL', right: 'greenR' }, - qxz: { left: 'blueL', right: 'blueR' }, - spjk: { left: 'blueL', right: 'blueR' }, - gjllz: { left: 'blueL', right: 'blueR' }, - zjllz: { left: 'blueL', right: 'blueR' }, - fbfm_spjk: { left: 'blueL', right: 'blueR' }, - eqs_spjk: { left: 'blueL', right: 'blueR' }, - wq_spjk: { left: 'blueL', right: 'blueR' }, - fp_spjk: { left: 'blueL', right: 'blueR' }, - fb_spjk: { left: 'blueL', right: 'blueR' }, - fh_spjk: { left: 'blueL', right: 'blueR' }, - va_spjk: { left: 'blueL', right: 'blueR' }, - vp_spjk: { left: 'blueL', right: 'blueR' }, - dw_spjk: { left: 'blueL', right: 'blueR' }, - wt_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - wt_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wt_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - wq_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - wq_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wq_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - rz_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - rz_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - rz_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - operat_alarm_1: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - operat_alarm_2: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - operat_alarm_0: { left: 'wushujvlg', right: 'wushujvlg' }, - dxsdz_ywc: { left: 'dxsdzDabiaolv-2', right: 'dxsdzDabiaolv-2' }, - dxsdz_wwc: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - dxsdz_wtb: { left: 'dxsdzDabiaolv-3', right: 'dxsdzDabiaolv-3' }, - zxsdz_ywc: { left: 'zxsdzDabiaolv-2', right: 'zxsdzDabiaolv-2' }, - zxsdz_wwc: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - zxsdz_wtb: { left: 'zxsdzDabiaolv-3', right: 'zxsdzDabiaolv-3' }, - gy_1: { left: 'greenL', right: 'greenR' }, - gy_2: { left: 'greenL', right: 'greenR' }, - gy_3: { left: 'greenL', right: 'greenR' }, - gy_4: { left: 'greenL', right: 'greenR' }, - gy_5: { left: 'greenL', right: 'greenR' }, - dws_1: { left: 'greenL', right: 'greenR' }, - dws_2: { left: 'greenL', right: 'greenR' }, - dws_3: { left: 'greenL', right: 'greenR' }, - dws_4: { left: 'greenL', right: 'greenR' }, - EQ_7: { left: 'greenL', right: 'greenR' }, - EQ_6: { left: 'greenL', right: 'greenR' }, - EQ_2: { left: 'greenL', right: 'greenR' }, - EQ_3: { left: 'greenL', right: 'greenR' }, - EQ_4: { left: 'greenL', right: 'greenR' }, - EQ_5: { left: 'greenL', right: 'greenR' }, - EQ_1: { left: 'greenL', right: 'greenR' }, - FB: { left: 'greenL', right: 'greenR' }, - VP: { left: 'greenL', right: 'greenR' }, - VA: { left: 'greenL', right: 'greenR' }, - SG: { left: 'greenL', right: 'greenR' }, - FH: { left: 'greenL', right: 'greenR' }, - ylfb: { left: 'greenL', right: 'greenR' }, - fish_along: { left: 'greenL', right: 'greenR' }, - gy_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - gy_5_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - dws_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_6_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_2_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_3_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_4_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_5_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - EQ_1_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - FB_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - VP_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - VA_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - SG_incomplete: { left: 'wushujvlg', right: 'wushujvlg' }, - large_eng_built_alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - mid_eng_built_alarm_range_0: { left: 'sdzGaojing0', right: 'sdzGaojing0' }, - large_eng_built_alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - mid_eng_built_alarm_range_1: { left: 'sdzGaojing1', right: 'sdzGaojing1' }, - large_eng_built_alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - mid_eng_built_alarm_range_2: { left: 'sdzGaojing2', right: 'sdzGaojing2' }, - large_eng_built_alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - mid_eng_built_alarm_range_3: { left: 'sdzGaojing3', right: 'sdzGaojing3' }, - wild_animal_legend: { left: 'greenL', right: 'greenR' }, - fd_legend: { left: 'greenL', right: 'greenR' }, - stinfo_wtzj_legend: { left: 'greenL', right: 'greenR' }, - fhwt_legend: { left: 'greenL', right: 'greenR' }, - fhwq_legend: { left: 'greenL', right: 'greenR' }, - terrestrial_animals: { left: 'greenL', right: 'greenR' } -}; - -export const level_Altitude: any = { - '0': 91189921, - '1': 45454474, - '2': 22657211, - '3': 11293700, - '4': 5629451, - '5': 2806053, - '6': 1398704, - '7': 697197, - '8': 347524, - '9': 173227, - '10': 86347, - '11': 43040, - '12': 21454, - '13': 10694, - '14': 5330, - '15': 2657, - '16': 1324, - '17': 660, - '18': 329, - '19': 164, - '20': 82, - '21': 41, - '22': 20, - '23': 10, - '24': 5 -}; -export const anchoToIconCode: any = { - large_eng_built: 'map-dxsdzYijian', - large_eng_ubuilt: 'map-dxsdzZaijian', - large_eng_nbuilt: 'map-dxsdzGuihua', - mid_eng_built: 'map-zxsdzYijian', - mid_eng_ubuilt: 'map-zxsdzZaijian', - mid_eng_nbuilt: 'map-zxsdzGuihua' -}; diff --git a/frontend-sjgl/src/views/conventionalHydropower/tempMit/EditDwModal.vue b/frontend-sjgl/src/views/conventionalHydropower/tempMit/EditDwModal.vue index 1671f1d2..2989ca10 100644 --- a/frontend-sjgl/src/views/conventionalHydropower/tempMit/EditDwModal.vue +++ b/frontend-sjgl/src/views/conventionalHydropower/tempMit/EditDwModal.vue @@ -174,7 +174,7 @@ /> - + -
- - - - - - - - - - -
- -
-
- - 监控列表 -
-
- - - - - - - - - - - - - - -
-
- - -
- -
- -
- - 实时视频 - 录像 - -
- - - - - -
- - -
- -
- - - - - -
-
-
- - -
-
-
- -
-
-
- - -
-
- 回放列表 -
- -
- -
- - -
- - -
- -
- - -
-
-
- -
-
- -
- - -
-
{{ item.name }}
-
- {{ dayjs(item.time).format('YYYY-MM-DD HH') }} -
-
-
-
-
-
-
-
-
-
-
- - - - - diff --git a/frontend-sjgl/vite.config.ts b/frontend-sjgl/vite.config.ts index 7747e529..79186866 100644 --- a/frontend-sjgl/vite.config.ts +++ b/frontend-sjgl/vite.config.ts @@ -2,7 +2,6 @@ import { UserConfig, ConfigEnv, loadEnv } from 'vite'; import vue from '@vitejs/plugin-vue'; import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; import path from 'path'; -import cesium from 'vite-plugin-cesium'; export default ({ mode }: ConfigEnv): UserConfig => { // 获取 .env 环境配置文件 @@ -11,7 +10,6 @@ export default ({ mode }: ConfigEnv): UserConfig => { return { plugins: [ vue(), - cesium(), createSvgIconsPlugin({ // 指定需要缓存的图标文件夹 iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], @@ -122,7 +120,6 @@ export default ({ mode }: ConfigEnv): UserConfig => { if (id.includes('element-plus')) return 'element-plus'; if (id.includes('ant-design-vue')) return 'ant-design-vue'; if (id.includes('lodash')) return 'lodash'; - if (id.includes('cesium')) return 'cesium'; // 其他 node_modules 归为 vendor return 'vendor'; } diff --git a/frontend/src/api/DataQueryMenuModule/index.ts b/frontend/src/api/DataQueryMenuModule/index.ts index 57c0c5b1..1a5fabd0 100644 --- a/frontend/src/api/DataQueryMenuModule/index.ts +++ b/frontend/src/api/DataQueryMenuModule/index.ts @@ -326,3 +326,156 @@ export function deleteFlowStationInfo(data) { data }); } +// 水温监测 - 分页列表 +export function getWtPageList(data) { + return request({ + url: '/base/wt/page', + method: 'post', + data + }); +} +// 水质监测 - 分页列表 +export function getWqPageList(data) { + return request({ + url: '/base/wq/page', + method: 'post', + data + }); +} +// 生态流量 - 分页列表 +export function getEqPageList(data) { + return request({ + url: '/base/eq/page', + method: 'post', + data + }); +} +// 低温水减缓设施 - 分页列表 +export function getDwPageList(data) { + return request({ + url: '/base/dw/page', + method: 'post', + data + }); +} +// 栖息地 - 分页列表 +export function getFhPageList(data) { + return request({ + url: '/base/fhbt/page', + method: 'post', + data + }); +} +// 珍稀植物园 - 分页列表 +export function getVpPageList(data) { + return request({ + url: '/base/vp/page', + method: 'post', + data + }); +} +// 动物救助站 - 分页列表 +export function getVaPageList(data) { + return request({ + url: '/base/va/page', + method: 'post', + data + }); +} +// 过鱼设施 - 分页列表 +export function getFpPageList(data) { + return request({ + url: '/env/fpss/page', + method: 'post', + data + }); +} +// 水生生态 - 分页列表 +export function getWePageList(data) { + return request({ + url: '/base/we/page', + method: 'post', + data + }); +} +// 鱼类调查 - 分页列表 +export function getAiPageList(data) { + return request({ + url: '/base/aimonitor/page', + method: 'post', + data + }); +} +// 智能告警 - 分页列表 +export function getWarnPageList(data) { + return request({ + url: '/base/warnRule/page', + method: 'post', + data + }); +} +// AI边缘计算盒子 - 分页列表 +export function getAiboxPageList(data) { + return request({ + url: '/base/aibox/page', + method: 'post', + data + }); +} +// 人工产卵场 - 分页列表 +export function getSgPageList(data) { + return request({ + url: '/base/artsg/page', + method: 'post', + data + }); +} +// 其他水生生态保护 - 分页列表 +export function getOtwePageList(data) { + return request({ + url: '/base/otwe/page', + method: 'post', + data + }); +} +// 陆生生态 - 分页列表 +export function getTePageList(data) { + return request({ + url: '/base/te/page', + method: 'post', + data + }); +} + +// 其他陆生生态保护 - 分页列表 +export function getOttePageList(data) { + return request({ + url: '/base/otte/page', + method: 'post', + data + }); +} +// 视频监控 - 分页列表 +export function getVdPageList(data) { + return request({ + url: '/base/vdinfo/page', + method: 'post', + data + }); +} +// 鱼类增殖站 - 分页列表 +export function getFbPageList(data) { + return request({ + url: '/base/fbrd/page', + method: 'post', + data + }); +} +// 声呐及水下摄像头 - 分页列表 +export function getSonarPageList(data) { + return request({ + url: '/base/sonar/page', + method: 'post', + data + }); +} \ No newline at end of file diff --git a/frontend/src/components/BasicTable/index.vue b/frontend/src/components/BasicTable/index.vue index 59384bc2..9148e7cf 100644 --- a/frontend/src/components/BasicTable/index.vue +++ b/frontend/src/components/BasicTable/index.vue @@ -668,7 +668,19 @@ const enhancedColumns = computed(() => { title: String(text) }, { - default: () => String(text) + default: () => h( + 'span', + { + style: { + display: 'inline-block', + maxWidth: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + } + }, + String(text) + ) } ) ); diff --git a/frontend/src/modules/shuidianhuangjingjieruMod/DataTable.vue b/frontend/src/modules/shuidianhuangjingjieruMod/DataTable.vue index f0513a14..96871189 100644 --- a/frontend/src/modules/shuidianhuangjingjieruMod/DataTable.vue +++ b/frontend/src/modules/shuidianhuangjingjieruMod/DataTable.vue @@ -36,7 +36,6 @@ defineOptions({ const emit = defineEmits<{ (e: 'row-click', record: any): void; }>(); - const JidiSelectEventStore = useJidiSelectEventStore(); const baseid = ref(''); const dataLoading = ref(false); @@ -169,7 +168,6 @@ const getData = async () => { }; }); }); - const result = { total: 0, buildinstall: 0, ubuildinstall: 0, nbuildinstall: 0 }; list.forEach((e: any) => { result.total += Number(e.total) * 100; @@ -180,16 +178,15 @@ const getData = async () => { const { total, nbuildinstall, ubuildinstall, buildinstall } = newList[e.key]; newList[e.key] = { ...e, - total: Number(total) + Number(e.total), - nbuildinstall: Number(nbuildinstall) + Number(e.nbuildinstall), - ubuildinstall: Number(ubuildinstall) + Number(e.ubuildinstall), - buildinstall: Number(buildinstall) + Number(e.buildinstall) + total: (Number(total) + Number(e.total)).toFixed(2), + nbuildinstall: (Number(nbuildinstall) + Number(e.nbuildinstall)).toFixed(2), + ubuildinstall: (Number(ubuildinstall) + Number(e.ubuildinstall)).toFixed(2), + buildinstall: (Number(buildinstall) + Number(e.buildinstall)).toFixed(2) }; } else { newList[e.key] = e; } }); - const { total, buildinstall, ubuildinstall, nbuildinstall } = result; const arr: any[] = keyList.map((key) => newList[key]); tableData.value = [ @@ -202,6 +199,7 @@ const getData = async () => { nbuildinstall: (nbuildinstall / 100)?.toFixed(2) } ]; + } else { tableData.value = []; } diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/Approval/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/Approval/index.vue index 81d45c7c..392ce217 100644 --- a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/Approval/index.vue +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/Approval/index.vue @@ -342,7 +342,7 @@ const openUrl = (url:string)=>{ } onMounted(() => { nextTick(() => { - tableScrollY.value = calcTableScrollY(); + tableScrollY.value = calcTableScrollY(tableRef.value); }); }); diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/index.vue index c46c1e56..c5dd014a 100644 --- a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/index.vue +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/index.vue @@ -27,25 +27,6 @@ }" :list-url="getPowerTableList" > - @@ -281,7 +262,7 @@ const exportBtn = () => { const customFilterBtn = () => { customColumnVisible.value = true; // 打开弹窗前刷新列配置数据 - fetchColumnConfig(); + // fetchColumnConfig(); }; // 确认自定义列选择 @@ -501,24 +482,12 @@ const fetchColumnConfig = () => { }); // 添加操作列 - cols.push({ - key: 'action', - title: '操作', - dataIndex: 'action', - fixed: 'right', - width: isEdit.value ? 180 : 100 - }); + columns.value = cols; }).catch(() => { // API 失败时,至少显示操作列 - columns.value = [{ - key: 'action', - title: '操作', - dataIndex: 'action', - fixed: 'right', - width: isEdit.value ? 180 : 100 - }]; + }).finally(() => { columnLoaded.value = true; // 等待表格渲染完成后再请求数据 @@ -531,7 +500,7 @@ onMounted(() => { init(); fetchColumnConfig(); nextTick(() => { - tableScrollY.value = calcTableScrollY(); + tableScrollY.value = calcTableScrollY(tableRef.value); }); }); diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aiBox/AiBoxSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aiBox/AiBoxSearch.vue new file mode 100644 index 00000000..c3757777 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aiBox/AiBoxSearch.vue @@ -0,0 +1,77 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aiBox/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aiBox/index.vue new file mode 100644 index 00000000..895d7435 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aiBox/index.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aquaEco/OtweSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aquaEco/OtweSearch.vue new file mode 100644 index 00000000..2e155d52 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aquaEco/OtweSearch.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aquaEco/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aquaEco/index.vue new file mode 100644 index 00000000..f94b060f --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/aquaEco/index.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/fishSurvey/FishSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/fishSurvey/FishSearch.vue new file mode 100644 index 00000000..b14f2ecd --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/fishSurvey/FishSearch.vue @@ -0,0 +1,117 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/fishSurvey/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/fishSurvey/index.vue new file mode 100644 index 00000000..efc6499d --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/fishSurvey/index.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/guoYuSheShi/FpSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/guoYuSheShi/FpSearch.vue new file mode 100644 index 00000000..e1c2aa3a --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/guoYuSheShi/FpSearch.vue @@ -0,0 +1,40 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/guoYuSheShi/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/guoYuSheShi/index.vue new file mode 100644 index 00000000..36acf66c --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/guoYuSheShi/index.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/luShengShengTaiDiaoCha/TeSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/luShengShengTaiDiaoCha/TeSearch.vue new file mode 100644 index 00000000..224e1502 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/luShengShengTaiDiaoCha/TeSearch.vue @@ -0,0 +1,117 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/luShengShengTaiDiaoCha/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/luShengShengTaiDiaoCha/index.vue new file mode 100644 index 00000000..d4bf1a5e --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/luShengShengTaiDiaoCha/index.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/photoBase/PhotoSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/photoBase/PhotoSearch.vue new file mode 100644 index 00000000..e047e3a3 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/photoBase/PhotoSearch.vue @@ -0,0 +1,121 @@ + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/photoBase/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/photoBase/index.vue new file mode 100644 index 00000000..1a551ea1 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/photoBase/index.vue @@ -0,0 +1,275 @@ + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/qiXiDi/FhSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/qiXiDi/FhSearch.vue new file mode 100644 index 00000000..8b55b732 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/qiXiDi/FhSearch.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/qiXiDi/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/qiXiDi/index.vue new file mode 100644 index 00000000..fd6c9907 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/qiXiDi/index.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/rescueStation/VaSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/rescueStation/VaSearch.vue new file mode 100644 index 00000000..91f8de1e --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/rescueStation/VaSearch.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/rescueStation/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/rescueStation/index.vue new file mode 100644 index 00000000..969ceb4c --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/rescueStation/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shengTaiLiuLiang/EqSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shengTaiLiuLiang/EqSearch.vue new file mode 100644 index 00000000..c01e64cf --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shengTaiLiuLiang/EqSearch.vue @@ -0,0 +1,42 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shengTaiLiuLiang/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shengTaiLiuLiang/index.vue new file mode 100644 index 00000000..9267de91 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shengTaiLiuLiang/index.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shiPinJianKong/VdSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shiPinJianKong/VdSearch.vue new file mode 100644 index 00000000..3c544bc1 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shiPinJianKong/VdSearch.vue @@ -0,0 +1,40 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shiPinJianKong/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shiPinJianKong/index.vue new file mode 100644 index 00000000..69ce1feb --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shiPinJianKong/index.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiShengShengTaiDiaoCha/WeSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiShengShengTaiDiaoCha/WeSearch.vue new file mode 100644 index 00000000..79435617 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiShengShengTaiDiaoCha/WeSearch.vue @@ -0,0 +1,116 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiShengShengTaiDiaoCha/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiShengShengTaiDiaoCha/index.vue new file mode 100644 index 00000000..cb5f9cf5 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiShengShengTaiDiaoCha/index.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiWenJianCe/WtSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiWenJianCe/WtSearch.vue new file mode 100644 index 00000000..cc102d89 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiWenJianCe/WtSearch.vue @@ -0,0 +1,116 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiWenJianCe/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiWenJianCe/index.vue new file mode 100644 index 00000000..b5777224 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiWenJianCe/index.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiZhiJianCe/WqSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiZhiJianCe/WqSearch.vue new file mode 100644 index 00000000..f0365a63 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiZhiJianCe/WqSearch.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiZhiJianCe/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiZhiJianCe/index.vue new file mode 100644 index 00000000..f50161ef --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/shuiZhiJianCe/index.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/sonarCam/SonarSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/sonarCam/SonarSearch.vue new file mode 100644 index 00000000..6186223f --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/sonarCam/SonarSearch.vue @@ -0,0 +1,117 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/sonarCam/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/sonarCam/index.vue new file mode 100644 index 00000000..f483320a --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/sonarCam/index.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/EditSpawnModal.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/EditSpawnModal.vue new file mode 100644 index 00000000..f59a0880 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/EditSpawnModal.vue @@ -0,0 +1,506 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/SpawnSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/SpawnSearch.vue new file mode 100644 index 00000000..71ef218f --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/SpawnSearch.vue @@ -0,0 +1,121 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/index.vue new file mode 100644 index 00000000..8ee14689 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/spawnGround/index.vue @@ -0,0 +1,295 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/tempMit/DwSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/tempMit/DwSearch.vue new file mode 100644 index 00000000..b43cf164 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/tempMit/DwSearch.vue @@ -0,0 +1,116 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/tempMit/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/tempMit/index.vue new file mode 100644 index 00000000..e8f425e6 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/tempMit/index.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/terrEco/OtteSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/terrEco/OtteSearch.vue new file mode 100644 index 00000000..b856ee48 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/terrEco/OtteSearch.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/terrEco/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/terrEco/index.vue new file mode 100644 index 00000000..47f5c9a6 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/terrEco/index.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/EditWarnModal.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/EditWarnModal.vue new file mode 100644 index 00000000..24219bfb --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/EditWarnModal.vue @@ -0,0 +1,394 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/WarnSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/WarnSearch.vue new file mode 100644 index 00000000..dc5db725 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/WarnSearch.vue @@ -0,0 +1,82 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/index.vue new file mode 100644 index 00000000..8554be30 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/yunXingGaoJing/index.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zengZhiFangLiu/FbSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zengZhiFangLiu/FbSearch.vue new file mode 100644 index 00000000..73a4b6dd --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zengZhiFangLiu/FbSearch.vue @@ -0,0 +1,92 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zengZhiFangLiu/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zengZhiFangLiu/index.vue new file mode 100644 index 00000000..4d3bcd6e --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zengZhiFangLiu/index.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zhenXIZhiWuYuan/VpSearch.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zhenXIZhiWuYuan/VpSearch.vue new file mode 100644 index 00000000..0240ddc9 --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zhenXIZhiWuYuan/VpSearch.vue @@ -0,0 +1,39 @@ + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zhenXIZhiWuYuan/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zhenXIZhiWuYuan/index.vue new file mode 100644 index 00000000..76be352a --- /dev/null +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/zhenXIZhiWuYuan/index.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/FlowStation/EditFlowStationModal.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/FlowStation/EditFlowStationModal.vue deleted file mode 100644 index 75e38474..00000000 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/FlowStation/EditFlowStationModal.vue +++ /dev/null @@ -1,327 +0,0 @@ - - - - - diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/FlowStation/index.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/FlowStation/index.vue index feaf9520..f8f953c2 100644 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/FlowStation/index.vue +++ b/frontend/src/views/DataQueryMenuModule/components/monitorData/FlowStation/index.vue @@ -19,48 +19,15 @@ sort: sort }" > - - - - + diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/SurfaceTemp/EditSurfaceTempModal.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/SurfaceTemp/EditSurfaceTempModal.vue deleted file mode 100644 index d69a1345..00000000 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/SurfaceTemp/EditSurfaceTempModal.vue +++ /dev/null @@ -1,252 +0,0 @@ - - - - - diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/SurfaceTemp/index.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/SurfaceTemp/index.vue index 7e73361c..8bd728b2 100644 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/SurfaceTemp/index.vue +++ b/frontend/src/views/DataQueryMenuModule/components/monitorData/SurfaceTemp/index.vue @@ -19,54 +19,18 @@ sort: sort }" > - - - - - diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/VerticalTemp/EditVerticalTempModal.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/VerticalTemp/EditVerticalTempModal.vue deleted file mode 100644 index f27450ae..00000000 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/VerticalTemp/EditVerticalTempModal.vue +++ /dev/null @@ -1,288 +0,0 @@ - - - - - diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/VerticalTemp/index.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/VerticalTemp/index.vue index 06b4c5f3..c772cde8 100644 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/VerticalTemp/index.vue +++ b/frontend/src/views/DataQueryMenuModule/components/monitorData/VerticalTemp/index.vue @@ -59,43 +59,9 @@ :min-selection-count="1" @selection-change="handleCxswSelectionChange" > - - - - - @@ -111,8 +77,6 @@ import { import dayjs from 'dayjs'; import * as echarts from 'echarts'; import VerticalTempSearch from './VerticalTempSearch.vue'; -import EditVerticalTempModal from './EditVerticalTempModal.vue'; -import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue'; import BasicTable from '@/components/BasicTable/index.vue'; import { getVerticalList, deleteVerticalInfo } from '@/api/DataQueryMenuModule'; import { calcTableScrollY } from '@/utils/index'; @@ -221,20 +185,7 @@ const tableColumns = computed(() => { : '-'; } })); - - const actionCols = - currentSearchParams.value.timeScale === 'tm' - ? [ - { - title: '操作', - dataIndex: 'action', - fixed: 'right', - width: 120 - } - ] - : []; - - return [...fixedCols, ...dataCols, ...actionCols]; + return [...fixedCols, ...dataCols ]; }); const tableScrollX = computed(() => { @@ -243,13 +194,7 @@ const tableScrollX = computed(() => { return total > 600 ? total : undefined; }); -// 默认选中行 -const defaultSelectedRowKeys = computed(() => { - if (tableData.value && tableData.value.length > 0) { - return [tableData.value[0].dt]; - } - return []; -}); + // 图表颜色 const COLORS = [ @@ -434,33 +379,7 @@ const exportBtn = () => { }); }; -const handleEdit = (record: any) => { - editRecord.value = { ...record }; - editVisible.value = true; -}; -const handleDeleteFn = async (record: any, reason: string) => { - return deleteVerticalInfo({ - dataList: [ - { - id: record.stcd ?? record.id, - tm: record.tm ?? record.dt - } - ], - dataType: 'TIME', - source: reason - }); -}; - -const handleDelete = (record: any) => { - deleteModalRef.value?.open(record, () => { - initTable(currentSearchParams.value); - }); -}; - -const handleEditSuccess = () => { - initTable(currentSearchParams.value); -}; const initTable = (values: any) => { const filters = [ @@ -627,7 +546,7 @@ let resizeObserver: ResizeObserver | null = null; onMounted(() => { nextTick(() => { - tableScrollY.value = calcTableScrollY(); + tableScrollY.value = calcTableScrollY(tableRef.value); // 初始化图表 if (chartRef.value && !chartInstance) { diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/WaterData/EditWaterDataModal.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/WaterData/EditWaterDataModal.vue deleted file mode 100644 index bd30f661..00000000 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/WaterData/EditWaterDataModal.vue +++ /dev/null @@ -1,405 +0,0 @@ - - - - - diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/WaterData/index.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/WaterData/index.vue index eb0f6552..4b4e5e1c 100644 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/WaterData/index.vue +++ b/frontend/src/views/DataQueryMenuModule/components/monitorData/WaterData/index.vue @@ -20,50 +20,14 @@ sort: sort }" > - + - - - - - -