| | |
| | | onBeforeUnmount, |
| | | inject, |
| | | reactive, |
| | | watchEffect, |
| | | } from "vue"; |
| | | import ratelevel from "@/components/menu/flowRate_waterLevel.vue"; |
| | | import crossanalysis from "@/components/menu/CrossSectionalAnalysis.vue"; |
| | |
| | | import { useSimStore } from "@/store/simulation"; |
| | | import { storeToRefs } from "pinia"; |
| | | const simStore = useSimStore(); |
| | | const { selectedScheme, frameNum } = storeToRefs(simStore); |
| | | const { selectedScheme, frameNum, layerDate } = storeToRefs(simStore); |
| | | |
| | | const emit = defineEmits([ |
| | | "timeUpdate", |
| | |
| | | const isColorRenderEnabled = ref(false); // 假设这是你的颜色渲染开关状态 |
| | | const isWaterPrimitiveCreated = ref(false); |
| | | let playInterval = null; |
| | | let timeStepInfo = null; |
| | | let rainTotalInfo = []; |
| | | const isRainEnabled = ref(false); |
| | | const rainParams = reactive({ |
| | |
| | | }); |
| | | let minFlowRate = ref(); |
| | | let maxFlowRate = ref(); |
| | | // 全局变量记录最大阶段和透明度 |
| | | let maxStage = 0; |
| | | let maxAlpha = -0.3; // 初始透明度对应stage 0 |
| | | // 计算属性 |
| | | const progressPercentage = computed( |
| | | () => (currentTime.value / duration.value) * 100 |
| | |
| | | new Set(waterTimestamps.value.map((ts) => dayjs(ts).format("YYYY-MM-DD"))) |
| | | ).map((date) => dayjs(date).toDate()) |
| | | ); |
| | | |
| | | // 我需要加一个判断 |
| | | const finishPlay = ref(false); |
| | | // 播放控制 |
| | | const togglePlay = () => { |
| | | // 这里应该再设定几个限制,如果缺少什么数据,无法进行仿真 |
| | |
| | | 8: 125, // 8倍速 |
| | | }; |
| | | // 播放逻辑 |
| | | // const startPlayback = () => { |
| | | // clearInterval(playInterval); |
| | | |
| | | // playInterval = setInterval(() => { |
| | | // // 找到当前时间对应的索引 |
| | | // const currentIndex = findClosestTimestampIndex(currentTime.value); |
| | | // const nextIndex = currentIndex + 1; |
| | | |
| | | // // 如果已经是最后一个时间点,停止播放 |
| | | // if (nextIndex >= waterTimestamps.value.length) { |
| | | // currentTime.value = duration.value; |
| | | // stopPlayback(); |
| | | // isPlaying.value = false; |
| | | // emit("isPlaying", false); |
| | | // emit("playbackFinished", true); |
| | | // return; |
| | | // } |
| | | |
| | | // // 更新时间为下一个时间点的时间差(秒) |
| | | // const nextTimestamp = waterTimestamps.value[nextIndex]; |
| | | // const baseTimestamp = waterTimestamps.value[0]; |
| | | // currentTime.value = (nextTimestamp - baseTimestamp) / 1000; |
| | | |
| | | // // 触发更新 |
| | | // if (selectedScheme.value.type !== 2) { |
| | | // updateWaterColorByTime(); |
| | | // updateWeatherByProgress(); |
| | | // } |
| | | |
| | | // const progress = currentTime.value / duration.value; |
| | | // emit("timeUpdate", progress * 100); |
| | | // }, 1000 / playbackRate.value); // 根据播放速率调整间隔 |
| | | // }; |
| | | const startPlayback = () => { |
| | | clearInterval(playInterval); |
| | | |
| | | playInterval = setInterval(() => { |
| | | // 找到当前时间对应的索引 |
| | | const currentIndex = findClosestTimestampIndex(currentTime.value); |
| | | const nextIndex = currentIndex + 1; |
| | | if (selectedScheme.value.type === 2) { |
| | | // 类型为 2:每 5 秒跳动一次 |
| | | playInterval = setInterval(() => { |
| | | const fiveSeconds = 5; |
| | | const totalDuration = duration.value; // 总时长(秒) |
| | | |
| | | // 如果已经是最后一个时间点,停止播放 |
| | | if (nextIndex >= waterTimestamps.value.length) { |
| | | currentTime.value = duration.value; |
| | | stopPlayback(); |
| | | isPlaying.value = false; |
| | | emit("isPlaying", false); |
| | | emit("playbackFinished", true); |
| | | return; |
| | | } |
| | | currentTime.value += fiveSeconds; |
| | | |
| | | // 更新时间为下一个时间点的时间差(秒) |
| | | const nextTimestamp = waterTimestamps.value[nextIndex]; |
| | | const baseTimestamp = waterTimestamps.value[0]; |
| | | currentTime.value = (nextTimestamp - baseTimestamp) / 1000; |
| | | if (currentTime.value >= totalDuration) { |
| | | currentTime.value = totalDuration; |
| | | stopPlayback(); |
| | | isPlaying.value = false; |
| | | finishPlay.value = true; |
| | | emit("isPlaying", false); |
| | | emit("playbackFinished", true); |
| | | return; |
| | | } |
| | | |
| | | // 触发更新 |
| | | if (selectedScheme.value.type !== 2) { |
| | | // 触发进度更新 |
| | | const progress = currentTime.value / totalDuration; |
| | | emit("timeUpdate", progress * 100); |
| | | |
| | | // 如果需要触发某些更新函数,也可以保留 |
| | | updateWaterColorByTime(); |
| | | updateWeatherByProgress(); |
| | | } |
| | | }, 5000); // 每 5 秒执行一次 |
| | | } else { |
| | | // 这里面还是你的播放代码,上面的if中是五秒钟跳动一次的实时模拟 |
| | | playInterval = setInterval(() => { |
| | | const currentIndex = findClosestTimestampIndex(currentTime.value); |
| | | const nextIndex = currentIndex + 1; |
| | | |
| | | const progress = currentTime.value / duration.value; |
| | | emit("timeUpdate", progress * 100); |
| | | }, 1000 / playbackRate.value); // 根据播放速率调整间隔 |
| | | if (nextIndex >= waterTimestamps.value.length) { |
| | | currentTime.value = duration.value; |
| | | stopPlayback(); |
| | | isPlaying.value = false; |
| | | emit("isPlaying", false); |
| | | emit("playbackFinished", true); |
| | | return; |
| | | } |
| | | |
| | | const nextTimestamp = waterTimestamps.value[nextIndex]; |
| | | const baseTimestamp = waterTimestamps.value[0]; |
| | | currentTime.value = (nextTimestamp - baseTimestamp) / 1000; |
| | | |
| | | if (selectedScheme.value.type !== 2) { |
| | | updateWaterColorByTime(); |
| | | updateWeatherByProgress(); |
| | | } |
| | | |
| | | const progress = currentTime.value / duration.value; |
| | | emit("timeUpdate", progress * 100); |
| | | }, 1000 / playbackRate.value); |
| | | } |
| | | }; |
| | | // 降雨变化部分 |
| | | // 降雨数据相关变量 |
| | |
| | | const rainfallList = data.rainfalls; |
| | | console.log("最终的 rainfallList:", rainfallList); |
| | | rainTotalInfo.value = rainfallList; |
| | | calculateTimeStep(rainTotalInfo.value); |
| | | // 使用示例 |
| | | timeStepInfo = calculateTimeStep(rainTotalInfo.value); |
| | | |
| | | // 提取 intensity 值 |
| | | rainFallValues.value = rainfallList.map((r) => r.intensity); |
| | |
| | | // 根据播放进度更新天气效果(已优化) |
| | | let lastUsedIndex = -1; // 缓存上一次使用的索引,防止重复更新 |
| | | let lastRainValue = null; |
| | | |
| | | function calculateTimeStep(dataArray) { |
| | | if (!dataArray || dataArray.length < 2) { |
| | | console.warn("数据不足,无法计算时间步长"); |
| | | return null; |
| | | } |
| | | |
| | | // 解析时间字符串为 Date 对象 |
| | | function parseTime(timeStr) { |
| | | return new Date(timeStr.replace(" ", "T")); // 兼容 ISO 格式 |
| | | } |
| | | |
| | | const firstTime = parseTime(dataArray[0].time); |
| | | const secondTime = parseTime(dataArray[1].time); |
| | | |
| | | // 计算时间差(毫秒) |
| | | const diffMs = Math.abs(secondTime - firstTime); |
| | | |
| | | // 转换为小时数(保留小数) |
| | | let timeStepHours = diffMs / (1000 * 60 * 60); // 毫秒 -> 小时 |
| | | |
| | | // 可选:遍历所有相邻项检查是否一致 |
| | | for (let i = 1; i < dataArray.length - 1; i++) { |
| | | const current = parseTime(dataArray[i].time); |
| | | const next = parseTime(dataArray[i + 1].time); |
| | | const step = Math.abs(next - current) / (1000 * 60 * 60); // 毫秒 -> 小时 |
| | | if (Math.abs(step - timeStepHours) > 0.01) { |
| | | console.warn( |
| | | `在索引 ${i} 处发现了不同的时间步长: ${step.toFixed(2)} 小时` |
| | | ); |
| | | } |
| | | } |
| | | |
| | | return timeStepHours; |
| | | } |
| | | // 全局状态记录 |
| | | const colorState = { |
| | | maxStage: 0, // 记录历史最高阶段 |
| | | maxAlpha: -0.3, // 记录历史最小透明度(负值) |
| | | maxLuminance: 240.4, // 记录历史最低亮度(对应stage 0初始值) |
| | | currentColor: "#F5F0E6", // 当前颜色 |
| | | }; |
| | | |
| | | function updateWaterColorByTime() { |
| | | if (!rainTotalInfo.value || rainTotalInfo.value.length === 0) return; |
| | | const progress = currentTime.value / duration.value; |
| | | const floatIndex = progress * (rainTotalInfo.value.length - 1); |
| | | const index = Math.floor(floatIndex); |
| | | const nextIndex = Math.min(index + 1, rainTotalInfo.value.length - 1); |
| | | const currentData = rainTotalInfo.value[index]; |
| | | const nextData = rainTotalInfo.value[nextIndex]; |
| | | // 启用插值(alpha 平滑过渡) |
| | | const alpha = floatIndex - index; |
| | | const currentTotal = currentData.total; |
| | | const nextTotal = nextData.total; |
| | | const total = currentTotal + (nextTotal - currentTotal) * alpha; |
| | | // 根据 total 设置颜色 |
| | | let color = "#D4F2E7"; // 默认蓝色 |
| | | |
| | | if (total >= 150) { |
| | | color = "#663300"; // 黄 - 大雨 |
| | | } else if (total >= 125) { |
| | | color = "#B26633"; // 黄绿 - 中雨 |
| | | } else if (total >= 100) { |
| | | color = "#CC9966"; // 绿 - 中雨 |
| | | } else if (total >= 75) { |
| | | color = "#CCE5FF"; // 青绿 - 小雨 |
| | | } else if (total >= 50) { |
| | | color = "#99CCFF"; // 天蓝 - 小雨 |
| | | } else if (total >= 25) { |
| | | color = "#66B3FF"; // 浅蓝 - 微量 |
| | | // 1. 计算基础数据 |
| | | const { intensity, IR } = calculateRainData(); |
| | | |
| | | // 2. 颜色配置(亮度严格递减) |
| | | const COLOR_STOPS = [ |
| | | { hex: "#F5F0E6", luminance: 240.4 }, // stage 0 |
| | | { hex: "#D4F2E7", luminance: 231.8 }, // stage 1 |
| | | { hex: "#E6D5B8", luminance: 214.8 }, // stage 2 |
| | | { hex: "#D4B483", luminance: 184.0 }, // stage 3 |
| | | { hex: "#B78B6A", luminance: 148.4 }, // stage 4 |
| | | { hex: "#8B5A3A", luminance: 101.0 }, // stage 5 |
| | | { hex: "#4A3123", luminance: 54.9 }, // stage 6 |
| | | ]; |
| | | const alphaStops = [ |
| | | 1 - 0.3, // stage 0 |
| | | -0.4, // stage 1 |
| | | -0.5, // stage 2 |
| | | -0.6, // stage 3 |
| | | -0.7, // stage 4 |
| | | -0.75, // stage 5 |
| | | -0.8, // stage 6 |
| | | ]; |
| | | // 3. 更新阶段状态 |
| | | updateStageState(intensity, IR); |
| | | |
| | | // 4. 计算并锁定颜色(确保亮度不回升) |
| | | updateColorState(COLOR_STOPS, intensity, IR); |
| | | |
| | | // 5. 应用颜色 |
| | | updateWaterColor(colorState.currentColor, colorState.maxAlpha); |
| | | |
| | | // --- 辅助函数 --- |
| | | function calculateRainData() { |
| | | const initialTimestamp = new Date(rainTotalInfo.value[0].time).getTime(); |
| | | const currentTimestamp = new Date( |
| | | rainTotalInfo.value[ |
| | | Math.min( |
| | | Math.floor( |
| | | (currentTime.value / duration.value) * |
| | | (rainTotalInfo.value.length - 1) |
| | | ), |
| | | rainTotalInfo.value.length - 2 |
| | | ) |
| | | ].time |
| | | ).getTime(); |
| | | |
| | | // 降雨强度计算(带插值) |
| | | const progress = currentTime.value / duration.value; |
| | | const floatIndex = progress * (rainTotalInfo.value.length - 1); |
| | | let index = Math.floor(floatIndex); |
| | | if (index >= rainTotalInfo.value.length - 1) { |
| | | index = rainTotalInfo.value.length - 2; // 防止 index+1 越界 |
| | | } |
| | | const lerpAlpha = floatIndex - index; |
| | | const intensity = |
| | | rainTotalInfo.value[index].intensity * (1 - lerpAlpha) + |
| | | rainTotalInfo.value[index + 1].intensity * lerpAlpha; |
| | | |
| | | // 临界降雨强度计算 |
| | | const D = (currentTimestamp - initialTimestamp) / (1000 * 60 * 60) + 0.0001; |
| | | const IR = 56.9 * Math.pow(D, -0.746); |
| | | |
| | | return { intensity, IR }; |
| | | } |
| | | // console.log(`当前 total: ${total.toFixed(2)}, 颜色: ${color}`); |
| | | // updateWaterColor(color) |
| | | |
| | | function updateStageState(intensity, IR) { |
| | | // 计算理论阶段 |
| | | let stage = 0; |
| | | const thresholds = [0, 0.2, 0.4, 0.6, 0.8, 1.0]; |
| | | for (let i = thresholds.length - 1; i >= 0; i--) { |
| | | if (intensity >= thresholds[i] * IR) { |
| | | stage = i + 1; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 更新最大阶段(单向递增) |
| | | colorState.maxStage = Math.max(colorState.maxStage, stage); |
| | | } |
| | | |
| | | function updateColorState(colorStops, intensity, IR) { |
| | | // 已达最终阶段 |
| | | if (colorState.maxStage >= colorStops.length - 1) { |
| | | colorState.currentColor = colorStops[colorStops.length - 1].hex; |
| | | colorState.maxAlpha = -0.8; |
| | | colorState.maxLuminance = colorStops[colorStops.length - 1].luminance; |
| | | return; |
| | | } |
| | | |
| | | // 计算当前阶段进度 |
| | | const stageThresholds = [0, 0.2, 0.4, 0.6, 0.8, 1.0]; |
| | | const lowerThreshold = stageThresholds[colorState.maxStage - 1] * IR; |
| | | const upperThreshold = stageThresholds[colorState.maxStage] * IR; |
| | | const ratio = Math.min( |
| | | 1, |
| | | Math.max( |
| | | 0, |
| | | (intensity - lowerThreshold) / (upperThreshold - lowerThreshold) |
| | | ) |
| | | ); |
| | | |
| | | // 颜色插值 |
| | | const startColor = colorStops[colorState.maxStage]; |
| | | const endColor = colorStops[colorState.maxStage + 1]; |
| | | const newColor = lerpColor(startColor.hex, endColor.hex, ratio); |
| | | const newLuminance = calculateLuminance(newColor); |
| | | |
| | | // 只接受更暗的颜色(亮度更低) |
| | | if (newLuminance < colorState.maxLuminance) { |
| | | colorState.currentColor = newColor; |
| | | colorState.maxLuminance = newLuminance; |
| | | colorState.maxAlpha = Math.min( |
| | | colorState.maxAlpha, |
| | | lerp( |
| | | alphaStops[colorState.maxStage], |
| | | alphaStops[colorState.maxStage + 1], |
| | | ratio |
| | | ) |
| | | ); |
| | | } |
| | | |
| | | console.log( |
| | | `阶段: ${colorState.maxStage} | 亮度: ${colorState.maxLuminance.toFixed( |
| | | 1 |
| | | )} | 颜色: ${colorState.currentColor}` |
| | | ); |
| | | } |
| | | |
| | | // 颜色插值工具函数 |
| | | function lerpColor(c1, c2, t) { |
| | | const [r1, g1, b1] = hexToRgb(c1); |
| | | const [r2, g2, b2] = hexToRgb(c2); |
| | | return rgbToHex(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t); |
| | | } |
| | | |
| | | function calculateLuminance(hex) { |
| | | const [r, g, b] = hexToRgb(hex); |
| | | return 0.299 * r + 0.587 * g + 0.114 * b; |
| | | } |
| | | |
| | | function hexToRgb(hex) { |
| | | const bigint = parseInt(hex.slice(1), 16); |
| | | return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255]; |
| | | } |
| | | |
| | | function rgbToHex(r, g, b) { |
| | | return `#${[r, g, b] |
| | | .map((x) => Math.round(x).toString(16).padStart(2, "0")) |
| | | .join("")}`; |
| | | } |
| | | |
| | | function lerp(a, b, t) { |
| | | return a + (b - a) * t; |
| | | } |
| | | } |
| | | |
| | | function updateWeatherByProgress() { |
| | |
| | | // const rainValue = currentRain + (nextRain - currentRain) * alpha; |
| | | const rainValue = currentRain + (nextRain - currentRain); |
| | | // 打印当前处理的雨量数据 |
| | | console.log( |
| | | `正在处理的雨量数据点: 当前=${currentRain}, 下一个=${nextRain}, 插值后=${rainValue.toFixed( |
| | | 2 |
| | | )}, 索引=${index}` |
| | | ); |
| | | // console.log( |
| | | // `正在处理的雨量数据点: 当前=${currentRain}, 下一个=${nextRain}, 插值后=${rainValue.toFixed( |
| | | // 2 |
| | | // )}, 索引=${index}` |
| | | // ); |
| | | // 如果当前索引未变化且插值差异不大,跳过重复更新 |
| | | if (index === lastUsedIndex && Math.abs(rainValue - lastRainValue) < 0.1) { |
| | | // console.log('由于数据无显著变化,跳过本次更新'); |
| | |
| | | { immediate: true } |
| | | ); |
| | | |
| | | onMounted(async () => { |
| | | const jsonFetch = ref(null); |
| | | |
| | | // 提取为独立函数 |
| | | async function initializeSimulationData(force = false) { |
| | | try { |
| | | // 当前方案的所有信息 |
| | | const schemeInfo = selectedScheme.value; |
| | | serviceInfo = schemeInfo.serviceName; |
| | | if (selectedScheme.value.type == 2) { |
| | | |
| | | if (schemeInfo.type == 2) { |
| | | speedShow.value = false; |
| | | jsonFetch.value = layerDate.value; |
| | | } else { |
| | | getRainfallData(); |
| | | speedShow.value = true; |
| | | jsonFetch.value = null; |
| | | } |
| | | |
| | | // console.log('获取到的 serviceName:', serviceInfo); |
| | | |
| | | // 根据layer.json去获取时间轴信息 |
| | | // 根据 layer.json 获取时间轴信息 |
| | | const { |
| | | waterTimestamps: timestamps, |
| | | watersMaxHeight, |
| | | watersMinHeight, |
| | | } = await fetchWaterSimulationData(serviceInfo); |
| | | } = await fetchWaterSimulationData(serviceInfo, jsonFetch.value); |
| | | |
| | | console.log( |
| | | "当前方案下的最大水位深度和最小水位深度", |
| | | watersMaxHeight, |
| | | watersMinHeight |
| | | ); |
| | | |
| | | // 现在是按照总共有多少个点来渲染时间轴 |
| | | // 更新时间轴相关数据 |
| | | if (timestamps) { |
| | | frameNum.value = timestamps.length; |
| | | waterTimestamps.value = timestamps; |
| | |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | } |
| | | |
| | | minFlowRate = watersMinHeight; |
| | | maxFlowRate = watersMaxHeight; |
| | | } catch (error) { |
| | |
| | | type: "warning", |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 挂载时调用 |
| | | onMounted(async () => { |
| | | // 因为这个函数实时模拟监听也需要使用,所以封装了一个函数 |
| | | await initializeSimulationData(); |
| | | }); |
| | | |
| | | const shouldAutoPlay = ref(false); |
| | | |
| | | // 监听 layerDate 变化后标记准备播放 |
| | | watch( |
| | | () => layerDate.value, |
| | | async (newVal) => { |
| | | if (selectedScheme.value.type === 2 && newVal) { |
| | | shouldAutoPlay.value = true; |
| | | } |
| | | }, |
| | | { deep: true } |
| | | ); |
| | | |
| | | // 等待 finishPlay 成功后再播放 |
| | | watchEffect(() => { |
| | | if (shouldAutoPlay.value && finishPlay.value && !isPlaying.value) { |
| | | initializeSimulationData(); |
| | | togglePlay(); |
| | | shouldAutoPlay.value = false; |
| | | } |
| | | }); |
| | | |
| | | // 根据返回数据的个数去渲染时间轴 |
| | | function updateTimelineRange() { |
| | | if (waterTimestamps.value.length > 0) { |
| | |
| | | const { endSimulate } = inject("simulateActions"); |
| | | function handleBack() { |
| | | endSimulate(); |
| | | // 停止实时模拟定时器 |
| | | EventBus.emit("close-time"); |
| | | |
| | | isWaterPrimitiveCreated.value = false; |
| | | if (ratelevelRef.value) { |
| | | ratelevelRef.value.endCalculation(); |