属性查询面板添加计算值;完善时间轴获取数据的缓存机制;

This commit is contained in:
JIANG
2025-10-17 18:15:09 +08:00
parent 6ddce65445
commit 4e819b20ea
4 changed files with 251 additions and 77 deletions

View File

@@ -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: "管道图层样式设置成功,等待数据更新。",
});
}
}
// 触发样式更新

View File

@@ -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]);
// 首次加载时,如果 selectedDatecurrentTime 未定义,则跳过执行,避免报错
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(() => {

View File

@@ -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 (
<>

View File

@@ -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,