添加推演以及脉冲

This commit is contained in:
wzy-warehouse
2026-06-14 19:50:28 +08:00
parent bc584dc900
commit 1ef2fec9c9
7 changed files with 197 additions and 18 deletions
+4 -4
View File
@@ -57,6 +57,7 @@ import type { XianReservoirList } from '@/types/base/XianReservoirList';
import type { XianSubwayStations } from '@/types/base/XianSubwayStations'; import type { XianSubwayStations } from '@/types/base/XianSubwayStations';
import { modelDeduction as rainfallModelDeduction } from './rainfall'; import { modelDeduction as rainfallModelDeduction } from './rainfall';
import type { RainPredictResponse } from '@/types/rainstorm/RainPredictResponse'; import type { RainPredictResponse } from '@/types/rainstorm/RainPredictResponse';
import type { RainPredictRequest } from '@/types/rainstorm/RainPredictRequest';
/** /**
* API接口统一导出对象 * API接口统一导出对象
@@ -284,12 +285,11 @@ export const $api = {
rainfall: { rainfall: {
/** /**
* 进行模型推演 * 进行模型推演
* @param disasterName 灾害名称 * @param req 请求体
* @returns 推演点的概率 * @returns 推演点的概率
*/ */
modelDeduction: ( modelDeduction: (
disasterName: string req: RainPredictRequest
): Promise<ApiResponse<RainPredictResponse>> => ): Promise<ApiResponse<RainPredictResponse>> => rainfallModelDeduction(req),
rainfallModelDeduction(disasterName),
}, },
}; };
+5 -6
View File
@@ -1,16 +1,15 @@
import type { ApiResponse } from '@/types/ApiResponse'; import type { ApiResponse } from '@/types/ApiResponse';
import type { RainPredictRequest } from '@/types/rainstorm/RainPredictRequest';
import type { RainPredictResponse } from '@/types/rainstorm/RainPredictResponse'; import type { RainPredictResponse } from '@/types/rainstorm/RainPredictResponse';
import httpInstance from '@/utils/request/http'; import httpInstance from '@/utils/request/http';
/** /**
* 进行模型推演 * 进行模型推演
* @param disasterName 灾害名称 * @param req 请求体
* @returns 推演点的概率 * @returns 推演点的概率
*/ */
export const modelDeduction = ( export const modelDeduction = (
disasterName: string req: RainPredictRequest
): Promise<ApiResponse<RainPredictResponse[]>> => { ): Promise<ApiResponse<RainPredictResponse>> => {
return httpInstance.post('/algorithm-api/rainfall/predict', { return httpInstance.post('/algorithm-api/rainfall/predict', req);
disaster_name: disasterName,
});
}; };
@@ -10,6 +10,7 @@
import { useStepStore } from '@/stores/useStepStore'; import { useStepStore } from '@/stores/useStepStore';
import type { ApiResponse } from '@/types/ApiResponse'; import type { ApiResponse } from '@/types/ApiResponse';
import type { RainfallGridResponse } from '@/types/rainstorm/RainfallGridResponse'; import type { RainfallGridResponse } from '@/types/rainstorm/RainfallGridResponse';
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
import { WebSocketService } from '@/utils/request/websocket'; import { WebSocketService } from '@/utils/request/websocket';
import { Utils } from '@/utils/utils'; import { Utils } from '@/utils/utils';
import { onMounted, onUnmounted, watch } from 'vue'; import { onMounted, onUnmounted, watch } from 'vue';
@@ -51,14 +52,18 @@
// 进行模型计算 // 进行模型计算
$api.rainfall $api.rainfall
.modelDeduction( .modelDeduction({
`${Utils.formatDate('YYYYMMDDHHmmss')}暴雨自动推演` disaster_name: `${Utils.formatDate('YYYYMMDDHHmmss', new Date('2025-09-16 20:00:00'))}暴雨自动推演`,
) occurred_time: '2025-09-16 20:00:00',
operation_type: '暴雨灾害链自动推演',
})
.then((res) => { .then((res) => {
// 推进到下一步 // 推进到下一步
stepStore.nextStep(); stepStore.nextStep();
// 报告产出 // 进行预警
CesiumUtilsSingleton.addPulseEffect(res.data.list);
console.log(res);
}); });
} else { } else {
console.warn('响应错误:', response.message); console.warn('响应错误:', response.message);
+8
View File
@@ -0,0 +1,8 @@
/**
* 预警列表
*/
export interface WarningList {
probability: number;
lon: number;
lat: number;
}
+19
View File
@@ -0,0 +1,19 @@
/**
* 降雨预测请求值
*/
export interface RainPredictRequest {
/** 灾害名称 */
disaster_name: string;
/** id列表 */
point_ids?: number[];
/** 行政区划代码 */
region_code?: string;
/** 累计降雨量 */
rainfall?: number;
/** 持续时间 */
duration?: number;
/** 发生时间 */
occurred_time?: string;
/** 操作类型 */
operation_type?: string;
}
+4 -4
View File
@@ -1,6 +1,6 @@
import type { WarningList } from '../common/WarningList';
export interface RainPredictResponse { export interface RainPredictResponse {
id: number; record_id: number;
type: string; list: Record<string, WarningList>;
probability: number;
level: string;
} }
+148
View File
@@ -20,6 +20,11 @@ import {
SceneTransforms, SceneTransforms,
Rectangle, Rectangle,
Color, Color,
JulianDate,
CallbackProperty,
HeightReference,
VerticalOrigin,
HorizontalOrigin,
} from 'cesium'; } from 'cesium';
import { CesiumViewerManager } from './CesiumViewerManager'; import { CesiumViewerManager } from './CesiumViewerManager';
import { EntityManager } from './EntityManager'; import { EntityManager } from './EntityManager';
@@ -29,6 +34,7 @@ import { GeoJsonManager, type ClearType } from './GeoJsonManager';
import { CameraController } from './CameraController'; import { CameraController } from './CameraController';
import config from '@/config/config.json'; import config from '@/config/config.json';
import type { ClickObject } from '@/types/cesium/ClickObject'; import type { ClickObject } from '@/types/cesium/ClickObject';
import type { WarningList } from '@/types/common/WarningList';
// 导出 ClearType 类型 // 导出 ClearType 类型
export type { ClearType }; export type { ClearType };
@@ -48,6 +54,12 @@ export class CesiumUtils {
// 颜色缓存 // 颜色缓存
#colorCache = new Map<string, Color>(); #colorCache = new Map<string, Color>();
// 脉冲相关状态
#pulseMap: Record<string, { pulseId: string; probability: number }> = {};
#maxPulseRadius = 30;
#pulseDuration = 5;
#pulseCircleImage: string | null = null;
constructor() { constructor() {
this.#viewerManager = new CesiumViewerManager(); this.#viewerManager = new CesiumViewerManager();
} }
@@ -613,6 +625,44 @@ export class CesiumUtils {
return color; return color;
} }
/**
* 添加脉冲效果
* @param list 点列表
* @returns
*/
addPulseEffect(list: Record<string, WarningList>) {
const viewer = this.getViewer();
if (!viewer) return;
// 移除已有脉冲
this.#removeAllPulses();
// 生成圆形贴图
if (!this.#pulseCircleImage) {
this.#pulseCircleImage = this.#createCircleImage(this.#maxPulseRadius);
}
for (const [disasterType, warning] of Object.entries(list)) {
const { lon, lat, probability } = warning;
// 根据概率确定颜色
let color: Color;
if (probability >= 0.7) {
color = Color.RED;
} else if (probability >= 0.5) {
color = Color.YELLOW;
} else {
continue;
}
const key = `${disasterType}_${lon}_${lat}`;
const pulseId = `PULSE_${key}_${Date.now()}`;
this.#createPulseCircle(pulseId, lon, lat, color);
this.#pulseMap[key] = { pulseId, probability };
}
}
// ===================== 私有方法 ===================== // ===================== 私有方法 =====================
/** /**
@@ -625,6 +675,104 @@ export class CesiumUtils {
throw new Error(`${managerName} 未初始化,请先调用 initCesiumViewer()`); throw new Error(`${managerName} 未初始化,请先调用 initCesiumViewer()`);
} }
} }
/**
* 生成脉冲圆形贴图
*/
#createCircleImage(maxRadius: number): string {
const canvas = document.createElement('canvas');
canvas.width = maxRadius * 2;
canvas.height = maxRadius * 2;
const ctx = canvas.getContext('2d')!;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(maxRadius, maxRadius, maxRadius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fillStyle = 'rgba(255,255,255,0.7)';
ctx.fill();
return canvas.toDataURL('image/png');
}
/**
* 创建脉冲实体
* @param pulseId - 脉冲实体 ID
* @param lon - 经度
* @param lat - 纬度
* @param color - 颜色
*/
#createPulseCircle(
pulseId: string,
lon: number,
lat: number,
color: Color
): void {
const viewer = this.getViewer();
if (!viewer || !this.#pulseCircleImage) return;
const startTime = JulianDate.now();
const maxRadius = this.#maxPulseRadius;
const duration = this.#pulseDuration;
viewer.entities.add({
id: pulseId,
position: Cartesian3.fromDegrees(lon, lat),
billboard: {
image: this.#pulseCircleImage,
width: new CallbackProperty((time) => {
const elapsed =
JulianDate.secondsDifference(time, startTime) % duration;
const progress = elapsed / duration;
return maxRadius * 2 * Math.abs(Math.sin(progress * Math.PI));
}, false),
height: new CallbackProperty((time) => {
const elapsed =
JulianDate.secondsDifference(time, startTime) % duration;
const progress = elapsed / duration;
return maxRadius * 2 * Math.abs(Math.sin(progress * Math.PI));
}, false),
color: new CallbackProperty((time) => {
const elapsed =
JulianDate.secondsDifference(time, startTime) % duration;
const progress = elapsed / duration;
const alpha = 0.7 * (1 - progress);
return color.withAlpha(alpha);
}, false),
heightReference: HeightReference.CLAMP_TO_GROUND,
verticalOrigin: VerticalOrigin.CENTER,
horizontalOrigin: HorizontalOrigin.CENTER,
eyeOffset: new Cartesian3(0.0, 0.0, -10.0),
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
});
}
/**
* 移除所有脉冲实体
*/
#removeAllPulses(): void {
for (const key of Object.keys(this.#pulseMap)) {
this.#deletePulseEntity(key);
}
}
/**
* 删除单个脉冲实体
* @param key - 脉冲映射 key
*/
#deletePulseEntity(key: string): void {
const viewer = this.getViewer();
if (!viewer) return;
const entry = this.#pulseMap[key];
if (!entry) return;
const entity = viewer.entities.getById(entry.pulseId);
if (entity) {
viewer.entities.remove(entity);
}
delete this.#pulseMap[key];
}
} }
/** /**