完善时间轴数据更新函数;完善图层显示控制函数;修复图例样式显示异常问题;

This commit is contained in:
JIANG
2025-10-16 17:35:39 +08:00
parent fa0970bd79
commit 665160ae65
5 changed files with 589 additions and 308 deletions

View File

@@ -214,7 +214,7 @@ const BaseLayers: React.FC = () => {
{isExpanded && ( {isExpanded && (
<div <div
className={clsx( className={clsx(
"absolute flex right-24 bottom-0 w-90 h-25 bg-white rounded-xl drop-shadow-xl shadow-black transition-all duration-300", "absolute flex right-24 bottom-0 w-90 h-25 bg-white rounded-xl drop-shadow-xl shadow-black transition-all duration-300",
isShow ? "opacity-100" : "opacity-0" isShow ? "opacity-100" : "opacity-0"
)} )}
onMouseEnter={handleEnter} onMouseEnter={handleEnter}

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useRef } from "react";
// 导入Material-UI图标和组件 // 导入Material-UI图标和组件
import ColorLensIcon from "@mui/icons-material/ColorLens"; import ColorLensIcon from "@mui/icons-material/ColorLens";
@@ -19,6 +19,7 @@ import {
// 导入OpenLayers样式相关模块 // 导入OpenLayers样式相关模块
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile"; import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import VectorTileSource from "ol/source/VectorTile";
import { useData, useMap } from "../MapComponent"; import { useData, useMap } from "../MapComponent";
import StyleLegend, { LegendStyleConfig } from "./StyleLegend"; import StyleLegend, { LegendStyleConfig } from "./StyleLegend";
@@ -26,6 +27,8 @@ import { FlatStyleLike } from "ol/style/flat";
import { calculateClassification } from "@utils/breaks_classification"; import { calculateClassification } from "@utils/breaks_classification";
import { parseColor } from "@utils/parseColor"; import { parseColor } from "@utils/parseColor";
import { VectorTile } from "ol";
import { se } from "date-fns/locale";
interface StyleConfig { interface StyleConfig {
property: string; property: string;
@@ -94,7 +97,8 @@ const GRADIENT_PALETTES = [
// 预设分类方法 // 预设分类方法
const CLASSIFICATION_METHODS = [ const CLASSIFICATION_METHODS = [
{ name: "优雅分段", value: "pretty_breaks" }, { name: "优雅分段", value: "pretty_breaks" },
{ name: "自然间断", value: "jenks_optimized" }, // 浏览器中实现Jenks算法性能较差暂时移除
// { name: "自然间断", value: "jenks_optimized" },
]; ];
const StyleEditorPanel: React.FC = () => { const StyleEditorPanel: React.FC = () => {
@@ -104,8 +108,10 @@ const StyleEditorPanel: React.FC = () => {
return <div>Loading...</div>; // 或其他占位符 return <div>Loading...</div>; // 或其他占位符
} }
const { const {
junctionData, currentJunctionCalData,
pipeData, currentPipeCalData,
junctionText,
pipeText,
setShowJunctionText, setShowJunctionText,
setShowPipeText, setShowPipeText,
setJunctionText, setJunctionText,
@@ -114,6 +120,8 @@ const StyleEditorPanel: React.FC = () => {
const [applyJunctionStyle, setApplyJunctionStyle] = useState(false); const [applyJunctionStyle, setApplyJunctionStyle] = useState(false);
const [applyPipeStyle, setApplyPipeStyle] = useState(false); const [applyPipeStyle, setApplyPipeStyle] = useState(false);
const [styleUpdateTrigger, setStyleUpdateTrigger] = useState(0); // 用于触发样式更新的状态
const prevStyleUpdateTriggerRef = useRef<number>(0);
const [renderLayers, setRenderLayers] = useState<WebGLVectorTileLayer[]>([]); const [renderLayers, setRenderLayers] = useState<WebGLVectorTileLayer[]>([]);
const [selectedRenderLayer, setSelectedRenderLayer] = const [selectedRenderLayer, setSelectedRenderLayer] =
@@ -131,7 +139,7 @@ const StyleEditorPanel: React.FC = () => {
classificationMethod: "pretty_breaks", classificationMethod: "pretty_breaks",
segments: 5, segments: 5,
minSize: 4, minSize: 4,
maxSize: 15, maxSize: 12,
minStrokeWidth: 2, minStrokeWidth: 2,
maxStrokeWidth: 6, maxStrokeWidth: 6,
fixedStrokeWidth: 3, fixedStrokeWidth: 3,
@@ -142,17 +150,6 @@ const StyleEditorPanel: React.FC = () => {
opacity: 0.9, opacity: 0.9,
adjustWidthByProperty: true, adjustWidthByProperty: true,
}); });
const [legendStyleConfig, setLegendStyleConfig] = useState<LegendStyleConfig>(
{
layerName: "",
layerId: "",
property: "",
colors: [],
type: "point",
dimensions: [],
breaks: [],
}
);
// 样式状态管理 - 存储多个图层的样式状态 // 样式状态管理 - 存储多个图层的样式状态
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>( const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>(
[] []
@@ -185,41 +182,141 @@ const StyleEditorPanel: React.FC = () => {
}, },
[gradientPaletteIndex, parseColor] [gradientPaletteIndex, parseColor]
); );
// 应用分类样式 // 保存当前图层的样式状态
const setStyleState = (layer: any) => { const saveLayerStyle = useCallback(
if ( (layerId?: string, legendConfig?: LegendStyleConfig) => {
layer.get("value") !== undefined && if (!selectedRenderLayer || !styleConfig.property) {
styleConfig.property !== undefined console.warn("无法保存样式:缺少必要的图层或样式配置");
) { return;
}
if (!layerId) return; // 如果没有传入 layerId则不保存
const layerName = selectedRenderLayer.get("name") || `图层${layerId}`;
// 如果没有传入图例配置,则创建一个默认的空配置
const finalLegendConfig: LegendStyleConfig = legendConfig || {
layerId,
layerName,
property: styleConfig.property,
colors: [],
type: "point",
dimensions: [],
breaks: [],
};
const newStyleState: LayerStyleState = {
layerId,
layerName,
styleConfig: { ...styleConfig },
legendConfig: { ...finalLegendConfig },
isActive: true,
};
setLayerStyleStates((prev) => {
// 检查是否已存在该图层的样式状态
const existingIndex = prev.findIndex(
(state) => state.layerId === layerId
);
if (existingIndex !== -1) {
// 更新已存在的状态
const updated = [...prev];
updated[existingIndex] = newStyleState;
return updated;
} else {
// 添加新的状态
return [...prev, newStyleState];
}
});
},
[selectedRenderLayer, styleConfig]
);
// 设置分类样式参数,触发样式应用
const setStyleState = () => {
if (!selectedRenderLayer) return;
const layerId = selectedRenderLayer.get("value");
const property = styleConfig.property;
if (layerId !== undefined && property !== undefined) {
// 更新文字标签设置 // 更新文字标签设置
if (layer.get("value") === "junctions") { if (layerId === "junctions") {
if (setJunctionText && setShowJunctionText) { if (setJunctionText && setShowJunctionText) {
setJunctionText(styleConfig.property); setJunctionText(property);
setShowJunctionText(styleConfig.showLabels); setShowJunctionText(styleConfig.showLabels);
setApplyJunctionStyle(true); setApplyJunctionStyle(true);
saveLayerStyle(layerId);
} }
} }
if (layer.get("value") === "pipes") { if (layerId === "pipes") {
console.log(styleConfig);
if (setPipeText && setShowPipeText) { if (setPipeText && setShowPipeText) {
setPipeText(styleConfig.property); setPipeText(property);
setShowPipeText(styleConfig.showLabels); setShowPipeText(styleConfig.showLabels);
setApplyPipeStyle(true); setApplyPipeStyle(true);
saveLayerStyle(layerId);
} }
} }
// 触发样式更新
setStyleUpdateTrigger((prev) => prev + 1);
} }
}; };
const applyStyle = (layerId: string, breaks?: number[]) => { // 计算分类样式,并应用到对应图层
const applyClassificationStyle = (
layerType: "junctions" | "pipes",
styleConfig: any
) => {
if (
layerType === "junctions" &&
currentJunctionCalData &&
currentJunctionCalData.length > 0
) {
// 应用节点样式
let junctionStyleConfigState = layerStyleStates.find(
(s) => s.layerId === "junctions"
);
// 更新节点数据属性
const segments = junctionStyleConfigState?.styleConfig.segments ?? 5;
const breaks = calculateClassification(
currentJunctionCalData.map((d) => d.value),
segments,
styleConfig.classificationMethod
);
if (breaks.length === 0) {
console.warn("计算的 breaks 为空,无法应用样式");
return;
}
if (junctionStyleConfigState)
applyLayerStyle(junctionStyleConfigState, breaks);
} else if (
layerType === "pipes" &&
currentPipeCalData &&
currentPipeCalData.length > 0
) {
// 应用管道样式
let pipeStyleConfigState = layerStyleStates.find(
(s) => s.layerId === "pipes"
);
// 更新管道数据属性
const segments = pipeStyleConfigState?.styleConfig.segments ?? 5;
const breaks = calculateClassification(
currentPipeCalData.map((d) => d.value),
segments,
styleConfig.classificationMethod
);
if (pipeStyleConfigState) applyLayerStyle(pipeStyleConfigState, breaks);
}
};
// 应用样式函数,传入 breaks 数据
const applyLayerStyle = (
layerStyleConfig: LayerStyleState,
breaks?: number[]
) => {
// 使用传入的 breaks 数据 // 使用传入的 breaks 数据
if (!breaks || breaks.length === 0) { if (!breaks || breaks.length === 0) {
console.warn("没有有效的 breaks 数据"); console.warn("没有有效的 breaks 数据");
return; return;
} }
const styleConfig = layerStyleStates.find( const styleConfig = layerStyleConfig.styleConfig;
(s) => s.layerId === layerId const selectedRenderLayer = renderLayers.filter((layer) => {
)?.styleConfig; return layer.get("value") === layerStyleConfig.layerId;
const selectedRenderLayer = renderLayers.find( })[0];
(l) => l.get("id") === layerId
);
if (!selectedRenderLayer || !styleConfig?.property) return; if (!selectedRenderLayer || !styleConfig?.property) return;
const layerType: string = selectedRenderLayer?.get("type"); const layerType: string = selectedRenderLayer?.get("type");
const source = selectedRenderLayer.getSource(); const source = selectedRenderLayer.getSource();
@@ -257,26 +354,14 @@ const StyleEditorPanel: React.FC = () => {
(styleConfig.maxSize - styleConfig.minSize) * ratio (styleConfig.maxSize - styleConfig.minSize) * ratio
); );
}); });
// 创建图例配置对象
const legendConfig: LegendStyleConfig = {
layerName: selectedRenderLayer.get("name"),
layerId: selectedRenderLayer.get("value"),
property: selectedProperty.name,
colors: colors,
type: layerType,
dimensions: dimensions,
breaks: breaks,
};
// 更新图例配置
setLegendStyleConfig(legendConfig);
// 动态生成颜色条件表达式 // 动态生成颜色条件表达式
const generateColorConditions = (): any[] => { const generateColorConditions = (property: string): any[] => {
const conditions: any[] = ["case"]; const conditions: any[] = ["case"];
for (let i = 0; i < breaks.length; i++) { for (let i = 0; i < breaks.length; i++) {
// 添加条件:属性值 <= 当前断点 // 添加条件:属性值 <= 当前断点
conditions.push(["<=", ["get", styleConfig.property], breaks[i]]); conditions.push(["<=", ["get", property], breaks[i]]);
// 添加对应的颜色值 // 添加对应的颜色值
const colorObj = parseColor(colors[i]); const colorObj = parseColor(colors[i]);
const color = `rgba(${colorObj.r}, ${colorObj.g}, ${colorObj.b}, ${styleConfig.opacity})`; const color = `rgba(${colorObj.r}, ${colorObj.g}, ${colorObj.b}, ${styleConfig.opacity})`;
@@ -289,14 +374,12 @@ const StyleEditorPanel: React.FC = () => {
return conditions; return conditions;
}; };
// 动态生成尺寸条件表达式 // 动态生成尺寸条件表达式
const generateDimensionConditions = (): any[] => { const generateDimensionConditions = (property: string): any[] => {
const conditions: any[] = ["case"]; const conditions: any[] = ["case"];
for (let i = 0; i < breaks.length; i++) { for (let i = 0; i < breaks.length; i++) {
conditions.push(["<=", ["get", styleConfig.property], breaks[i]]); conditions.push(["<=", ["get", property], breaks[i]]);
conditions.push(dimensions[i]); conditions.push(dimensions[i]);
} }
conditions.push(dimensions[dimensions.length - 1]); conditions.push(dimensions[dimensions.length - 1]);
return conditions; return conditions;
}; };
@@ -305,25 +388,29 @@ const StyleEditorPanel: React.FC = () => {
// 根据图层类型设置不同的样式属性 // 根据图层类型设置不同的样式属性
if (layerType === "linestring") { if (layerType === "linestring") {
dynamicStyle["stroke-color"] = generateColorConditions(); dynamicStyle["stroke-color"] = generateColorConditions(
dynamicStyle["stroke-width"] = generateDimensionConditions(); styleConfig.property
);
dynamicStyle["stroke-width"] = generateDimensionConditions(
styleConfig.property
);
} else if (layerType === "point") { } else if (layerType === "point") {
dynamicStyle["circle-fill-color"] = generateColorConditions(); dynamicStyle["circle-fill-color"] = generateColorConditions(
dynamicStyle["circle-radius"] = generateDimensionConditions(); styleConfig.property
dynamicStyle["circle-stroke-color"] = generateColorConditions(); );
dynamicStyle["circle-radius"] = generateDimensionConditions(
styleConfig.property
);
dynamicStyle["circle-stroke-color"] = generateColorConditions(
styleConfig.property
);
dynamicStyle["circle-stroke-width"] = 2; dynamicStyle["circle-stroke-width"] = 2;
} else {
// 面要素
dynamicStyle["fill-color"] = generateColorConditions();
dynamicStyle["stroke-color"] = generateColorConditions();
dynamicStyle["stroke-width"] = generateDimensionConditions();
} }
selectedRenderLayer.setStyle(dynamicStyle); selectedRenderLayer.setStyle(dynamicStyle);
// console.log(map?.getAllLayers());
// 创建图例配置对象 // 创建图例配置对象
const finalLegendConfig: LegendStyleConfig = { const legendConfig: LegendStyleConfig = {
layerName: selectedRenderLayer.get("name"), layerName: selectedRenderLayer.get("name"),
layerId: selectedRenderLayer.get("value"), layerId: selectedRenderLayer.get("value"),
property: selectedProperty.name, property: selectedProperty.name,
@@ -332,28 +419,29 @@ const StyleEditorPanel: React.FC = () => {
dimensions: dimensions, dimensions: dimensions,
breaks: breaks, breaks: breaks,
}; };
// 更新图例配置
setLegendStyleConfig(finalLegendConfig);
// 自动保存样式状态,直接传入图例配置 // 自动保存样式状态,直接传入图例配置
setTimeout(() => { setTimeout(() => {
saveLayerStyle(finalLegendConfig); saveLayerStyle(selectedRenderLayer.get("value"), legendConfig);
}, 100); }, 100);
}; };
// 重置样式 // 重置样式
const resetStyle = useCallback(() => { const resetStyle = useCallback(() => {
if (!selectedRenderLayer) return; if (!selectedRenderLayer) return;
// 重置 WebGL 图层样式 // 重置 WebGL 图层样式
const defaultFlatStyle: FlatStyleLike = { const defaultFlatStyle: FlatStyleLike = {
"stroke-width": 2, "stroke-width": 3,
"stroke-color": `rgba(51, 153, 204, 0.9)`, "stroke-color": "rgba(51, 153, 204, 0.9)",
"fill-color": `rgba(51, 153, 204, 0.5)`, "circle-fill-color": "rgba(255,255,255,0.4)",
"circle-radius": 7, "circle-stroke-color": "rgba(255,255,255,0.9)",
"circle-stroke-width": 2, "circle-radius": [
"circle-stroke-color": `rgba(51, 153, 204, 0.9)`, "interpolate",
"circle-fill-color": `rgba(51, 153, 204, 0.5)`, ["linear"],
["zoom"],
12,
1, // 在缩放级别 12 时,圆形半径为 1px
24,
12, // 在缩放级别 24 时,圆形半径为 12px
],
}; };
selectedRenderLayer.setStyle(defaultFlatStyle); selectedRenderLayer.setStyle(defaultFlatStyle);
@@ -366,85 +454,190 @@ const StyleEditorPanel: React.FC = () => {
// 重置样式应用状态 // 重置样式应用状态
if (layerId === "junctions") { if (layerId === "junctions") {
setApplyJunctionStyle(false); setApplyJunctionStyle(false);
if (setShowJunctionText) setShowJunctionText(false);
if (setJunctionText) setJunctionText("");
} else if (layerId === "pipes") { } else if (layerId === "pipes") {
setApplyPipeStyle(false); setApplyPipeStyle(false);
if (setShowPipeText) setShowPipeText(false);
if (setPipeText) setPipeText("");
} }
} }
}, [selectedRenderLayer]); }, [selectedRenderLayer]);
// 更新当前 VectorTileSource 中的所有缓冲要素属性
const updateVectorTileSource = (property: string, data: any[]) => {
if (!map) return;
const vectorTileSources = map
.getAllLayers()
.filter((layer) => layer instanceof WebGLVectorTileLayer)
.map((layer) => layer.getSource() as VectorTileSource)
.filter((source) => source);
if (!vectorTileSources.length) return;
// 创建 id 到 value 的映射
const dataMap = new Map<string, number>();
data.forEach((d: any) => {
dataMap.set(d.ID, d.value || 0);
});
// 直接遍历所有瓦片和要素,无需分批处理
vectorTileSources.forEach((vectorTileSource) => {
const sourceTiles = vectorTileSource.sourceTiles_;
Object.values(sourceTiles).forEach((vectorTile) => {
const renderFeatures = vectorTile.getFeatures();
if (!renderFeatures || renderFeatures.length === 0) return;
// 直接更新要素属性
renderFeatures.forEach((renderFeature) => {
const featureId = renderFeature.get("id");
const value = dataMap.get(featureId);
if (value !== undefined) {
(renderFeature as any).properties_[property] = value;
}
});
});
});
};
// 新增事件,监听 VectorTileSource 的 tileloadend 事件,为新增瓦片数据动态更新要素属性
const [tileLoadListeners, setTileLoadListeners] = useState<
Map<VectorTileSource, (event: any) => void>
>(new Map());
const attachVectorTileSourceLoadedEvent = (
layerId: string,
property: string,
data: any[]
) => {
if (!map) return;
const vectorTileSource = map
.getAllLayers()
.filter((layer) => layer.get("value") === layerId)
.map((layer) => layer.getSource() as VectorTileSource)
.filter((source) => source)[0];
if (!vectorTileSource) return;
// 创建 id 到 value 的映射
const dataMap = new Map<string, number>();
data.forEach((d: any) => {
dataMap.set(d.ID, d.value || 0);
});
// 新增监听器并保存
const newListeners = new Map<VectorTileSource, (event: any) => void>();
const listener = (event: any) => {
try {
if (event.tile instanceof VectorTile) {
const renderFeatures = event.tile.getFeatures();
if (!renderFeatures || renderFeatures.length === 0) return;
// 直接更新要素属性
renderFeatures.forEach((renderFeature: any) => {
const featureId = renderFeature.get("id");
const value = dataMap.get(featureId);
if (value !== undefined) {
(renderFeature as any).properties_[property] = value;
}
});
}
} catch (error) {
console.error("Error processing tile load event:", error);
}
};
vectorTileSource.on("tileloadend", listener);
newListeners.set(vectorTileSource, listener);
setTileLoadListeners(newListeners);
};
// 新增函数:取消对应 layerId 已添加的 on 事件
const removeVectorTileSourceLoadedEvent = (layerId: string) => {
if (!map) return;
const vectorTileSource = map
.getAllLayers()
.filter((layer) => layer.get("value") === layerId)
.map((layer) => layer.getSource() as VectorTileSource)
.filter((source) => source)[0];
if (!vectorTileSource) return;
const listener = tileLoadListeners.get(vectorTileSource);
if (listener) {
vectorTileSource.un("tileloadend", listener);
setTileLoadListeners((prev) => {
const newMap = new Map(prev);
newMap.delete(vectorTileSource);
return newMap;
});
}
};
// 监听数据变化,重新应用样式。由样式应用按钮触发,或由数据变化触发
useEffect(() => { useEffect(() => {
if (applyJunctionStyle && junctionData.length > 0) { // 判断此次触发是否由用户点击“应用”按钮引起
// 应用节点样式 const isUserTrigger =
styleUpdateTrigger !== prevStyleUpdateTriggerRef.current;
// 更新 prevStyleUpdateTriggerRef
prevStyleUpdateTriggerRef.current = styleUpdateTrigger;
const updateJunctionStyle = () => {
if (!currentJunctionCalData) return;
const junctionStyleConfigState = layerStyleStates.find( const junctionStyleConfigState = layerStyleStates.find(
(s) => s.layerId === "junctions" (s) => s.layerId === "junctions"
); );
if (!junctionStyleConfigState) return; applyClassificationStyle(
const segments = junctionStyleConfigState?.styleConfig.segments; "junctions",
const breaks = calculateClassification( junctionStyleConfigState?.styleConfig
junctionData,
segments,
styleConfig.classificationMethod
); );
applyStyle(junctionStyleConfigState.layerId, breaks); // 更新现有的 VectorTileSource
} updateVectorTileSource(junctionText, currentJunctionCalData);
if (applyPipeStyle && pipeData.length > 0) { // 移除旧的监听器,并添加新的监听器
// 应用管道样式 removeVectorTileSourceLoadedEvent("junctions");
attachVectorTileSourceLoadedEvent(
"junctions",
junctionText,
currentJunctionCalData
);
};
const updatePipeStyle = () => {
if (!currentPipeCalData) return;
const pipeStyleConfigState = layerStyleStates.find( const pipeStyleConfigState = layerStyleStates.find(
(s) => s.layerId === "pipes" (s) => s.layerId === "pipes"
); );
if (!pipeStyleConfigState) return; applyClassificationStyle("pipes", pipeStyleConfigState?.styleConfig);
const segments = pipeStyleConfigState?.styleConfig.segments; // 更新现有的 VectorTileSource
const breaks = calculateClassification( updateVectorTileSource(pipeText, currentPipeCalData);
pipeData, // 移除旧的监听器,并添加新的监听器
segments, removeVectorTileSourceLoadedEvent("pipes");
styleConfig.classificationMethod attachVectorTileSourceLoadedEvent("pipes", pipeText, currentPipeCalData);
); };
applyStyle(pipeStyleConfigState.layerId, breaks); if (isUserTrigger) {
} if (selectedRenderLayer?.get("value") === "junctions") {
}, [junctionData, pipeData, applyJunctionStyle, applyPipeStyle]); updateJunctionStyle();
// 样式状态管理功能 } else if (selectedRenderLayer?.get("value") === "pipes") {
// 保存当前图层的样式状态 updatePipeStyle();
const saveLayerStyle = useCallback(
(overrideLegendConfig?: LegendStyleConfig) => {
if (!selectedRenderLayer || !styleConfig.property) {
console.warn("无法保存样式:缺少必要的图层或样式配置");
return;
} }
return;
}
if (
applyJunctionStyle &&
currentJunctionCalData &&
currentJunctionCalData.length > 0
) {
updateJunctionStyle();
}
if (applyPipeStyle && currentPipeCalData && currentPipeCalData.length > 0) {
updatePipeStyle();
}
if (!applyJunctionStyle) {
removeVectorTileSourceLoadedEvent("junctions");
}
if (!applyPipeStyle) {
removeVectorTileSourceLoadedEvent("pipes");
}
}, [
styleUpdateTrigger,
applyJunctionStyle,
applyPipeStyle,
currentJunctionCalData,
currentPipeCalData,
]);
const layerId = selectedRenderLayer.get("value"); // 获取地图中的矢量图层,用于选择图层选项
const layerName = selectedRenderLayer.get("name") || `图层${layerId}`;
// 使用传入的图例配置,或者使用当前状态的图例配置
const finalLegendConfig = overrideLegendConfig || legendStyleConfig;
const newStyleState: LayerStyleState = {
layerId,
layerName,
styleConfig: { ...styleConfig },
legendConfig: { ...finalLegendConfig },
isActive: true,
};
setLayerStyleStates((prev) => {
// 检查是否已存在该图层的样式状态
const existingIndex = prev.findIndex(
(state) => state.layerId === layerId
);
if (existingIndex !== -1) {
// 更新已存在的状态
const updated = [...prev];
updated[existingIndex] = newStyleState;
return updated;
} else {
// 添加新的状态
return [...prev, newStyleState];
}
});
},
[selectedRenderLayer, styleConfig, legendStyleConfig]
);
// 获取地图中的矢量图层
useEffect(() => { useEffect(() => {
if (!map) return; if (!map) return;
@@ -461,18 +654,16 @@ const StyleEditorPanel: React.FC = () => {
updateVisibleLayers(); updateVisibleLayers();
}, [map]); }, [map]);
// 获取选中图层的属性 // 获取选中图层的属性,并检查是否有已缓存的样式状态
useEffect(() => { useEffect(() => {
// 如果没有矢量图层或没有选中图层,清空属性列表 // 如果没有矢量图层或没有选中图层,清空属性列表
if (!renderLayers || renderLayers.length === 0) { if (!renderLayers || renderLayers.length === 0) {
setAvailableProperties([]); setAvailableProperties([]);
// console.log("没有可用的矢量图层");
return; return;
} }
// 如果没有选中图层,清空属性列表 // 如果没有选中图层,清空属性列表
if (!selectedRenderLayer) { if (!selectedRenderLayer) {
setAvailableProperties([]); setAvailableProperties([]);
// console.log("没有选中的图层");
return; return;
} }
@@ -491,11 +682,8 @@ const StyleEditorPanel: React.FC = () => {
const cachedStyleState = layerStyleStates.find( const cachedStyleState = layerStyleStates.find(
(state) => state.layerId === layerId (state) => state.layerId === layerId
); );
if (cachedStyleState) { if (cachedStyleState) {
setStyleConfig(cachedStyleState.styleConfig); setStyleConfig(cachedStyleState.styleConfig);
setLegendStyleConfig(cachedStyleState.legendConfig);
// console.log(`已自动恢复图层 ${cachedStyleState.layerName} 的样式状态`);
} }
}, [renderLayers, selectedRenderLayer, map, renderLayers, layerStyleStates]); }, [renderLayers, selectedRenderLayer, map, renderLayers, layerStyleStates]);
// 同步属性状态 // 同步属性状态
@@ -643,8 +831,8 @@ const StyleEditorPanel: React.FC = () => {
minSize: value as number, minSize: value as number,
})) }))
} }
min={5} min={2}
max={15} max={8}
step={1} step={1}
size="small" size="small"
/> />
@@ -661,8 +849,8 @@ const StyleEditorPanel: React.FC = () => {
maxSize: value as number, maxSize: value as number,
})) }))
} }
min={20} min={10}
max={30} max={16}
step={1} step={1}
size="small" size="small"
/> />
@@ -966,7 +1154,7 @@ const StyleEditorPanel: React.FC = () => {
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => { onClick={() => {
setStyleState(selectedRenderLayer); setStyleState();
}} }}
disabled={!selectedRenderLayer || !styleConfig.property} disabled={!selectedRenderLayer || !styleConfig.property}
startIcon={<ApplyIcon />} startIcon={<ApplyIcon />}
@@ -989,7 +1177,7 @@ const StyleEditorPanel: React.FC = () => {
</div> </div>
{/* 显示多图层图例 */} {/* 显示多图层图例 */}
{getActiveLegendConfigs().length > 0 && ( {getActiveLegendConfigs().length > 0 && (
<div className=" absolute bottom-40 right-4 shadow-lg flex flex-row items-end max-w-screen-lg overflow-x-auto z-10"> <div className=" absolute bottom-40 right-4 drop-shadow-xl flex flex-row items-end max-w-screen-lg overflow-x-auto z-10">
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
{getActiveLegendConfigs().map((config, index) => ( {getActiveLegendConfigs().map((config, index) => (
<StyleLegend key={`${config.layerId}-${index}`} {...config} /> <StyleLegend key={`${config.layerId}-${index}`} {...config} />

View File

@@ -27,7 +27,7 @@ const StyleLegend: React.FC<LegendStyleConfig> = ({
return ( return (
<Box <Box
key={layerId} key={layerId}
className="bg-white p-3 rounded-xl shadow-lg max-w-xs opacity-95 transition-opacity duration-300 hover:opacity-100" className="bg-white p-3 rounded-xl max-w-xs opacity-95 transition-opacity duration-300 hover:opacity-100"
> >
<Typography variant="subtitle2" gutterBottom> <Typography variant="subtitle2" gutterBottom>
{layerName} - {property} {layerName} - {property}
@@ -71,4 +71,4 @@ const StyleLegend: React.FC<LegendStyleConfig> = ({
}; };
export default StyleLegend; export default StyleLegend;
export type { LegendStyleConfig }; export type { LegendStyleConfig };

View File

@@ -1,6 +1,8 @@
"use client"; "use client";
import React, { useState, useEffect, useRef, useCallback } from "react"; import React, { useState, useEffect, useRef, useCallback } from "react";
import { useNotification } from "@refinedev/core";
import { import {
Box, Box,
Button, Button,
@@ -20,21 +22,28 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import { zhCN } from "date-fns/locale"; import { zhCN } from "date-fns/locale";
import { PlayArrow, Pause, Stop, Refresh } from "@mui/icons-material"; import { PlayArrow, Pause, Stop, Refresh } from "@mui/icons-material";
import { TbRewindBackward5, TbRewindForward5 } from "react-icons/tb"; import { TbRewindBackward15, TbRewindForward15 } from "react-icons/tb";
import { useData } from "../MapComponent"; import { useData } from "../MapComponent";
import { config } from "@/config/config"; import { config } from "@/config/config";
import { useMap } from "../MapComponent";
import { set } from "ol/transform";
const backendUrl = config.backendUrl; const backendUrl = config.backendUrl;
const Timeline: React.FC = () => { const Timeline: React.FC = () => {
const data = useData(); const data = useData();
if (!data) { if (!data) {
return <div>Loading...</div>; // 或其他占位符 return <div>Loading...</div>; // 或其他占位符
} }
const { setJunctionDataState, setPipeDataState, junctionText, pipeText } = const {
data; setCurrentJunctionCalData,
setCurrentPipeCalData,
junctionText,
pipeText,
} = data;
const { open, close } = useNotification();
const [currentTime, setCurrentTime] = useState<number>(0); // 分钟数 (0-1439) const [currentTime, setCurrentTime] = useState<number>(0); // 分钟数 (0-1439)
const [selectedDate, setSelectedDate] = useState<Date>(new Date()); const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
// const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 默认今天
const [isPlaying, setIsPlaying] = useState<boolean>(false); const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [playInterval, setPlayInterval] = useState<number>(5000); // 毫秒 const [playInterval, setPlayInterval] = useState<number>(5000); // 毫秒
const [calculatedInterval, setCalculatedInterval] = useState<number>(1440); // 分钟 const [calculatedInterval, setCalculatedInterval] = useState<number>(1440); // 分钟
@@ -52,7 +61,9 @@ const Timeline: React.FC = () => {
const fetchFrameData = async (queryTime: Date) => { const fetchFrameData = async (queryTime: Date) => {
const query_time = queryTime.toISOString(); const query_time = queryTime.toISOString();
const cacheKey = query_time; const cacheKey = query_time;
// console.log("Fetching data for time:", query_time);
// console.log("Junction Property:", junctionText);
// console.log("Pipe Property:", pipeText);
// 检查缓存 // 检查缓存
if (cacheRef.current.has(cacheKey)) { if (cacheRef.current.has(cacheKey)) {
const { nodeRecords, linkRecords } = cacheRef.current.get(cacheKey)!; const { nodeRecords, linkRecords } = cacheRef.current.get(cacheKey)!;
@@ -65,12 +76,8 @@ const Timeline: React.FC = () => {
// 定义需要查询的属性 // 定义需要查询的属性
const junctionProperties = junctionText; const junctionProperties = junctionText;
const pipeProperties = pipeText; const pipeProperties = pipeText;
if ( // 如果属性未定义或为空,直接返回
!junctionProperties || if (junctionProperties === "" || pipeProperties === "") {
!pipeProperties ||
junctionProperties === "" ||
pipeProperties === ""
) {
return; return;
} }
console.log( console.log(
@@ -105,50 +112,16 @@ const Timeline: React.FC = () => {
// 提取更新状态的逻辑 // 提取更新状态的逻辑
const updateDataStates = (nodeResults: any[], linkResults: any[]) => { const updateDataStates = (nodeResults: any[], linkResults: any[]) => {
const junctionProperties = junctionText; if (setCurrentJunctionCalData) {
const pipeProperties = pipeText; setCurrentJunctionCalData(nodeResults);
} else {
// 将 nodeRecords 转换为 Map 以提高查找效率 console.log("setCurrentJunctionCalData is undefined");
const nodeMap: Map<string, any> = new Map( }
nodeResults.map((r: any) => [r.ID, r]) if (setCurrentPipeCalData) {
); setCurrentPipeCalData(linkResults);
// 将 linkRecords 转换为 Map 以提高查找效率 } else {
const linkMap: Map<string, any> = new Map( console.log("setCurrentPipeCalData is undefined");
linkResults.map((r: any) => [r.ID, r]) }
);
// 更新junctionData
setJunctionDataState((prev: any[]) =>
prev.map((j) => {
const record = nodeMap.get(j.id);
if (record) {
return {
...j,
[junctionProperties]: record.value,
};
}
return j;
})
);
// 更新pipeData
setPipeDataState((prev: any[]) =>
prev.map((p) => {
const record = linkMap.get(p.id);
if (record) {
return {
...p,
flowFlag: pipeProperties === "flow" && record.value < 0 ? -1 : 1,
path:
pipeProperties === "flow" && record.value < 0 && p.flowFlag > 0
? [...p.path].reverse()
: p.path,
[pipeProperties]: record.value,
};
}
return p;
})
);
}; };
// 时间刻度数组 (每5分钟一个刻度) // 时间刻度数组 (每5分钟一个刻度)
@@ -209,11 +182,18 @@ const Timeline: React.FC = () => {
// 播放控制 // 播放控制
const handlePlay = useCallback(() => { const handlePlay = useCallback(() => {
if (!isPlaying) { if (!isPlaying) {
if (junctionText === "" || pipeText === "") {
open?.({
type: "error",
message: "请先设置节点和管道的属性。",
});
return;
}
setIsPlaying(true); setIsPlaying(true);
intervalRef.current = setInterval(() => { intervalRef.current = setInterval(() => {
setCurrentTime((prev) => { setCurrentTime((prev) => {
const next = prev >= 1435 ? 0 : prev + 5; // 到达23:55后回到00:00 const next = prev >= 1440 ? 0 : prev + 15; // 到达24:00后回到00:00
setSliderValue(next); setSliderValue(next);
return next; return next;
}); });
@@ -242,7 +222,7 @@ const Timeline: React.FC = () => {
// 步进控制 // 步进控制
const handleStepBackward = useCallback(() => { const handleStepBackward = useCallback(() => {
setCurrentTime((prev) => { setCurrentTime((prev) => {
const next = prev <= 0 ? 1435 : prev - 5; const next = prev <= 0 ? 1440 : prev - 15;
setSliderValue(next); setSliderValue(next);
return next; return next;
}); });
@@ -250,7 +230,7 @@ const Timeline: React.FC = () => {
const handleStepForward = useCallback(() => { const handleStepForward = useCallback(() => {
setCurrentTime((prev) => { setCurrentTime((prev) => {
const next = prev >= 1435 ? 0 : prev + 5; const next = prev >= 1440 ? 0 : prev + 15;
setSliderValue(next); setSliderValue(next);
return next; return next;
}); });
@@ -274,7 +254,7 @@ const Timeline: React.FC = () => {
clearInterval(intervalRef.current); clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => { intervalRef.current = setInterval(() => {
setCurrentTime((prev) => { setCurrentTime((prev) => {
const next = prev >= 1435 ? 0 : prev + 5; const next = prev >= 1440 ? 0 : prev + 15;
setSliderValue(next); setSliderValue(next);
return next; return next;
}); });
@@ -305,6 +285,23 @@ const Timeline: React.FC = () => {
} }
}; };
}, []); }, []);
// 获取地图实例
const map = useMap();
// 这里防止地图缩放时,瓦片重新加载引起的属性更新出错
useEffect(() => {
// 监听地图缩放事件,缩放时停止播放
if (map) {
const onZoom = () => {
handlePause();
};
map.getView().on("change:resolution", onZoom);
// 清理事件监听
return () => {
map.getView().un("change:resolution", onZoom);
};
}
}, [map, handlePause]);
return ( return (
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={zhCN}> <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={zhCN}>
@@ -370,7 +367,7 @@ const Timeline: React.FC = () => {
onClick={handleStepBackward} onClick={handleStepBackward}
size="small" size="small"
> >
<TbRewindBackward5 /> <TbRewindBackward15 />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@@ -390,7 +387,7 @@ const Timeline: React.FC = () => {
onClick={handleStepForward} onClick={handleStepForward}
size="small" size="small"
> >
<TbRewindForward5 /> <TbRewindForward15 />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@@ -448,8 +445,8 @@ const Timeline: React.FC = () => {
<Slider <Slider
value={sliderValue} value={sliderValue}
min={0} min={0}
max={1435} // 23:55 = 1435分钟 max={1440} // 24:00 = 1440分钟
step={5} step={15} // 每15分钟一个步进
marks={timeMarks.filter((_, index) => index % 12 === 0)} // 每小时显示一个标记 marks={timeMarks.filter((_, index) => index % 12 === 0)} // 每小时显示一个标记
onChange={handleSliderChange} onChange={handleSliderChange}
valueLabelDisplay="auto" valueLabelDisplay="auto"

View File

@@ -24,15 +24,18 @@ import { bearing } from "@turf/turf";
import { Deck } from "@deck.gl/core"; import { Deck } from "@deck.gl/core";
import { TextLayer } from "@deck.gl/layers"; import { TextLayer } from "@deck.gl/layers";
import { TripsLayer } from "@deck.gl/geo-layers"; import { TripsLayer } from "@deck.gl/geo-layers";
import { el, tr } from "date-fns/locale";
import RenderFeature from "ol/render/Feature";
import { set } from "date-fns";
interface MapComponentProps { interface MapComponentProps {
children?: React.ReactNode; children?: React.ReactNode;
} }
interface DataContextType { interface DataContextType {
junctionData: any[]; currentJunctionCalData?: any[]; // 当前计算结果
pipeData: any[]; setCurrentJunctionCalData?: React.Dispatch<React.SetStateAction<any[]>>;
setJunctionDataState: React.Dispatch<React.SetStateAction<any[]>>; currentPipeCalData?: any[]; // 当前计算结果
setPipeDataState: React.Dispatch<React.SetStateAction<any[]>>; setCurrentPipeCalData?: React.Dispatch<React.SetStateAction<any[]>>;
showJunctionText?: boolean; // 是否显示节点文本 showJunctionText?: boolean; // 是否显示节点文本
showPipeText?: boolean; // 是否显示管道文本 showPipeText?: boolean; // 是否显示管道文本
setShowJunctionText?: React.Dispatch<React.SetStateAction<boolean>>; setShowJunctionText?: React.Dispatch<React.SetStateAction<boolean>>;
@@ -96,6 +99,12 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
const deckRef = useRef<Deck | null>(null); const deckRef = useRef<Deck | null>(null);
const [map, setMap] = useState<OlMap>(); const [map, setMap] = useState<OlMap>();
// currentCalData 用于存储当前计算结果
const [currentJunctionCalData, setCurrentJunctionCalData] = useState<any[]>(
[]
);
const [currentPipeCalData, setCurrentPipeCalData] = useState<any[]>([]);
// junctionData 和 pipeData 分别缓存瓦片解析后节点和管道的数据,用于 deck.gl 定位、标签渲染
const [junctionData, setJunctionDataState] = useState<any[]>([]); const [junctionData, setJunctionDataState] = useState<any[]>([]);
const [pipeData, setPipeDataState] = useState<any[]>([]); const [pipeData, setPipeDataState] = useState<any[]>([]);
const junctionDataIds = useRef(new Set<string>()); const junctionDataIds = useRef(new Set<string>());
@@ -105,9 +114,11 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
const [showJunctionText, setShowJunctionText] = useState(false); // 控制节点文本显示 const [showJunctionText, setShowJunctionText] = useState(false); // 控制节点文本显示
const [showPipeText, setShowPipeText] = useState(false); // 控制管道文本显示 const [showPipeText, setShowPipeText] = useState(false); // 控制管道文本显示
const [junctionText, setJunctionText] = useState(""); const [showJunctionTextLayer, setShowJunctionTextLayer] = useState(true); // 控制节点文本图层显示
const [pipeText, setPipeText] = useState(""); const [showPipeTextLayer, setShowPipeTextLayer] = useState(true); // 控制管道文本图层显示
const flowAnimation = useRef(true); // 添加动画控制标志 const [junctionText, setJunctionText] = useState("pressure");
const [pipeText, setPipeText] = useState("flow");
const flowAnimation = useRef(false); // 添加动画控制标志
const [currentZoom, setCurrentZoom] = useState(12); // 当前缩放级别 const [currentZoom, setCurrentZoom] = useState(12); // 当前缩放级别
// 防抖更新函数 // 防抖更新函数
const debouncedUpdateData = useRef( const debouncedUpdateData = useRef(
@@ -149,37 +160,82 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
setPipeDataState((prev) => [...prev, ...uniqueNewData]); setPipeDataState((prev) => [...prev, ...uniqueNewData]);
} }
}; };
const defaultFlatStyle: FlatStyleLike = {
"stroke-width": 3,
"stroke-color": "rgba(51, 153, 204, 0.9)",
"circle-fill-color": "rgba(255,255,255,0.4)",
"circle-stroke-color": "rgba(255,255,255,0.9)",
"circle-radius": [
"interpolate",
["linear"],
["zoom"],
12,
1, // 在缩放级别 12 时,圆形半径为 1px
24,
12, // 在缩放级别 24 时,圆形半径为 12px
],
};
// 矢量瓦片数据源和图层
const junctionSource = new VectorTileSource({
url: `${mapUrl}/gwc/service/tms/1.0.0/TJWater:geo_junctions_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL
format: new MVT(),
projection: "EPSG:3857",
});
const pipeSource = new VectorTileSource({
url: `${mapUrl}/gwc/service/tms/1.0.0/TJWater:geo_pipes_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL
format: new MVT(),
projection: "EPSG:3857",
});
// WebGL 渲染优化显示
const junctionLayer = new WebGLVectorTileLayer({
source: junctionSource as any, // 使用 WebGL 渲染
style: defaultFlatStyle,
extent: extent, // 设置图层范围
maxZoom: 24,
minZoom: 12,
properties: {
name: "节点图层", // 设置图层名称
value: "junctions",
type: "point",
properties: [
// { name: "需求量", value: "demand" },
// { name: "海拔高度", value: "elevation" },
{ name: "实际需求量", value: "actualdemand" },
{ name: "水头", value: "head" },
{ name: "压力", value: "pressure" },
{ name: "水质", value: "quality" },
],
},
});
const pipeLayer = new WebGLVectorTileLayer({
source: pipeSource as any, // 使用 WebGL 渲染
style: defaultFlatStyle,
extent: extent, // 设置图层范围
maxZoom: 24,
minZoom: 12,
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: "quality" },
{ name: "反应速率", value: "reaction" },
{ name: "设置值", value: "setting" },
{ name: "状态", value: "status" },
{ name: "流速", value: "velocity" },
],
},
});
useEffect(() => { useEffect(() => {
if (!mapRef.current) return; if (!mapRef.current) return;
// 添加 MVT 瓦片加载逻辑 // 缓存 junction、pipe 数据,提供给 deck.gl 显示标签使用
const defaultFlatStyle: FlatStyleLike = {
"stroke-width": 3,
"stroke-color": "rgba(51, 153, 204, 0.9)",
"circle-fill-color": "rgba(255,255,255,0.4)",
"circle-stroke-color": "rgba(255,255,255,0.9)",
"circle-radius": [
"interpolate",
["linear"],
["zoom"],
12,
1, // 在缩放级别 12 时,圆形半径为 1px
24,
12, // 在缩放级别 24 时,圆形半径为 12px
],
};
const junctionSource = new VectorTileSource({
url: `${mapUrl}/gwc/service/tms/1.0.0/TJWater:geo_junctions_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL
format: new MVT(),
projection: "EPSG:3857",
});
const pipeSource = new VectorTileSource({
url: `${mapUrl}/gwc/service/tms/1.0.0/TJWater:geo_pipes_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL
format: new MVT(),
projection: "EPSG:3857",
});
// 缓存数据
junctionSource.on("tileloadend", (event) => { junctionSource.on("tileloadend", (event) => {
try { try {
if (event.tile instanceof VectorTile) { if (event.tile instanceof VectorTile) {
@@ -290,54 +346,20 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
console.error("Pipe tile load error:", error); console.error("Pipe tile load error:", error);
} }
}); });
// 更新标签可见性状态
// WebGL 渲染优化显示 // 监听 junctionLayer 的 visible 变化
const junctionLayer = new WebGLVectorTileLayer({ const handleJunctionVisibilityChange = () => {
source: junctionSource as any, // 使用 WebGL 渲染 const isVisible = junctionLayer.getVisible();
style: defaultFlatStyle, setShowJunctionTextLayer(isVisible);
extent: extent, // 设置图层范围 };
maxZoom: 24, // 监听 pipeLayer 的 visible 变化
minZoom: 12, const handlePipeVisibilityChange = () => {
properties: { const isVisible = pipeLayer.getVisible();
name: "节点图层", // 设置图层名称 setShowPipeTextLayer(isVisible);
value: "junctions", };
type: "point", // 添加事件监听器
properties: [ junctionLayer.on("change:visible", handleJunctionVisibilityChange);
// { name: "需求量", value: "demand" }, pipeLayer.on("change:visible", handlePipeVisibilityChange);
// { name: "海拔高度", value: "elevation" },
{ name: "实际需求量", value: "actualdemand" },
{ name: "水头", value: "head" },
{ name: "压力", value: "pressure" },
{ name: "水质", value: "quality" },
],
},
});
const pipeLayer = new WebGLVectorTileLayer({
source: pipeSource as any, // 使用 WebGL 渲染
style: defaultFlatStyle,
extent: extent, // 设置图层范围
maxZoom: 24,
minZoom: 12,
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: "quality" },
{ name: "反应速率", value: "reaction" },
{ name: "设置值", value: "setting" },
{ name: "状态", value: "status" },
{ name: "流速", value: "velocity" },
],
},
});
const map = new OlMap({ const map = new OlMap({
target: mapRef.current, target: mapRef.current,
@@ -375,8 +397,11 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
const deckLayer = new DeckLayer(deck); const deckLayer = new DeckLayer(deck);
// deckLayer.setZIndex(1000); // 确保在最上层 // deckLayer.setZIndex(1000); // 确保在最上层
map.addLayer(deckLayer); map.addLayer(deckLayer);
// 清理函数 // 清理函数
return () => { return () => {
junctionLayer.un("change:visible", handleJunctionVisibilityChange);
pipeLayer.un("change:visible", handlePipeVisibilityChange);
map.setTarget(undefined); map.setTarget(undefined);
map.dispose(); map.dispose();
deck.finalize(); deck.finalize();
@@ -402,7 +427,8 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
getTextAnchor: "middle", getTextAnchor: "middle",
getAlignmentBaseline: "center", getAlignmentBaseline: "center",
getPixelOffset: [0, -10], getPixelOffset: [0, -10],
visible: currentZoom >= 15 && currentZoom <= 24, visible:
showJunctionTextLayer && currentZoom >= 15 && currentZoom <= 24,
// --- 修改以下属性 --- // --- 修改以下属性 ---
// characterSet: "auto", // characterSet: "auto",
// outlineWidth: 4, // outlineWidth: 4,
@@ -422,7 +448,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
getPixelOffset: [0, -8], getPixelOffset: [0, -8],
getTextAnchor: "middle", getTextAnchor: "middle",
getAlignmentBaseline: "bottom", getAlignmentBaseline: "bottom",
visible: currentZoom >= 15 && currentZoom <= 24, visible: showPipeTextLayer && currentZoom >= 15 && currentZoom <= 24,
// --- 修改以下属性 --- // --- 修改以下属性 ---
// characterSet: "auto", // characterSet: "auto",
// outlineWidth: 5, // outlineWidth: 5,
@@ -456,6 +482,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
}, },
getColor: [0, 220, 255], getColor: [0, 220, 255],
opacity: 0.8, opacity: 0.8,
visible: currentZoom >= 12 && currentZoom <= 24,
widthMinPixels: 5, widthMinPixels: 5,
jointRounded: true, // 拐角变圆 jointRounded: true, // 拐角变圆
// capRounded: true, // 端点变圆 // capRounded: true, // 端点变圆
@@ -476,16 +503,85 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
requestAnimationFrame(animate); requestAnimationFrame(animate);
}; };
animate(); animate();
}, [flowAnimation, junctionData, pipeData]); }, [
flowAnimation,
junctionData,
pipeData,
currentZoom,
showJunctionText,
showPipeText,
showJunctionTextLayer,
showPipeTextLayer,
junctionText,
pipeText,
]);
useEffect(() => {
if (pipeText === "flow") {
flowAnimation.current = true;
} else {
flowAnimation.current = false;
}
}, [pipeText]);
// 计算值更新时,更新 junctionData 和 pipeData
useEffect(() => {
const junctionProperties = junctionText;
const pipeProperties = pipeText;
// 将 nodeRecords 转换为 Map 以提高查找效率
const nodeMap: Map<string, any> = new Map(
currentJunctionCalData.map((r: any) => [r.ID, r])
);
// 将 linkRecords 转换为 Map 以提高查找效率
const linkMap: Map<string, any> = new Map(
currentPipeCalData.map((r: any) => [r.ID, r])
);
// 更新junctionData
setJunctionDataState((prev: any[]) =>
prev.map((j) => {
const record = nodeMap.get(j.id);
if (record) {
return {
...j,
[junctionProperties]: record.value,
};
}
return j;
})
);
// 更新pipeData
setPipeDataState((prev: any[]) =>
prev.map((p) => {
const record = linkMap.get(p.id);
if (record) {
return {
...p,
flowFlag: pipeProperties === "flow" && record.value < 0 ? -1 : 1,
path:
pipeProperties === "flow" && record.value < 0 && p.flowFlag > 0
? [...p.path].reverse()
: p.path,
[pipeProperties]: record.value,
};
}
return p;
})
);
}, [currentJunctionCalData, currentPipeCalData]);
return ( return (
<> <>
<DataContext.Provider <DataContext.Provider
value={{ value={{
junctionData, currentJunctionCalData,
pipeData, setCurrentJunctionCalData,
setJunctionDataState, currentPipeCalData,
setPipeDataState, setCurrentPipeCalData,
setShowJunctionText,
setShowPipeText,
setJunctionText,
setPipeText,
showJunctionText, showJunctionText,
showPipeText, showPipeText,
junctionText, junctionText,