airport-qingdao-vue3/src/components/map/controls/VehicleMovementControlRefactored.vue
2025-07-15 17:46:00 +08:00

1471 lines
45 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>