更新 deckLayer 类

This commit is contained in:
JIANG
2025-11-24 15:09:37 +08:00
parent 15e80f105e
commit b4aef962dd
5 changed files with 309 additions and 227 deletions

View File

@@ -40,7 +40,7 @@ const LayerControl: React.FC = () => {
return deckLayers.some((dl: any) => dl.id === "waterflowLayer"); return deckLayers.some((dl: any) => dl.id === "waterflowLayer");
} }
return false; return false;
}) as Layer[]; }) as DeckLayer[];
// 合并所有可控制的图层 // 合并所有可控制的图层
const allLayers = [...mapLayers, ...deckFlowLayers]; const allLayers = [...mapLayers, ...deckFlowLayers];
@@ -55,6 +55,7 @@ const LayerControl: React.FC = () => {
"valves", "valves",
"scada", "scada",
"waterflow", "waterflow",
"contourLayer",
]; ];
// 过滤并排序图层:只显示在 layerOrder 中的图层 // 过滤并排序图层:只显示在 layerOrder 中的图层
@@ -81,10 +82,18 @@ const LayerControl: React.FC = () => {
if (userChangedRef.current.has(layer)) { if (userChangedRef.current.has(layer)) {
visible.set(layer, prevVisibilities.get(layer) ?? true); visible.set(layer, prevVisibilities.get(layer) ?? true);
} else if (layer instanceof DeckLayer) { } else if (layer instanceof DeckLayer) {
// 对于 DeckLayer获取内部 deck.gl 图层的可见性 // 对于 DeckLayer需要设置内部 deck.gl 图层的可见性
const waterflowVisible = const deckLayers = layer.getDeckLayers();
layer.getDeckLayerVisible("waterflowLayer"); deckLayers.forEach((deckLayer: any) => {
visible.set(layer, waterflowVisible ?? true); if (
deckLayer &&
(deckLayer.id === "waterflowLayer" ||
deckLayer.id === "contourLayer")
) {
const visible = layer.getDeckLayerVisible(deckLayer.id);
layer.setDeckLayerVisible(deckLayer.id, !visible);
}
});
} else { } else {
// 对于普通 OpenLayers 图层 // 对于普通 OpenLayers 图层
visible.set(layer, layer.getVisible()); visible.set(layer, layer.getVisible());

View File

@@ -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 { constructNow, min } from "date-fns"; import { DeckLayer } from "@utils/layers";
interface StyleConfig { interface StyleConfig {
property: string; property: string;
@@ -190,6 +190,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
pipeText, pipeText,
setShowJunctionText, setShowJunctionText,
setShowPipeText, setShowPipeText,
setShowContourLayer,
setJunctionText, setJunctionText,
setPipeText, setPipeText,
} = data; } = data;
@@ -443,6 +444,7 @@ 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 &&
@@ -1426,6 +1428,110 @@ 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 (
<> <>

View File

@@ -241,13 +241,13 @@ const Timeline: React.FC<TimelineProps> = ({
// 播放控制 // 播放控制
const handlePlay = useCallback(() => { const handlePlay = useCallback(() => {
if (!isPlaying) { if (!isPlaying) {
if (junctionText === "" && pipeText === "") { // if (junctionText === "" && pipeText === "") {
open?.({ // open?.({
type: "error", // type: "error",
message: "请至少设定并应用一个图层的样式。", // message: "请至少设定并应用一个图层的样式。",
}); // });
// return; // return;
} // }
setIsPlaying(true); setIsPlaying(true);
intervalRef.current = setInterval(() => { intervalRef.current = setInterval(() => {
@@ -367,13 +367,13 @@ const Timeline: React.FC<TimelineProps> = ({
// 检查至少一个属性有值 // 检查至少一个属性有值
const junctionProperties = junctionText; const junctionProperties = junctionText;
const pipeProperties = pipeText; const pipeProperties = pipeText;
if (junctionProperties === "" && pipeProperties === "") { // if (junctionProperties === "" && pipeProperties === "") {
open?.({ // open?.({
type: "error", // type: "error",
message: "请至少设定并应用一个图层的样式。", // message: "请至少设定并应用一个图层的样式。",
}); // });
return; // return;
} // }
fetchFrameData( fetchFrameData(
currentTimeToDate(selectedDate, currentTime), currentTimeToDate(selectedDate, currentTime),
junctionText, junctionText,

View File

@@ -49,6 +49,7 @@ interface DataContextType {
showPipeText?: boolean; // 是否显示管道文本 showPipeText?: boolean; // 是否显示管道文本
setShowJunctionText?: React.Dispatch<React.SetStateAction<boolean>>; setShowJunctionText?: React.Dispatch<React.SetStateAction<boolean>>;
setShowPipeText?: React.Dispatch<React.SetStateAction<boolean>>; setShowPipeText?: React.Dispatch<React.SetStateAction<boolean>>;
setShowContourLayer?: 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>>;
@@ -86,7 +87,7 @@ 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 deckRef = useRef<Deck | null>(null);
const deckFlowRef = useRef<Deck | null>(null); const deckLayerRef = useRef<DeckLayer | null>(null);
const [map, setMap] = useState<OlMap>(); const [map, setMap] = useState<OlMap>();
// currentCalData 用于存储当前计算结果 // currentCalData 用于存储当前计算结果
@@ -111,10 +112,10 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
const [showPipeText, setShowPipeText] = useState(false); // 控制管道文本显示 const [showPipeText, setShowPipeText] = useState(false); // 控制管道文本显示
const [showJunctionTextLayer, setShowJunctionTextLayer] = useState(true); // 控制节点文本图层显示 const [showJunctionTextLayer, setShowJunctionTextLayer] = useState(true); // 控制节点文本图层显示
const [showPipeTextLayer, setShowPipeTextLayer] = useState(true); // 控制管道文本图层显示 const [showPipeTextLayer, setShowPipeTextLayer] = useState(true); // 控制管道文本图层显示
const [showContourLayer, setShowContourLayer] = useState(true); // 控制等高线图层显示
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 waterflowUserVisible = useRef<boolean>(true); // 用户设置的水流图层可见性
const [currentZoom, setCurrentZoom] = useState(11); // 当前缩放级别 const [currentZoom, setCurrentZoom] = useState(11); // 当前缩放级别
// 防抖更新函数 // 防抖更新函数
@@ -571,6 +572,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
controls: [], controls: [],
}); });
setMap(map); setMap(map);
// 恢复上次视图;如果没有则适配 MAP_EXTENT // 恢复上次视图;如果没有则适配 MAP_EXTENT
try { try {
const stored = localStorage.getItem(MAP_VIEW_STORAGE_KEY); const stored = localStorage.getItem(MAP_VIEW_STORAGE_KEY);
@@ -650,37 +652,13 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
layers: [], layers: [],
}); });
deckRef.current = deck; deckRef.current = deck;
const deckLayer = new DeckLayer(deck); const deckLayer = new DeckLayer(deck, {
// deckLayer.setZIndex(1000); // 确保在最上层 name: "deckLayer",
value: "deckLayer",
});
deckLayerRef.current = deckLayer;
map.addLayer(deckLayer); map.addLayer(deckLayer);
// 初始化水流动画的 deck.gl
const deckFlow = new Deck({
initialViewState: {
longitude: 0,
latitude: 0,
zoom: 1,
},
canvas: "deck-flow-canvas",
controller: false,
layers: [],
});
deckFlowRef.current = deckFlow;
const deckFlowLayer = new DeckLayer(deckFlow, {
name: "水流动画",
value: "waterflow",
type: "animation",
});
// 初始化用户可见性状态(默认为 true
deckFlowLayer.initUserVisibility("waterflowLayer", true);
// 设置可见性变化回调,同步更新 waterflowUserVisible
deckFlowLayer.setVisibilityChangeCallback((layerId, visible) => {
if (layerId === "waterflowLayer") {
waterflowUserVisible.current = visible;
}
});
map.addLayer(deckFlowLayer);
// 清理函数 // 清理函数
return () => { return () => {
junctionsLayer.un("change:visible", handleJunctionVisibilityChange); junctionsLayer.un("change:visible", handleJunctionVisibilityChange);
@@ -688,85 +666,119 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
map.setTarget(undefined); map.setTarget(undefined);
map.dispose(); map.dispose();
deck.finalize(); deck.finalize();
deckFlow.finalize();
}; };
}, []); }, []);
// 当数据变化时,更新 deck.gl 图层 // 当数据变化时,更新 deck.gl 图层
useEffect(() => { useEffect(() => {
const deck = deckRef.current; const deckLayer = deckLayerRef.current;
if (!deck) return; // 如果 deck 实例还未创建,则退出 if (!deckLayer) return; // 如果 deck 实例还未创建,则退出
const newLayers = [ if (!junctionData.length) return;
new TextLayer({ if (!pipeData.length) return;
id: "junctionTextLayer", console.log(pipeData);
zIndex: 10, console.log(pipeText);
data: showJunctionText ? junctionData : [], const junctionTextLayer = new TextLayer({
getPosition: (d: any) => d.position, id: "junctionTextLayer",
fontFamily: "Monaco, monospace", zIndex: 10,
getText: (d: any) => data: showJunctionText ? junctionData : [],
d[junctionText] ? (d[junctionText] as number).toFixed(3) : "", getPosition: (d: any) => d.position,
getSize: 18, fontFamily: "Monaco, monospace",
fontWeight: "bold", getText: (d: any) =>
getColor: [0, 0, 0], d[junctionText] ? (d[junctionText] as number).toFixed(3) : "",
getAngle: 0, getSize: 18,
getTextAnchor: "middle", fontWeight: "bold",
getAlignmentBaseline: "center", getColor: [0, 0, 0],
getPixelOffset: [0, -10], getAngle: 0,
visible: getTextAnchor: "middle",
showJunctionTextLayer && currentZoom >= 15 && currentZoom <= 24, getAlignmentBaseline: "center",
extensions: [new CollisionFilterExtension()], getPixelOffset: [0, -10],
collisionTestProps: { visible: showJunctionTextLayer && currentZoom >= 15 && currentZoom <= 24,
sizeScale: 3, // 增加碰撞检测的尺寸以提供更大间距 extensions: [new CollisionFilterExtension()],
}, collisionTestProps: {
// 可读性设置 sizeScale: 3, // 增加碰撞检测的尺寸以提供更大间距
characterSet: "auto", },
fontSettings: { // 可读性设置
sdf: true, characterSet: "auto",
fontSize: 64, fontSettings: {
buffer: 6, sdf: true,
}, fontSize: 64,
// outlineWidth: 10, buffer: 6,
// outlineColor: [242, 244, 246, 255], },
}), // outlineWidth: 10,
new TextLayer({ // outlineColor: [242, 244, 246, 255],
id: "pipeTextLayer", });
zIndex: 10, const pipeTextLayer = new TextLayer({
data: showPipeText ? pipeData : [], id: "pipeTextLayer",
getPosition: (d: any) => d.position, zIndex: 10,
fontFamily: "Monaco, monospace", data: showPipeText ? pipeData : [],
getText: (d: any) => getPosition: (d: any) => d.position,
d[pipeText] ? Math.abs(d[pipeText] as number).toFixed(3) : "", fontFamily: "Monaco, monospace",
getSize: 18, getText: (d: any) =>
fontWeight: "bold", d[pipeText] ? Math.abs(d[pipeText] as number).toFixed(3) : "",
getColor: [0, 0, 0], getSize: 18,
getAngle: (d: any) => d.angle || 0, fontWeight: "bold",
getPixelOffset: [0, -8], getColor: [0, 0, 0],
getTextAnchor: "middle", getAngle: (d: any) => d.angle || 0,
getAlignmentBaseline: "bottom", getPixelOffset: [0, -8],
visible: showPipeTextLayer && currentZoom >= 15 && currentZoom <= 24, getTextAnchor: "middle",
extensions: [new CollisionFilterExtension()], getAlignmentBaseline: "bottom",
collisionTestProps: { visible: showPipeTextLayer && currentZoom >= 15 && currentZoom <= 24,
sizeScale: 3, // 增加碰撞检测的尺寸以提供更大间距 extensions: [new CollisionFilterExtension()],
}, collisionTestProps: {
// 可读性设置 sizeScale: 3, // 增加碰撞检测的尺寸以提供更大间距
characterSet: "auto", },
fontSettings: { // 可读性设置
sdf: true, characterSet: "auto",
fontSize: 64, fontSettings: {
buffer: 6, sdf: true,
}, fontSize: 64,
// outlineWidth: 10, buffer: 6,
// outlineColor: [242, 244, 246, 255], },
}), // outlineWidth: 10,
]; // outlineColor: [242, 244, 246, 255],
deck.setProps({ layers: newLayers }); });
if (deckLayer.getDeckLayerById("junctionTextLayer")) {
// 传入完整 layer 实例以保证 clone/替换时保留 layer 类型和方法
deckLayer.updateDeckLayer("junctionTextLayer", junctionTextLayer);
} else {
deckLayer.addDeckLayer(junctionTextLayer);
}
if (deckLayer.getDeckLayerById("pipeTextLayer")) {
deckLayer.updateDeckLayer("pipeTextLayer", pipeTextLayer);
} else {
deckLayer.addDeckLayer(pipeTextLayer);
}
console.log(deckLayer.getDeckLayers());
}, [
junctionData,
pipeData,
currentZoom,
showJunctionText,
showPipeText,
showJunctionTextLayer,
showPipeTextLayer,
showContourLayer,
junctionText,
pipeText,
]);
// 控制流动动画开关
useEffect(() => {
if (pipeText === "flow" && currentPipeCalData.length > 0) {
flowAnimation.current = true;
} else {
flowAnimation.current = false;
}
const deckLayer = deckLayerRef.current;
if (!deckLayer) return; // 如果 deck 实例还未创建,则退出
let animationFrameId: number; // 保存 requestAnimationFrame 的 ID
// 动画循环 // 动画循环
const animate = () => { const animate = () => {
if (!deck || !flowAnimation.current) return; // 添加检查,防止空数据或停止旧循环 if (!deckRef.current || !flowAnimation.current) return; // 添加检查,防止空数据或停止旧循环
// 动画总时长(秒) // 动画总时长(秒)
if (pipeData.length === 0) { if (pipeData.length === 0) {
requestAnimationFrame(animate); animationFrameId = requestAnimationFrame(animate);
return; return;
} }
const animationDuration = 10; const animationDuration = 10;
@@ -787,119 +799,31 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
getColor: [0, 220, 255], getColor: [0, 220, 255],
opacity: 0.8, opacity: 0.8,
visible: visible:
waterflowUserVisible.current && flowAnimation.current && currentZoom >= 12 && currentZoom <= 24,
flowAnimation.current &&
currentZoom >= 12 &&
currentZoom <= 24,
widthMinPixels: 5, widthMinPixels: 5,
jointRounded: true, // 拐角变圆 jointRounded: true, // 拐角变圆
// capRounded: true, // 端点变圆 // capRounded: true, // 端点变圆
trailLength: 2, // 水流尾迹淡出时间 trailLength: 2, // 水流尾迹淡出时间
currentTime: currentTime, currentTime: currentTime,
}); });
if (deckLayer.getDeckLayerById("waterflowLayer")) {
// 获取当前除 waterflowLayer 之外的所有图层 deckLayer.updateDeckLayer("waterflowLayer", waterflowLayer);
const otherLayers = deck.props.layers.filter( } else {
(layer: any) => layer && layer.id !== "waterflowLayer" deckLayer.addDeckLayer(waterflowLayer);
); }
deck.setProps({
layers: [...otherLayers, waterflowLayer],
});
// 继续请求动画帧,每帧执行一次函数 // 继续请求动画帧,每帧执行一次函数
requestAnimationFrame(animate); animationFrameId = requestAnimationFrame(animate);
};
animate();
}, [
flowAnimation,
junctionData,
pipeData,
currentZoom,
showJunctionText,
showPipeText,
showJunctionTextLayer,
showPipeTextLayer,
junctionText,
pipeText,
]);
// 控制流动动画开关
useEffect(() => {
if (pipeText === "flow" && currentPipeCalData.length > 0) {
flowAnimation.current = true;
} else {
flowAnimation.current = false;
}
}, [currentPipeCalData, pipeText]);
// 水流动画循环
useEffect(() => {
const deckFlow = deckFlowRef.current;
if (!deckFlow || !flowAnimation.current || pipeData.length === 0) {
// 如果不需要动画,清空图层
if (deckFlow) {
deckFlow.setProps({ layers: [] });
}
return;
}
let animationId: number;
const animate = () => {
if (!deckFlow || !flowAnimation.current) return;
if (pipeData.length === 0) {
animationId = requestAnimationFrame(animate);
return;
}
// 动画总时长(秒)
const animationDuration = 10;
// 缓冲时间(秒)
const bufferTime = 2;
// 完整循环周期
const loopLength = animationDuration + bufferTime;
// 确保时间范围与你的时间戳数据匹配
const currentTime = (Date.now() / 1000) % loopLength; // (0,12) 之间循环
const waterflowLayer = new TripsLayer({
id: "waterflowLayer",
data: pipeData,
getPath: (d) => d.path,
getTimestamps: (d) => {
return d.timestamps; // 这些应该是与 currentTime 匹配的数值
},
getColor: [0, 220, 255],
opacity: 0.8,
visible:
waterflowUserVisible.current &&
flowAnimation.current &&
currentZoom >= 12 &&
currentZoom <= 24,
widthMinPixels: 5,
jointRounded: true, // 拐角变圆
// capRounded: true, // 端点变圆
trailLength: 2, // 水流尾迹淡出时间
currentTime: currentTime,
});
deckFlow.setProps({
layers: [waterflowLayer],
});
// 继续请求动画帧,每帧执行一次函数
animationId = requestAnimationFrame(animate);
}; };
animate(); animate();
// 清理函数 // 清理函数:取消动画帧
return () => { return () => {
if (animationId) { if (animationFrameId) {
cancelAnimationFrame(animationId); cancelAnimationFrame(animationFrameId);
}
if (deckFlow) {
deckFlow.setProps({ layers: [] });
} }
}; };
}, [flowAnimation, pipeData, currentZoom]); }, [currentZoom, currentPipeCalData, pipeText, pipeData.length]);
// 计算值更新时,更新 junctionData 和 pipeData // 计算值更新时,更新 junctionData 和 pipeData
useEffect(() => { useEffect(() => {
const junctionProperties = junctionText; const junctionProperties = junctionText;
@@ -965,6 +889,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
setCurrentPipeCalData, setCurrentPipeCalData,
setShowJunctionText, setShowJunctionText,
setShowPipeText, setShowPipeText,
setShowContourLayer,
setJunctionText, setJunctionText,
setPipeText, setPipeText,
showJunctionText, showJunctionText,
@@ -980,7 +905,6 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
{children} {children}
</div> </div>
<canvas id="deck-canvas" /> <canvas id="deck-canvas" />
<canvas id="deck-flow-canvas" />
</MapContext.Provider> </MapContext.Provider>
</DataContext.Provider> </DataContext.Provider>
</> </>

View File

@@ -67,6 +67,14 @@ export class DeckLayer extends Layer {
// 添加图层 // 添加图层
addDeckLayer(layer: any): void { addDeckLayer(layer: any): void {
const currentLayers = this.getDeckLayers(); const currentLayers = this.getDeckLayers();
// 如果已有同 id 图层,则替换保持顺序;否则追加
const idx = currentLayers.findIndex((l: any) => l && l.id === layer.id);
if (idx >= 0) {
const copy = [...currentLayers];
copy[idx] = layer;
this.deck.setProps({ layers: copy });
return;
}
this.deck.setProps({ layers: [...currentLayers, layer] }); this.deck.setProps({ layers: [...currentLayers, layer] });
} }
@@ -85,15 +93,41 @@ export class DeckLayer extends Layer {
return layers.find((layer: any) => layer && layer.id === layerId); return layers.find((layer: any) => layer && layer.id === layerId);
} }
// 更新特定图层 // 更新特定图层:支持传入一个新的 Layer 实例或一个 props 对象
updateDeckLayer(layerId: string, props: any): void { // - 如果传入的是 Layer 实例,则直接替换同 id 的图层为该实例
// - 如果传入的是 props普通对象则基于原图层调用 clone(props)
updateDeckLayer(layerId: string, layerOrProps: any): void {
const layers = this.getDeckLayers(); const layers = this.getDeckLayers();
const updatedLayers = layers.map((layer: any) => { const updatedLayers = layers.map((layer: any) => {
if (layer && layer.id === layerId) { if (!layer || layer.id !== layerId) return layer;
return layer.clone(props);
// 如果传入的是一个 deck.gl Layer 实例(通常包含 id 和 props
if (
layerOrProps &&
typeof layerOrProps === "object" &&
layerOrProps.id !== undefined &&
typeof layerOrProps.clone === "function"
) {
// 替换为新的 layer 实例
return layerOrProps;
}
// 否则假定传入的是 props 对象,使用现有 layer.clone(props) 创建新实例
try {
return layer.clone(layerOrProps);
} catch (err) {
// 如果 clone 失败,作为降级策略尝试手动复制 props 到新对象(保留原 layer
// 这通常不应该发生,但保证不会抛出而破坏整个 layers 列表
const newLayer = layer.clone
? layer.clone(layerOrProps)
: {
...layer,
props: { ...(layer.props || {}), ...(layerOrProps || {}) },
};
return newLayer;
} }
return layer;
}); });
this.deck.setProps({ layers: updatedLayers }); this.deck.setProps({ layers: updatedLayers });
} }
@@ -113,7 +147,16 @@ export class DeckLayer extends Layer {
// 存储用户设置的可见性 // 存储用户设置的可见性
this.userVisibility.set(layerId, visible); this.userVisibility.set(layerId, visible);
// 更新图层(注意:实际的 visible 可能还受其他条件控制) // 更新图层(注意:实际的 visible 可能还受其他条件控制)
this.updateDeckLayer(layerId, { visible }); // 优先尝试使用传入的 layer 实例替换,否则使用 clone({ visible }) 来保留图层类型
const found = this.getDeckLayerById(layerId);
if (!found) return;
try {
// 使用 clone 来确保保持同类型实例
this.updateDeckLayer(layerId, { ...found.props, visible });
} catch (err) {
// 降级:直接替换属性
this.updateDeckLayer(layerId, { visible });
}
// 触发回调通知外部 // 触发回调通知外部
if (this.onVisibilityChange) { if (this.onVisibilityChange) {
this.onVisibilityChange(layerId, visible); this.onVisibilityChange(layerId, visible);