From dc7271e3dad05f4b74aab34a33331bfca9857a3e Mon Sep 17 00:00:00 2001 From: JIANG Date: Tue, 25 Nov 2025 10:05:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E5=B1=82=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/OlMap/Controls/LayerControl.tsx | 273 ++++++++++---------- src/app/OlMap/Controls/StyleEditorPanel.tsx | 115 +-------- src/app/OlMap/MapComponent.tsx | 57 +++- 3 files changed, 201 insertions(+), 244 deletions(-) diff --git a/src/app/OlMap/Controls/LayerControl.tsx b/src/app/OlMap/Controls/LayerControl.tsx index a669a9f..fd12c38 100644 --- a/src/app/OlMap/Controls/LayerControl.tsx +++ b/src/app/OlMap/Controls/LayerControl.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from "react"; -import { useMap } from "../MapComponent"; +import React, { useState, useEffect, useCallback } from "react"; +import { useData, useMap } from "../MapComponent"; import { Layer } from "ol/layer"; import { Checkbox, FormControlLabel } from "@mui/material"; import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile"; @@ -7,159 +7,168 @@ import VectorLayer from "ol/layer/Vector"; import VectorTileLayer from "ol/layer/VectorTile"; import { DeckLayer } from "@utils/layers"; +// 定义统一的图层项接口 +interface LayerItem { + id: string; + name: string; + visible: boolean; + type: "ol" | "deck"; + layerRef: any; // OpenLayers Layer 实例或 deck.gl layer 对象 +} + const LayerControl: React.FC = () => { const map = useMap(); - const [layers, setLayers] = useState([]); - const [layerVisibilities, setLayerVisibilities] = useState< - Map - >(new Map()); - const userChangedRef = React.useRef>(new Set()); + const data = useData(); + if (!data) return; + const { + deckLayer, + isContourLayerAvailable, + isWaterflowLayerAvailable, + setShowContourLayer, + flowAnimation, + } = data; + const [layerItems, setLayerItems] = useState([]); - useEffect(() => { - if (!map) return; + // 更新图层列表 + const updateLayers = useCallback(() => { + if (!map || !data) return; + const { deckLayer } = data; - const updateLayers = () => { - const mapLayers = map - .getLayers() - .getArray() - .filter( - (layer) => - layer instanceof WebGLVectorTileLayer || - layer instanceof VectorTileLayer || - layer instanceof VectorLayer - ) as Layer[]; + const items: LayerItem[] = []; - // 查找包含 waterflowLayer 的 DeckLayer - const deckFlowLayers = map - .getLayers() - .getArray() - .filter((layer) => { - if (layer instanceof DeckLayer) { - const deckLayers = layer.getDeckLayers(); - // 检查是否包含 waterflowLayer - return deckLayers.some((dl: any) => dl.id === "waterflowLayer"); + // 1. 获取 OpenLayers 图层 + const mapLayers = map.getLayers().getArray(); + mapLayers.forEach((layer) => { + // 筛选特定类型的 OpenLayers 图层 + if ( + layer instanceof WebGLVectorTileLayer || + layer instanceof VectorTileLayer || + layer instanceof VectorLayer + ) { + const value = layer.get("value"); + const name = layer.get("name"); + // 只有设置了 value (作为 ID) 的图层才会被纳入控制 + if (value) { + items.push({ + id: value, + name: name || value, + visible: layer.getVisible(), + type: "ol", + layerRef: layer, + }); + } + } + }); + + // 2. 获取 DeckLayer 中的子图层 + if (deckLayer && deckLayer instanceof DeckLayer) { + const deckLayers = deckLayer.getDeckLayers(); + deckLayers.forEach((layer: any) => { + if (layer && layer.id) { + // 仅处理 junctionContourLayer 和 waterflowLayer + if ( + layer.id !== "junctionContourLayer" && + layer.id !== "waterflowLayer" + ) { + return; } - return false; - }) as DeckLayer[]; - - // 合并所有可控制的图层 - const allLayers = [...mapLayers, ...deckFlowLayers]; - - // 定义图层排序顺序 - const layerOrder = [ - "junctions", - "reservoirs", - "tanks", - "pipes", - "pumps", - "valves", - "scada", - "waterflow", - "contourLayer", - ]; - - // 过滤并排序图层:只显示在 layerOrder 中的图层 - const sortedLayers = allLayers - .filter((layer) => { - const value = layer.get("value")?.toLowerCase(); - return layerOrder.includes(value); - }) - .sort((a, b) => { - const nameA = a.get("value")?.toLowerCase(); - const nameB = b.get("value")?.toLowerCase(); - const indexA = layerOrder.indexOf(nameA); - const indexB = layerOrder.indexOf(nameB); - - return indexA - indexB; - }); - - setLayers(sortedLayers); - - setLayerVisibilities((prevVisibilities) => { - const visible = new Map(); - sortedLayers.forEach((layer) => { - // 如果用户刚手动改变了这个图层,使用本地状态 - if (userChangedRef.current.has(layer)) { - visible.set(layer, prevVisibilities.get(layer) ?? true); - } else if (layer instanceof DeckLayer) { - // 对于 DeckLayer,需要设置内部 deck.gl 图层的可见性 - const deckLayers = layer.getDeckLayers(); - deckLayers.forEach((deckLayer: any) => { - if ( - deckLayer && - (deckLayer.id === "waterflowLayer" || - deckLayer.id === "contourLayer") - ) { - const visible = layer.getDeckLayerVisible(deckLayer.id); - layer.setDeckLayerVisible(deckLayer.id, !visible); - } - }); - } else { - // 对于普通 OpenLayers 图层 - visible.set(layer, layer.getVisible()); + // 检查可用性 + if ( + (layer.id === "junctionContourLayer" && !isContourLayerAvailable) || + (layer.id === "waterflowLayer" && !isWaterflowLayerAvailable) + ) { + return; // 跳过不可用图层 } - }); - return visible; - }); - }; - - // 初始更新 - updateLayers(); - - // 监听图层集合的变化 - const layerCollection = map.getLayers(); - layerCollection.on("change:length", updateLayers); - - // 设置定时器定期检查 DeckLayer 内部图层的变化 - const intervalId = setInterval(updateLayers, 500); - - return () => { - layerCollection.un("change:length", updateLayers); - clearInterval(intervalId); - }; - }, [map]); - - const handleVisibilityChange = (layer: Layer, visible: boolean) => { - // 判断图层类型并调用相应的方法 - if (layer instanceof DeckLayer) { - // 对于 DeckLayer,需要设置内部 deck.gl 图层的可见性 - const deckLayers = layer.getDeckLayers(); - deckLayers.forEach((deckLayer: any) => { - if (deckLayer && deckLayer.id === "waterflowLayer") { - layer.setDeckLayerVisible("waterflowLayer", visible); + const visible = + deckLayer.getDeckLayerVisible(layer.id) ?? + layer.props?.visible ?? + true; + items.push({ + id: layer.props.id, + name: layer.props.name, // 使用 name 属性作为显示名称 + visible: visible, + type: "deck", + layerRef: layer, + }); } }); - } else { - // 对于普通 OpenLayers 图层,使用 setVisible 方法 - layer.setVisible(visible); } - // 标记这个图层为用户手动改变 - userChangedRef.current.add(layer); - setLayerVisibilities((prev) => new Map(prev).set(layer, visible)); + // 3. 定义图层显示顺序和过滤白名单 + const layerOrder = [ + "junctions", + "reservoirs", + "tanks", + "pipes", + "pumps", + "valves", + "scada", + "waterflowLayer", + "junctionContourLayer", + ]; - // 1秒后移除标记,允许定时器更新 - setTimeout(() => { - userChangedRef.current.delete(layer); - }, 1000); + // 过滤并排序 + const sortedItems = items + .filter((item) => layerOrder.includes(item.id)) + .sort((a, b) => { + const indexA = layerOrder.indexOf(a.id); + const indexB = layerOrder.indexOf(b.id); + return indexA - indexB; + }); + + setLayerItems(sortedItems); + }, [map, isWaterflowLayerAvailable, isContourLayerAvailable]); + + useEffect(() => { + updateLayers(); + + if (map) { + const layerCollection = map.getLayers(); + layerCollection.on("change:length", updateLayers); + } + + return () => { + if (map) { + map.getLayers().un("change:length", updateLayers); + } + }; + }, [map, updateLayers]); + + const handleVisibilityChange = (item: LayerItem, checked: boolean) => { + if (item.type === "ol") { + item.layerRef.setVisible(checked); + } else if (item.type === "deck" && deckLayer) { + if (item.id === "junctionContourLayer") { + setShowContourLayer && setShowContourLayer(checked); + } + if (item.id === "waterflowLayer") { + if (flowAnimation) flowAnimation.current = checked; + } + } + + setLayerItems((prev) => + prev.map((i) => (i.id === item.id ? { ...i, visible: checked } : i)) + ); }; + if (!data) { + return
Loading...
; + } + return (
- {layers.map((layer, index) => ( + {layerItems.map((item) => ( - handleVisibilityChange(layer, e.target.checked) - } + checked={item.visible} + onChange={(e) => handleVisibilityChange(item, e.target.checked)} size="small" /> } - label={layer.get("name") || `Layer ${index + 1}`} + label={item.name} sx={{ fontSize: "0.7rem", "& .MuiFormControlLabel-label": { fontSize: "0.7rem" }, diff --git a/src/app/OlMap/Controls/StyleEditorPanel.tsx b/src/app/OlMap/Controls/StyleEditorPanel.tsx index 650b806..585a12d 100644 --- a/src/app/OlMap/Controls/StyleEditorPanel.tsx +++ b/src/app/OlMap/Controls/StyleEditorPanel.tsx @@ -31,7 +31,7 @@ import { parseColor } from "@utils/parseColor"; import { VectorTile } from "ol"; import { useNotification } from "@refinedev/core"; import { config } from "@/config/config"; -import { DeckLayer } from "@utils/layers"; +import { set } from "ol/transform"; interface StyleConfig { property: string; @@ -190,9 +190,11 @@ const StyleEditorPanel: React.FC = ({ pipeText, setShowJunctionTextLayer, setShowPipeTextLayer, - setShowContourLayer, + setContourLayerAvailable, + setWaterflowLayerAvailable, setJunctionText, setPipeText, + deckLayer, } = data; const { open } = useNotification(); @@ -358,6 +360,7 @@ const StyleEditorPanel: React.FC = ({ setJunctionText(property); setShowJunctionTextLayer(styleConfig.showLabels); setApplyJunctionStyle(true); + setContourLayerAvailable && setContourLayerAvailable(true); saveLayerStyle(layerId); open?.({ type: "success", @@ -370,6 +373,7 @@ const StyleEditorPanel: React.FC = ({ setPipeText(property); setShowPipeTextLayer(styleConfig.showLabels); setApplyPipeStyle(true); + setWaterflowLayerAvailable && setWaterflowLayerAvailable(true); saveLayerStyle(layerId); open?.({ type: "success", @@ -443,7 +447,6 @@ const StyleEditorPanel: React.FC = ({ } if (junctionStyleConfigState) applyLayerStyle(junctionStyleConfigState, breaks); - updateContourLayerStyle(breaks, junctionStyleConfigState?.styleConfig); } else if ( layerType === "pipes" && currentPipeCalData && @@ -677,10 +680,12 @@ const StyleEditorPanel: React.FC = ({ setApplyJunctionStyle(false); if (setShowJunctionTextLayer) setShowJunctionTextLayer(false); if (setJunctionText) setJunctionText(""); + setContourLayerAvailable && setContourLayerAvailable(false); } else if (layerId === "pipes") { setApplyPipeStyle(false); if (setShowPipeTextLayer) setShowPipeTextLayer(false); if (setPipeText) setPipeText(""); + setWaterflowLayerAvailable && setWaterflowLayerAvailable(false); } } }, [selectedRenderLayer]); @@ -1427,110 +1432,6 @@ const StyleEditorPanel: React.FC = ({ ); } }; - // 更新 ContourLayer 的样式,并显示在地图上 - const updateContourLayerStyle = (breaks: any, styleConfig: any) => { - if (!map) return; - // 查找包含 contourLayer 的 DeckLayer - const deckLayerWrapper = map - .getLayers() - .getArray() - .find((layer) => { - if (layer instanceof DeckLayer) { - const deckLayers = layer.getDeckLayers(); - // 检查是否包含 contourLayer - return deckLayers.some((dl: any) => dl.id === "contourLayer"); - } - return false; - }) as DeckLayer | undefined; - - if (!deckLayerWrapper) return; - - // 计算颜色 - const segmentCount = breaks.length - 1; - if (segmentCount <= 0) return; - - const thresholdColor = () => { - let colors: string[] = []; - if (styleConfig.colorType === "single") { - const c = SINGLE_COLOR_PALETTES[styleConfig.singlePaletteIndex].color; - colors = Array(segmentCount).fill(c); - } else if (styleConfig.colorType === "gradient") { - const { start, end } = - GRADIENT_PALETTES[styleConfig.gradientPaletteIndex]; - const startColor = parseColor(start); - const endColor = parseColor(end); - for (let i = 0; i < segmentCount; i++) { - const ratio = segmentCount > 1 ? i / (segmentCount - 1) : 1; - const r = Math.round( - startColor.r + (endColor.r - startColor.r) * ratio - ); - const g = Math.round( - startColor.g + (endColor.g - startColor.g) * ratio - ); - const b = Math.round( - startColor.b + (endColor.b - startColor.b) * ratio - ); - colors.push(`rgba(${r}, ${g}, ${b}, 1)`); - } - } else if (styleConfig.colorType === "rainbow") { - const baseColors = - RAINBOW_PALETTES[styleConfig.rainbowPaletteIndex].colors; - colors = Array.from( - { length: segmentCount }, - (_, i) => baseColors[i % baseColors.length] - ); - } else if (styleConfig.colorType === "custom") { - const custom = styleConfig.customColors || []; - const result = [...custom]; - const reverseRainbowColors = RAINBOW_PALETTES[1].colors; - while (result.length < segmentCount) { - result.push( - reverseRainbowColors[ - (result.length - custom.length) % reverseRainbowColors.length - ] - ); - } - colors = result.slice(0, segmentCount); - } - return colors; - }; - - const colors = thresholdColor(); - // 构建 contours 配置 - const contours: any[] = []; - for (let i = 0; i < segmentCount; i++) { - const start = breaks[i]; - const end = breaks[i + 1]; - const colorStr = colors[i]; - try { - const c = parseColor(colorStr); - contours.push({ - threshold: [start, end], - color: [c.r, c.g, c.b], - }); - } catch (e) { - console.warn("Color parse error", colorStr); - } - } - // 更新 DeckLayer - const deck = (deckLayerWrapper as any).deck; - if (deck && deck.props && deck.props.layers) { - const currentLayers = deck.props.layers; - const newLayers = currentLayers.map((layer: any) => { - if (layer.id === "contourLayer") { - return layer.clone({ - contours: contours, - }); - } - return layer; - }); - console.log(newLayers); - deck.setProps({ layers: newLayers }); - } - - // 显示 contourLayer - // if (setShowContourLayer) setShowContourLayer(true); - }; return ( <> diff --git a/src/app/OlMap/MapComponent.tsx b/src/app/OlMap/MapComponent.tsx index e72c233..ef3416b 100644 --- a/src/app/OlMap/MapComponent.tsx +++ b/src/app/OlMap/MapComponent.tsx @@ -30,6 +30,7 @@ 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"; interface MapComponentProps { children?: React.ReactNode; @@ -50,11 +51,16 @@ interface DataContextType { setShowJunctionTextLayer?: React.Dispatch>; setShowPipeTextLayer?: React.Dispatch>; setShowContourLayer?: React.Dispatch>; + flowAnimation?: React.RefObject; + isContourLayerAvailable?: boolean; + setContourLayerAvailable?: React.Dispatch>; + isWaterflowLayerAvailable?: boolean; + setWaterflowLayerAvailable?: React.Dispatch>; junctionText: string; pipeText: string; setJunctionText?: React.Dispatch>; setPipeText?: React.Dispatch>; - scadaData?: any[]; // SCADA 数据 + deckLayer?: DeckLayer; // DeckLayer 实例 } // 跨组件传递 @@ -86,10 +92,10 @@ export const useData = () => { const MapComponent: React.FC = ({ children }) => { const mapRef = useRef(null); - const deckRef = useRef(null); const deckLayerRef = useRef(null); const [map, setMap] = useState(); + const [deckLayer, setDeckLayer] = useState(); // currentCalData 用于存储当前计算结果 const [currentTime, setCurrentTime] = useState(-1); // 默认选择当前时间 // const [selectedDate, setSelectedDate] = useState(new Date("2025-9-17")); @@ -114,6 +120,9 @@ const MapComponent: React.FC = ({ children }) => { const [junctionText, setJunctionText] = useState("pressure"); const [pipeText, setPipeText] = useState("flow"); const flowAnimation = useRef(false); // 添加动画控制标志 + const [isContourLayerAvailable, setContourLayerAvailable] = useState(false); // 控制等高线图层显示 + const [isWaterflowLayerAvailable, setWaterflowLayerAvailable] = + useState(false); // 控制等高线图层显示 const [currentZoom, setCurrentZoom] = useState(11); // 当前缩放级别 // 防抖更新函数 @@ -649,12 +658,12 @@ const MapComponent: React.FC = ({ children }) => { controller: false, // 由 OpenLayers 控制视图 layers: [], }); - deckRef.current = deck; const deckLayer = new DeckLayer(deck, { name: "deckLayer", value: "deckLayer", }); deckLayerRef.current = deckLayer; + setDeckLayer(deckLayer); map.addLayer(deckLayer); // 清理函数 @@ -675,6 +684,7 @@ const MapComponent: React.FC = ({ children }) => { if (!pipeData.length) return; const junctionTextLayer = new TextLayer({ id: "junctionTextLayer", + name: "节点文字", zIndex: 10, data: junctionData, getPosition: (d: any) => d.position, @@ -705,6 +715,7 @@ const MapComponent: React.FC = ({ children }) => { }); const pipeTextLayer = new TextLayer({ id: "pipeTextLayer", + name: "管道文字", zIndex: 10, data: pipeData, getPosition: (d: any) => d.position, @@ -733,6 +744,24 @@ const MapComponent: React.FC = ({ children }) => { // outlineWidth: 10, // outlineColor: [242, 244, 246, 255], }); + const contourLayer = new ContourLayer({ + id: "junctionContourLayer", + name: "等值线", + data: junctionData, + cellSize: 400, + contours: [ + { threshold: [0, 10], color: [255, 0, 0] }, + { threshold: [10, 20], color: [255, 127, 0] }, + { threshold: [20, 30], color: [255, 215, 0] }, + { threshold: [30, 40], color: [199, 224, 0] }, + { threshold: [40, 9999], color: [142, 68, 173] }, + ], + getPosition: (d) => d.position, + getWeight: (d: any) => + d[junctionText] ? Math.abs(d[junctionText] as number) : -1, + opacity: 0.4, + visible: showContourLayer && currentZoom >= 12 && currentZoom <= 24, + }); if (deckLayer.getDeckLayerById("junctionTextLayer")) { // 传入完整 layer 实例以保证 clone/替换时保留 layer 类型和方法 deckLayer.updateDeckLayer("junctionTextLayer", junctionTextLayer); @@ -744,7 +773,11 @@ const MapComponent: React.FC = ({ children }) => { } else { deckLayer.addDeckLayer(pipeTextLayer); } - console.log(deckLayer.getDeckLayers()); + if (deckLayer.getDeckLayerById("junctionContourLayer")) { + deckLayer.updateDeckLayer("junctionContourLayer", contourLayer); + } else { + deckLayer.addDeckLayer(contourLayer); + } }, [ junctionData, pipeData, @@ -769,7 +802,14 @@ const MapComponent: React.FC = ({ children }) => { // 动画循环 const animate = () => { - if (!flowAnimation.current) return; // 添加检查,防止空数据或停止旧循环 + if (!flowAnimation.current) { + try { + deckLayer.removeDeckLayer("waterflowLayer"); + } catch (error) { + console.error("Error in animation loop:", error); + } + return; + } // 动画总时长(秒) if (pipeData.length === 0) { animationFrameId = requestAnimationFrame(animate); @@ -785,6 +825,7 @@ const MapComponent: React.FC = ({ children }) => { // console.log("Current Time:", currentTime); const waterflowLayer = new TripsLayer({ id: "waterflowLayer", + name: "水流", data: pipeData, getPath: (d) => d.path, getTimestamps: (d) => { @@ -884,10 +925,16 @@ const MapComponent: React.FC = ({ children }) => { setShowJunctionTextLayer, setShowPipeTextLayer, setShowContourLayer, + flowAnimation, + isContourLayerAvailable, + setContourLayerAvailable, + isWaterflowLayerAvailable, + setWaterflowLayerAvailable, setJunctionText, setPipeText, junctionText, pipeText, + deckLayer, }} >