1471 lines
45 KiB
Vue
1471 lines
45 KiB
Vue
<template>
|
||
<!-- 气象监测站弹窗 -->
|
||
<WeatherStationPopup
|
||
:visible="weatherStationVisible"
|
||
@close="weatherStationVisible = false"
|
||
/>
|
||
|
||
<!-- 车辆详情弹窗 -->
|
||
<VehicleDetailPopup
|
||
:visible="vehicleDetailVisible"
|
||
:detail="vehicleDetail"
|
||
:popup-style="detailPopupStyle"
|
||
@close="vehicleDetailVisible = false"
|
||
/>
|
||
|
||
<!-- 告警/预警提示框 -->
|
||
<!-- <AlertNotificationSystem
|
||
:alert-message="alertMessage"
|
||
:alert-type="alertType"
|
||
@show="handleAlertShow"
|
||
/>
|
||
-->
|
||
<!-- 车辆动画系统 -->
|
||
<VehicleAnimationSystem
|
||
ref="animationSystem"
|
||
:map="map"
|
||
:vehicle-source="vehicleSource"
|
||
:vehicles="vehicles"
|
||
:get-vehicle-style="styleManager?.value?.getVehicleStyle || defaultGetVehicleStyle"
|
||
/>
|
||
|
||
<!-- 车辆标签系统 -->
|
||
<VehicleLabelSystem
|
||
ref="labelSystem"
|
||
:map="map"
|
||
:vehicles="vehicles"
|
||
/>
|
||
|
||
<!-- 车辆样式管理器 -->
|
||
<VehicleStyleManager
|
||
ref="styleManager"
|
||
:vehicles="vehicles"
|
||
/>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted, watch, onActivated, onDeactivated } from 'vue';
|
||
import { Vector as VectorSource } from 'ol/source';
|
||
import { Vector as VectorLayer } from 'ol/layer';
|
||
import { Style, Icon, Stroke, Fill, Circle, Text } from 'ol/style';
|
||
import Feature from 'ol/Feature';
|
||
import Point from 'ol/geom/Point';
|
||
import { LineString } from 'ol/geom';
|
||
import { transform } from 'ol/proj';
|
||
import WebSocketService, { createWebSocket, resetWebSocketInstance } from '../../../utils/websocket.js';
|
||
|
||
// 导入子组件
|
||
import VehicleAnimationSystem from './VehicleAnimationSystem.vue';
|
||
import VehicleLabelSystem from './VehicleLabelSystem.vue';
|
||
import VehicleDetailPopup from './VehicleDetailPopup.vue';
|
||
import WeatherStationPopup from './WeatherStationPopup.vue';
|
||
// import AlertNotificationSystem from './AlertNotificationSystem.vue';
|
||
import VehicleStyleManager from './VehicleStyleManager.vue';
|
||
|
||
// 导入默认图标
|
||
import carIcon from '../../../assets/images/noPeopleCar.png';
|
||
import aircraftIcon from '../../../assets/images/Aircraft.png'; // 航空器使用Aircraft.png图标
|
||
// 不再使用不同状态的航空器图标
|
||
// import aircraftInIcon from '../../../assets/images/Aircraft1.png'; // 滑入航空器使用黄色图标
|
||
// import aircraftOutIcon from '../../../assets/images/Aircraft.png'; // 滑出航空器使用蓝色图标
|
||
|
||
// 预加载图标资源,确保图标立即可用
|
||
const carIconImg = new Image();
|
||
carIconImg.src = carIcon;
|
||
const aircraftIconImg = new Image();
|
||
aircraftIconImg.src = aircraftIcon;
|
||
// 不再预加载不同状态的航空器图标
|
||
// const aircraftInIconImg = new Image();
|
||
// aircraftInIconImg.src = aircraftInIcon;
|
||
// const aircraftOutIconImg = new Image();
|
||
// aircraftOutIconImg.src = aircraftOutIcon;
|
||
|
||
// 为SockJS提供polyfill
|
||
if (typeof window !== 'undefined' && !window.global) {
|
||
window.global = window;
|
||
}
|
||
|
||
// 定义props接收地图实例
|
||
const props = defineProps({
|
||
map: Object
|
||
});
|
||
|
||
// 子组件引用
|
||
const animationSystem = ref(null);
|
||
const labelSystem = ref(null);
|
||
const styleManager = ref(null);
|
||
|
||
// 气象监测站数据
|
||
const weatherStationVisible = ref(true);
|
||
|
||
// 车辆详情数据
|
||
const vehicleDetailVisible = ref(false);
|
||
const vehicleDetail = ref({
|
||
id: 'QN001',
|
||
type: '驱鸟车',
|
||
status: '任务中',
|
||
startTime: '11-19 11:30',
|
||
currentLocation: 'A区T3点',
|
||
startLocation: 'T1航站楼',
|
||
endLocation: 'T3航站楼',
|
||
totalDistance: '1.3km',
|
||
battery: '60%',
|
||
manager: '张三',
|
||
phone: '18661910988'
|
||
});
|
||
|
||
// 弹窗位置样式
|
||
const detailPopupStyle = ref({
|
||
left: '0px',
|
||
top: '0px'
|
||
});
|
||
|
||
// WebSocket连接状态
|
||
const wsConnected = ref(false);
|
||
let wsService = null;
|
||
let reconnectTimer = null;
|
||
|
||
// 车辆数据存储
|
||
const vehicles = ref({});
|
||
let vehicleLayer = null;
|
||
let vehicleSource = null;
|
||
|
||
// 车辆超速状态超时计时器存储
|
||
const speedViolationTimers = {};
|
||
|
||
// 飞机路线数据存储
|
||
const aircraftRoutes = ref({}); // 存储飞机路线数据,键为flightNo
|
||
let aircraftRouteLayer = null; // 飞机路线图层
|
||
let aircraftRouteSource = null; // 飞机路线数据源
|
||
|
||
// 动态收集所有车辆/航空器类别
|
||
const vehicleCategories = ref({
|
||
'AIRCRAFT_IN': { visible: true, showLabel: true, name: '滑入航空器' },
|
||
'AIRCRAFT_OUT': { visible: true, showLabel: true, name: '滑出航空器' },
|
||
'AIRCRAFT': { visible: true, showLabel: true, name: '其他航空器' },
|
||
'UNMANNED_VEHICLE': { visible: true, showLabel: true, name: '无人车' },
|
||
'AIRPORT_VEHICLE': { visible: true, showLabel: true, name: '特勤车' },
|
||
'SHUTTLE_VEHICLE': { visible: true, showLabel: true, name: '摆渡车' }
|
||
});
|
||
|
||
// 告警/预警状态
|
||
const alertMessage = ref('');
|
||
const alertType = ref('');
|
||
let alertTimer = null;
|
||
|
||
// 违规状态计时器
|
||
const violationStatusTimers = ref({});
|
||
|
||
// 处理告警显示
|
||
function handleAlertShow({ message, type, duration = 5000 }) {
|
||
if (alertTimer) {
|
||
clearTimeout(alertTimer);
|
||
}
|
||
|
||
alertMessage.value = message;
|
||
alertType.value = type;
|
||
|
||
alertTimer = setTimeout(() => {
|
||
alertMessage.value = '';
|
||
alertType.value = '';
|
||
}, duration);
|
||
}
|
||
|
||
// 显示告警/预警消息
|
||
function showAlert(message, type, duration = 5000) {
|
||
handleAlertShow({ message, type, duration });
|
||
}
|
||
|
||
// 创建车辆图层
|
||
function createVehicleLayer() {
|
||
if (!props.map) return;
|
||
|
||
vehicleSource = new VectorSource();
|
||
|
||
vehicleLayer = new VectorLayer({
|
||
source: vehicleSource,
|
||
zIndex: 20,
|
||
});
|
||
|
||
props.map.addLayer(vehicleLayer);
|
||
|
||
setupMapListeners();
|
||
|
||
props.map.on('click', handleMapClick);
|
||
}
|
||
|
||
// 创建飞机路线图层
|
||
function createAircraftRouteLayer() {
|
||
if (!props.map) return;
|
||
|
||
aircraftRouteSource = new VectorSource();
|
||
|
||
aircraftRouteLayer = new VectorLayer({
|
||
source: aircraftRouteSource,
|
||
zIndex: 15, // 确保在车辆图层下方
|
||
style: new Style({
|
||
stroke: new Stroke({
|
||
color: '#3388ff',
|
||
width: 3
|
||
})
|
||
})
|
||
});
|
||
|
||
props.map.addLayer(aircraftRouteLayer);
|
||
}
|
||
|
||
// 处理地图点击事件
|
||
function handleMapClick(event) {
|
||
const feature = props.map.forEachFeatureAtPixel(event.pixel, function(feature) {
|
||
return feature;
|
||
});
|
||
|
||
if (feature) {
|
||
const objectId = feature.getId();
|
||
if (objectId && vehicles.value[objectId]) {
|
||
const vehicle = vehicles.value[objectId];
|
||
|
||
vehicleDetail.value = {
|
||
id: objectId,
|
||
type: vehicle.type,
|
||
status: '任务中',
|
||
startTime: '11-19 11:30',
|
||
currentLocation: '当前位置',
|
||
startLocation: 'T1航站楼',
|
||
endLocation: 'T3航站楼',
|
||
totalDistance: '1.3km',
|
||
battery: '60%',
|
||
manager: '张三',
|
||
phone: '18661910988'
|
||
};
|
||
|
||
const pixel = event.pixel;
|
||
const viewportPosition = props.map.getViewport().getBoundingClientRect();
|
||
|
||
detailPopupStyle.value = {
|
||
left: (viewportPosition.left + pixel[0] + 10) + 'px',
|
||
top: (viewportPosition.top + pixel[1] + 10) + 'px'
|
||
};
|
||
|
||
vehicleDetailVisible.value = true;
|
||
} else {
|
||
vehicleDetailVisible.value = false;
|
||
}
|
||
} else {
|
||
vehicleDetailVisible.value = false;
|
||
}
|
||
}
|
||
|
||
// 监听地图变化,确保标签位置正确更新
|
||
function setupMapListeners() {
|
||
if (!props.map) return;
|
||
|
||
// 监听地图移动结束事件
|
||
props.map.on('moveend', () => {
|
||
if (labelSystem.value) {
|
||
labelSystem.value.updateAllLabels();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 更新车辆位置
|
||
function updateVehiclePosition(vehicleData) {
|
||
if (!vehicleSource || !props.map) return;
|
||
|
||
const { object_id, object_type, position, heading, speed } = vehicleData;
|
||
|
||
// 计算正确的旋转角度
|
||
// 在OpenLayers中,0度是向上,顺时针增加
|
||
// 而heading是0度为北,顺时针增加
|
||
// 由于地图旋转了72度,heading=72对应地图上方
|
||
const rotationRad = ((heading - 72) * Math.PI) / 180;
|
||
console.log(`车辆${object_id}的heading值: ${heading}, 旋转角度计算: (${heading} - 72) * π/180 = ${rotationRad} 弧度, ${(heading - 72)}度, 车辆类型: ${object_type}`);
|
||
|
||
let coordinates;
|
||
|
||
coordinates = transform(
|
||
[position.longitude, position.latitude],
|
||
'EPSG:4326',
|
||
props.map.getView().getProjection()
|
||
);
|
||
|
||
let feature = vehicleSource.getFeatureById(object_id);
|
||
|
||
// 根据object_type正确分类车辆
|
||
let vehicleType = object_type.toUpperCase();
|
||
|
||
// 判断车辆类型
|
||
const isAircraft = vehicleType === 'AIRCRAFT'; // 航空器使用aircraft.png
|
||
const isUnmannedVehicle = vehicleType === 'UNMANNED_VEHICLE'; // 无人车
|
||
const isSpecialVehicle = vehicleType === 'SPECIAL_VEHICLE'; // 特种车辆
|
||
|
||
// 确保heading是有效的数值
|
||
const validHeading = heading !== undefined ? Number(heading) : 0;
|
||
|
||
if (!feature) {
|
||
// 先创建车辆数据对象,确保在创建feature前就有类型信息
|
||
vehicles.value[object_id] = {
|
||
id: object_id,
|
||
type: object_type,
|
||
position: coordinates,
|
||
heading: validHeading,
|
||
speed: speed,
|
||
isAircraft: isAircraft,
|
||
isUnmannedVehicle: isUnmannedVehicle,
|
||
isSpecialVehicle: isSpecialVehicle,
|
||
lastHeading: validHeading // 初始化lastHeading
|
||
};
|
||
|
||
feature = new Feature({
|
||
geometry: new Point(coordinates),
|
||
name: `${object_type} ${object_id}`,
|
||
type: object_type,
|
||
speed: speed,
|
||
isAircraft: isAircraft,
|
||
isUnmannedVehicle: isUnmannedVehicle,
|
||
isSpecialVehicle: isSpecialVehicle
|
||
});
|
||
|
||
feature.setId(object_id);
|
||
|
||
// 直接根据车辆类型选择正确的图标,不使用默认图标
|
||
let iconStyle;
|
||
if (isAircraft) {
|
||
iconStyle = new Style({
|
||
image: new Icon({
|
||
src: aircraftIcon,
|
||
scale: 1.5,
|
||
anchor: [0.5, 0.5],
|
||
rotation: rotationRad, // 使用计算好的旋转角度
|
||
})
|
||
});
|
||
} else {
|
||
iconStyle = new Style({
|
||
image: new Icon({
|
||
src: carIcon,
|
||
scale: 1.5,
|
||
anchor: [0.5, 0.5],
|
||
rotation: rotationRad, // 使用计算好的旋转角度
|
||
})
|
||
});
|
||
}
|
||
|
||
feature.setStyle(iconStyle);
|
||
vehicleSource.addFeature(feature);
|
||
|
||
// 保存feature引用到车辆数据对象
|
||
vehicles.value[object_id].feature = feature;
|
||
|
||
// 初始化动画数据
|
||
if (animationSystem.value) {
|
||
animationSystem.value.initVehicleAnimation(object_id, coordinates, heading, speed);
|
||
}
|
||
|
||
// 创建标签
|
||
if (labelSystem.value) {
|
||
labelSystem.value.updateVehicleLabel(object_id, coordinates, speed);
|
||
}
|
||
} else {
|
||
// 更新动画目标
|
||
if (animationSystem.value) {
|
||
animationSystem.value.updateVehicleAnimationTarget(object_id, coordinates, heading, speed);
|
||
}
|
||
|
||
vehicles.value[object_id] = {
|
||
...vehicles.value[object_id],
|
||
position: coordinates,
|
||
heading: validHeading,
|
||
speed: speed
|
||
};
|
||
|
||
// 更新标签位置,确保标签始终跟随图标
|
||
// 但是如果车辆处于超速状态,不在这里更新标签,避免与超速状态标签冲突
|
||
if (labelSystem.value && !vehicles.value[object_id].speedViolation) {
|
||
labelSystem.value.updateVehicleLabel(object_id, coordinates, speed);
|
||
}
|
||
|
||
// 更新车辆图标样式,确保旋转角度正确应用
|
||
if (feature) {
|
||
let iconSrc = isAircraft ? aircraftIcon : carIcon;
|
||
|
||
// 移除角度变化阈值,确保每次角度变化都更新图标旋转
|
||
feature.setStyle(new Style({
|
||
image: new Icon({
|
||
src: iconSrc,
|
||
scale: 1.5,
|
||
anchor: [0.5, 0.5],
|
||
rotation: rotationRad, // 使用计算好的旋转角度
|
||
})
|
||
}));
|
||
|
||
// 更新lastHeading
|
||
vehicles.value[object_id].lastHeading = validHeading;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 连接WebSocket
|
||
function connectWebSocket() {
|
||
// 重置WebSocket实例以强制创建新实例
|
||
resetWebSocketInstance();
|
||
|
||
try {
|
||
// 使用WebSocketService创建连接
|
||
const wsUrl = import.meta.env.VITE_APP_WEBSOCKET_URL;
|
||
console.log(`正在连接WebSocket: ${wsUrl}`);
|
||
|
||
wsService = createWebSocket(wsUrl, {
|
||
reconnectInterval: 3000,
|
||
maxReconnectAttempts: 5
|
||
});
|
||
|
||
// 注册事件监听
|
||
wsService.on('open', (event) => {
|
||
console.log('WebSocket连接成功!');
|
||
wsConnected.value = true;
|
||
|
||
// 连接成功后自动订阅消息
|
||
setTimeout(() => {
|
||
sendSubscribe();
|
||
}, 1000); // 延迟1秒发送订阅,确保连接稳定
|
||
});
|
||
|
||
wsService.on('message', (data) => {
|
||
handleWsMessage(data);
|
||
});
|
||
|
||
wsService.on('error', (event) => {
|
||
console.error('WebSocket错误:', event);
|
||
wsConnected.value = false;
|
||
});
|
||
|
||
wsService.on('close', (event) => {
|
||
console.log(`WebSocket连接关闭: ${event.code} - ${event.reason}`);
|
||
wsConnected.value = false;
|
||
});
|
||
|
||
wsService.on('reconnect_failed', () => {
|
||
console.error('WebSocket重连失败,已达到最大重试次数');
|
||
wsConnected.value = false;
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('创建WebSocket连接失败:', error);
|
||
}
|
||
}
|
||
|
||
// 处理WebSocket消息
|
||
function handleWsMessage(message) {
|
||
try {
|
||
const data = JSON.parse(message);
|
||
console.log('收到消息:', data);
|
||
|
||
// 根据消息类型处理
|
||
switch (data.type) {
|
||
case 'connection':
|
||
console.log(`连接确认: ${data.message}`);
|
||
break;
|
||
|
||
case 'position_update':
|
||
// 处理车辆位置更新
|
||
console.log(`位置更新: ${data.payload?.object_id} (${data.payload?.object_type})`);
|
||
handlePositionUpdate(data.payload);
|
||
break;
|
||
|
||
case 'path_conflict_alert':
|
||
// 处理冲突告警和预警
|
||
console.log(`冲突告警/预警: ${data.payload?.object1?.objectName || data.payload?.messageType}`);
|
||
handlePathConflictAlert(data.payload);
|
||
break;
|
||
|
||
case 'rule_violation':
|
||
// 处理规则违规(超速和越界)
|
||
console.log(`规则违规: ${data.payload?.ruleName || data.payload?.violationType}`);
|
||
handleRuleViolation(data.payload);
|
||
break;
|
||
|
||
case 'pong':
|
||
console.log('收到心跳响应');
|
||
break;
|
||
|
||
case 'vehicle_command':
|
||
console.log('收到车辆控制指令:', data.payload);
|
||
break;
|
||
|
||
case 'aircraftRouteUpdate':
|
||
console.log('收到飞机路由更新:', data.data);
|
||
handleAircraftRouteUpdate(data.data);
|
||
break;
|
||
|
||
default:
|
||
// 其他类型的消息可以根据需要处理
|
||
console.log(`未知消息类型: ${data.type}`, data);
|
||
break;
|
||
}
|
||
} catch (e) {
|
||
console.error('处理WebSocket消息出错:', e, message);
|
||
}
|
||
}
|
||
|
||
// 处理位置更新消息
|
||
function handlePositionUpdate(payload) {
|
||
if (!payload || !payload.object_id) {
|
||
console.error('位置更新消息格式错误:', payload);
|
||
return;
|
||
}
|
||
|
||
// 清除车辆的告警状态(如果需要)
|
||
clearVehicleAlertStatus(payload);
|
||
|
||
// 更新车辆位置
|
||
updateVehiclePosition(payload);
|
||
}
|
||
|
||
// 处理飞机路由更新消息
|
||
function handleAircraftRouteUpdate(payload) {
|
||
if (!payload || !payload.flightNo || !payload.routeGeometry) {
|
||
console.error('飞机路由更新消息格式错误:', payload);
|
||
return;
|
||
}
|
||
|
||
const { flightNo, routeType, routeStatus, routeGeometry } = payload;
|
||
|
||
// 解析LINESTRING格式的routeGeometry
|
||
const coordinates = parseLineStringGeometry(routeGeometry);
|
||
if (!coordinates || coordinates.length === 0) {
|
||
console.error('解析routeGeometry失败:', routeGeometry);
|
||
return;
|
||
}
|
||
|
||
// 更新或创建路线
|
||
updateAircraftRoute(flightNo, coordinates, routeType, routeStatus);
|
||
}
|
||
|
||
// 解析LINESTRING格式的字符串,提取坐标点
|
||
function parseLineStringGeometry(lineString) {
|
||
// 移除LINESTRING前缀和括号
|
||
const coordsString = lineString.replace(/LINESTRING\s*\(|\)/gi, '');
|
||
|
||
// 分割坐标点
|
||
const coordPairs = coordsString.split(',').map(pair => pair.trim());
|
||
|
||
// 解析每个坐标点
|
||
return coordPairs.map(pair => {
|
||
const [lon, lat] = pair.split(' ').map(Number);
|
||
return [lon, lat];
|
||
});
|
||
}
|
||
|
||
// 更新或创建飞机路线
|
||
function updateAircraftRoute(flightNo, coordinates, routeType, routeStatus) {
|
||
if (!aircraftRouteSource || !props.map) return;
|
||
|
||
// 转换坐标系
|
||
const projectedCoords = coordinates.map(coord =>
|
||
transform(coord, 'EPSG:4326', props.map.getView().getProjection())
|
||
);
|
||
|
||
// 获取起点和终点坐标
|
||
const startPoint = projectedCoords[0];
|
||
const endPoint = projectedCoords[projectedCoords.length - 1];
|
||
|
||
// 检查是否已存在该航班的路线
|
||
let feature = aircraftRouteSource.getFeatureById(flightNo);
|
||
let startFeature = aircraftRouteSource.getFeatureById(`${flightNo}-start`);
|
||
let endFeature = aircraftRouteSource.getFeatureById(`${flightNo}-end`);
|
||
|
||
if (!feature) {
|
||
// 创建新的LineString几何体
|
||
const lineGeometry = new LineString(projectedCoords);
|
||
|
||
// 创建新的Feature
|
||
feature = new Feature({
|
||
geometry: lineGeometry,
|
||
name: `Flight ${flightNo} Route`,
|
||
flightNo: flightNo,
|
||
routeType: routeType,
|
||
routeStatus: routeStatus
|
||
});
|
||
|
||
// 设置ID和样式
|
||
feature.setId(flightNo);
|
||
feature.setStyle(getRouteStyle(routeType, routeStatus));
|
||
|
||
// 添加到数据源
|
||
aircraftRouteSource.addFeature(feature);
|
||
|
||
// 创建起点标记
|
||
createRoutePointMarker(flightNo, 'start', startPoint, routeType);
|
||
|
||
// 创建终点标记
|
||
createRoutePointMarker(flightNo, 'end', endPoint, routeType);
|
||
|
||
// 存储路线信息
|
||
aircraftRoutes.value[flightNo] = {
|
||
flightNo: flightNo,
|
||
routeType: routeType,
|
||
routeStatus: routeStatus,
|
||
coordinates: projectedCoords,
|
||
feature: feature
|
||
};
|
||
} else {
|
||
// 更新现有Feature的几何体和属性
|
||
feature.getGeometry().setCoordinates(projectedCoords);
|
||
feature.set('routeType', routeType);
|
||
feature.set('routeStatus', routeStatus);
|
||
|
||
// 更新样式
|
||
feature.setStyle(getRouteStyle(routeType, routeStatus));
|
||
|
||
// 更新起点标记位置
|
||
updateRoutePointMarker(flightNo, 'start', startPoint);
|
||
|
||
// 更新终点标记位置
|
||
updateRoutePointMarker(flightNo, 'end', endPoint);
|
||
|
||
// 更新存储的路线信息
|
||
aircraftRoutes.value[flightNo] = {
|
||
...aircraftRoutes.value[flightNo],
|
||
routeType: routeType,
|
||
routeStatus: routeStatus,
|
||
coordinates: projectedCoords
|
||
};
|
||
}
|
||
|
||
// 如果路线状态为完成,可以考虑在一段时间后移除路线
|
||
if (routeStatus === 'COMPLETE') {
|
||
setTimeout(() => {
|
||
removeAircraftRoute(flightNo);
|
||
}, 60000); // 1分钟后移除
|
||
}
|
||
}
|
||
|
||
// 根据路线类型和状态获取样式
|
||
function getRouteStyle(routeType, routeStatus) {
|
||
let color = '#3388ff'; // 默认蓝色
|
||
let width = 3;
|
||
let lineDash = undefined;
|
||
|
||
// 根据路线类型设置颜色
|
||
if (routeType === 'IN') {
|
||
color = '#292C38'; // 深蓝色表示滑入
|
||
} else if (routeType === 'OUT') {
|
||
color = '#3388ff'; // 蓝色表示滑出
|
||
}
|
||
|
||
// // 根据路线状态设置线型
|
||
// if (routeStatus === 'PLANNED') {
|
||
// lineDash = [4, 8]; // 计划中的路线使用虚线
|
||
// width = 2;
|
||
// } else if (routeStatus === 'ACTIVE') {
|
||
// width = 4; // 活动中的路线使用粗线
|
||
// } else if (routeStatus === 'COMPLETE') {
|
||
// width = 2; // 完成的路线使用细线
|
||
// color = color + '80'; // 添加透明度
|
||
// }
|
||
|
||
return new Style({
|
||
stroke: new Stroke({
|
||
color: color,
|
||
width: width,
|
||
lineDash: lineDash
|
||
})
|
||
});
|
||
}
|
||
|
||
// 获取点样式(起点或终点)
|
||
function getPointStyle(pointType, routeType, flightNo) {
|
||
// 根据路线类型选择颜色
|
||
let color = '#3388ff'; // 默认蓝色
|
||
if (routeType === 'IN') {
|
||
color = '#292C38'; // 深蓝色表示滑入
|
||
} else if (routeType === 'OUT') {
|
||
color = '#3388ff'; // 蓝色表示滑出
|
||
}
|
||
|
||
// 创建圆点样式
|
||
const circleStyle = new Style({
|
||
image: new Circle({
|
||
radius: 6,
|
||
fill: new Fill({
|
||
color: color
|
||
}),
|
||
stroke: new Stroke({
|
||
color: '#ffffff',
|
||
width: 2
|
||
})
|
||
}),
|
||
// 添加文本标签
|
||
text: new Text({
|
||
text: pointType === 'start' ? '起点' : '终点',
|
||
font: 'bold 12px Arial',
|
||
fill: new Fill({
|
||
color: '#ffffff'
|
||
}),
|
||
stroke: new Stroke({
|
||
color: '#000000',
|
||
width: 3
|
||
}),
|
||
offsetX: 0,
|
||
offsetY: pointType === 'start' ? -15 : -15, // 文字位置偏移
|
||
textAlign: 'center',
|
||
textBaseline: 'bottom'
|
||
}),
|
||
zIndex: 10 // 确保点标记显示在线的上方
|
||
});
|
||
|
||
return circleStyle;
|
||
}
|
||
|
||
// 创建路线点标记(起点或终点)
|
||
function createRoutePointMarker(flightNo, pointType, coordinates, routeType) {
|
||
// 创建点几何体
|
||
const pointGeometry = new Point(coordinates);
|
||
|
||
// 创建Feature
|
||
const pointFeature = new Feature({
|
||
geometry: pointGeometry,
|
||
name: `${flightNo} ${pointType === 'start' ? '起点' : '终点'}`,
|
||
flightNo: flightNo,
|
||
pointType: pointType
|
||
});
|
||
|
||
// 设置ID
|
||
pointFeature.setId(`${flightNo}-${pointType}`);
|
||
|
||
// 设置样式
|
||
pointFeature.setStyle(getPointStyle(pointType, routeType, flightNo));
|
||
|
||
// 添加到数据源
|
||
aircraftRouteSource.addFeature(pointFeature);
|
||
|
||
return pointFeature;
|
||
}
|
||
|
||
// 更新路线点标记位置
|
||
function updateRoutePointMarker(flightNo, pointType, coordinates) {
|
||
const pointFeature = aircraftRouteSource.getFeatureById(`${flightNo}-${pointType}`);
|
||
|
||
if (pointFeature) {
|
||
// 更新几何体
|
||
pointFeature.getGeometry().setCoordinates(coordinates);
|
||
} else {
|
||
// 如果不存在,创建新的点标记
|
||
const routeType = aircraftRoutes.value[flightNo]?.routeType || 'UNKNOWN';
|
||
createRoutePointMarker(flightNo, pointType, coordinates, routeType);
|
||
}
|
||
}
|
||
|
||
// 移除飞机路线
|
||
function removeAircraftRoute(flightNo) {
|
||
if (!aircraftRouteSource) return;
|
||
|
||
// 移除路线Feature
|
||
const feature = aircraftRouteSource.getFeatureById(flightNo);
|
||
if (feature) {
|
||
aircraftRouteSource.removeFeature(feature);
|
||
}
|
||
|
||
// 移除起点Feature
|
||
const startFeature = aircraftRouteSource.getFeatureById(`${flightNo}-start`);
|
||
if (startFeature) {
|
||
aircraftRouteSource.removeFeature(startFeature);
|
||
}
|
||
|
||
// 移除终点Feature
|
||
const endFeature = aircraftRouteSource.getFeatureById(`${flightNo}-end`);
|
||
if (endFeature) {
|
||
aircraftRouteSource.removeFeature(endFeature);
|
||
}
|
||
|
||
// 从存储中移除
|
||
if (aircraftRoutes.value[flightNo]) {
|
||
delete aircraftRoutes.value[flightNo];
|
||
}
|
||
}
|
||
|
||
// 处理冲突告警和预警
|
||
function handlePathConflictAlert(payload) {
|
||
if (!payload) {
|
||
console.error('冲突告警消息格式错误:', payload);
|
||
return;
|
||
}
|
||
|
||
console.log('收到冲突告警/预警:', payload);
|
||
|
||
const object1 = payload.object1 || {};
|
||
const object2 = payload.object2 || {};
|
||
const vehicleId = object1.objectName || '未知车辆';
|
||
const otherVehicleId = object2.objectName || '未知车辆';
|
||
const distance = payload.object2Distance || 0;
|
||
const message = payload.message || `与${otherVehicleId}可能发生冲突`;
|
||
|
||
// 根据messageType区分冲突告警和冲突预警
|
||
if (payload.messageType === 'PATH_CONFLICT_ALERT' || payload.alertType === 'CONFLICT_WARNING') {
|
||
console.log('处理冲突预警:', vehicleId, otherVehicleId);
|
||
const warningMessage = `预警:${message}`;
|
||
showAlert(warningMessage, 'warning', 8000);
|
||
|
||
// 在地图上标记该车辆的预警状态
|
||
if (vehicles.value[vehicleId]) {
|
||
vehicles.value[vehicleId].warning = true;
|
||
vehicles.value[vehicleId].alarm = false;
|
||
vehicles.value[vehicleId].critical = false;
|
||
vehicles.value[vehicleId].info = false;
|
||
|
||
// 如果车辆已有位置信息,更新标签显示
|
||
if (vehicles.value[vehicleId].position) {
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed, {
|
||
description: message,
|
||
isWarning: true
|
||
});
|
||
}
|
||
}
|
||
} else if (payload.alertType === 'CONFLICT_ALERT') {
|
||
console.log('处理冲突告警:', vehicleId, otherVehicleId);
|
||
const alertMessage = `⚠️ 告警:${message}`;
|
||
showAlert(alertMessage, 'alarm', 10000);
|
||
|
||
// 在地图上标记该车辆的告警状态
|
||
if (vehicles.value[vehicleId]) {
|
||
vehicles.value[vehicleId].alarm = true;
|
||
vehicles.value[vehicleId].warning = false;
|
||
vehicles.value[vehicleId].critical = false;
|
||
vehicles.value[vehicleId].info = false;
|
||
|
||
// 更新车辆图标为告警图标
|
||
if (vehicles.value[vehicleId].feature && styleManager.value) {
|
||
vehicles.value[vehicleId].feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicles.value[vehicleId].speed, vehicles.value[vehicleId].heading));
|
||
}
|
||
|
||
// 更新标签显示
|
||
if (vehicles.value[vehicleId].position && labelSystem.value) {
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed, {
|
||
description: message,
|
||
isAlarm: true
|
||
});
|
||
}
|
||
}
|
||
} else {
|
||
console.log(`未知的冲突消息类型: ${payload.messageType || payload.alertType}`);
|
||
}
|
||
}
|
||
|
||
// 处理规则违规(超速和越界)
|
||
function handleRuleViolation(payload) {
|
||
if (!payload) {
|
||
console.error('规则违规消息格式错误:', payload);
|
||
return;
|
||
}
|
||
|
||
console.log('收到规则违规:', payload);
|
||
|
||
const vehicleId = payload.object_id || payload.vehicleId || payload.vehicleLicense || '未知车辆';
|
||
const description = payload.description || '';
|
||
const limitValue = payload.limitValue;
|
||
const actualValue = payload.actualValue;
|
||
const ruleName = payload.ruleName || '交通规则';
|
||
const violationType = payload.violationType || '';
|
||
|
||
// 根据violationType区分超速和越界
|
||
switch (violationType.toUpperCase()) {
|
||
|
||
case 'SPEED':
|
||
// 超速违规
|
||
handleSpeedViolation(vehicleId, payload);
|
||
break;
|
||
case 'ACCESS':
|
||
// 越界处理
|
||
handleUnauthorizedEntry(vehicleId, payload);
|
||
break;
|
||
|
||
default:
|
||
console.log(`未知的规则违规类型: ${violationType}`);
|
||
// 默认按越界处理
|
||
handleUnauthorizedEntry(vehicleId, payload);
|
||
}
|
||
}
|
||
|
||
// 处理超速违规
|
||
function handleSpeedViolation(vehicleId, payload) {
|
||
const actualValue = payload.actualValue;
|
||
const limitValue = payload.limitValue;
|
||
const description = payload.description || '超速违规';
|
||
const ruleName = payload.ruleName || '速度限制';
|
||
|
||
console.log(`检测到超速违规: ${vehicleId}, 实际速度: ${actualValue}, 限速: ${limitValue}`);
|
||
|
||
// 显示超速告警提示
|
||
showAlert(`⚠️ 超速告警:${vehicleId} ${description}`, 'warning', 8000);
|
||
|
||
// 在地图上标记该车辆的超速状态
|
||
if (vehicles.value[vehicleId]) {
|
||
// 避免重复设置超速状态导致闪烁
|
||
const alreadyInSpeedViolation = vehicles.value[vehicleId].speedViolation;
|
||
|
||
// 强制设置超速状态,确保图标正确显示
|
||
vehicles.value[vehicleId].info = false;
|
||
vehicles.value[vehicleId].alarm = false;
|
||
vehicles.value[vehicleId].warning = false; // 清除warning状态,使用speedViolation状态
|
||
vehicles.value[vehicleId].critical = false;
|
||
vehicles.value[vehicleId].speedViolation = true; // 标记为超速状态
|
||
vehicles.value[vehicleId].limitValue = limitValue;
|
||
vehicles.value[vehicleId].actualValue = actualValue;
|
||
vehicles.value[vehicleId].description = description;
|
||
vehicles.value[vehicleId].ruleName = ruleName;
|
||
vehicles.value[vehicleId].lastSpeedViolationTime = Date.now(); // 记录最后一次超速时间
|
||
|
||
// 设置状态锁定,确保一段时间内不会改变状态
|
||
if (!vehicles.value[vehicleId].statusLock) {
|
||
vehicles.value[vehicleId].statusLock = {
|
||
active: false,
|
||
type: null,
|
||
until: 0
|
||
};
|
||
}
|
||
|
||
// 锁定超速状态20秒,确保图标不会来回切换
|
||
vehicles.value[vehicleId].statusLock = {
|
||
active: true,
|
||
type: 'speedViolation',
|
||
until: Date.now() + 20000 // 20秒锁定
|
||
};
|
||
|
||
// 清除任何可能存在的缓存图标,确保使用警告图标
|
||
vehicles.value[vehicleId].cachedIconSrc = null;
|
||
|
||
// 更新车辆图标为超速警告图标(只在首次设置或状态改变时更新)
|
||
if (!alreadyInSpeedViolation) {
|
||
if (vehicles.value[vehicleId].feature && styleManager.value) {
|
||
vehicles.value[vehicleId].feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicles.value[vehicleId].speed, vehicles.value[vehicleId].heading));
|
||
vehicles.value[vehicleId].lastIconUpdateTime = Date.now();
|
||
}
|
||
}
|
||
|
||
// 更新标签显示为超速状态(只显示超速相关信息)
|
||
if (vehicles.value[vehicleId].position && labelSystem.value) {
|
||
// 先移除可能存在的旧标签,避免重复显示
|
||
labelSystem.value.removeVehicleLabel(vehicleId);
|
||
|
||
// 然后创建新的超速状态标签
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, actualValue || vehicles.value[vehicleId].speed, {
|
||
description: description || `超速违规`,
|
||
limitValue: limitValue,
|
||
actualValue: actualValue,
|
||
ruleName: ruleName,
|
||
isSpeedViolation: true // 标记为超速状态,避免显示默认信息
|
||
});
|
||
}
|
||
|
||
// 设置或重置超速状态自动清除计时器
|
||
if (speedViolationTimers[vehicleId]) {
|
||
clearTimeout(speedViolationTimers[vehicleId]);
|
||
}
|
||
|
||
// 设置新的超时计时器,如果在一定时间内(如20秒)没有再次接收到超速消息,则自动清除超速状态
|
||
speedViolationTimers[vehicleId] = setTimeout(() => {
|
||
// 确保车辆仍然存在
|
||
if (vehicles.value[vehicleId]) {
|
||
|
||
|
||
|
||
|
||
|
||
|
||
console.log(`超速状态超时: ${vehicleId}, 自动清除超速状态`);
|
||
|
||
// 检查是否可以清除状态锁定
|
||
if (!vehicles.value[vehicleId].statusLock ||
|
||
!vehicles.value[vehicleId].statusLock.active ||
|
||
Date.now() > vehicles.value[vehicleId].statusLock.until) {
|
||
|
||
// 清除超速状态
|
||
clearSpeedViolationStatus(vehicleId);
|
||
|
||
// 清除计时器引用
|
||
delete speedViolationTimers[vehicleId];
|
||
} else {
|
||
// 状态仍然锁定,延迟清除
|
||
console.log(`车辆${vehicleId}状态仍然锁定,延迟清除超速状态`);
|
||
|
||
// 重新设置计时器,等待锁定结束
|
||
speedViolationTimers[vehicleId] = setTimeout(() => {
|
||
clearSpeedViolationStatus(vehicleId);
|
||
delete speedViolationTimers[vehicleId];
|
||
}, vehicles.value[vehicleId].statusLock.until - Date.now());
|
||
}
|
||
}
|
||
}, 20000); // 20秒超时,确保状态稳定
|
||
}
|
||
}
|
||
|
||
// 处理越界告警
|
||
function handleUnauthorizedEntry(vehicleId, payload) {
|
||
const description = payload.description || '越界告警';
|
||
const alertLevel = payload.alertLevel || 'CRITICAL';
|
||
const ruleName = payload.ruleName || '区域控制';
|
||
|
||
console.log(`检测到越界告警: ${vehicleId}, ${description}, 规则: ${ruleName}`);
|
||
|
||
// 显示越界告警提示
|
||
showAlert(`⚠️ 越界告警:${vehicleId} ${description}`, 'critical', 10000);
|
||
|
||
// 在地图上标记该车辆的越界状态
|
||
if (vehicles.value[vehicleId]) {
|
||
vehicles.value[vehicleId].critical = true;
|
||
vehicles.value[vehicleId].alarm = false;
|
||
vehicles.value[vehicleId].warning = false;
|
||
vehicles.value[vehicleId].info = false;
|
||
vehicles.value[vehicleId].speedViolation = false;
|
||
vehicles.value[vehicleId].description = description;
|
||
vehicles.value[vehicleId].ruleName = ruleName;
|
||
|
||
// 更新车辆图标为越界警告图标
|
||
if (vehicles.value[vehicleId].feature && styleManager.value) {
|
||
vehicles.value[vehicleId].feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicles.value[vehicleId].speed, vehicles.value[vehicleId].heading));
|
||
}
|
||
|
||
// 更新标签显示
|
||
if (vehicles.value[vehicleId].position && labelSystem.value) {
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed, {
|
||
description: description,
|
||
ruleName: ruleName,
|
||
isUnauthorizedEntry: true
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清除车辆超速状态的专门函数
|
||
function clearSpeedViolationStatus(vehicleId) {
|
||
const vehicle = vehicles.value[vehicleId];
|
||
if (!vehicle) return;
|
||
|
||
console.log(`开始清除车辆${vehicleId}的超速状态`);
|
||
|
||
// 检查是否可以清除状态锁定
|
||
if (vehicle.statusLock && vehicle.statusLock.active && Date.now() < vehicle.statusLock.until) {
|
||
console.log(`车辆${vehicleId}状态锁定中,暂不清除超速状态`);
|
||
return;
|
||
}
|
||
|
||
// 清除所有告警状态
|
||
vehicle.info = false;
|
||
vehicle.alarm = false;
|
||
vehicle.warning = false;
|
||
vehicle.critical = false;
|
||
vehicle.speedViolation = false;
|
||
vehicle.limitValue = undefined;
|
||
vehicle.actualValue = undefined;
|
||
vehicle.description = undefined;
|
||
vehicle.ruleName = undefined;
|
||
|
||
// 清除状态锁定
|
||
if (vehicle.statusLock) {
|
||
vehicle.statusLock.active = false;
|
||
vehicle.statusLock.type = null;
|
||
vehicle.statusLock.until = 0;
|
||
}
|
||
|
||
// 清除缓存的图标,确保使用默认图标
|
||
vehicle.cachedIconSrc = null;
|
||
vehicle.lastIconUpdateTime = Date.now();
|
||
|
||
// 更新车辆图标为默认图标
|
||
if (vehicle.feature && styleManager.value) {
|
||
vehicle.feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicle.speed, vehicle.heading));
|
||
}
|
||
|
||
// 更新标签显示为正常状态(只显示车辆ID,不显示0.00km/h)
|
||
if (vehicle.position && labelSystem.value) {
|
||
// 先移除可能存在的旧标签,避免重复显示
|
||
labelSystem.value.removeVehicleLabel(vehicleId);
|
||
|
||
// 创建新的标准标签
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicle.position, vehicle.speed > 0.1 ? vehicle.speed : 0);
|
||
}
|
||
|
||
console.log(`已成功清除车辆${vehicleId}的超速状态`);
|
||
}
|
||
|
||
// 清除车辆告警状态(如果需要)
|
||
function clearVehicleAlertStatus(vehicleData) {
|
||
const { object_id, speed } = vehicleData;
|
||
|
||
// 检查该车辆是否已存在且有告警状态
|
||
const existingVehicle = vehicles.value[object_id];
|
||
if (!existingVehicle) return;
|
||
|
||
// 检查是否有状态锁定
|
||
if (existingVehicle.statusLock &&
|
||
existingVehicle.statusLock.active &&
|
||
Date.now() < existingVehicle.statusLock.until) {
|
||
// 状态锁定中,不清除状态
|
||
return;
|
||
}
|
||
|
||
// 检查是否有超速状态 - 更严格的清除条件,避免图标来回切换
|
||
if (existingVehicle.speedViolation && existingVehicle.limitValue) {
|
||
// 如果当前速度低于限速值,清除超速状态
|
||
if (speed < existingVehicle.limitValue) {
|
||
// 添加更大的缓冲区,避免在临界值附近频繁切换状态
|
||
// 只有当速度低于限速值的70%时才清除超速状态,确保状态稳定
|
||
const speedBuffer = existingVehicle.limitValue * 0.7;
|
||
|
||
if (speed < speedBuffer) {
|
||
console.log(`检测到车辆${object_id}速度降低到${speed.toFixed(1)}km/h,低于限速${existingVehicle.limitValue}km/h的70%,考虑清除超速状态`);
|
||
|
||
// 检查是否应该清除超速状态
|
||
// 如果最后一次超速时间在15秒内,不清除状态以避免闪烁
|
||
const now = Date.now();
|
||
if (!existingVehicle.lastSpeedViolationTime || now - existingVehicle.lastSpeedViolationTime > 15000) {
|
||
// 检查状态锁定
|
||
if (!existingVehicle.statusLock ||
|
||
!existingVehicle.statusLock.active ||
|
||
now > existingVehicle.statusLock.until) {
|
||
// 清除超速状态
|
||
clearSpeedViolationStatus(object_id);
|
||
console.log(`已清除${object_id}的超速状态,恢复为普通状态`);
|
||
} else {
|
||
console.log(`车辆${object_id}状态锁定中,暂不清除超速状态`);
|
||
}
|
||
} else {
|
||
console.log(`车辆${object_id}刚刚处于超速状态,暂不清除状态以避免闪烁`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
// 发送心跳
|
||
function sendPing() {
|
||
if (wsService) {
|
||
wsService.send('ping');
|
||
console.log('发送心跳: ping');
|
||
}
|
||
}
|
||
|
||
// 订阅消息
|
||
function sendSubscribe() {
|
||
if (wsService) {
|
||
const message = JSON.stringify({
|
||
type: 'subscribe',
|
||
topics: ['position_update', 'collision_warning', 'rule_violation'],
|
||
timestamp: Date.now()
|
||
});
|
||
wsService.send(message);
|
||
console.log('发送订阅请求');
|
||
}
|
||
}
|
||
|
||
// 清理资源
|
||
function cleanup() {
|
||
// 断开WebSocket连接
|
||
if (wsService) {
|
||
wsService.close();
|
||
wsService = null;
|
||
}
|
||
|
||
if (reconnectTimer) {
|
||
clearTimeout(reconnectTimer);
|
||
reconnectTimer = null;
|
||
}
|
||
|
||
// 清理所有超速状态计时器
|
||
Object.keys(speedViolationTimers).forEach(vehicleId => {
|
||
clearTimeout(speedViolationTimers[vehicleId]);
|
||
delete speedViolationTimers[vehicleId];
|
||
});
|
||
|
||
// 移除图层
|
||
if (vehicleLayer && props.map) {
|
||
props.map.removeLayer(vehicleLayer);
|
||
vehicleLayer = null;
|
||
}
|
||
|
||
// 移除飞机路线图层
|
||
if (aircraftRouteLayer && props.map) {
|
||
props.map.removeLayer(aircraftRouteLayer);
|
||
aircraftRouteLayer = null;
|
||
}
|
||
|
||
// 清空飞机路线数据
|
||
aircraftRoutes.value = {};
|
||
|
||
// 移除标签
|
||
if (props.map && labelSystem.value) {
|
||
Object.values(vehicles.value).forEach(vehicle => {
|
||
if (vehicle.overlay) {
|
||
console.log(`移除标签: ${vehicle.id}`);
|
||
labelSystem.value.removeVehicleLabel(vehicle.id);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 清空车辆数据
|
||
vehicles.value = {};
|
||
}
|
||
|
||
// 组件挂载时初始化
|
||
onMounted(() => {
|
||
// 创建图层并连接WebSocket
|
||
if (props.map) {
|
||
createVehicleLayer();
|
||
createAircraftRouteLayer(); // 创建飞机路线图层
|
||
}
|
||
|
||
// 连接WebSocket
|
||
connectWebSocket();
|
||
|
||
// 设置定时发送心跳
|
||
const pingInterval = setInterval(() => {
|
||
if (wsService && wsConnected.value) {
|
||
sendPing();
|
||
}
|
||
}, 30000); // 每30秒发送一次心跳
|
||
|
||
// 组件卸载时清理资源
|
||
onUnmounted(() => {
|
||
clearInterval(pingInterval);
|
||
if (reconnectTimer) {
|
||
clearTimeout(reconnectTimer);
|
||
reconnectTimer = null;
|
||
}
|
||
cleanup();
|
||
});
|
||
});
|
||
|
||
// 组件被激活时(切换回该路由时)
|
||
onActivated(() => {
|
||
console.log('VehicleMovementControl组件被激活');
|
||
// 如果WebSocket未连接,重新连接
|
||
if (!wsConnected.value && !wsService) {
|
||
console.log('组件激活,WebSocket未连接,正在重新连接...');
|
||
// 延迟一点时间再连接,确保DOM已完全渲染
|
||
reconnectTimer = setTimeout(() => {
|
||
connectWebSocket();
|
||
}, 500);
|
||
} else {
|
||
console.log('组件激活,WebSocket已连接');
|
||
}
|
||
});
|
||
|
||
// 组件被停用时(切换到其他路由时)
|
||
onDeactivated(() => {
|
||
console.log('VehicleMovementControl组件被停用');
|
||
// 可以选择在这里关闭WebSocket,也可以保持连接
|
||
// 如果希望在组件隐藏时断开连接,可以取消下面的注释
|
||
/*
|
||
if (wsService) {
|
||
console.log('组件停用,关闭WebSocket连接');
|
||
wsService.close();
|
||
wsService = null;
|
||
wsConnected.value = false;
|
||
}
|
||
*/
|
||
});
|
||
|
||
// 监听地图实例变化
|
||
watch(() => props.map, (newMap) => {
|
||
if (newMap) {
|
||
createVehicleLayer();
|
||
createAircraftRouteLayer(); // 创建飞机路线图层
|
||
}
|
||
});
|
||
|
||
// 向外暴露方法
|
||
defineExpose({
|
||
updateVehiclePosition,
|
||
wsConnected,
|
||
sendPing,
|
||
sendSubscribe,
|
||
vehicleCategories,
|
||
weatherStationVisible,
|
||
vehicleDetailVisible,
|
||
vehicleDetail,
|
||
toggleWeatherStationVisibility() {
|
||
weatherStationVisible.value = !weatherStationVisible.value;
|
||
},
|
||
toggleVehicleDetailVisibility() {
|
||
vehicleDetailVisible.value = !vehicleDetailVisible.value;
|
||
},
|
||
updateVehicleDetail(data) {
|
||
if (data) {
|
||
vehicleDetail.value = {...vehicleDetail.value, ...data};
|
||
vehicleDetailVisible.value = true;
|
||
}
|
||
},
|
||
showVehicleDetail(vehicleId, pixelPosition) {
|
||
if (vehicles.value[vehicleId]) {
|
||
// 更新车辆详情
|
||
vehicleDetail.value = {
|
||
id: vehicleId,
|
||
type: vehicles.value[vehicleId].type,
|
||
status: '任务中',
|
||
startTime: '11-19 11:30',
|
||
currentLocation: '当前位置',
|
||
startLocation: 'T1航站楼',
|
||
endLocation: 'T3航站楼',
|
||
totalDistance: '1.3km',
|
||
battery: '60%',
|
||
manager: '张三',
|
||
phone: '18661910988'
|
||
};
|
||
|
||
// 设置弹窗位置
|
||
if (pixelPosition) {
|
||
const viewportPosition = props.map.getViewport().getBoundingClientRect();
|
||
detailPopupStyle.value = {
|
||
left: (viewportPosition.left + pixelPosition[0] + 10) + 'px',
|
||
top: (viewportPosition.top + pixelPosition[1] + 10) + 'px'
|
||
};
|
||
}
|
||
|
||
// 显示详情弹窗
|
||
vehicleDetailVisible.value = true;
|
||
}
|
||
},
|
||
setCategoryVisibility(type, { visible, showLabel }) {
|
||
if (vehicleCategories.value[type]) {
|
||
// 更新分类设置
|
||
vehicleCategories.value[type].visible = visible;
|
||
vehicleCategories.value[type].showLabel = showLabel;
|
||
|
||
// 立即应用更改 - 更新所有属于此类别的车辆
|
||
Object.values(vehicles.value).forEach(vehicle => {
|
||
let vehicleTypeKey = vehicle.type;
|
||
|
||
// 根据车辆特征确定类别
|
||
if (vehicle.isAircraftIn) vehicleTypeKey = 'AIRCRAFT_IN';
|
||
else if (vehicle.isAircraftOut) vehicleTypeKey = 'AIRCRAFT_OUT';
|
||
else if (vehicle.isUnmannedVehicle) vehicleTypeKey = 'UNMANNED_VEHICLE';
|
||
else if (vehicle.isSpecialVehicle) vehicleTypeKey = 'AIRPORT_VEHICLE';
|
||
else if (vehicle.isShuttleVehicle) vehicleTypeKey = 'SHUTTLE_VEHICLE';
|
||
|
||
// 如果车辆属于当前修改的类别
|
||
if (vehicleTypeKey === type) {
|
||
// 更新图标可见性 - 如果不可见,则完全移除图标样式
|
||
if (vehicle.feature) {
|
||
if (visible) {
|
||
// 显示图标 - 确保styleManager.value存在
|
||
if (styleManager.value) {
|
||
vehicle.feature.setStyle(styleManager.value.getVehicleStyle(vehicle.id, vehicle.speed, vehicle.heading));
|
||
} else {
|
||
// 提供一个默认样式,根据车辆类型选择图标
|
||
vehicle.feature.setStyle(defaultGetVehicleStyle(vehicle.id, vehicle.speed, vehicle.heading));
|
||
}
|
||
} else {
|
||
// 完全隐藏图标,使用空样式而非null
|
||
vehicle.feature.setStyle(new Style({}));
|
||
}
|
||
}
|
||
|
||
// 单独控制文本标签可见性,与图标显示状态无关
|
||
if (labelSystem.value) {
|
||
labelSystem.value.setLabelVisibility(vehicle.id, showLabel);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
},
|
||
reconnectWebSocket() {
|
||
console.log('手动重连WebSocket');
|
||
connectWebSocket();
|
||
},
|
||
|
||
// 添加平滑动画相关方法
|
||
startVehicleSmoothing() {
|
||
if (animationSystem.value) {
|
||
animationSystem.value.startAnimationLoop();
|
||
}
|
||
},
|
||
|
||
stopVehicleSmoothing() {
|
||
if (animationSystem.value) {
|
||
animationSystem.value.stopAnimationLoop();
|
||
}
|
||
},
|
||
|
||
resetVehicleAnimations() {
|
||
if (animationSystem.value) {
|
||
animationSystem.value.resetAnimations();
|
||
}
|
||
},
|
||
|
||
// 飞机路线相关方法
|
||
getAircraftRoutes() {
|
||
return aircraftRoutes.value;
|
||
},
|
||
|
||
showAircraftRoute(flightNo) {
|
||
const route = aircraftRoutes.value[flightNo];
|
||
if (route && route.feature) {
|
||
// 定位到路线
|
||
const extent = route.feature.getGeometry().getExtent();
|
||
props.map.getView().fit(extent, {
|
||
padding: [50, 50, 50, 50],
|
||
duration: 1000
|
||
});
|
||
}
|
||
},
|
||
|
||
setAircraftRouteVisibility(visible) {
|
||
if (aircraftRouteLayer) {
|
||
aircraftRouteLayer.setVisible(visible);
|
||
}
|
||
},
|
||
|
||
// 飞机路线点标记相关方法
|
||
createRoutePointMarker(flightNo, pointType, coordinates, routeType) {
|
||
return createRoutePointMarker(flightNo, pointType, coordinates, routeType);
|
||
},
|
||
|
||
updateRoutePointMarker(flightNo, pointType, coordinates) {
|
||
return updateRoutePointMarker(flightNo, pointType, coordinates);
|
||
},
|
||
|
||
removeAircraftRoute(flightNo) {
|
||
return removeAircraftRoute(flightNo);
|
||
}
|
||
});
|
||
|
||
// 添加一个默认的getVehicleStyle函数
|
||
function defaultGetVehicleStyle(id, _speed, heading) {
|
||
// 检查车辆类型
|
||
const vehicle = vehicles.value[id];
|
||
if (!vehicle) return createDefaultStyle(carIcon, heading);
|
||
|
||
// 只根据车辆类型选择图标,不根据事件状态改变
|
||
if (vehicle.isAircraft) {
|
||
// 航空器使用Aircraft.png图标
|
||
return createDefaultStyle(aircraftIcon, heading);
|
||
} else {
|
||
// 其他所有车辆使用noPeopleCar.png图标
|
||
return createDefaultStyle(carIcon, heading);
|
||
}
|
||
}
|
||
|
||
// 创建默认样式的辅助函数
|
||
function createDefaultStyle(iconSrc, heading) {
|
||
// 确保heading是有效的数值并正确转换为弧度
|
||
const validHeading = heading !== undefined ? Number(heading) : 0;
|
||
|
||
// 计算正确的旋转角度
|
||
// 在OpenLayers中,0度是向上,顺时针增加
|
||
// 而heading是0度为北,顺时针增加
|
||
// 由于地图旋转了72度,heading=72对应地图上方
|
||
const rotationRad = ((validHeading - 72) * Math.PI) / 180;
|
||
// console.log(`createDefaultStyle: heading=${validHeading}, 旋转角度计算: (${validHeading} - 72) * π/180 = ${rotationRad} 弧度, ${(validHeading - 72)}度`);
|
||
|
||
return new Style({
|
||
image: new Icon({
|
||
src: iconSrc, // 使用传入的图标源
|
||
scale: 1.5,
|
||
anchor: [0.5, 0.5],
|
||
rotation: rotationRad, // 应用计算后的旋转角度
|
||
})
|
||
});
|
||
}
|
||
</script> |