完善数据更新函数;调整时间轴显示范围符合数据返回的内容
This commit is contained in:
@@ -4,7 +4,6 @@ import React, { useState, useEffect, useRef, useCallback } from "react";
|
|||||||
import { useNotification } from "@refinedev/core";
|
import { useNotification } from "@refinedev/core";
|
||||||
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
||||||
import { parseColor } from "@/utils/parseColor";
|
import { parseColor } from "@/utils/parseColor";
|
||||||
import { calculateClassification } from "@/utils/breaks_classification";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -51,16 +50,16 @@ interface PredictionResult {
|
|||||||
|
|
||||||
// 彩虹色配置
|
// 彩虹色配置
|
||||||
const RAINBOW_COLORS = [
|
const RAINBOW_COLORS = [
|
||||||
"rgba(142, 68, 173, 1)", // 紫
|
"rgba(142, 68, 173, 0.9)", // 紫
|
||||||
"rgba(63, 81, 181, 1)", // 靛青
|
"rgba(63, 81, 181, 0.9)", // 靛青
|
||||||
"rgba(33, 150, 243, 1)", // 天蓝
|
"rgba(33, 150, 243, 0.9)", // 天蓝
|
||||||
"rgba(0, 188, 212, 1)", // 青色
|
"rgba(0, 188, 212, 0.9)", // 青色
|
||||||
"rgba(0, 158, 115, 1)", // 青绿
|
"rgba(0, 158, 115, 0.9)", // 青绿
|
||||||
"rgba(76, 175, 80, 1)", // 中绿
|
"rgba(76, 175, 80, 0.9)", // 中绿
|
||||||
"rgba(199, 224, 0, 1)", // 黄绿
|
"rgba(199, 224, 0, 0.9)", // 黄绿
|
||||||
"rgba(255, 215, 0, 1)", // 金黄
|
"rgba(255, 215, 0, 0.9)", // 金黄
|
||||||
"rgba(255, 127, 0, 1)", // 橙
|
"rgba(255, 127, 0, 0.9)", // 橙
|
||||||
"rgba(255, 0, 0, 1)", // 红
|
"rgba(255, 0, 0, 0.9)", // 红
|
||||||
];
|
];
|
||||||
|
|
||||||
interface TimelineProps {
|
interface TimelineProps {
|
||||||
@@ -79,7 +78,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
}
|
}
|
||||||
const { open } = useNotification();
|
const { open } = useNotification();
|
||||||
const [selectedDateTime, setSelectedDateTime] = useState<Date>(new Date());
|
const [selectedDateTime, setSelectedDateTime] = useState<Date>(new Date());
|
||||||
const [currentYear, setCurrentYear] = useState<number>(1); // 时间轴当前值 (1-100)
|
const [currentYear, setCurrentYear] = useState<number>(4); // 时间轴当前值 (4-73)
|
||||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||||
const [playInterval, setPlayInterval] = useState<number>(15000); // 毫秒
|
const [playInterval, setPlayInterval] = useState<number>(15000); // 毫秒
|
||||||
const [isPredicting, setIsPredicting] = useState<boolean>(false);
|
const [isPredicting, setIsPredicting] = useState<boolean>(false);
|
||||||
@@ -88,9 +87,12 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
>([]);
|
>([]);
|
||||||
const [pipeLayer, setPipeLayer] = useState<WebGLVectorTileLayer | null>(null);
|
const [pipeLayer, setPipeLayer] = useState<WebGLVectorTileLayer | null>(null);
|
||||||
|
|
||||||
// 计算时间轴范围 (1-100)
|
// 使用 ref 存储当前的健康数据,供事件监听器读取,避免重复绑定
|
||||||
const minTime = 1;
|
const healthDataRef = useRef<Map<string, number>>(new Map());
|
||||||
const maxTime = 100;
|
|
||||||
|
// 计算时间轴范围 (4-73)
|
||||||
|
const minTime = 4;
|
||||||
|
const maxTime = 73;
|
||||||
|
|
||||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const timelineRef = useRef<HTMLDivElement>(null);
|
const timelineRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -98,10 +100,10 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
// 添加防抖引用
|
// 添加防抖引用
|
||||||
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// 时间刻度数组 (1-100,每10个单位一个刻度)
|
// 时间刻度数组 (4-73,每3个单位一个刻度)
|
||||||
const valueMarks = Array.from({ length: 10 }, (_, i) => ({
|
const valueMarks = Array.from({ length: 24 }, (_, i) => ({
|
||||||
value: (i + 1) * 10,
|
value: 4 + i * 3,
|
||||||
label: `${(i + 1) * 10}`,
|
label: `${4 + i * 3}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 播放时间间隔选项
|
// 播放时间间隔选项
|
||||||
@@ -256,34 +258,86 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
clearTimeout(debounceRef.current);
|
clearTimeout(debounceRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [pipeLayer]);
|
||||||
|
|
||||||
// 获取地图实例
|
// 获取地图实例
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
|
||||||
// 根据年份从 survival_function 中插值获取生存概率
|
// 根据索引从 survival_function 中获取生存概率
|
||||||
const getSurvivalProbabilityAtYear = useCallback(
|
const getSurvivalProbabilityAtYear = useCallback(
|
||||||
(survivalFunc: SurvivalFunction, year: number): number => {
|
(survivalFunc: SurvivalFunction, index: number): number => {
|
||||||
const { x, y } = survivalFunc;
|
const { y } = survivalFunc;
|
||||||
if (x.length === 0 || y.length === 0) return 1;
|
if (y.length === 0) return 1;
|
||||||
|
|
||||||
// 如果年份小于最小值,返回第一个概率
|
// 确保索引在范围内
|
||||||
if (year <= x[0]) return y[0];
|
const safeIndex = Math.max(0, Math.min(index, y.length - 1));
|
||||||
// 如果年份大于最大值,返回最后一个概率
|
return y[safeIndex];
|
||||||
if (year >= x[x.length - 1]) return y[y.length - 1];
|
|
||||||
|
|
||||||
// 线性插值
|
|
||||||
for (let i = 0; i < x.length - 1; i++) {
|
|
||||||
if (year >= x[i] && year <= x[i + 1]) {
|
|
||||||
const ratio = (year - x[i]) / (x[i + 1] - x[i]);
|
|
||||||
return y[i] + ratio * (y[i + 1] - y[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 更新管道图层中的 healthRisk 属性
|
||||||
|
const updatePipeHealthData = useCallback(
|
||||||
|
(healthData: Map<string, number>) => {
|
||||||
|
if (!pipeLayer) return;
|
||||||
|
const source = pipeLayer.getSource() as any;
|
||||||
|
if (!source) return;
|
||||||
|
|
||||||
|
const sourceTiles = source.sourceTiles_;
|
||||||
|
if (!sourceTiles) return;
|
||||||
|
|
||||||
|
Object.values(sourceTiles).forEach((vectorTile: any) => {
|
||||||
|
const renderFeatures = vectorTile.getFeatures();
|
||||||
|
if (!renderFeatures || renderFeatures.length === 0) return;
|
||||||
|
renderFeatures.forEach((renderFeature: any) => {
|
||||||
|
const featureId = renderFeature.get("id");
|
||||||
|
const value = healthData.get(featureId);
|
||||||
|
if (value !== undefined) {
|
||||||
|
renderFeature.properties_["healthRisk"] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[pipeLayer]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听瓦片加载,为新瓦片设置 healthRisk 属性
|
||||||
|
// 只在 pipeLayer 变化时绑定一次,通过 ref 获取最新数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pipeLayer) return;
|
||||||
|
const source = pipeLayer.getSource() as any;
|
||||||
|
if (!source) return;
|
||||||
|
|
||||||
|
const listener = (event: any) => {
|
||||||
|
const vectorTile = event.tile;
|
||||||
|
const renderFeatures = vectorTile.getFeatures();
|
||||||
|
if (!renderFeatures || renderFeatures.length === 0) return;
|
||||||
|
|
||||||
|
const healthData = healthDataRef.current;
|
||||||
|
let i = 0;
|
||||||
|
renderFeatures.forEach((renderFeature: any) => {
|
||||||
|
const featureId = renderFeature.get("id");
|
||||||
|
const value = healthData.get(featureId);
|
||||||
|
if (value !== undefined) {
|
||||||
|
renderFeature.properties_["healthRisk"] = value;
|
||||||
|
// 输出前10个点的信息以供调试
|
||||||
|
if (i < 10) {
|
||||||
|
console.log(
|
||||||
|
`瓦片加载 - 设置特征 ${featureId} 的 healthRisk 为 ${value}`
|
||||||
|
);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
source.on("tileloadend", listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
source.un("tileloadend", listener);
|
||||||
|
};
|
||||||
|
}, [pipeLayer]);
|
||||||
|
|
||||||
// 应用样式到管道图层
|
// 应用样式到管道图层
|
||||||
const applyPipeHealthStyle = useCallback(() => {
|
const applyPipeHealthStyle = useCallback(() => {
|
||||||
if (!pipeLayer || predictionResults.length === 0) {
|
if (!pipeLayer || predictionResults.length === 0) {
|
||||||
@@ -291,38 +345,32 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 为每条管道计算当前年份的生存概率
|
// 为每条管道计算当前年份的生存概率
|
||||||
const pipeHealthData: { [key: string]: number } = {};
|
const pipeHealthData = new Map<string, number>();
|
||||||
predictionResults.forEach((result) => {
|
predictionResults.forEach((result) => {
|
||||||
const probability = getSurvivalProbabilityAtYear(
|
const probability = getSurvivalProbabilityAtYear(
|
||||||
result.survival_function,
|
result.survival_function,
|
||||||
currentYear
|
currentYear - 1 // 使用索引 (0-based)
|
||||||
);
|
);
|
||||||
pipeHealthData[result.link_id] = probability;
|
pipeHealthData.set(result.link_id, probability);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取所有概率值用于分类
|
// 更新 ref 数据
|
||||||
const probabilities = Object.values(pipeHealthData);
|
healthDataRef.current = pipeHealthData;
|
||||||
if (probabilities.length === 0) return;
|
// 输出前 10 条数据以供调试
|
||||||
|
console.log(
|
||||||
// 使用优雅分段方法计算断点(10个分段)
|
`更新健康数据,年份: ${currentYear}`,
|
||||||
const segments = 10;
|
Array.from(pipeHealthData.entries()).slice(0, 10)
|
||||||
const breaks = calculateClassification(
|
|
||||||
probabilities,
|
|
||||||
segments,
|
|
||||||
"pretty_breaks"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 确保包含最小值和最大值
|
// 更新图层数据
|
||||||
const minVal = Math.min(...probabilities);
|
updatePipeHealthData(pipeHealthData);
|
||||||
const maxVal = Math.max(...probabilities);
|
|
||||||
if (!breaks.includes(minVal)) {
|
// 获取所有概率值用于分类
|
||||||
breaks.push(minVal);
|
const probabilities = Array.from(pipeHealthData.values());
|
||||||
breaks.sort((a, b) => a - b);
|
if (probabilities.length === 0) return;
|
||||||
}
|
|
||||||
if (!breaks.includes(maxVal)) {
|
// 使用等距分段,从0-1分为十类
|
||||||
breaks.push(maxVal);
|
const breaks = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
|
||||||
breaks.sort((a, b) => a - b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成彩虹色(从紫色到红色,低生存概率=高风险=红色)
|
// 生成彩虹色(从紫色到红色,低生存概率=高风险=红色)
|
||||||
const colors = RAINBOW_COLORS;
|
const colors = RAINBOW_COLORS;
|
||||||
@@ -361,7 +409,13 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
console.log(
|
console.log(
|
||||||
`已应用健康风险样式,年份: ${currentYear}, 分段: ${breaks.length}`
|
`已应用健康风险样式,年份: ${currentYear}, 分段: ${breaks.length}`
|
||||||
);
|
);
|
||||||
}, [pipeLayer, predictionResults, currentYear, getSurvivalProbabilityAtYear]);
|
}, [
|
||||||
|
pipeLayer,
|
||||||
|
predictionResults,
|
||||||
|
currentYear,
|
||||||
|
getSurvivalProbabilityAtYear,
|
||||||
|
updatePipeHealthData,
|
||||||
|
]);
|
||||||
|
|
||||||
// 初始化管道图层
|
// 初始化管道图层
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -375,24 +429,15 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
|
|
||||||
if (pipesLayer) {
|
if (pipesLayer) {
|
||||||
setPipeLayer(pipesLayer);
|
setPipeLayer(pipesLayer);
|
||||||
console.log("管道图层已找到");
|
|
||||||
}
|
}
|
||||||
}, [map]);
|
}, [map]);
|
||||||
|
|
||||||
// 监听时间轴变化,更新样式
|
// 监听依赖变化,更新样式
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (predictionResults.length > 0 && pipeLayer) {
|
if (predictionResults.length > 0 && pipeLayer) {
|
||||||
applyPipeHealthStyle();
|
applyPipeHealthStyle();
|
||||||
}
|
}
|
||||||
}, [currentYear, predictionResults, pipeLayer, applyPipeHealthStyle]);
|
}, [applyPipeHealthStyle]);
|
||||||
|
|
||||||
// 监听预测结果变化,首次应用样式
|
|
||||||
useEffect(() => {
|
|
||||||
if (predictionResults.length > 0 && pipeLayer) {
|
|
||||||
console.log("预测结果已更新,应用样式");
|
|
||||||
applyPipeHealthStyle();
|
|
||||||
}
|
|
||||||
}, [predictionResults, pipeLayer, applyPipeHealthStyle]);
|
|
||||||
|
|
||||||
// 这里防止地图缩放时,瓦片重新加载引起的属性更新出错
|
// 这里防止地图缩放时,瓦片重新加载引起的属性更新出错
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -440,7 +485,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const results: PredictionResult[] = await response.json();
|
const results: PredictionResult[] = await response.json();
|
||||||
setPredictionResults(results);
|
setPredictionResults(results);
|
||||||
console.log("预测结果:", results);
|
console.log("预测结果:", results[0], results[1], results.length);
|
||||||
open?.({
|
open?.({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: `模拟预测完成,获取到 ${results.length} 条管道数据`,
|
message: `模拟预测完成,获取到 ${results.length} 条管道数据`,
|
||||||
@@ -613,15 +658,20 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
color: "primary.main",
|
color: "primary.main",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
预测年份:{currentYear}
|
预测年份:
|
||||||
|
{predictionResults.length > 0 &&
|
||||||
|
predictionResults[0].survival_function.x[currentYear - 1] !==
|
||||||
|
undefined
|
||||||
|
? predictionResults[0].survival_function.x[currentYear - 1]
|
||||||
|
: currentYear}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Box ref={timelineRef} sx={{ px: 2, position: "relative" }}>
|
<Box ref={timelineRef} sx={{ px: 2, position: "relative" }}>
|
||||||
<Slider
|
<Slider
|
||||||
value={currentYear}
|
value={currentYear}
|
||||||
min={1}
|
min={minTime}
|
||||||
max={100} // 1-100的范围
|
max={maxTime} // 4-73的范围
|
||||||
step={1} // 每1个单位一个步进
|
step={1} // 每1个单位一个步进
|
||||||
marks={valueMarks} // 显示刻度
|
marks={valueMarks} // 显示刻度
|
||||||
onChange={handleSliderChange}
|
onChange={handleSliderChange}
|
||||||
|
|||||||
Reference in New Issue
Block a user