更新图层控制逻辑
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { useMap } from "../MapComponent";
|
import { useData, useMap } from "../MapComponent";
|
||||||
import { Layer } from "ol/layer";
|
import { Layer } from "ol/layer";
|
||||||
import { Checkbox, FormControlLabel } from "@mui/material";
|
import { Checkbox, FormControlLabel } from "@mui/material";
|
||||||
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
||||||
@@ -7,159 +7,168 @@ import VectorLayer from "ol/layer/Vector";
|
|||||||
import VectorTileLayer from "ol/layer/VectorTile";
|
import VectorTileLayer from "ol/layer/VectorTile";
|
||||||
import { DeckLayer } from "@utils/layers";
|
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 LayerControl: React.FC = () => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const [layers, setLayers] = useState<Layer[]>([]);
|
const data = useData();
|
||||||
const [layerVisibilities, setLayerVisibilities] = useState<
|
if (!data) return;
|
||||||
Map<Layer, boolean>
|
const {
|
||||||
>(new Map());
|
deckLayer,
|
||||||
const userChangedRef = React.useRef<Set<Layer>>(new Set());
|
isContourLayerAvailable,
|
||||||
|
isWaterflowLayerAvailable,
|
||||||
|
setShowContourLayer,
|
||||||
|
flowAnimation,
|
||||||
|
} = data;
|
||||||
|
const [layerItems, setLayerItems] = useState<LayerItem[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
// 更新图层列表
|
||||||
if (!map) return;
|
const updateLayers = useCallback(() => {
|
||||||
|
if (!map || !data) return;
|
||||||
|
const { deckLayer } = data;
|
||||||
|
|
||||||
const updateLayers = () => {
|
const items: LayerItem[] = [];
|
||||||
const mapLayers = map
|
|
||||||
.getLayers()
|
|
||||||
.getArray()
|
|
||||||
.filter(
|
|
||||||
(layer) =>
|
|
||||||
layer instanceof WebGLVectorTileLayer ||
|
|
||||||
layer instanceof VectorTileLayer ||
|
|
||||||
layer instanceof VectorLayer
|
|
||||||
) as Layer[];
|
|
||||||
|
|
||||||
// 查找包含 waterflowLayer 的 DeckLayer
|
// 1. 获取 OpenLayers 图层
|
||||||
const deckFlowLayers = map
|
const mapLayers = map.getLayers().getArray();
|
||||||
.getLayers()
|
mapLayers.forEach((layer) => {
|
||||||
.getArray()
|
// 筛选特定类型的 OpenLayers 图层
|
||||||
.filter((layer) => {
|
if (
|
||||||
if (layer instanceof DeckLayer) {
|
layer instanceof WebGLVectorTileLayer ||
|
||||||
const deckLayers = layer.getDeckLayers();
|
layer instanceof VectorTileLayer ||
|
||||||
// 检查是否包含 waterflowLayer
|
layer instanceof VectorLayer
|
||||||
return deckLayers.some((dl: any) => dl.id === "waterflowLayer");
|
) {
|
||||||
|
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[];
|
if (
|
||||||
|
(layer.id === "junctionContourLayer" && !isContourLayerAvailable) ||
|
||||||
// 合并所有可控制的图层
|
(layer.id === "waterflowLayer" && !isWaterflowLayerAvailable)
|
||||||
const allLayers = [...mapLayers, ...deckFlowLayers];
|
) {
|
||||||
|
return; // 跳过不可用图层
|
||||||
// 定义图层排序顺序
|
|
||||||
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<Layer, boolean>();
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
});
|
const visible =
|
||||||
return visible;
|
deckLayer.getDeckLayerVisible(layer.id) ??
|
||||||
});
|
layer.props?.visible ??
|
||||||
};
|
true;
|
||||||
|
items.push({
|
||||||
// 初始更新
|
id: layer.props.id,
|
||||||
updateLayers();
|
name: layer.props.name, // 使用 name 属性作为显示名称
|
||||||
|
visible: visible,
|
||||||
// 监听图层集合的变化
|
type: "deck",
|
||||||
const layerCollection = map.getLayers();
|
layerRef: layer,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// 对于普通 OpenLayers 图层,使用 setVisible 方法
|
|
||||||
layer.setVisible(visible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记这个图层为用户手动改变
|
// 3. 定义图层显示顺序和过滤白名单
|
||||||
userChangedRef.current.add(layer);
|
const layerOrder = [
|
||||||
setLayerVisibilities((prev) => new Map(prev).set(layer, visible));
|
"junctions",
|
||||||
|
"reservoirs",
|
||||||
|
"tanks",
|
||||||
|
"pipes",
|
||||||
|
"pumps",
|
||||||
|
"valves",
|
||||||
|
"scada",
|
||||||
|
"waterflowLayer",
|
||||||
|
"junctionContourLayer",
|
||||||
|
];
|
||||||
|
|
||||||
// 1秒后移除标记,允许定时器更新
|
// 过滤并排序
|
||||||
setTimeout(() => {
|
const sortedItems = items
|
||||||
userChangedRef.current.delete(layer);
|
.filter((item) => layerOrder.includes(item.id))
|
||||||
}, 1000);
|
.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 <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute left-4 bottom-4 bg-white rounded-md drop-shadow-lg z-10 opacity-85 hover:opacity-100 transition-opacity max-w-xs">
|
<div className="absolute left-4 bottom-4 bg-white rounded-md drop-shadow-lg z-10 opacity-85 hover:opacity-100 transition-opacity max-w-xs">
|
||||||
<div className="ml-3 grid grid-cols-3">
|
<div className="ml-3 grid grid-cols-3">
|
||||||
{layers.map((layer, index) => (
|
{layerItems.map((item) => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
key={index}
|
key={item.id}
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={layerVisibilities.get(layer) ?? false}
|
checked={item.visible}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleVisibilityChange(item, e.target.checked)}
|
||||||
handleVisibilityChange(layer, e.target.checked)
|
|
||||||
}
|
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={layer.get("name") || `Layer ${index + 1}`}
|
label={item.name}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "0.7rem",
|
fontSize: "0.7rem",
|
||||||
"& .MuiFormControlLabel-label": { fontSize: "0.7rem" },
|
"& .MuiFormControlLabel-label": { fontSize: "0.7rem" },
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import { parseColor } from "@utils/parseColor";
|
|||||||
import { VectorTile } from "ol";
|
import { VectorTile } from "ol";
|
||||||
import { useNotification } from "@refinedev/core";
|
import { useNotification } from "@refinedev/core";
|
||||||
import { config } from "@/config/config";
|
import { config } from "@/config/config";
|
||||||
import { DeckLayer } from "@utils/layers";
|
import { set } from "ol/transform";
|
||||||
|
|
||||||
interface StyleConfig {
|
interface StyleConfig {
|
||||||
property: string;
|
property: string;
|
||||||
@@ -190,9 +190,11 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
pipeText,
|
pipeText,
|
||||||
setShowJunctionTextLayer,
|
setShowJunctionTextLayer,
|
||||||
setShowPipeTextLayer,
|
setShowPipeTextLayer,
|
||||||
setShowContourLayer,
|
setContourLayerAvailable,
|
||||||
|
setWaterflowLayerAvailable,
|
||||||
setJunctionText,
|
setJunctionText,
|
||||||
setPipeText,
|
setPipeText,
|
||||||
|
deckLayer,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
const { open } = useNotification();
|
const { open } = useNotification();
|
||||||
@@ -358,6 +360,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
setJunctionText(property);
|
setJunctionText(property);
|
||||||
setShowJunctionTextLayer(styleConfig.showLabels);
|
setShowJunctionTextLayer(styleConfig.showLabels);
|
||||||
setApplyJunctionStyle(true);
|
setApplyJunctionStyle(true);
|
||||||
|
setContourLayerAvailable && setContourLayerAvailable(true);
|
||||||
saveLayerStyle(layerId);
|
saveLayerStyle(layerId);
|
||||||
open?.({
|
open?.({
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -370,6 +373,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
setPipeText(property);
|
setPipeText(property);
|
||||||
setShowPipeTextLayer(styleConfig.showLabels);
|
setShowPipeTextLayer(styleConfig.showLabels);
|
||||||
setApplyPipeStyle(true);
|
setApplyPipeStyle(true);
|
||||||
|
setWaterflowLayerAvailable && setWaterflowLayerAvailable(true);
|
||||||
saveLayerStyle(layerId);
|
saveLayerStyle(layerId);
|
||||||
open?.({
|
open?.({
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -443,7 +447,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
}
|
}
|
||||||
if (junctionStyleConfigState)
|
if (junctionStyleConfigState)
|
||||||
applyLayerStyle(junctionStyleConfigState, breaks);
|
applyLayerStyle(junctionStyleConfigState, breaks);
|
||||||
updateContourLayerStyle(breaks, junctionStyleConfigState?.styleConfig);
|
|
||||||
} else if (
|
} else if (
|
||||||
layerType === "pipes" &&
|
layerType === "pipes" &&
|
||||||
currentPipeCalData &&
|
currentPipeCalData &&
|
||||||
@@ -677,10 +680,12 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
setApplyJunctionStyle(false);
|
setApplyJunctionStyle(false);
|
||||||
if (setShowJunctionTextLayer) setShowJunctionTextLayer(false);
|
if (setShowJunctionTextLayer) setShowJunctionTextLayer(false);
|
||||||
if (setJunctionText) setJunctionText("");
|
if (setJunctionText) setJunctionText("");
|
||||||
|
setContourLayerAvailable && setContourLayerAvailable(false);
|
||||||
} else if (layerId === "pipes") {
|
} else if (layerId === "pipes") {
|
||||||
setApplyPipeStyle(false);
|
setApplyPipeStyle(false);
|
||||||
if (setShowPipeTextLayer) setShowPipeTextLayer(false);
|
if (setShowPipeTextLayer) setShowPipeTextLayer(false);
|
||||||
if (setPipeText) setPipeText("");
|
if (setPipeText) setPipeText("");
|
||||||
|
setWaterflowLayerAvailable && setWaterflowLayerAvailable(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedRenderLayer]);
|
}, [selectedRenderLayer]);
|
||||||
@@ -1427,110 +1432,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 更新 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import VectorLayer from "ol/layer/Vector";
|
|||||||
import { Icon, Style } from "ol/style.js";
|
import { Icon, Style } from "ol/style.js";
|
||||||
import { FeatureLike } from "ol/Feature";
|
import { FeatureLike } from "ol/Feature";
|
||||||
import { Point } from "ol/geom";
|
import { Point } from "ol/geom";
|
||||||
|
import { ContourLayer } from "deck.gl";
|
||||||
|
|
||||||
interface MapComponentProps {
|
interface MapComponentProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -50,11 +51,16 @@ interface DataContextType {
|
|||||||
setShowJunctionTextLayer?: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowJunctionTextLayer?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setShowPipeTextLayer?: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowPipeTextLayer?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setShowContourLayer?: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowContourLayer?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
flowAnimation?: React.RefObject<boolean>;
|
||||||
|
isContourLayerAvailable?: boolean;
|
||||||
|
setContourLayerAvailable?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
isWaterflowLayerAvailable?: boolean;
|
||||||
|
setWaterflowLayerAvailable?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
junctionText: string;
|
junctionText: string;
|
||||||
pipeText: string;
|
pipeText: string;
|
||||||
setJunctionText?: React.Dispatch<React.SetStateAction<string>>;
|
setJunctionText?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setPipeText?: React.Dispatch<React.SetStateAction<string>>;
|
setPipeText?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
scadaData?: any[]; // SCADA 数据
|
deckLayer?: DeckLayer; // DeckLayer 实例
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跨组件传递
|
// 跨组件传递
|
||||||
@@ -86,10 +92,10 @@ export const useData = () => {
|
|||||||
|
|
||||||
const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||||
const mapRef = useRef<HTMLDivElement | null>(null);
|
const mapRef = useRef<HTMLDivElement | null>(null);
|
||||||
const deckRef = useRef<Deck | null>(null);
|
|
||||||
const deckLayerRef = useRef<DeckLayer | null>(null);
|
const deckLayerRef = useRef<DeckLayer | null>(null);
|
||||||
|
|
||||||
const [map, setMap] = useState<OlMap>();
|
const [map, setMap] = useState<OlMap>();
|
||||||
|
const [deckLayer, setDeckLayer] = useState<DeckLayer>();
|
||||||
// currentCalData 用于存储当前计算结果
|
// currentCalData 用于存储当前计算结果
|
||||||
const [currentTime, setCurrentTime] = useState<number>(-1); // 默认选择当前时间
|
const [currentTime, setCurrentTime] = useState<number>(-1); // 默认选择当前时间
|
||||||
// const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
|
// const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
|
||||||
@@ -114,6 +120,9 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
const [junctionText, setJunctionText] = useState("pressure");
|
const [junctionText, setJunctionText] = useState("pressure");
|
||||||
const [pipeText, setPipeText] = useState("flow");
|
const [pipeText, setPipeText] = useState("flow");
|
||||||
const flowAnimation = useRef(false); // 添加动画控制标志
|
const flowAnimation = useRef(false); // 添加动画控制标志
|
||||||
|
const [isContourLayerAvailable, setContourLayerAvailable] = useState(false); // 控制等高线图层显示
|
||||||
|
const [isWaterflowLayerAvailable, setWaterflowLayerAvailable] =
|
||||||
|
useState(false); // 控制等高线图层显示
|
||||||
const [currentZoom, setCurrentZoom] = useState(11); // 当前缩放级别
|
const [currentZoom, setCurrentZoom] = useState(11); // 当前缩放级别
|
||||||
|
|
||||||
// 防抖更新函数
|
// 防抖更新函数
|
||||||
@@ -649,12 +658,12 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
controller: false, // 由 OpenLayers 控制视图
|
controller: false, // 由 OpenLayers 控制视图
|
||||||
layers: [],
|
layers: [],
|
||||||
});
|
});
|
||||||
deckRef.current = deck;
|
|
||||||
const deckLayer = new DeckLayer(deck, {
|
const deckLayer = new DeckLayer(deck, {
|
||||||
name: "deckLayer",
|
name: "deckLayer",
|
||||||
value: "deckLayer",
|
value: "deckLayer",
|
||||||
});
|
});
|
||||||
deckLayerRef.current = deckLayer;
|
deckLayerRef.current = deckLayer;
|
||||||
|
setDeckLayer(deckLayer);
|
||||||
map.addLayer(deckLayer);
|
map.addLayer(deckLayer);
|
||||||
|
|
||||||
// 清理函数
|
// 清理函数
|
||||||
@@ -675,6 +684,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
if (!pipeData.length) return;
|
if (!pipeData.length) return;
|
||||||
const junctionTextLayer = new TextLayer({
|
const junctionTextLayer = new TextLayer({
|
||||||
id: "junctionTextLayer",
|
id: "junctionTextLayer",
|
||||||
|
name: "节点文字",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
data: junctionData,
|
data: junctionData,
|
||||||
getPosition: (d: any) => d.position,
|
getPosition: (d: any) => d.position,
|
||||||
@@ -705,6 +715,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
});
|
});
|
||||||
const pipeTextLayer = new TextLayer({
|
const pipeTextLayer = new TextLayer({
|
||||||
id: "pipeTextLayer",
|
id: "pipeTextLayer",
|
||||||
|
name: "管道文字",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
data: pipeData,
|
data: pipeData,
|
||||||
getPosition: (d: any) => d.position,
|
getPosition: (d: any) => d.position,
|
||||||
@@ -733,6 +744,24 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
// outlineWidth: 10,
|
// outlineWidth: 10,
|
||||||
// outlineColor: [242, 244, 246, 255],
|
// 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")) {
|
if (deckLayer.getDeckLayerById("junctionTextLayer")) {
|
||||||
// 传入完整 layer 实例以保证 clone/替换时保留 layer 类型和方法
|
// 传入完整 layer 实例以保证 clone/替换时保留 layer 类型和方法
|
||||||
deckLayer.updateDeckLayer("junctionTextLayer", junctionTextLayer);
|
deckLayer.updateDeckLayer("junctionTextLayer", junctionTextLayer);
|
||||||
@@ -744,7 +773,11 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
} else {
|
} else {
|
||||||
deckLayer.addDeckLayer(pipeTextLayer);
|
deckLayer.addDeckLayer(pipeTextLayer);
|
||||||
}
|
}
|
||||||
console.log(deckLayer.getDeckLayers());
|
if (deckLayer.getDeckLayerById("junctionContourLayer")) {
|
||||||
|
deckLayer.updateDeckLayer("junctionContourLayer", contourLayer);
|
||||||
|
} else {
|
||||||
|
deckLayer.addDeckLayer(contourLayer);
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
junctionData,
|
junctionData,
|
||||||
pipeData,
|
pipeData,
|
||||||
@@ -769,7 +802,14 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
|
|
||||||
// 动画循环
|
// 动画循环
|
||||||
const animate = () => {
|
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) {
|
if (pipeData.length === 0) {
|
||||||
animationFrameId = requestAnimationFrame(animate);
|
animationFrameId = requestAnimationFrame(animate);
|
||||||
@@ -785,6 +825,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
// console.log("Current Time:", currentTime);
|
// console.log("Current Time:", currentTime);
|
||||||
const waterflowLayer = new TripsLayer({
|
const waterflowLayer = new TripsLayer({
|
||||||
id: "waterflowLayer",
|
id: "waterflowLayer",
|
||||||
|
name: "水流",
|
||||||
data: pipeData,
|
data: pipeData,
|
||||||
getPath: (d) => d.path,
|
getPath: (d) => d.path,
|
||||||
getTimestamps: (d) => {
|
getTimestamps: (d) => {
|
||||||
@@ -884,10 +925,16 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
setShowJunctionTextLayer,
|
setShowJunctionTextLayer,
|
||||||
setShowPipeTextLayer,
|
setShowPipeTextLayer,
|
||||||
setShowContourLayer,
|
setShowContourLayer,
|
||||||
|
flowAnimation,
|
||||||
|
isContourLayerAvailable,
|
||||||
|
setContourLayerAvailable,
|
||||||
|
isWaterflowLayerAvailable,
|
||||||
|
setWaterflowLayerAvailable,
|
||||||
setJunctionText,
|
setJunctionText,
|
||||||
setPipeText,
|
setPipeText,
|
||||||
junctionText,
|
junctionText,
|
||||||
pipeText,
|
pipeText,
|
||||||
|
deckLayer,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MapContext.Provider value={map}>
|
<MapContext.Provider value={map}>
|
||||||
|
|||||||
Reference in New Issue
Block a user