更新图层控制逻辑

This commit is contained in:
JIANG
2025-11-25 10:05:55 +08:00
parent 543725ee1f
commit dc7271e3da
3 changed files with 201 additions and 244 deletions

View File

@@ -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<Layer[]>([]);
const [layerVisibilities, setLayerVisibilities] = useState<
Map<Layer, boolean>
>(new Map());
const userChangedRef = React.useRef<Set<Layer>>(new Set());
const data = useData();
if (!data) return;
const {
deckLayer,
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 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<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());
// 检查可用性
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 <div>Loading...</div>;
}
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="ml-3 grid grid-cols-3">
{layers.map((layer, index) => (
{layerItems.map((item) => (
<FormControlLabel
key={index}
key={item.id}
control={
<Checkbox
checked={layerVisibilities.get(layer) ?? false}
onChange={(e) =>
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" },

View File

@@ -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<StyleEditorPanelProps> = ({
pipeText,
setShowJunctionTextLayer,
setShowPipeTextLayer,
setShowContourLayer,
setContourLayerAvailable,
setWaterflowLayerAvailable,
setJunctionText,
setPipeText,
deckLayer,
} = data;
const { open } = useNotification();
@@ -358,6 +360,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
setJunctionText(property);
setShowJunctionTextLayer(styleConfig.showLabels);
setApplyJunctionStyle(true);
setContourLayerAvailable && setContourLayerAvailable(true);
saveLayerStyle(layerId);
open?.({
type: "success",
@@ -370,6 +373,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
setPipeText(property);
setShowPipeTextLayer(styleConfig.showLabels);
setApplyPipeStyle(true);
setWaterflowLayerAvailable && setWaterflowLayerAvailable(true);
saveLayerStyle(layerId);
open?.({
type: "success",
@@ -443,7 +447,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
}
if (junctionStyleConfigState)
applyLayerStyle(junctionStyleConfigState, breaks);
updateContourLayerStyle(breaks, junctionStyleConfigState?.styleConfig);
} else if (
layerType === "pipes" &&
currentPipeCalData &&
@@ -677,10 +680,12 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
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<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 (
<>