添加推演以及脉冲
This commit is contained in:
+4
-4
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* 预警列表
|
||||||
|
*/
|
||||||
|
export interface WarningList {
|
||||||
|
probability: number;
|
||||||
|
lon: number;
|
||||||
|
lat: number;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user