属性查询面板添加计算值;完善时间轴获取数据的缓存机制;
This commit is contained in:
@@ -28,6 +28,7 @@ import { FlatStyleLike } from "ol/style/flat";
|
||||
import { calculateClassification } from "@utils/breaks_classification";
|
||||
import { parseColor } from "@utils/parseColor";
|
||||
import { VectorTile } from "ol";
|
||||
import { useNotification } from "@refinedev/core";
|
||||
|
||||
interface StyleConfig {
|
||||
property: string;
|
||||
@@ -117,6 +118,8 @@ const StyleEditorPanel: React.FC = () => {
|
||||
setPipeText,
|
||||
} = data;
|
||||
|
||||
const { open, close } = useNotification();
|
||||
|
||||
const [applyJunctionStyle, setApplyJunctionStyle] = useState(false);
|
||||
const [applyPipeStyle, setApplyPipeStyle] = useState(false);
|
||||
const [styleUpdateTrigger, setStyleUpdateTrigger] = useState(0); // 用于触发样式更新的状态
|
||||
@@ -194,7 +197,7 @@ const StyleEditorPanel: React.FC = () => {
|
||||
const finalLegendConfig: LegendStyleConfig = legendConfig || {
|
||||
layerId,
|
||||
layerName,
|
||||
property: styleConfig.property,
|
||||
property: selectedProperty.name,
|
||||
colors: [],
|
||||
type: "point",
|
||||
dimensions: [],
|
||||
@@ -240,15 +243,22 @@ const StyleEditorPanel: React.FC = () => {
|
||||
setShowJunctionText(styleConfig.showLabels);
|
||||
setApplyJunctionStyle(true);
|
||||
saveLayerStyle(layerId);
|
||||
open?.({
|
||||
type: "success",
|
||||
message: "节点图层样式设置成功,等待数据更新。",
|
||||
});
|
||||
}
|
||||
}
|
||||
if (layerId === "pipes") {
|
||||
console.log(styleConfig);
|
||||
if (setPipeText && setShowPipeText) {
|
||||
setPipeText(property);
|
||||
setShowPipeText(styleConfig.showLabels);
|
||||
setApplyPipeStyle(true);
|
||||
saveLayerStyle(layerId);
|
||||
open?.({
|
||||
type: "success",
|
||||
message: "管道图层样式设置成功,等待数据更新。",
|
||||
});
|
||||
}
|
||||
}
|
||||
// 触发样式更新
|
||||
|
||||
@@ -33,16 +33,25 @@ const Timeline: React.FC = () => {
|
||||
return <div>Loading...</div>; // 或其他占位符
|
||||
}
|
||||
const {
|
||||
currentTime,
|
||||
setCurrentTime,
|
||||
selectedDate,
|
||||
setSelectedDate,
|
||||
setCurrentJunctionCalData,
|
||||
setCurrentPipeCalData,
|
||||
junctionText,
|
||||
pipeText,
|
||||
} = data;
|
||||
if (
|
||||
setCurrentTime === undefined ||
|
||||
currentTime === undefined ||
|
||||
selectedDate === undefined ||
|
||||
setSelectedDate === undefined
|
||||
) {
|
||||
return <div>Loading...</div>; // 或其他占位符
|
||||
}
|
||||
const { open, close } = useNotification();
|
||||
|
||||
const [currentTime, setCurrentTime] = useState<number>(0); // 分钟数 (0-1439)
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
|
||||
// const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 默认今天
|
||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||
const [playInterval, setPlayInterval] = useState<number>(5000); // 毫秒
|
||||
const [calculatedInterval, setCalculatedInterval] = useState<number>(1440); // 分钟
|
||||
@@ -51,62 +60,79 @@ const Timeline: React.FC = () => {
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
// 添加缓存引用
|
||||
const cacheRef = useRef<
|
||||
Map<string, { nodeRecords: any[]; linkRecords: any[] }>
|
||||
>(new Map());
|
||||
const nodeCacheRef = useRef<Map<string, any[]>>(new Map());
|
||||
const linkCacheRef = useRef<Map<string, any[]>>(new Map());
|
||||
// 添加防抖引用
|
||||
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const fetchFrameData = async (queryTime: Date) => {
|
||||
const fetchFrameData = async (
|
||||
queryTime: Date,
|
||||
junctionProperties: string,
|
||||
pipeProperties: string
|
||||
) => {
|
||||
const query_time = queryTime.toISOString();
|
||||
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)) {
|
||||
const { nodeRecords, linkRecords } = cacheRef.current.get(cacheKey)!;
|
||||
// 使用缓存数据更新状态
|
||||
updateDataStates(nodeRecords, linkRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 定义需要查询的属性
|
||||
const junctionProperties = junctionText;
|
||||
const pipeProperties = pipeText;
|
||||
// 如果属性未定义或为空,直接返回
|
||||
if (junctionProperties === "" || pipeProperties === "") {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"Query Time:",
|
||||
queryTime.toLocaleDateString() + " " + queryTime.toLocaleTimeString()
|
||||
);
|
||||
// 同时查询节点和管道数据
|
||||
const [nodeResponse, linkResponse] = await Promise.all([
|
||||
fetch(
|
||||
let nodeRecords: any = { results: [] };
|
||||
let linkRecords: any = { results: [] };
|
||||
const requests: Promise<Response>[] = [];
|
||||
let nodePromise: Promise<any> | null = null;
|
||||
let linkPromise: Promise<any> | null = null;
|
||||
// 检查node缓存
|
||||
if (junctionProperties !== "") {
|
||||
const nodeCacheKey = `${query_time}_${junctionProperties}`;
|
||||
if (nodeCacheRef.current.has(nodeCacheKey)) {
|
||||
nodeRecords = nodeCacheRef.current.get(nodeCacheKey)!;
|
||||
} else {
|
||||
nodePromise = fetch(
|
||||
`${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=node&property=${junctionProperties}`
|
||||
),
|
||||
fetch(
|
||||
`${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}`
|
||||
),
|
||||
]);
|
||||
|
||||
const nodeRecords = await nodeResponse.json();
|
||||
const linkRecords = await linkResponse.json();
|
||||
|
||||
// 缓存数据
|
||||
cacheRef.current.set(cacheKey, {
|
||||
nodeRecords: nodeRecords.results,
|
||||
linkRecords: linkRecords.results,
|
||||
});
|
||||
|
||||
// 更新状态
|
||||
updateDataStates(nodeRecords.results, linkRecords.results);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
);
|
||||
requests.push(nodePromise);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查link缓存
|
||||
if (pipeProperties !== "") {
|
||||
const linkCacheKey = `${query_time}_${pipeProperties}`;
|
||||
if (linkCacheRef.current.has(linkCacheKey)) {
|
||||
linkRecords = linkCacheRef.current.get(linkCacheKey)!;
|
||||
} else {
|
||||
linkPromise = fetch(
|
||||
`${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}`
|
||||
);
|
||||
requests.push(linkPromise);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Query Time:",
|
||||
queryTime.toLocaleDateString() + " " + queryTime.toLocaleTimeString()
|
||||
);
|
||||
// 等待所有有效请求
|
||||
const responses = await Promise.all(requests);
|
||||
|
||||
if (nodePromise) {
|
||||
const nodeResponse = responses.shift()!;
|
||||
if (!nodeResponse.ok)
|
||||
throw new Error(`Node fetch failed: ${nodeResponse.status}`);
|
||||
nodeRecords = await nodeResponse.json();
|
||||
// 缓存数据
|
||||
nodeCacheRef.current.set(
|
||||
`${query_time}_${junctionProperties}`,
|
||||
nodeRecords || []
|
||||
);
|
||||
}
|
||||
if (linkPromise) {
|
||||
const linkResponse = responses.shift()!;
|
||||
if (!linkResponse.ok)
|
||||
throw new Error(`Link fetch failed: ${linkResponse.status}`);
|
||||
linkRecords = await linkResponse.json();
|
||||
// 缓存数据
|
||||
linkCacheRef.current.set(
|
||||
`${query_time}_${pipeProperties}`,
|
||||
linkRecords || []
|
||||
);
|
||||
}
|
||||
// 更新状态
|
||||
updateDataStates(nodeRecords.results || [], linkRecords.results || []);
|
||||
};
|
||||
|
||||
// 提取更新状态的逻辑
|
||||
@@ -181,10 +207,10 @@ const Timeline: React.FC = () => {
|
||||
// 播放控制
|
||||
const handlePlay = useCallback(() => {
|
||||
if (!isPlaying) {
|
||||
if (junctionText === "" || pipeText === "") {
|
||||
if (junctionText === "" && pipeText === "") {
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "请先设置节点和管道的属性。",
|
||||
message: "请至少设定并应用一个图层的样式。",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -270,8 +296,25 @@ const Timeline: React.FC = () => {
|
||||
|
||||
// 添加 useEffect 来监听 currentTime 和 selectedDate 的变化,并获取数据
|
||||
useEffect(() => {
|
||||
fetchFrameData(currentTimeToDate(selectedDate, currentTime));
|
||||
}, [currentTime, selectedDate]);
|
||||
// 首次加载时,如果 selectedDate 或 currentTime 未定义,则跳过执行,避免报错
|
||||
if (selectedDate && currentTime !== undefined) {
|
||||
// 检查至少一个属性有值
|
||||
const junctionProperties = junctionText;
|
||||
const pipeProperties = pipeText;
|
||||
if (junctionProperties === "" && pipeProperties === "") {
|
||||
open?.({
|
||||
type: "error",
|
||||
message: "请至少设定并应用一个图层的样式。",
|
||||
});
|
||||
return;
|
||||
}
|
||||
fetchFrameData(
|
||||
currentTimeToDate(selectedDate, currentTime),
|
||||
junctionText,
|
||||
pipeText
|
||||
);
|
||||
}
|
||||
}, [junctionText, pipeText, currentTime, selectedDate]);
|
||||
|
||||
// 组件卸载时清理定时器和防抖
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useMap } from "../MapComponent";
|
||||
import { useData, useMap } from "../MapComponent";
|
||||
import ToolbarButton from "@/components/olmap/common/ToolbarButton";
|
||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
||||
@@ -23,8 +23,14 @@ import { toLonLat } from "ol/proj";
|
||||
import { booleanIntersects, buffer, point, toWgs84 } from "@turf/turf";
|
||||
import RenderFeature from "ol/render/Feature";
|
||||
|
||||
import { config } from "@/config/config";
|
||||
const backendUrl = config.backendUrl;
|
||||
|
||||
const Toolbar: React.FC = () => {
|
||||
const map = useMap();
|
||||
const data = useData();
|
||||
if (!data) return null;
|
||||
const { currentTime, selectedDate } = data;
|
||||
const [activeTools, setActiveTools] = useState<string[]>([]);
|
||||
const [highlightFeature, setHighlightFeature] = useState<FeatureLike | null>(
|
||||
null
|
||||
@@ -186,7 +192,7 @@ const Toolbar: React.FC = () => {
|
||||
const features = results
|
||||
.filter((json) => json !== null) // 过滤掉失败的请求
|
||||
.flatMap((json) => new GeoJSON().readFeatures(json));
|
||||
console.log("查询到的要素:", features);
|
||||
// console.log("查询到的要素:", features);
|
||||
return features;
|
||||
} else {
|
||||
// 查询指定图层
|
||||
@@ -201,7 +207,7 @@ const Toolbar: React.FC = () => {
|
||||
}
|
||||
const json = await response.json();
|
||||
const features = new GeoJSON().readFeatures(json);
|
||||
console.log("查询到的要素:", features);
|
||||
// console.log("查询到的要素:", features);
|
||||
return features;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -399,45 +405,145 @@ const Toolbar: React.FC = () => {
|
||||
setShowDrawPanel(false);
|
||||
// 样式编辑器保持其当前状态,不自动关闭
|
||||
};
|
||||
const [computedProperties, setComputedProperties] = useState<
|
||||
Record<string, any>
|
||||
>({});
|
||||
// 添加 useEffect 来查询计算属性
|
||||
useEffect(() => {
|
||||
if (!highlightFeature || !selectedDate) {
|
||||
setComputedProperties({});
|
||||
return;
|
||||
}
|
||||
|
||||
const id = highlightFeature.getProperties().id;
|
||||
if (!id) {
|
||||
setComputedProperties({});
|
||||
return;
|
||||
}
|
||||
|
||||
const queryComputedProperties = async () => {
|
||||
try {
|
||||
const properties = highlightFeature?.getProperties?.() || {};
|
||||
const type =
|
||||
properties.geometry?.getType?.() === "LineString" ? "link" : "node";
|
||||
// selectedDate 格式化为 YYYY-MM-DD
|
||||
let dateObj: Date;
|
||||
if (selectedDate instanceof Date) {
|
||||
dateObj = new Date(selectedDate);
|
||||
} else {
|
||||
dateObj = new Date(selectedDate);
|
||||
}
|
||||
const minutes = Number(currentTime) || 0;
|
||||
dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
|
||||
// 转为 UTC ISO 字符串
|
||||
const querytime = dateObj.toISOString(); // 例如 "2025-09-16T16:30:00.000Z"
|
||||
const response = await fetch(
|
||||
`${backendUrl}/queryrecordsbyidtime/?id=${id}&querytime=${querytime}&type=${type}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("API request failed");
|
||||
}
|
||||
const data = await response.json();
|
||||
setComputedProperties(data.results[0] || {});
|
||||
} catch (error) {
|
||||
console.error("Error querying computed properties:", error);
|
||||
setComputedProperties({});
|
||||
}
|
||||
};
|
||||
|
||||
queryComputedProperties();
|
||||
}, [highlightFeature, currentTime, selectedDate]);
|
||||
|
||||
// 从要素属性中提取属性面板需要的数据
|
||||
const getFeatureProperties = useCallback(() => {
|
||||
if (!highlightFeature) return {};
|
||||
|
||||
const properties = highlightFeature.getProperties();
|
||||
console.log(properties, properties.geometry.type, "properties");
|
||||
// 计算属性字段,增加 key 字段
|
||||
const pipeComputedFields = [
|
||||
{ key: "flow", label: "流量", unit: "m³/s" },
|
||||
{ key: "friction", label: "摩阻", unit: "" },
|
||||
{ key: "headloss", label: "水头损失", unit: "m" },
|
||||
{ key: "quality", label: "水质", unit: "mg/L" },
|
||||
{ key: "reaction", label: "反应", unit: "1/s" },
|
||||
{ key: "setting", label: "设置", unit: "" },
|
||||
{ key: "status", label: "状态", unit: "" },
|
||||
{ key: "velocity", label: "流速", unit: "m/s" },
|
||||
];
|
||||
const nodeComputedFields = [
|
||||
{ key: "actualdemand", label: "实际需水量", unit: "m³/s" },
|
||||
{ key: "head", label: "水头", unit: "m" },
|
||||
{ key: "pressure", label: "压力", unit: "kPa" },
|
||||
{ key: "quality", label: "水质", unit: "mg/L" },
|
||||
];
|
||||
|
||||
if (properties.geometry.getType() === "LineString") {
|
||||
console.log(properties, "properties");
|
||||
return {
|
||||
let result = {
|
||||
id: properties.id,
|
||||
type: "管道",
|
||||
properties: [
|
||||
{ label: "起始节点ID", value: properties.node1 },
|
||||
{ label: "终点节点ID", value: properties.node2 },
|
||||
{ label: "长度", value: properties.length.toFixed(1), unit: "m" },
|
||||
{ label: "管径", value: properties.diameter.toFixed(1), unit: "mm" },
|
||||
{ label: "长度", value: properties.length?.toFixed?.(1), unit: "m" },
|
||||
{
|
||||
label: "管径",
|
||||
value: properties.diameter?.toFixed?.(1),
|
||||
unit: "mm",
|
||||
},
|
||||
{ label: "粗糙度", value: properties.roughness },
|
||||
{ label: "局部损失", value: properties.minor_loss },
|
||||
{ label: "初始状态", value: "开" },
|
||||
],
|
||||
};
|
||||
// 追加计算属性
|
||||
if (computedProperties) {
|
||||
pipeComputedFields.forEach(({ key, label, unit }) => {
|
||||
if (computedProperties[key] !== undefined) {
|
||||
result.properties.push({
|
||||
label,
|
||||
value:
|
||||
computedProperties[key].toFixed?.(2) || computedProperties[key],
|
||||
unit,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (properties.geometry.getType() === "Point") {
|
||||
return {
|
||||
let result = {
|
||||
id: properties.id,
|
||||
type: "节点",
|
||||
properties: [
|
||||
{ label: "海拔", value: properties.elevation.toFixed(1), unit: "m" },
|
||||
{
|
||||
label: "海拔",
|
||||
value: properties.elevation?.toFixed?.(1),
|
||||
unit: "m",
|
||||
},
|
||||
{
|
||||
label: "需求量",
|
||||
value: properties.demand.toFixed(1),
|
||||
value: properties.demand?.toFixed?.(1),
|
||||
unit: "m³/s",
|
||||
},
|
||||
],
|
||||
};
|
||||
// 追加计算属性
|
||||
if (computedProperties) {
|
||||
nodeComputedFields.forEach(({ key, label, unit }) => {
|
||||
if (computedProperties[key] !== undefined) {
|
||||
result.properties.push({
|
||||
label,
|
||||
value:
|
||||
computedProperties[key].toFixed?.(2) || computedProperties[key],
|
||||
unit,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}, [highlightFeature]);
|
||||
}, [highlightFeature, computedProperties]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -29,6 +29,10 @@ interface MapComponentProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
interface DataContextType {
|
||||
currentTime?: number; // 当前时间
|
||||
setCurrentTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
selectedDate?: Date; // 选择的日期
|
||||
setSelectedDate?: React.Dispatch<React.SetStateAction<Date>>;
|
||||
currentJunctionCalData?: any[]; // 当前计算结果
|
||||
setCurrentJunctionCalData?: React.Dispatch<React.SetStateAction<any[]>>;
|
||||
currentPipeCalData?: any[]; // 当前计算结果
|
||||
@@ -97,6 +101,10 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
|
||||
const [map, setMap] = useState<OlMap>();
|
||||
// currentCalData 用于存储当前计算结果
|
||||
const [currentTime, setCurrentTime] = useState<number>(0);
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
|
||||
// const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 默认今天
|
||||
|
||||
const [currentJunctionCalData, setCurrentJunctionCalData] = useState<any[]>(
|
||||
[]
|
||||
);
|
||||
@@ -113,8 +121,8 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
const [showPipeText, setShowPipeText] = useState(false); // 控制管道文本显示
|
||||
const [showJunctionTextLayer, setShowJunctionTextLayer] = useState(true); // 控制节点文本图层显示
|
||||
const [showPipeTextLayer, setShowPipeTextLayer] = useState(true); // 控制管道文本图层显示
|
||||
const [junctionText, setJunctionText] = useState("pressure");
|
||||
const [pipeText, setPipeText] = useState("flow");
|
||||
const [junctionText, setJunctionText] = useState("");
|
||||
const [pipeText, setPipeText] = useState("");
|
||||
const flowAnimation = useRef(false); // 添加动画控制标志
|
||||
const [currentZoom, setCurrentZoom] = useState(12); // 当前缩放级别
|
||||
// 防抖更新函数
|
||||
@@ -418,7 +426,8 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
fontFamily: "Monaco, monospace",
|
||||
getText: (d: any) =>
|
||||
d[junctionText] ? (d[junctionText] as number).toFixed(3) : "",
|
||||
getSize: 12,
|
||||
getSize: 14,
|
||||
fontWeight: "bold",
|
||||
getColor: [150, 150, 255],
|
||||
getAngle: 0,
|
||||
getTextAnchor: "middle",
|
||||
@@ -448,7 +457,8 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
fontFamily: "Monaco, monospace",
|
||||
getText: (d: any) =>
|
||||
d[pipeText] ? (d[pipeText] as number).toFixed(3) : "",
|
||||
getSize: 14,
|
||||
getSize: 12,
|
||||
fontWeight: "bold",
|
||||
getColor: [120, 128, 181],
|
||||
getAngle: (d: any) => d.angle || 0,
|
||||
getPixelOffset: [0, -8],
|
||||
@@ -491,13 +501,14 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
const waterflowLayer = new TripsLayer({
|
||||
id: "waterflowLayer",
|
||||
data: pipeData,
|
||||
getPath: (d) => (flowAnimation.current ? d.path : []),
|
||||
getPath: (d) => d.path,
|
||||
getTimestamps: (d) => {
|
||||
return d.timestamps; // 这些应该是与 currentTime 匹配的数值
|
||||
},
|
||||
getColor: [0, 220, 255],
|
||||
opacity: 0.8,
|
||||
visible: currentZoom >= 12 && currentZoom <= 24,
|
||||
visible:
|
||||
flowAnimation.current && currentZoom >= 12 && currentZoom <= 24,
|
||||
widthMinPixels: 5,
|
||||
jointRounded: true, // 拐角变圆
|
||||
// capRounded: true, // 端点变圆
|
||||
@@ -589,6 +600,10 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
<>
|
||||
<DataContext.Provider
|
||||
value={{
|
||||
currentTime,
|
||||
setCurrentTime,
|
||||
selectedDate,
|
||||
setSelectedDate,
|
||||
currentJunctionCalData,
|
||||
setCurrentJunctionCalData,
|
||||
currentPipeCalData,
|
||||
|
||||
Reference in New Issue
Block a user