diff --git a/src/app/(main)/health-risk-analysis/loading.tsx b/src/app/(main)/(map)/health-risk-analysis/loading.tsx
similarity index 100%
rename from src/app/(main)/health-risk-analysis/loading.tsx
rename to src/app/(main)/(map)/health-risk-analysis/loading.tsx
diff --git a/src/app/(main)/(map)/health-risk-analysis/page.tsx b/src/app/(main)/(map)/health-risk-analysis/page.tsx
new file mode 100644
index 0000000..779e035
--- /dev/null
+++ b/src/app/(main)/(map)/health-risk-analysis/page.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import Timeline from "@components/olmap/HealthRiskAnalysis/Timeline";
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import { HealthRiskProvider } from "@components/olmap/HealthRiskAnalysis/HealthRiskContext";
+import HealthRiskStatistics from "@components/olmap/HealthRiskAnalysis/HealthRiskStatistics";
+import PredictDataPanel from "@components/olmap/HealthRiskAnalysis/PredictDataPanel";
+import StyleLegend from "@components/olmap/core/Controls/StyleLegend";
+import {
+ RAINBOW_COLORS,
+ RISK_BREAKS,
+} from "@components/olmap/HealthRiskAnalysis/types";
+import { Box } from "@mui/material";
+
+export default function Home() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/hydraulic-simulation/burst-detection/loading.tsx b/src/app/(main)/(map)/hydraulic-simulation/burst-detection/loading.tsx
similarity index 100%
rename from src/app/(main)/hydraulic-simulation/burst-detection/loading.tsx
rename to src/app/(main)/(map)/hydraulic-simulation/burst-detection/loading.tsx
diff --git a/src/app/(main)/(map)/hydraulic-simulation/burst-detection/page.tsx b/src/app/(main)/(map)/hydraulic-simulation/burst-detection/page.tsx
new file mode 100644
index 0000000..ca5408c
--- /dev/null
+++ b/src/app/(main)/(map)/hydraulic-simulation/burst-detection/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import BurstDetectionPanel from "@/components/olmap/BurstDetection/BurstDetectionPanel";
+
+export default function Home() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(main)/hydraulic-simulation/burst-location/loading.tsx b/src/app/(main)/(map)/hydraulic-simulation/burst-location/loading.tsx
similarity index 100%
rename from src/app/(main)/hydraulic-simulation/burst-location/loading.tsx
rename to src/app/(main)/(map)/hydraulic-simulation/burst-location/loading.tsx
diff --git a/src/app/(main)/(map)/hydraulic-simulation/burst-location/page.tsx b/src/app/(main)/(map)/hydraulic-simulation/burst-location/page.tsx
new file mode 100644
index 0000000..d5770b1
--- /dev/null
+++ b/src/app/(main)/(map)/hydraulic-simulation/burst-location/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import BurstLocationPanel from "@/components/olmap/BurstLocation/BurstLocationPanel";
+
+export default function Home() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(main)/hydraulic-simulation/burst-simulation/loading.tsx b/src/app/(main)/(map)/hydraulic-simulation/burst-simulation/loading.tsx
similarity index 100%
rename from src/app/(main)/hydraulic-simulation/burst-simulation/loading.tsx
rename to src/app/(main)/(map)/hydraulic-simulation/burst-simulation/loading.tsx
diff --git a/src/app/(main)/(map)/hydraulic-simulation/burst-simulation/page.tsx b/src/app/(main)/(map)/hydraulic-simulation/burst-simulation/page.tsx
new file mode 100644
index 0000000..699997f
--- /dev/null
+++ b/src/app/(main)/(map)/hydraulic-simulation/burst-simulation/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import BurstPipeAnalysisPanel from "@/components/olmap/BurstSimulation/BurstPipeAnalysisPanel";
+
+export default function Home() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(main)/hydraulic-simulation/contaminant-simulation/loading.tsx b/src/app/(main)/(map)/hydraulic-simulation/contaminant-simulation/loading.tsx
similarity index 100%
rename from src/app/(main)/hydraulic-simulation/contaminant-simulation/loading.tsx
rename to src/app/(main)/(map)/hydraulic-simulation/contaminant-simulation/loading.tsx
diff --git a/src/app/(main)/(map)/hydraulic-simulation/contaminant-simulation/page.tsx b/src/app/(main)/(map)/hydraulic-simulation/contaminant-simulation/page.tsx
new file mode 100644
index 0000000..6addcb6
--- /dev/null
+++ b/src/app/(main)/(map)/hydraulic-simulation/contaminant-simulation/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import WaterQualityPanel from "@/components/olmap/ContaminantSimulation/WaterQualityPanel";
+
+export default function Home() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(main)/hydraulic-simulation/dma-leak-detection/loading.tsx b/src/app/(main)/(map)/hydraulic-simulation/dma-leak-detection/loading.tsx
similarity index 100%
rename from src/app/(main)/hydraulic-simulation/dma-leak-detection/loading.tsx
rename to src/app/(main)/(map)/hydraulic-simulation/dma-leak-detection/loading.tsx
diff --git a/src/app/(main)/(map)/hydraulic-simulation/dma-leak-detection/page.tsx b/src/app/(main)/(map)/hydraulic-simulation/dma-leak-detection/page.tsx
new file mode 100644
index 0000000..39058bb
--- /dev/null
+++ b/src/app/(main)/(map)/hydraulic-simulation/dma-leak-detection/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import DMALeakDetectionPanel from "@/components/olmap/DMALeakDetection/DMALeakDetectionPanel";
+
+export default function Home() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(main)/hydraulic-simulation/flushing-analysis/loading.tsx b/src/app/(main)/(map)/hydraulic-simulation/flushing-analysis/loading.tsx
similarity index 100%
rename from src/app/(main)/hydraulic-simulation/flushing-analysis/loading.tsx
rename to src/app/(main)/(map)/hydraulic-simulation/flushing-analysis/loading.tsx
diff --git a/src/app/(main)/(map)/hydraulic-simulation/flushing-analysis/page.tsx b/src/app/(main)/(map)/hydraulic-simulation/flushing-analysis/page.tsx
new file mode 100644
index 0000000..19df843
--- /dev/null
+++ b/src/app/(main)/(map)/hydraulic-simulation/flushing-analysis/page.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import FlushingAnalysisPanel from "@/components/olmap/FlushingAnalysis/FlushingAnalysisPanel";
+
+export default function Home() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(main)/(map)/layout.tsx b/src/app/(main)/(map)/layout.tsx
new file mode 100644
index 0000000..18c0bae
--- /dev/null
+++ b/src/app/(main)/(map)/layout.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import type { ReactNode } from "react";
+
+import MapComponent from "@components/olmap/core/MapComponent";
+
+export default function MapLayout({ children }: { children: ReactNode }) {
+ return {children};
+}
diff --git a/src/app/(main)/monitoring-place-optimization/loading.tsx b/src/app/(main)/(map)/monitoring-place-optimization/loading.tsx
similarity index 100%
rename from src/app/(main)/monitoring-place-optimization/loading.tsx
rename to src/app/(main)/(map)/monitoring-place-optimization/loading.tsx
diff --git a/src/app/(main)/(map)/monitoring-place-optimization/page.tsx b/src/app/(main)/(map)/monitoring-place-optimization/page.tsx
new file mode 100644
index 0000000..0272841
--- /dev/null
+++ b/src/app/(main)/(map)/monitoring-place-optimization/page.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import MapToolbar from "@components/olmap/core/Controls/Toolbar";
+import MonitoringPlaceOptimizationPanel from "@components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel";
+
+export default function Home() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(main)/network-simulation/loading.tsx b/src/app/(main)/(map)/network-simulation/loading.tsx
similarity index 100%
rename from src/app/(main)/network-simulation/loading.tsx
rename to src/app/(main)/(map)/network-simulation/loading.tsx
diff --git a/src/app/(main)/network-simulation/page.tsx b/src/app/(main)/(map)/network-simulation/page.tsx
similarity index 60%
rename from src/app/(main)/network-simulation/page.tsx
rename to src/app/(main)/(map)/network-simulation/page.tsx
index 18ec7c7..705afe1 100644
--- a/src/app/(main)/network-simulation/page.tsx
+++ b/src/app/(main)/(map)/network-simulation/page.tsx
@@ -1,7 +1,6 @@
"use client";
import { useCallback, useState } from "react";
-import MapComponent from "@components/olmap/core/MapComponent";
import Timeline from "@components/olmap/core/Controls/Timeline";
import MapToolbar from "@components/olmap/core/Controls/Toolbar";
@@ -22,17 +21,15 @@ export default function Home() {
}, []);
return (
-
{
const map = useMap();
@@ -31,14 +32,16 @@ const Scale: React.FC = () => {
updateZoomLevel();
// ScaleLine control
- const scaleControl = new ScaleLine({
- target: scaleLineRef.current || undefined,
- units: "metric",
- bar: false,
- steps: 4,
- text: true,
- minWidth: 64,
- });
+ const scaleControl = markMapResourcePersistent(
+ new ScaleLine({
+ target: scaleLineRef.current || undefined,
+ units: "metric",
+ bar: false,
+ steps: 4,
+ text: true,
+ minWidth: 64,
+ }),
+ );
map.addControl(scaleControl);
return () => {
diff --git a/src/components/olmap/core/MapComponent.tsx b/src/components/olmap/core/MapComponent.tsx
index b8eca89..2936f48 100644
--- a/src/components/olmap/core/MapComponent.tsx
+++ b/src/components/olmap/core/MapComponent.tsx
@@ -17,24 +17,21 @@ import MapTools from "./MapTools";
// 导入 DeckLayer
import { DeckLayer } from "@utils/layers";
-import VectorTileSource from "ol/source/VectorTile";
-import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
-import MVT from "ol/format/MVT";
-import { FlatStyleLike } from "ol/style/flat";
import { toLonLat } from "ol/proj";
-import { along, bearing, lineString, length, toMercator } from "@turf/turf";
+import { along, bearing, lineString, length } from "@turf/turf";
import { Deck } from "@deck.gl/core";
import { TextLayer } from "@deck.gl/layers";
import { TripsLayer } from "@deck.gl/geo-layers";
import { CollisionFilterExtension } from "@deck.gl/extensions";
-import VectorSource from "ol/source/Vector";
-import GeoJson from "ol/format/GeoJSON";
-import VectorLayer from "ol/layer/Vector";
-import { Icon, Style } from "ol/style.js";
-import { FeatureLike } from "ol/Feature";
-import { Point } from "ol/geom";
import { ContourLayer } from "deck.gl";
import { toM3h } from "@utils/units";
+import { usePathname } from "next/navigation";
+import {
+ cleanupTransientMapResources,
+ disposeMapResources,
+ markMapResourcePersistent,
+} from "./mapLifecycle";
+import { createOperationalMapResources } from "./operationalLayers";
interface MapComponentProps {
children?: React.ReactNode;
@@ -131,6 +128,7 @@ export const useData = () => {
};
const MapComponent: React.FC = ({ children }) => {
+ const pathname = usePathname();
const project = useProject();
const MAP_WORKSPACE = project?.workspace || config.MAP_WORKSPACE;
const MAP_EXTENT = (project?.extent || config.MAP_EXTENT) as [
@@ -371,402 +369,20 @@ const MapComponent: React.FC = ({ children }) => {
debouncedUpdateDataRef.current = null;
};
}, []);
- // 配置地图数据源、图层和样式
- const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
- // 定义 SCADA 图层的样式函数,根据 type 字段选择不同图标
- const scadaStyle = (feature: any) => {
- const type = feature.get("type");
- const scadaPressureIcon = "/icons/scada_pressure.svg";
- const scadaFlowIcon = "/icons/scada_flow.svg";
- // 如果 type 不匹配,可以设置默认图标或不显示
- return new Style({
- image: new Icon({
- src: type === "pipe_flow" ? scadaFlowIcon : scadaPressureIcon,
- scale: 0.1, // 根据需要调整图标大小
- anchor: [0.5, 0.5], // 图标锚点居中
+ const operationalResources = useMemo(
+ () =>
+ createOperationalMapResources({
+ mapUrl: MAP_URL,
+ workspace: MAP_WORKSPACE,
+ extent: MAP_EXTENT,
+ persistent: true,
}),
- });
- };
- // 定义 reservoirs 图层的样式函数,使用固定图标
- const reservoirStyle = () => {
- const reserviorIcon = "/icons/reservior.svg";
- return new Style({
- image: new Icon({
- src: reserviorIcon,
- scale: 0.1, // 根据需要调整图标大小
- anchor: [0.5, 0.5], // 图标锚点居中
- }),
- });
- };
- // 定义 tanks 图层的样式函数,使用固定图标
- const tankStyle = () => {
- const tankIcon = "/icons/tank.svg";
- return new Style({
- image: new Icon({
- src: tankIcon,
- scale: 0.1, // 根据需要调整图标大小
- anchor: [0.5, 0.5], // 图标锚点居中
- }),
- });
- };
- const valveStyle = {
- "icon-src": "/icons/valve.svg",
- "icon-scale": 0.1,
- };
- // 定义 pumps 图层的样式函数,使用固定图标
- const pumpStyle = function (feature: FeatureLike) {
- const styles = [];
- const pumpIcon = "/icons/pump.svg";
-
- const geometry = feature.getGeometry();
- const lineCoords =
- geometry?.getType() === "LineString"
- ? (geometry as any).getCoordinates()
- : null;
- if (geometry) {
- const lineCoordsWGS84 = lineCoords.map((coord: []) => {
- const [lon, lat] = toLonLat(coord);
- return [lon, lat];
- });
- // 计算中点
- const lineStringFeature = lineString(lineCoordsWGS84);
- const lineLength = length(lineStringFeature);
- const midPoint = along(lineStringFeature, lineLength / 2).geometry
- .coordinates;
- // 在中点添加 icon 样式
- const midPointMercator = toMercator(midPoint);
- styles.push(
- new Style({
- geometry: new Point(midPointMercator),
- image: new Icon({
- src: pumpIcon,
- scale: 0.12,
- anchor: [0.5, 0.5],
- }),
- }),
- );
- }
- return styles;
- };
- // 矢量瓦片数据源和图层
- const junctionSource = new VectorTileSource({
- url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_junctions@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL
- format: new MVT(),
- projection: "EPSG:3857",
- });
- const pipeSource = new VectorTileSource({
- url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_pipes@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL
- format: new MVT(),
- projection: "EPSG:3857",
- });
- const valveSource = new VectorTileSource({
- url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_valves@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL
- format: new MVT(),
- projection: "EPSG:3857",
- });
- const reservoirSource = new VectorSource({
- url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_reservoirs&outputFormat=application/json`,
- format: new GeoJson(),
- });
- const pumpSource = new VectorSource({
- url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_pumps&outputFormat=application/json`,
- format: new GeoJson(),
- });
- const tankSource = new VectorSource({
- url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_tanks&outputFormat=application/json`,
- format: new GeoJson(),
- });
- const scadaSource = new VectorSource({
- url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_scada&outputFormat=application/json`,
- format: new GeoJson(),
- });
-
- // WebGL 渲染优化显示
- const junctionsLayer = new WebGLVectorTileLayer({
- source: junctionSource as any, // 使用 WebGL 渲染
- style: defaultFlatStyle,
- extent: MAP_EXTENT, // 设置图层范围
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "节点", // 设置图层名称
- value: "junctions",
- type: "point",
- properties: [
- // { name: "需求量", value: "demand" },
- { name: "高程", value: "elevation" },
- // 计算属性
- { name: "实际需水量", value: "actual_demand" },
- { name: "水头", value: "total_head" },
- { name: "压力", value: "pressure" },
- { name: "水质", value: "quality" },
- ],
- },
- });
- const pipesLayer = new WebGLVectorTileLayer({
- source: pipeSource as any, // 使用 WebGL 渲染
- style: defaultFlatStyle,
- extent: MAP_EXTENT, // 设置图层范围
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "管道", // 设置图层名称
- value: "pipes",
- type: "linestring",
- properties: [
- { name: "管径", value: "diameter" },
- // { name: "粗糙度", value: "roughness" },
- // { name: "局部损失", value: "minor_loss" },
- // 计算属性
- { name: "流量", value: "flow" },
- { name: "摩阻系数", value: "friction" },
- { name: "水头损失", value: "headloss" },
- { name: "单位水头损失", value: "unit_headloss" },
- { name: "水质", value: "quality" },
- { name: "反应速率", value: "reaction" },
- { name: "设置值", value: "setting" },
- { name: "状态", value: "status" },
- { name: "流速", value: "velocity" },
- ],
- },
- });
- const valvesLayer = new WebGLVectorTileLayer({
- source: valveSource as any,
- style: valveStyle,
- extent: MAP_EXTENT, // 设置图层范围
- maxZoom: 24,
- minZoom: 16,
- properties: {
- name: "阀门", // 设置图层名称
- value: "valves",
- type: "linestring",
- properties: [],
- },
- });
- const reservoirsLayer = new VectorLayer({
- source: reservoirSource,
- style: reservoirStyle,
- extent: MAP_EXTENT, // 设置图层范围
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "水库", // 设置图层名称
- value: "reservoirs",
- type: "point",
- properties: [],
- },
- });
- const pumpsLayer = new VectorLayer({
- source: pumpSource,
- style: pumpStyle,
- extent: MAP_EXTENT, // 设置图层范围
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "水泵", // 设置图层名称
- value: "pumps",
- type: "linestring",
- properties: [],
- },
- });
- const tanksLayer = new VectorLayer({
- source: tankSource,
- style: tankStyle,
- extent: MAP_EXTENT, // 设置图层范围
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "水箱", // 设置图层名称
- value: "tanks",
- type: "point",
- properties: [],
- },
- });
- const scadaLayer = new VectorLayer({
- source: scadaSource,
- style: scadaStyle,
- extent: MAP_EXTENT, // 设置图层范围
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "SCADA", // 设置图层名称
- value: "scada",
- type: "point",
- properties: [],
- },
- });
-
- const createOperationalLayers = () => {
- const nextJunctionSource = new VectorTileSource({
- url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_junctions@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`,
- format: new MVT(),
- projection: "EPSG:3857",
- });
- const nextPipeSource = new VectorTileSource({
- url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_pipes@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`,
- format: new MVT(),
- projection: "EPSG:3857",
- });
- const nextJunctionsLayer = new WebGLVectorTileLayer({
- source: nextJunctionSource as any,
- style: defaultFlatStyle,
- extent: MAP_EXTENT,
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "节点",
- value: "junctions",
- type: "point",
- properties: [
- { name: "高程", value: "elevation" },
- { name: "实际需水量", value: "actual_demand" },
- { name: "水头", value: "total_head" },
- { name: "压力", value: "pressure" },
- { name: "水质", value: "quality" },
- ],
- },
- });
- const nextPipesLayer = new WebGLVectorTileLayer({
- source: nextPipeSource as any,
- style: defaultFlatStyle,
- extent: MAP_EXTENT,
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "管道",
- value: "pipes",
- type: "linestring",
- properties: [
- { name: "管径", value: "diameter" },
- { name: "流量", value: "flow" },
- { name: "摩阻系数", value: "friction" },
- { name: "水头损失", value: "headloss" },
- { name: "单位水头损失", value: "unit_headloss" },
- { name: "水质", value: "quality" },
- { name: "反应速率", value: "reaction" },
- { name: "设置值", value: "setting" },
- { name: "状态", value: "status" },
- { name: "流速", value: "velocity" },
- ],
- },
- });
- const nextValvesLayer = new WebGLVectorTileLayer({
- source: valveSource as any,
- style: valveStyle,
- extent: MAP_EXTENT,
- maxZoom: 24,
- minZoom: 16,
- properties: {
- name: "阀门",
- value: "valves",
- type: "linestring",
- properties: [],
- },
- });
- const nextReservoirsLayer = new VectorLayer({
- source: reservoirSource,
- style: reservoirStyle,
- extent: MAP_EXTENT,
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "水库",
- value: "reservoirs",
- type: "point",
- properties: [],
- },
- });
- const nextPumpsLayer = new VectorLayer({
- source: pumpSource,
- style: pumpStyle,
- extent: MAP_EXTENT,
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "水泵",
- value: "pumps",
- type: "linestring",
- properties: [],
- },
- });
- const nextTanksLayer = new VectorLayer({
- source: tankSource,
- style: tankStyle,
- extent: MAP_EXTENT,
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "水箱",
- value: "tanks",
- type: "point",
- properties: [],
- },
- });
- const nextScadaLayer = new VectorLayer({
- source: scadaSource,
- style: scadaStyle,
- extent: MAP_EXTENT,
- maxZoom: 24,
- minZoom: 11,
- properties: {
- name: "SCADA",
- value: "scada",
- type: "point",
- properties: [],
- },
- });
-
- const availableLayers: any[] = [];
- config.MAP_AVAILABLE_LAYERS.forEach((layerValue) => {
- switch (layerValue) {
- case "junctions":
- availableLayers.push(nextJunctionsLayer);
- break;
- case "pipes":
- availableLayers.push(nextPipesLayer);
- break;
- case "valves":
- availableLayers.push(nextValvesLayer);
- break;
- case "reservoirs":
- availableLayers.push(nextReservoirsLayer);
- break;
- case "pumps":
- availableLayers.push(nextPumpsLayer);
- break;
- case "tanks":
- availableLayers.push(nextTanksLayer);
- break;
- case "scada":
- availableLayers.push(nextScadaLayer);
- break;
- }
- });
- availableLayers.sort((a, b) => {
- const order = [
- "valves",
- "junctions",
- "scada",
- "reservoirs",
- "pumps",
- "tanks",
- "pipes",
- ].reverse();
- const getValue = (layer: any) => {
- const props = layer.get ? layer.get("properties") : undefined;
- return (props && props.value) || layer.get?.("value") || "";
- };
- const aVal = getValue(a);
- const bVal = getValue(b);
- let ia = order.indexOf(aVal);
- let ib = order.indexOf(bVal);
- if (ia === -1) ia = order.length;
- if (ib === -1) ib = order.length;
- return ia - ib;
- });
-
- return availableLayers;
- };
+ [MAP_URL, MAP_WORKSPACE, MAP_EXTENT],
+ );
+ const { junctions: junctionSource, pipes: pipeSource } =
+ operationalResources.sources;
+ const { junctions: junctionsLayer, pipes: pipesLayer } =
+ operationalResources.layers;
// The map and layer instances are intentionally rebuilt only when workspace or extent changes.
useEffect(() => {
@@ -775,6 +391,8 @@ const MapComponent: React.FC = ({ children }) => {
return;
}
isDisposingRef.current = false;
+ const activeJunctionDataIds = junctionDataIds.current;
+ const activePipeDataIds = pipeDataIds.current;
const addTimeout = (callback: () => void, delay: number) => {
const timerId = window.setTimeout(() => {
@@ -927,58 +545,6 @@ const MapComponent: React.FC = ({ children }) => {
// 添加事件监听器
junctionsLayer.on("change:visible", handleJunctionVisibilityChange);
pipesLayer.on("change:visible", handlePipeVisibilityChange);
- const availableLayers: any[] = [];
- config.MAP_AVAILABLE_LAYERS.forEach((layerValue) => {
- switch (layerValue) {
- case "junctions":
- availableLayers.push(junctionsLayer);
- break;
- case "pipes":
- availableLayers.push(pipesLayer);
- break;
- case "valves":
- availableLayers.push(valvesLayer);
- break;
- case "reservoirs":
- availableLayers.push(reservoirsLayer);
- break;
- case "pumps":
- availableLayers.push(pumpsLayer);
- break;
- case "tanks":
- availableLayers.push(tanksLayer);
- break;
- case "scada":
- availableLayers.push(scadaLayer);
- break;
- }
- });
- // 重新排列图层顺序,确保顺序 点>线>面
- availableLayers.sort((a, b) => {
- // 明确顺序(点类优先),这里 valves 特殊处理
- const order = [
- "valves",
- "junctions",
- "scada",
- "reservoirs",
- "pumps",
- "tanks",
- "pipes",
- ].reverse();
- // 取值时做安全检查,兼容不同写法(properties.value 或 直接 value)
- const getValue = (layer: any) => {
- const props = layer.get ? layer.get("properties") : undefined;
- return (props && props.value) || layer.get?.("value") || "";
- };
- const aVal = getValue(a);
- const bVal = getValue(b);
- let ia = order.indexOf(aVal);
- let ib = order.indexOf(bVal);
- // 如果未在 order 中找到,放到末尾
- if (ia === -1) ia = order.length;
- if (ib === -1) ib = order.length;
- return ia - ib;
- });
const map = new OlMap({
target: mapRef.current,
view: new View({
@@ -986,9 +552,10 @@ const MapComponent: React.FC = ({ children }) => {
projection: "EPSG:3857",
}),
// 图层依面、线、点、标注次序添加
- layers: availableLayers.slice(),
+ layers: operationalResources.orderedLayers.slice(),
controls: [],
});
+ map.getInteractions().forEach(markMapResourcePersistent);
setMap(map);
// 恢复上次视图;如果没有则适配 MAP_EXTENT
@@ -1072,10 +639,12 @@ const MapComponent: React.FC = ({ children }) => {
controller: false, // 由 OpenLayers 控制视图
layers: [],
});
- const deckLayer = new DeckLayer(deck, canvasRef.current, {
- name: "deckLayer",
- value: "deckLayer",
- });
+ const deckLayer = markMapResourcePersistent(
+ new DeckLayer(deck, canvasRef.current, {
+ name: "deckLayer",
+ value: "deckLayer",
+ }),
+ );
deckLayerRef.current = deckLayer;
setDeckLayer(deckLayer);
map.addLayer(deckLayer);
@@ -1101,8 +670,13 @@ const MapComponent: React.FC = ({ children }) => {
}
deckLayerRef.current = null;
setDeckLayer(undefined);
- map.setTarget(undefined);
- map.dispose();
+ // React Strict Mode re-runs effects with the same memoized layer instances.
+ // Detach and clear them here, but leave final layer/source disposal to GC.
+ disposeMapResources(map, { disposeLayers: false });
+ activeJunctionDataIds.clear();
+ activePipeDataIds.clear();
+ tileJunctionDataBuffer.current = [];
+ tilePipeDataBuffer.current = [];
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [MAP_WORKSPACE, MAP_EXTENT]);
@@ -1117,11 +691,15 @@ const MapComponent: React.FC = ({ children }) => {
if (!map || !compareMapRef.current || !compareCanvasRef.current) return;
isCompareDisposingRef.current = false;
- const availableLayers = createOperationalLayers();
+ const compareResources = createOperationalMapResources({
+ mapUrl: MAP_URL,
+ workspace: MAP_WORKSPACE,
+ extent: MAP_EXTENT,
+ });
const nextCompareMap = new OlMap({
target: compareMapRef.current,
view: map.getView(),
- layers: availableLayers.slice(),
+ layers: compareResources.orderedLayers.slice(),
controls: [],
});
nextCompareMap.getAllLayers().forEach((layer) => {
@@ -1180,8 +758,7 @@ const MapComponent: React.FC = ({ children }) => {
compareDeckLayerRef.current = null;
setCompareDeckLayer(undefined);
setCompareMap(undefined);
- nextCompareMap.setTarget(undefined);
- nextCompareMap.dispose();
+ disposeMapResources(nextCompareMap);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isCompareMode, map]);
@@ -1197,6 +774,34 @@ const MapComponent: React.FC = ({ children }) => {
};
}, [compareMap, isCompareMode, map]);
+ useEffect(() => {
+ setCurrentTime(-1);
+ setSelectedDate(new Date());
+ setSchemeName("");
+ setCurrentJunctionCalData([]);
+ setCurrentPipeCalData([]);
+ setCompareJunctionCalData([]);
+ setComparePipeCalData([]);
+ setCompareMode(false);
+ setShowJunctionTextLayer(false);
+ setShowPipeTextLayer(false);
+ setShowJunctionId(false);
+ setShowPipeId(false);
+ setShowContourLayer(false);
+ setContours([]);
+ setContourLayerAvailable(false);
+ setWaterflowLayerAvailable(false);
+ setShowWaterflowLayer(false);
+ setForceStyleAutoApplyVersion(0);
+
+ return () => {
+ if (!map) return;
+ cleanupTransientMapResources(map);
+ deckLayerRef.current?.resetSessionLayers();
+ operationalResources.resetStyles();
+ };
+ }, [pathname, map, operationalResources]);
+
// 当数据变化时,更新 deck.gl 图层
useEffect(() => {
const syncDeckOverlay = (
@@ -1348,7 +953,10 @@ const MapComponent: React.FC = ({ children }) => {
})
: null;
- if (junctionTextLayer && targetDeckLayer.getDeckLayerById("junctionTextLayer")) {
+ if (
+ junctionTextLayer &&
+ targetDeckLayer.getDeckLayerById("junctionTextLayer")
+ ) {
targetDeckLayer.updateDeckLayer("junctionTextLayer", junctionTextLayer);
} else if (junctionTextLayer) {
targetDeckLayer.addDeckLayer(junctionTextLayer);
@@ -1358,7 +966,10 @@ const MapComponent: React.FC = ({ children }) => {
} else if (pipeTextLayer) {
targetDeckLayer.addDeckLayer(pipeTextLayer);
}
- if (contourLayer && targetDeckLayer.getDeckLayerById("junctionContourLayer")) {
+ if (
+ contourLayer &&
+ targetDeckLayer.getDeckLayerById("junctionContourLayer")
+ ) {
targetDeckLayer.updateDeckLayer("junctionContourLayer", contourLayer);
} else if (contourLayer) {
targetDeckLayer.addDeckLayer(contourLayer);
diff --git a/src/components/olmap/core/mapLifecycle.test.ts b/src/components/olmap/core/mapLifecycle.test.ts
new file mode 100644
index 0000000..38c4056
--- /dev/null
+++ b/src/components/olmap/core/mapLifecycle.test.ts
@@ -0,0 +1,94 @@
+import {
+ cleanupTransientMapResources,
+ disposeMapResources,
+ isMapResourcePersistent,
+ markMapResourcePersistent,
+} from "./mapLifecycle";
+
+const createCollection = (items: T[]) => ({
+ getArray: () => items,
+ clear: () => items.splice(0, items.length),
+});
+
+const createResource = () => {
+ const properties = new Map();
+ return {
+ get: (key: string) => properties.get(key),
+ set: (key: string, value: unknown) => properties.set(key, value),
+ dispose: jest.fn(),
+ };
+};
+
+describe("map lifecycle", () => {
+ it("preserves shared resources and releases session resources", () => {
+ const persistentLayer = markMapResourcePersistent(createResource());
+ const transientSource = { clear: jest.fn(), dispose: jest.fn() };
+ const transientLayer = {
+ ...createResource(),
+ getSource: () => transientSource,
+ };
+ const persistentInteraction = markMapResourcePersistent(createResource());
+ const transientInteraction = createResource();
+ const persistentControl = markMapResourcePersistent(createResource());
+ const transientControl = createResource();
+
+ const layers = [persistentLayer, transientLayer];
+ const interactions = [persistentInteraction, transientInteraction];
+ const controls = [persistentControl, transientControl];
+ const overlays: ReturnType[] = [];
+ const map = {
+ getLayers: () => createCollection(layers),
+ removeLayer: (resource: unknown) => layers.splice(layers.indexOf(resource as never), 1),
+ getInteractions: () => createCollection(interactions),
+ removeInteraction: (resource: unknown) =>
+ interactions.splice(interactions.indexOf(resource as never), 1),
+ getControls: () => createCollection(controls),
+ removeControl: (resource: unknown) => controls.splice(controls.indexOf(resource as never), 1),
+ getOverlays: () => createCollection(overlays),
+ removeOverlay: (resource: unknown) => overlays.splice(overlays.indexOf(resource as never), 1),
+ } as any;
+
+ cleanupTransientMapResources(map);
+
+ expect(layers).toEqual([persistentLayer]);
+ expect(interactions).toEqual([persistentInteraction]);
+ expect(controls).toEqual([persistentControl]);
+ expect(transientSource.clear).toHaveBeenCalledTimes(1);
+ expect(transientSource.dispose).toHaveBeenCalledTimes(1);
+ expect(transientLayer.dispose).toHaveBeenCalledTimes(1);
+ expect(transientInteraction.dispose).toHaveBeenCalledTimes(1);
+ expect(transientControl.dispose).toHaveBeenCalledTimes(1);
+ });
+
+ it("marks only explicitly shared resources as persistent", () => {
+ const resource = createResource();
+ expect(isMapResourcePersistent(resource)).toBe(false);
+ markMapResourcePersistent(resource);
+ expect(isMapResourcePersistent(resource)).toBe(true);
+ });
+
+ it("keeps memoized layers reusable across Strict Mode effect cleanup", () => {
+ const source = { clear: jest.fn(), dispose: jest.fn() };
+ const layer = { ...createResource(), getSource: () => source };
+ const layers = [layer];
+ const interactions: ReturnType[] = [];
+ const controls: ReturnType[] = [];
+ const overlays: ReturnType[] = [];
+ const map = {
+ getLayers: () => createCollection(layers),
+ removeLayer: (resource: unknown) => layers.splice(layers.indexOf(resource as never), 1),
+ getInteractions: () => createCollection(interactions),
+ getControls: () => createCollection(controls),
+ getOverlays: () => createCollection(overlays),
+ setTarget: jest.fn(),
+ dispose: jest.fn(),
+ } as any;
+
+ disposeMapResources(map, { disposeLayers: false });
+
+ expect(source.clear).toHaveBeenCalledTimes(1);
+ expect(source.dispose).not.toHaveBeenCalled();
+ expect(layer.dispose).not.toHaveBeenCalled();
+ expect(map.dispose).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/components/olmap/core/mapLifecycle.ts b/src/components/olmap/core/mapLifecycle.ts
new file mode 100644
index 0000000..8e6404c
--- /dev/null
+++ b/src/components/olmap/core/mapLifecycle.ts
@@ -0,0 +1,90 @@
+import type { Map as OlMap } from "ol";
+
+const RESOURCE_SCOPE_KEY = "tjwater_resource_scope";
+const PERSISTENT_SCOPE = "persistent";
+
+type MapResource = {
+ get?: (key: string) => unknown;
+ set?: (key: string, value: unknown, silent?: boolean) => void;
+ dispose?: () => void;
+};
+
+export const markMapResourcePersistent = (resource: T): T => {
+ resource.set?.(RESOURCE_SCOPE_KEY, PERSISTENT_SCOPE, true);
+ return resource;
+};
+
+export const isMapResourcePersistent = (resource: MapResource) =>
+ resource.get?.(RESOURCE_SCOPE_KEY) === PERSISTENT_SCOPE;
+
+const removeTransientResources = (
+ resources: T[],
+ remove: (resource: T) => void,
+) => {
+ [...resources].forEach((resource) => {
+ if (isMapResourcePersistent(resource)) return;
+ remove(resource);
+ resource.dispose?.();
+ });
+};
+
+const releaseLayer = (layer: any, dispose: boolean) => {
+ const childLayers = layer.getLayers?.().getArray?.();
+ if (Array.isArray(childLayers)) {
+ [...childLayers].forEach((childLayer) => releaseLayer(childLayer, dispose));
+ layer.getLayers().clear();
+ }
+
+ const source = layer.getSource?.();
+ try {
+ source?.clear?.();
+ } catch {
+ // Some third-party sources do not support explicit cache clearing.
+ }
+ if (dispose) {
+ try {
+ source?.dispose?.();
+ } catch {
+ // Source may already be disposed by its owning layer.
+ }
+ try {
+ layer.dispose?.();
+ } catch {
+ // Cleanup is deliberately idempotent for overlapping route unmounts.
+ }
+ }
+};
+
+export const cleanupTransientMapResources = (map: OlMap) => {
+ [...map.getLayers().getArray()].forEach((layer) => {
+ if (isMapResourcePersistent(layer)) return;
+ map.removeLayer(layer);
+ releaseLayer(layer, true);
+ });
+
+ removeTransientResources(map.getInteractions().getArray(), (interaction) =>
+ map.removeInteraction(interaction),
+ );
+ removeTransientResources(map.getControls().getArray(), (control) =>
+ map.removeControl(control),
+ );
+ removeTransientResources(map.getOverlays().getArray(), (overlay) =>
+ map.removeOverlay(overlay),
+ );
+};
+
+export const disposeMapResources = (
+ map: OlMap,
+ options: { disposeLayers?: boolean } = {},
+) => {
+ const disposeLayers = options.disposeLayers ?? true;
+ [...map.getLayers().getArray()].forEach((layer) => {
+ map.removeLayer(layer);
+ releaseLayer(layer, disposeLayers);
+ });
+ map.getInteractions().clear();
+ map.getControls().clear();
+ map.getOverlays().clear();
+ map.setTarget(undefined);
+ map.dispose();
+};
diff --git a/src/components/olmap/core/operationalLayers.ts b/src/components/olmap/core/operationalLayers.ts
new file mode 100644
index 0000000..7f5ecba
--- /dev/null
+++ b/src/components/olmap/core/operationalLayers.ts
@@ -0,0 +1,255 @@
+import { config } from "@/config/config";
+import { along, lineString, length, toMercator } from "@turf/turf";
+import type { FeatureLike } from "ol/Feature";
+import MVT from "ol/format/MVT";
+import { Point } from "ol/geom";
+import type BaseLayer from "ol/layer/Base";
+import VectorLayer from "ol/layer/Vector";
+import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
+import { toLonLat } from "ol/proj";
+import GeoJSON from "ol/format/GeoJSON";
+import VectorSource from "ol/source/Vector";
+import VectorTileSource from "ol/source/VectorTile";
+import { Icon, Style } from "ol/style";
+import type { FlatStyleLike } from "ol/style/flat";
+
+import { markMapResourcePersistent } from "./mapLifecycle";
+
+type MapExtent = [number, number, number, number];
+
+type CreateOperationalLayersOptions = {
+ mapUrl: string;
+ workspace: string;
+ extent: MapExtent;
+ persistent?: boolean;
+};
+
+const defaultFlatStyle = config.MAP_DEFAULT_STYLE as FlatStyleLike;
+const valveStyle = {
+ "icon-src": "/icons/valve.svg",
+ "icon-scale": 0.1,
+};
+
+const createIconStyle = (src: string, scale = 0.1) =>
+ new Style({ image: new Icon({ src, scale, anchor: [0.5, 0.5] }) });
+
+const scadaStyle = (feature: FeatureLike) =>
+ createIconStyle(
+ feature.get("type") === "pipe_flow"
+ ? "/icons/scada_flow.svg"
+ : "/icons/scada_pressure.svg",
+ );
+
+const pumpStyle = (feature: FeatureLike) => {
+ const geometry = feature.getGeometry();
+ if (!geometry || geometry.getType() !== "LineString") return [];
+
+ const coordinates = (geometry as any)
+ .getCoordinates()
+ .map((coordinate: number[]) => toLonLat(coordinate));
+ if (coordinates.length < 2) return [];
+
+ const featureLine = lineString(coordinates);
+ const midpoint = along(featureLine, length(featureLine) / 2).geometry
+ .coordinates;
+ return [
+ new Style({
+ geometry: new Point(toMercator(midpoint)),
+ image: new Icon({
+ src: "/icons/pump.svg",
+ scale: 0.12,
+ anchor: [0.5, 0.5],
+ }),
+ }),
+ ];
+};
+
+const pointProperties = [
+ { name: "高程", value: "elevation" },
+ { name: "实际需水量", value: "actual_demand" },
+ { name: "水头", value: "total_head" },
+ { name: "压力", value: "pressure" },
+ { name: "水质", value: "quality" },
+];
+
+const pipeProperties = [
+ { name: "管径", value: "diameter" },
+ { name: "流量", value: "flow" },
+ { name: "摩阻系数", value: "friction" },
+ { name: "水头损失", value: "headloss" },
+ { name: "单位水头损失", value: "unit_headloss" },
+ { name: "水质", value: "quality" },
+ { name: "反应速率", value: "reaction" },
+ { name: "设置值", value: "setting" },
+ { name: "状态", value: "status" },
+ { name: "流速", value: "velocity" },
+];
+
+export const createOperationalMapResources = ({
+ mapUrl,
+ workspace,
+ extent,
+ persistent = false,
+}: CreateOperationalLayersOptions) => {
+ const vectorTileUrl = (name: string) =>
+ `${mapUrl}/gwc/service/tms/1.0.0/${workspace}:${name}@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`;
+ const vectorUrl = (name: string) =>
+ `${mapUrl}/${workspace}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${workspace}:${name}&outputFormat=application/json`;
+
+ const sources = {
+ junctions: new VectorTileSource({
+ url: vectorTileUrl("geo_junctions"),
+ format: new MVT(),
+ projection: "EPSG:3857",
+ }),
+ pipes: new VectorTileSource({
+ url: vectorTileUrl("geo_pipes"),
+ format: new MVT(),
+ projection: "EPSG:3857",
+ }),
+ valves: new VectorTileSource({
+ url: vectorTileUrl("geo_valves"),
+ format: new MVT(),
+ projection: "EPSG:3857",
+ }),
+ reservoirs: new VectorSource({
+ url: vectorUrl("geo_reservoirs"),
+ format: new GeoJSON(),
+ }),
+ pumps: new VectorSource({
+ url: vectorUrl("geo_pumps"),
+ format: new GeoJSON(),
+ }),
+ tanks: new VectorSource({
+ url: vectorUrl("geo_tanks"),
+ format: new GeoJSON(),
+ }),
+ scada: new VectorSource({
+ url: vectorUrl("geo_scada"),
+ format: new GeoJSON(),
+ }),
+ };
+
+ const layers = {
+ junctions: new WebGLVectorTileLayer({
+ source: sources.junctions as any,
+ style: defaultFlatStyle,
+ extent,
+ maxZoom: 24,
+ minZoom: 11,
+ properties: {
+ name: "节点",
+ value: "junctions",
+ type: "point",
+ properties: pointProperties,
+ },
+ }),
+ pipes: new WebGLVectorTileLayer({
+ source: sources.pipes as any,
+ style: defaultFlatStyle,
+ extent,
+ maxZoom: 24,
+ minZoom: 11,
+ properties: {
+ name: "管道",
+ value: "pipes",
+ type: "linestring",
+ properties: pipeProperties,
+ },
+ }),
+ valves: new WebGLVectorTileLayer({
+ source: sources.valves as any,
+ style: valveStyle,
+ extent,
+ maxZoom: 24,
+ minZoom: 16,
+ properties: {
+ name: "阀门",
+ value: "valves",
+ type: "linestring",
+ properties: [],
+ },
+ }),
+ reservoirs: new VectorLayer({
+ source: sources.reservoirs,
+ style: () => createIconStyle("/icons/reservior.svg"),
+ extent,
+ maxZoom: 24,
+ minZoom: 11,
+ properties: {
+ name: "水库",
+ value: "reservoirs",
+ type: "point",
+ properties: [],
+ },
+ }),
+ pumps: new VectorLayer({
+ source: sources.pumps,
+ style: pumpStyle,
+ extent,
+ maxZoom: 24,
+ minZoom: 11,
+ properties: {
+ name: "水泵",
+ value: "pumps",
+ type: "linestring",
+ properties: [],
+ },
+ }),
+ tanks: new VectorLayer({
+ source: sources.tanks,
+ style: () => createIconStyle("/icons/tank.svg"),
+ extent,
+ maxZoom: 24,
+ minZoom: 11,
+ properties: {
+ name: "水箱",
+ value: "tanks",
+ type: "point",
+ properties: [],
+ },
+ }),
+ scada: new VectorLayer({
+ source: sources.scada,
+ style: scadaStyle,
+ extent,
+ maxZoom: 24,
+ minZoom: 11,
+ properties: {
+ name: "SCADA",
+ value: "scada",
+ type: "point",
+ properties: [],
+ },
+ }),
+ };
+
+ if (persistent) {
+ Object.values(layers).forEach(markMapResourcePersistent);
+ }
+
+ const layerById = layers as Record;
+ const enabledLayers = new Set(config.MAP_AVAILABLE_LAYERS);
+ const orderedLayers = [
+ "pipes",
+ "tanks",
+ "pumps",
+ "reservoirs",
+ "scada",
+ "junctions",
+ "valves",
+ ]
+ .filter((id) => enabledLayers.has(id))
+ .map((id) => layerById[id]);
+
+ return {
+ sources,
+ layers,
+ orderedLayers,
+ resetStyles: () => {
+ layers.junctions.setStyle(defaultFlatStyle);
+ layers.pipes.setStyle(defaultFlatStyle);
+ layers.valves.setStyle(valveStyle);
+ },
+ };
+};
diff --git a/src/utils/layers.ts b/src/utils/layers.ts
index c9ecc30..32ed54a 100644
--- a/src/utils/layers.ts
+++ b/src/utils/layers.ts
@@ -70,6 +70,12 @@ export class DeckLayer extends Layer {
this.deck.setProps({ layers });
}
+ resetSessionLayers(): void {
+ if (this.isDisposed) return;
+ this.userVisibility.clear();
+ this.deck.setProps({ layers: [] });
+ }
+
// 获取当前图层
getDeckLayers(): any[] {
if (this.isDisposed) return [];