341 lines
11 KiB
TypeScript
341 lines
11 KiB
TypeScript
import type { WorkerFunc } from './worketBlob';
|
||
import dayjs from 'dayjs';
|
||
import { serverIp } from '@/gis/common/config';
|
||
import EventBus from '@/gis/common/eventBus/eventBus.ts';
|
||
import { fitViewByExtent } from '@/gis/ol/index.ts';
|
||
import { setShouldAnimate } from '@/gis/ol/olClock';
|
||
import { addCustomGridLayer } from '@/gis/ol/olGirdHeatmap';
|
||
import { addHeatmapLayer } from '@/gis/ol/olHeatmap';
|
||
// import { addIsolineLayer } from '@/gis/ol/olIsoLine';
|
||
import playbackWorker from './playbackWorker';
|
||
import { getWorketImportUrl } from './worketBlob'; // 导入 WorkerFunc 类型
|
||
// 定义 Worker 消息数据的类型接口
|
||
interface WorkerMessage {
|
||
code: string;
|
||
[key: string]: any; // 允许其他自定义字段
|
||
}
|
||
|
||
// 定义初始化参数的类型接口(根据实际业务补充字段)
|
||
interface InitRealWorketOpt {
|
||
[key: string]: any; // 可根据实际使用场景扩展具体字段
|
||
}
|
||
|
||
// 定义发送给 Worker 的消息类型(根据实际业务补充)
|
||
type PostMessageData = Record<string, any>;
|
||
|
||
// 子线程的对象(明确类型为 Worker | null)
|
||
let playbackWorkerObj: Worker | null = null;
|
||
export const playback_EventBusCallback = new EventBus();
|
||
|
||
let hsjmnType = '浓度分布';
|
||
|
||
/**
|
||
* 计算经纬度点集合的 extent(地理范围)
|
||
* @param points 经纬度点数组 [{ lon: 经度, lat: 纬度, ... }]
|
||
* @returns extent 数组 [minLon, minLat, maxLon, maxLat],若无有效点返回 null
|
||
*/
|
||
function calculatePointsExtent(points: Array<{ lon: number; lat: number }>): number[] | null {
|
||
if (!points || points.length === 0) {
|
||
console.warn('无有效经纬度点,无法计算 extent');
|
||
return null;
|
||
}
|
||
|
||
// 提取所有经度和纬度
|
||
const lons = points.map(p => p.lon);
|
||
const lats = points.map(p => p.lat);
|
||
|
||
// 计算最小/最大经纬度(添加 0.01 缓冲,避免点贴边)
|
||
const minLon = Math.min(...lons) - 0.01;
|
||
const minLat = Math.min(...lats) - 0.01;
|
||
const maxLon = Math.max(...lons) + 0.01;
|
||
const maxLat = Math.max(...lats) + 0.01;
|
||
|
||
return [minLon, minLat, maxLon, maxLat];
|
||
}
|
||
/**
|
||
* 初始化 Web Worker 实例
|
||
* @param opt 初始化参数
|
||
*/
|
||
export function initPlaybackWorkerMa(): void {
|
||
// 校验 playbackWorker 是符合要求的 Worker 函数(类型约束)
|
||
if (typeof playbackWorker !== 'function' || !playbackWorker.name) {
|
||
throw new Error('playbackWorker 必须是具名函数,且无外部依赖');
|
||
}
|
||
|
||
// 创建 worket 对象(getWorketImportUrl 已约束入参为 WorkerFunc)
|
||
const workerUrl = getWorketImportUrl(playbackWorker as WorkerFunc);
|
||
playbackWorkerObj = new Worker(workerUrl);
|
||
const token = localStorage.getItem('token');
|
||
playbackWorkerObj.postMessage({
|
||
code: 'init',
|
||
value: {
|
||
serverIp,
|
||
token,
|
||
},
|
||
});
|
||
|
||
// 监听来自 Web Worker 的消息(明确 event 类型)
|
||
playbackWorkerObj.onmessage = function (event: MessageEvent<WorkerMessage>) {
|
||
const weaData = event.data;
|
||
switch (weaData.code) {
|
||
case 'querySYMNFirstSuccess':{ // 输运模拟第一次接口请求完成,开始绘制自定义热力图
|
||
// 定义最大值
|
||
let maxValue = 0;
|
||
let minValue = 0;
|
||
// 处理数据
|
||
const gridData = weaData.data.result.map((item: any) => {
|
||
if (item.value > maxValue) {
|
||
maxValue = item.value;
|
||
}
|
||
if (item.value !== '-Infinity' && item.value < minValue) {
|
||
minValue = item.value;
|
||
}
|
||
return {
|
||
lon: item.lon,
|
||
lat: item.lat,
|
||
value: item.value,
|
||
width: weaData.data.lonWidth,
|
||
height: weaData.data.latWidth,
|
||
};
|
||
});
|
||
|
||
// 加载自定义热力图
|
||
addCustomGridLayer(gridData, maxValue, minValue);
|
||
|
||
// 开始播放
|
||
setShouldAnimate(true);
|
||
|
||
// 显示图例
|
||
// playback_EventBusCallback.publish('legendUiData', {
|
||
// key: 'showLengend',
|
||
// value: true,
|
||
// });
|
||
// 更改图例最大值
|
||
// playback_EventBusCallback.publish('symnLegendUpdateMax', {
|
||
// maxValue,
|
||
// minValue,
|
||
// });
|
||
}
|
||
break;
|
||
case 'drawSYMN':{ // 绘制输运模拟自定义热力图
|
||
// 定义最大值
|
||
let maxValue = 0;
|
||
let minValue = 0;
|
||
|
||
// 加工数据
|
||
const gridData = weaData.data.map((item: any) => {
|
||
if (item.value > maxValue) {
|
||
maxValue = item.value;
|
||
}
|
||
if (item.value !== '-Infinity' && item.value < minValue) {
|
||
minValue = item.value;
|
||
}
|
||
return {
|
||
lon: item.lon,
|
||
lat: item.lat,
|
||
value: item.value,
|
||
width: weaData.lonWidth,
|
||
height: weaData.latWidth,
|
||
};
|
||
});
|
||
|
||
// 加载自定义热力图
|
||
addCustomGridLayer(gridData, maxValue, minValue);
|
||
// 更改最大值
|
||
// playback_EventBusCallback.publish('symnLegendUpdateMax', {
|
||
// maxValue,
|
||
// minValue,
|
||
// });
|
||
}
|
||
break;
|
||
case 'querySYMNOneFail': // 输运模拟接口请求失败
|
||
// 处理失败场景(如提示用户、重试循环)
|
||
console.error('⚠️ 请求失败', weaData.data);
|
||
break;
|
||
case 'querySYMNAllSuccess': // 输运模拟所有接口请求完成
|
||
// 解除进度条禁用
|
||
playback_EventBusCallback.publish('timeSliderUiData', {
|
||
key: 'isDisabledSlider',
|
||
value: false,
|
||
});
|
||
break;
|
||
case 'queryHSJMNType':
|
||
hsjmnType = weaData.data;
|
||
break;
|
||
case 'queryHSJMNFirstSuccess': { // 核事件模拟第一次请求完成,开始绘制
|
||
// 显示图例
|
||
playback_EventBusCallback.publish('legendUiData', {
|
||
key: 'showLengend',
|
||
value: true,
|
||
});
|
||
|
||
let data = null;
|
||
if (hsjmnType === '浓度分布') {
|
||
data = weaData.data.result;
|
||
// 加工数据
|
||
const gridData = data.dataList.map((item: any) => {
|
||
return {
|
||
lon: item[0],
|
||
lat: item[1],
|
||
value: item[2] === 0 ? 0.08 : item[2],
|
||
};
|
||
});
|
||
|
||
const extent = calculatePointsExtent(gridData);
|
||
if (extent) {
|
||
// 定位
|
||
fitViewByExtent(extent);
|
||
}
|
||
|
||
// 添加热力图
|
||
addHeatmapLayer(gridData, data.max === 0 ? 1 : data.max);
|
||
// 动态改变图例最大值
|
||
playback_EventBusCallback.publish('symnLegendUpdateMax', {
|
||
maxValue: data.max,
|
||
minValue: data.min,
|
||
});
|
||
}
|
||
else {
|
||
data = weaData.data.result;
|
||
|
||
// 加工数据
|
||
const fitData = data.dataList.map((item: any) => {
|
||
return {
|
||
lon: item[0],
|
||
lat: item[1],
|
||
};
|
||
});
|
||
|
||
const extent = calculatePointsExtent(fitData);
|
||
if (extent) {
|
||
// 定位
|
||
fitViewByExtent(extent);
|
||
}
|
||
|
||
// 加工数据
|
||
const gridData = data.dataList.filter(item => item[2] >= 1.5).map((item: any) => {
|
||
return {
|
||
lon: item[0],
|
||
lat: item[1],
|
||
value: item[2],
|
||
};
|
||
});
|
||
|
||
// 添加热力图
|
||
addHeatmapLayer(gridData, data.max === 0 ? 1 : data.max);
|
||
// 渲染等值线
|
||
// addIsolineLayer(gridData); // 可自定义等值线间隔
|
||
// 动态改变图例最大值
|
||
playback_EventBusCallback.publish('symnLegendUpdateMax', {
|
||
maxValue: data.max,
|
||
minValue: data.min,
|
||
});
|
||
}
|
||
|
||
// 开始播放
|
||
setShouldAnimate(true);
|
||
}
|
||
break;
|
||
case 'drawHSJMN':{ // 绘制核事件模拟自定义热力图
|
||
// 加工数据
|
||
if (!weaData.data)
|
||
return;
|
||
|
||
let data = null;
|
||
if (hsjmnType === '浓度分布') {
|
||
data = weaData.data.hsjmnResult1Count;
|
||
if (data) {
|
||
const gridData = data.dataList.map((item: any) => {
|
||
return {
|
||
lon: item[0],
|
||
lat: item[1],
|
||
value: item[2] === 0 ? 0.08 : item[2],
|
||
};
|
||
});
|
||
|
||
// 添加热力图
|
||
addHeatmapLayer(gridData, data.max === 0 ? 1 : data.max);
|
||
// 动态改变图例最大值
|
||
playback_EventBusCallback.publish('symnLegendUpdateMax', {
|
||
maxValue: data.max,
|
||
minValue: data.min,
|
||
});
|
||
}
|
||
}
|
||
else {
|
||
data = weaData.data.hsjmnResult2Count;
|
||
if (data) {
|
||
const gridData = data.dataList.filter(item => item[2] >= 1.5).map((item: any) => {
|
||
return {
|
||
lon: item[0],
|
||
lat: item[1],
|
||
value: item[2],
|
||
};
|
||
});
|
||
|
||
// 添加热力图
|
||
addHeatmapLayer(gridData, data.max === 0 ? 1 : data.max);
|
||
// 渲染等值线
|
||
// addIsolineLayer(gridData); // 可自定义等值线间隔
|
||
// 动态改变图例最大值
|
||
playback_EventBusCallback.publish('symnLegendUpdateMax', {
|
||
maxValue: data.max,
|
||
minValue: data.min,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case 'queryHSJMNAllSuccess': // 核事件模拟所有接口请求完成
|
||
// 解除进度条禁用
|
||
playback_EventBusCallback.publish('timeSliderUiData', {
|
||
key: 'isDisabledSlider',
|
||
value: false,
|
||
});
|
||
break;
|
||
}
|
||
};
|
||
|
||
// 监听 Worker 错误(可选:增强错误处理)
|
||
playbackWorkerObj.onerror = function (error) {
|
||
console.error('Worker 执行错误:', error.message, '行号:', error.lineno);
|
||
// 可根据需求添加错误上报或重试逻辑
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 向 Web Worker 发送消息
|
||
* @param massage 发送给 Worker 的数据
|
||
* @param opt 初始化参数(首次发送时使用)
|
||
*/
|
||
|
||
export function sendPlaybackPostMassage(
|
||
massage: PostMessageData,
|
||
opt: InitRealWorketOpt,
|
||
): void {
|
||
// console.log('发送给 Worker 的消息:', massage, '初始化参数:', opt);
|
||
|
||
// 无对象时初始化线程对象
|
||
if (!playbackWorkerObj) {
|
||
initPlaybackWorkerMa(opt);
|
||
}
|
||
|
||
// 确保 playbackWorkerObj 已初始化(TS 类型守卫)
|
||
if (playbackWorkerObj) {
|
||
playbackWorkerObj.postMessage(massage);
|
||
}
|
||
else {
|
||
throw new Error('Web Worker 初始化失败,无法发送消息');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 销毁 Web Worker 实例(可选:添加资源释放方法)
|
||
*/
|
||
export function destroyRealWorket(): void {
|
||
if (playbackWorkerObj) {
|
||
playbackWorkerObj.terminate(); // 终止 Worker 线程
|
||
playbackWorkerObj = null; // 清空引用
|
||
}
|
||
}
|