完善时间轴数据更新函数;完善图层显示控制函数;修复图例样式显示异常问题;
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user