airport-qingdao-vue3/src/components/map/controls/VehicleMovementControlRefactored.vue

3019 lines
97 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"
/>
<!-- 告警/预警提示框 -->
<!-- <AlarmNotification
ref="alarmNotification"
@close="handleAlarmClose"
/> -->
<!-- 车辆动画系统 -->
<VehicleAnimationSystem
ref="animationSystem"
:map="map"
:vehicle-source="vehicleSource"
:vehicles="vehicles"
:get-vehicle-style="styleManager?.value?.getVehicleStyle || defaultGetVehicleStyle"
:apply-style="applyVehicleStyleWithVisibility"
:apply-label-position="applyVehicleLabelPosition"
/>
<!-- 车辆标签系统 -->
<VehicleLabelSystem
ref="labelSystem"
:map="map"
:vehicles="vehicles"
/>
<!-- 车辆样式管理器 -->
<VehicleStyleManager
ref="styleManager"
:vehicles="vehicles"
/>
<div
v-if="pathConflictDisplayEnabled && currentPathConflict"
class="path-conflict-card"
:class="`level-${currentPathConflict.level}`"
>
<div class="path-conflict-header">
<span>{{ currentPathConflict.title }}</span>
<span class="path-conflict-level">{{ getConflictLevelText(currentPathConflict.level) }}</span>
</div>
<div class="path-conflict-body">
<div>{{ currentPathConflict.aircraftName || '未知航空器' }} / {{ currentPathConflict.vehicleName || '未知车辆' }}</div>
<div v-if="currentPathConflict.directionLockText" class="path-conflict-muted">
方向锁定:{{ currentPathConflict.directionLockText }}
</div>
<div>飞机距离:{{ formatConflictDistance(currentPathConflict.aircraftDistance) }}</div>
<div>车辆距离:{{ formatConflictDistance(currentPathConflict.vehicleDistance) }}</div>
<div v-if="hasConflictForwardDistance(currentPathConflict)" class="path-conflict-debug">
前向距离对象1 {{ formatConflictDistance(currentPathConflict.object1ForwardDistance) }} / 对象2 {{ formatConflictDistance(currentPathConflict.object2ForwardDistance) }}
</div>
<div
v-if="currentPathConflict.directionLockFailed && currentPathConflict.directionLockReason"
class="path-conflict-reason"
>
原因{{ currentPathConflict.directionLockReason }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch, onActivated, onDeactivated, defineProps, defineEmits } 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 GeoJSON from 'ol/format/GeoJSON';
import proj4 from 'proj4';
import { register } from 'ol/proj/proj4.js';
import { get as getProj } from 'ol/proj';
import WebSocketService, { createWebSocket, resetWebSocketInstance } from '../../../utils/websocket.js';
import useVehicleDisplayStore from '@/store/modules/vehicleDisplay';
import useSettingsStore from '@/store/modules/settings';
import { getPathConflictDisplayEnabled, isNewPathConflictPayload, normalizePathConflictMessage } from '@/utils/pathConflictDisplay.mjs';
// 导入子组件
import VehicleAnimationSystem from './VehicleAnimationSystem.vue';
import VehicleLabelSystem from './VehicleLabelSystem.vue';
import VehicleDetailPopup from './VehicleDetailPopup.vue';
import WeatherStationPopup from './WeatherStationPopup.vue';
import AlarmNotification from '../info/AlarmNotification.vue'; // 导入告警通知组件
import VehicleStyleManager from './VehicleStyleManager.vue';
// 导入默认图标
import carIcon from '../../../assets/images/noPeopleCar.svg';
import specialCar from '../../../assets/images/specialCar.svg';
import aircraftIcon from '../../../assets/images/Aircraft.png'; // 航空器使用Aircraft.png图标
import aircraftRouteIcon from '../../../assets/images/Aircraft1.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 aircraftRouteIconImg = new Image();
aircraftRouteIconImg.src = aircraftRouteIcon;
// 不再预加载不同状态的航空器图标
// const aircraftInIconImg = new Image();
// aircraftInIconImg.src = aircraftInIcon;
// const aircraftOutIconImg = new Image();
// aircraftOutIconImg.src = aircraftOutIcon;
const DEFAULT_VEHICLE_ICON_SCALE = 0.75;
const AIRCRAFT_ICON_SCALE = DEFAULT_VEHICLE_ICON_SCALE * 1.3;
// 为SockJS提供polyfill
if (typeof window !== 'undefined' && !window.global) {
window.global = window;
}
// 定义props接收地图实例
const props = defineProps({
map: Object
});
// 定义emit事件
const emit = defineEmits(['vehicle-details-updated']);
// 注册 EPSG:4528 坐标系
if (typeof window !== 'undefined' && proj4) {
// 检查是否已经注册过该坐标系
if (!proj4.defs('EPSG:4528')) {
proj4.defs(
"EPSG:4528",
"+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=40500000 +y_0=0 +ellps=GRS80 +units=m +no_defs",
);
console.log('已注册 EPSG:4528 坐标系');
}
// 注册到 OpenLayers
register(proj4);
}
console.log("注册EPSG:4528",getProj('EPSG:4528'));
// 子组件引用
const animationSystem = ref(null);
const labelSystem = ref(null);
const styleManager = ref(null);
const alarmNotification = ref(null); // 告警通知组件引用
const vehicleDisplayStore = useVehicleDisplayStore();
const settingsStore = useSettingsStore();
const pathConflictDisplayEnabled = computed(() => getPathConflictDisplayEnabled(settingsStore));
// 气象监测站数据
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;
let pathConflictLayer = null;
let pathConflictSource = null;
const pathConflicts = ref({});
const conflictLevelPriority = {
EMERGENCY: 4,
CRITICAL: 3,
WARNING: 2,
STATUS: 1
};
const currentPathConflict = computed(() => {
const list = Object.values(pathConflicts.value || {});
if (!list.length) return null;
return list.sort((a, b) => {
const levelDiff = (conflictLevelPriority[b.level] || 0) - (conflictLevelPriority[a.level] || 0);
if (levelDiff) return levelDiff;
return (b.updatedAt || 0) - (a.updatedAt || 0);
})[0];
});
function getConflictLevelText(level) {
if (level === 'EMERGENCY') return '紧急';
if (level === 'CRITICAL') return '告警';
if (level === 'WARNING') return '预警';
return '计算';
}
function formatConflictDistance(value) {
const num = Number(value);
if (!Number.isFinite(num)) return '--';
return `${Math.round(num)}`;
}
function hasConflictForwardDistance(conflict) {
if (!conflict) return false;
return Number.isFinite(Number(conflict.object1ForwardDistance)) ||
Number.isFinite(Number(conflict.object2ForwardDistance));
}
function refreshVehicleVisibility() {
const all = vehicles.value || {};
Object.keys(all).forEach((vehicleId) => {
const vehicle = all[vehicleId];
if (!vehicle?.feature) return;
const typeKey = getVehicleTypeKey(vehicle);
const enabled = typeKey === 'AIRCRAFT' ? true : vehicleDisplayStore.isVehicleEnabled(vehicleId);
if (!enabled) {
vehicle.feature.setStyle(new Style({}));
if (labelSystem.value?.setLabelVisibility) {
labelSystem.value.setLabelVisibility(vehicleId, false);
}
return;
}
setVehicleStyleRespectVisibility(vehicleId, getDefaultVehicleStyle(vehicle));
if (labelSystem.value?.setLabelVisibility) {
labelSystem.value.setLabelVisibility(vehicleId, isLabelAllowedForVehicle(vehicleId));
}
});
}
watch(
() => vehicleDisplayStore.enabledVehicleIds,
() => {
refreshVehicleVisibility();
},
{ deep: true }
);
watch(
() => vehicleDisplayStore.vehicleTypeOverrides,
() => {
refreshVehicleVisibility();
},
{ deep: true }
);
// 车辆超速状态超时计时器存储
const speedViolationTimers = {};
// 飞机路线数据存储
const aircraftRoutes = ref({}); // 存储飞机路线数据键为flightNo
let aircraftRouteLayer = null; // 飞机路线图层
let aircraftRouteSource = null; // 飞机路线数据源
// 飞机路线定时器存储
const aircraftRouteTimers = {}; // 存储每个航班路线的定时器
const aircraftRouteProgress = {};
function applyVehicleLabelPosition(vehicleId, coordinates) {
if (labelSystem.value && labelSystem.value.updateLabelPosition) {
labelSystem.value.updateLabelPosition(vehicleId, coordinates);
}
}
// 动态收集所有车辆/航空器类别
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: '摆渡车' }
});
// 监听vehicleCategories变化用于调试
watch(vehicleCategories, (newCategories) => {
console.log('VehicleMovementControl: vehicleCategories 发生变化:', newCategories);
}, { deep: true });
// 告警/预警状态
const alertMessage = ref('');
const alertType = ref('');
let alertTimer = null;
// 违规/冲突状态计时器
const violationStatusTimers = ref({});
function clearStatusTimer(key) {
const timers = violationStatusTimers.value || {};
const t = timers[key];
if (t) {
clearTimeout(t);
delete timers[key];
violationStatusTimers.value = timers;
}
}
function setStatusTimer(key, fn, ms) {
clearStatusTimer(key);
const timers = violationStatusTimers.value || {};
timers[key] = setTimeout(fn, ms);
violationStatusTimers.value = timers;
}
function clearConflictStatus(vehicleId) {
const v = vehicles.value?.[vehicleId];
if (!v) return;
clearStatusTimer(`conflict:${vehicleId}`);
v.warning = false;
v.alarm = false;
v.lastConflictLevel = undefined;
v.lastConflictAt = undefined;
if (v.position && labelSystem.value) {
labelSystem.value.removeVehicleLabel(vehicleId);
updateVehicleLabelIfAllowed(vehicleId, v.position, v.speed > 0.1 ? v.speed : 0);
}
if (v.feature && styleManager.value) {
setVehicleStyleRespectVisibility(vehicleId, styleManager.value.getVehicleStyle(vehicleId, v.speed, v.heading));
}
}
function markConflictStatus(vehicleId, level, message, ttlMs) {
const v = vehicles.value?.[vehicleId];
if (!v) return;
const now = Date.now();
v.lastConflictAt = now;
v.lastConflictLevel = level;
if (level === 'alarm') {
v.alarm = true;
v.warning = false;
} else if (!v.alarm) {
v.warning = true;
}
if (v.position) {
updateVehicleLabelIfAllowed(vehicleId, v.position, v.speed, {
description: message,
isWarning: level === 'warning',
isAlarm: level === 'alarm'
});
}
const timerKey = `conflict:${vehicleId}`;
if (!(Number(ttlMs) > 0)) {
clearStatusTimer(timerKey);
return;
}
setStatusTimer(
timerKey,
() => {
const latest = vehicles.value?.[vehicleId];
if (!latest) return;
if (latest.lastConflictAt !== now) return;
clearConflictStatus(vehicleId);
clearStatusTimer(timerKey);
},
Number(ttlMs) > 0 ? Number(ttlMs) : 10000
);
}
// 告警列表管理
const alarmList = ref([]); // 存储所有告警信息
const maxAlarmCount = 100; // 最大告警数量,防止列表过长
const alarmVisible = ref(true); // 控制告警面板显示状态
// 添加告警到列表
function addAlarm(alarm) {
// 确保alarm对象包含所有必要字段
const completeAlarm = {
id: `alarm-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, // 生成唯一ID
carId: alarm.carId || alarm.vehicleId || alarm.objectId || '未知车辆',
carType: alarm.carType || alarm.vehicleType || alarm.objectType || '未知类型',
time: alarm.time || formatTimeRange(new Date()),
description: alarm.description || '未知告警',
date: alarm.date || formatDateTime(new Date()),
level: alarm.level || 'medium', // high, medium, low
type: alarm.type || 'other', // car(冲突), report(越界), speed(超速), other
...alarm // 保留原始字段
};
// 添加到列表开头
alarmList.value.unshift(completeAlarm);
// 限制列表长度
if (alarmList.value.length > maxAlarmCount) {
alarmList.value = alarmList.value.slice(0, maxAlarmCount);
}
// 如果告警面板已打开,更新告警面板
updateAlarmNotification();
return completeAlarm.id; // 返回告警ID便于后续引用
}
// 更新告警
function updateAlarm(alarmId, updates) {
const index = alarmList.value.findIndex(alarm => alarm.id === alarmId);
if (index !== -1) {
alarmList.value[index] = { ...alarmList.value[index], ...updates };
updateAlarmNotification();
return true;
}
return false;
}
// 删除告警
function removeAlarm(alarmId) {
const index = alarmList.value.findIndex(alarm => alarm.id === alarmId);
if (index !== -1) {
alarmList.value.splice(index, 1);
updateAlarmNotification();
return true;
}
return false;
}
// 清空告警列表
function clearAlarms() {
alarmList.value = [];
updateAlarmNotification();
}
// 更新告警面板
function updateAlarmNotification() {
if (alarmNotification.value) {
alarmNotification.value.updateAlarmList(alarmList.value);
}
}
// 切换告警面板显示状态
function toggleAlarmNotification() {
alarmVisible.value = !alarmVisible.value;
if (alarmNotification.value) {
if (alarmVisible.value) {
alarmNotification.value.show();
} else {
alarmNotification.value.hide();
}
}
}
// 处理告警面板关闭事件
function handleAlarmClose() {
alarmVisible.value = false;
console.log('告警面板已关闭');
}
// 格式化时间范围例如T10:20-10:25
function formatTimeRange(date, durationMinutes = 5) {
const startTime = new Date(date);
const endTime = new Date(date);
endTime.setMinutes(endTime.getMinutes() + durationMinutes);
const startHour = startTime.getHours().toString().padStart(2, '0');
const startMinute = startTime.getMinutes().toString().padStart(2, '0');
const endHour = endTime.getHours().toString().padStart(2, '0');
const endMinute = endTime.getMinutes().toString().padStart(2, '0');
return `T${startHour}:${startMinute}-${endHour}:${endMinute}`;
}
// 格式化日期时间例如2025-03-19 10:30
function formatDateTime(date) {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hour = date.getHours().toString().padStart(2, '0');
const minute = date.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hour}:${minute}`;
}
// 处理告警显示
function handleAlertShow({ message, type, duration = 5000 }) {
if (alertTimer) {
clearTimeout(alertTimer);
alertTimer = null;
}
alertMessage.value = message;
alertType.value = type;
if (Number(duration) > 0) {
alertTimer = setTimeout(() => {
alertMessage.value = '';
alertType.value = '';
alertTimer = null;
}, duration);
}
}
// 显示告警/预警消息
function showAlert(message, type, duration = 5000) {
handleAlertShow({ message, type, duration });
}
function clearAlertMessage() {
if (alertTimer) {
clearTimeout(alertTimer);
alertTimer = null;
}
alertMessage.value = '';
alertType.value = '';
}
// 创建车辆图层
function createVehicleLayer() {
if (!props.map) return;
if (vehicleLayer) 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;
if (aircraftRouteLayer) 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 createPathConflictLayer() {
if (!props.map) return;
if (pathConflictLayer) return;
pathConflictSource = new VectorSource();
pathConflictLayer = new VectorLayer({
source: pathConflictSource,
zIndex: 2100,
});
props.map.addLayer(pathConflictLayer);
}
function getPathConflictStyle(conflict) {
const color = '#3b82f6';
return new Style({
image: new Circle({
radius: conflict.kind === 'alert' ? 10 : 8,
fill: new Fill({ color }),
stroke: new Stroke({ color: '#ffffff', width: 3 })
}),
text: new Text({
text: conflict.title,
offsetY: -22,
font: '12px sans-serif',
fill: new Fill({ color: '#ffffff' }),
stroke: new Stroke({ color: 'rgba(0,0,0,0.75)', width: 3 })
})
});
}
// 处理地图点击事件
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'
// };
// 使用实时数据填充车辆详情
vehicleDetail.value = {
id: objectId,
type: vehicle.type || '未知类型',
status: vehicle.status || '任务中',
startTime: vehicle.startTime || '11-19 11:30',
currentLocation: vehicle.currentLocation || '当前位置',
startLocation: vehicle.startLocation || 'T1航站楼',
endLocation: vehicle.endLocation || 'T3航站楼',
totalDistance: vehicle.totalDistance || '1.3km',
battery: vehicle.soc || vehicle.battery || '60%',
manager: vehicle.manager || vehicle.location || '张三',
phone: vehicle.phone || vehicle.contactPhone || '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);
const overrideType = vehicleDisplayStore.getVehicleTypeOverride(object_id);
const effectiveTypeRaw = String((overrideType || object_type || '')).trim();
const rawType = String(object_type || '').trim();
const vehicleType = effectiveTypeRaw.toUpperCase();
const isAircraft = vehicleType === 'AIRCRAFT' || vehicleType === 'AIRCRAFT_IN' || vehicleType === 'AIRCRAFT_OUT' || vehicleType === 'HANGKONG';
const isUnmannedVehicle = vehicleType === 'UNMANNED_VEHICLE' || vehicleType === 'WUREN' || effectiveTypeRaw.includes('无人');
const isSpecialVehicle = vehicleType === 'SPECIAL_VEHICLE' || vehicleType === 'TEQIN' || effectiveTypeRaw.includes('特勤');
const hasAircraftRoute = isAircraft && !!aircraftRoutes.value[object_id];
// 确保heading是有效的数值
const validHeading = heading !== undefined ? Number(heading) : 0;
if (hasAircraftRoute) {
tryAutoRemoveAircraftRouteByPosition(object_id, coordinates);
}
if (!feature) {
// 先创建车辆数据对象确保在创建feature前就有类型信息
vehicles.value[object_id] = {
id: object_id,
type: effectiveTypeRaw || rawType,
position: coordinates,
heading: validHeading,
speed: speed,
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
hasAircraftRoute: hasAircraftRoute,
lastHeading: validHeading // 初始化lastHeading
};
feature = new Feature({
geometry: new Point(coordinates),
name: `${effectiveTypeRaw || rawType} ${object_id}`,
type: effectiveTypeRaw || rawType,
speed: speed,
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle
});
feature.setId(object_id);
// 直接根据车辆类型选择正确的图标,不使用默认图标
let iconStyle;
let iconSrc;
if (isAircraft) {
iconSrc = hasAircraftRoute ? aircraftRouteIcon : aircraftIcon;
} else if (isUnmannedVehicle) {
iconSrc = carIcon; // 无人车使用noPeopleCar.svg
} else {
iconSrc = specialCar; // 其他车辆使用specialCar.svg
}
iconStyle = new Style({
image: new Icon({
src: iconSrc,
scale: isAircraft ? AIRCRAFT_ICON_SCALE : DEFAULT_VEHICLE_ICON_SCALE,
anchor: [0.5, 0.5],
rotation: rotationRad, // 使用计算好的旋转角度
})
});
{
const typeKey = getVehicleTypeKey(vehicles.value[object_id]);
const category = vehicleCategories.value?.[typeKey];
const enabled = typeKey === 'AIRCRAFT' ? true : vehicleDisplayStore.isVehicleEnabled(object_id);
const visible = enabled && !!(category && category.visible);
feature.setStyle(visible ? iconStyle : new Style({}));
}
vehicleSource.addFeature(feature);
// 保存feature引用到车辆数据对象
vehicles.value[object_id].feature = feature;
// 初始化动画数据
if (animationSystem.value) {
animationSystem.value.initVehicleAnimation(object_id, coordinates, heading, speed);
}
// 创建标签(受左侧类别文本开关控制)
if (labelSystem.value) {
updateVehicleLabelIfAllowed(object_id, coordinates, speed);
}
// 应用当前分类设置到新创建的车辆
if (vehicleCategories.value) {
const typeKey = getVehicleTypeKey(vehicles.value[object_id]);
const category = vehicleCategories.value?.[typeKey];
if (category && !category.visible && vehicles.value[object_id].feature) {
vehicles.value[object_id].feature.setStyle(new Style({}));
}
}
} else {
// 更新动画目标
if (animationSystem.value) {
animationSystem.value.updateVehicleAnimationTarget(object_id, coordinates, heading, speed);
}
// 更新车辆数据包括可能从position_update消息中获取的详情数据
const previousVehicleData = { ...vehicles.value[object_id] };
vehicles.value[object_id] = {
...vehicles.value[object_id],
type: effectiveTypeRaw || rawType || vehicles.value[object_id]?.type,
position: coordinates,
heading: validHeading,
speed: speed,
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
hasAircraftRoute: hasAircraftRoute,
// 更新车辆详情数据如果在payload中提供
...(vehicleData.carId && { carId: vehicleData.carId }),
...(vehicleData.type && { type: vehicleData.type }),
...(vehicleData.brand && { brand: vehicleData.brand }),
...(vehicleData.organization && { organization: vehicleData.organization }),
...(vehicleData.routeStatus && { routeStatus: vehicleData.routeStatus }),
...(vehicleData.status && { status: vehicleData.status }),
...(vehicleData.chargeStatus && { chargeStatus: vehicleData.chargeStatus }),
...(vehicleData.voltage && { voltage: vehicleData.voltage }),
...(vehicleData.current && { current: vehicleData.current }),
...(vehicleData.soc && { soc: vehicleData.soc }),
...(vehicleData.lastTime && { lastTime: vehicleData.lastTime }),
...(vehicleData.batteryLevel && { batteryLevel: vehicleData.batteryLevel }),
...(vehicleData.location && { location: vehicleData.location }),
...(vehicleData.contactPhone && { contactPhone: vehicleData.contactPhone }),
...(vehicleData.battery && { battery: vehicleData.battery }),
...(vehicleData.runningStatus && { runningStatus: vehicleData.runningStatus })
};
// 如果车辆详情有更新,则发射事件通知其他组件
const currentVehicleData = vehicles.value[object_id];
if (hasVehicleDetailsChanged(previousVehicleData, currentVehicleData)) {
emit('vehicle-details-updated', currentVehicleData);
}
// 更新标签位置,确保标签始终跟随图标
// 但是如果车辆处于超速状态,不在这里更新标签,避免与超速状态标签冲突
if (labelSystem.value && !vehicles.value[object_id].speedViolation) {
updateVehicleLabelIfAllowed(object_id, coordinates, speed);
}
// 更新车辆图标样式,确保旋转角度正确应用,并尊重分类显示状态
if (feature) {
// 根据车辆类型选择正确的图标
let iconSrc;
const vehicle = vehicles.value[object_id];
if (isAircraft) {
iconSrc = hasAircraftRoute ? aircraftRouteIcon : aircraftIcon; // 航空器使用aircraft.png
} else if (
vehicle &&
(String(vehicle.type || '').includes('无人') || String(vehicle.type || '').toUpperCase() === 'UNMANNED_VEHICLE' || String(vehicle.type || '').toUpperCase() === 'WUREN')
) {
iconSrc = carIcon; // 无人车使用noPeopleCar.svg
} else {
iconSrc = specialCar; // 其他车辆使用specialCar.svg
}
const typeKey = getVehicleTypeKey(vehicle);
const category = vehicleCategories.value?.[typeKey];
const enabled = typeKey === 'AIRCRAFT' ? true : vehicleDisplayStore.isVehicleEnabled(object_id);
const visible = enabled && !!(category && category.visible);
feature.setStyle(
visible
? new Style({
image: new Icon({
src: iconSrc,
scale: isAircraft ? AIRCRAFT_ICON_SCALE : DEFAULT_VEHICLE_ICON_SCALE,
anchor: [0.5, 0.5],
rotation: rotationRad,
}),
})
: new Style({})
);
// 更新lastHeading
vehicles.value[object_id].lastHeading = validHeading;
}
}
}
// 检查车辆详情是否有变化
function hasVehicleDetailsChanged(previous, current) {
// 检查关键详情字段是否有变化
return (
previous.carId !== current.carId ||
previous.type !== current.type ||
previous.brand !== current.brand ||
previous.organization !== current.organization ||
previous.routeStatus !== current.routeStatus ||
previous.status !== current.status ||
previous.chargeStatus !== current.chargeStatus ||
previous.voltage !== current.voltage ||
previous.current !== current.current ||
previous.soc !== current.soc ||
previous.lastTime !== current.lastTime ||
previous.batteryLevel !== current.batteryLevel ||
previous.location !== current.location ||
previous.contactPhone !== current.contactPhone ||
previous.battery !== current.battery ||
previous.runningStatus !== current.runningStatus
);
}
// 连接WebSocket
function connectWebSocket() {
// 重置WebSocket实例以强制创建新实例
resetWebSocketInstance();
try {
// 使用WebSocketService创建连接
const wsUrl = window.APP_CONFIG?.WS_BASE_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 'collision_warning':
console.log(`碰撞预警: ${data.payload?.message || data.payload?.object1?.objectName || ''}`);
handlePathConflictAlert(data.payload);
break;
case 'path_conflict_status':
console.log(`路径冲突计算状态: ${data.payload?.aircraftName || ''} ${data.payload?.vehicleName || ''}`);
handlePathConflictMessage(data);
break;
case 'path_conflict_alert':
// 处理冲突告警和预警
console.log(`冲突告警/预警: ${data.payload?.object1?.objectName || data.payload?.messageType}`);
if (isNewPathConflictPayload(data.payload)) {
handlePathConflictMessage(data);
} else {
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;
case 'intersection_traffic_light_status':
// 处理路口红绿灯状态更新
console.log('收到路口红绿灯状态更新:', data.payload);
handleIntersectionTrafficLightStatus(data.payload);
break;
case 'vehicle_status_update':
// 处理车辆状态更新消息
console.log('收到车辆状态更新:', data.payload);
handleVehicleStatusUpdate(data.payload);
break;
case 'trafficLight':
console.log('收到红绿灯消息:', data.payload);
handleTrafficLightWsMessage(data.payload);
break;
case 'trafficlight':
case 'TrafficLight':
console.log('收到红绿灯消息(大小写变体):', data.payload);
handleTrafficLightWsMessage(data.payload);
break;
case 'FLIGHT_NOTIFICATION':
// 处理航班进出港通知消息
console.log('收到航班进出港通知:', data.payload);
handleFlightNotification(data.payload);
break;
default:
// 其他类型的消息可以根据需要处理
if (data?.payload?.serviceType === 'rsu-traffic-lights') {
console.log('检测到 rsu-traffic-lights 服务消息,按红绿灯处理:', data.payload);
handleTrafficLightWsMessage(data.payload);
} else {
console.log(`未知消息类型: ${data.type}`, data);
}
break;
}
} catch (e) {
console.error('处理WebSocket消息出错:', e, message);
}
}
// 处理航班进出港通知消息
function handleFlightNotification(payload) {
if (!payload || !payload.flightNo) {
console.error('航班进出港通知消息格式错误:', payload);
return;
}
// 解构消息中的关键信息
const {
flightNo,
flightType,
eventType,
runway,
seat,
notificationLevel,
eventDescription
} = payload;
// 记录航班通知信息
console.log(`航班通知: ${flightNo} ${eventType} ${flightType}`, {
runway,
seat,
notificationLevel,
eventDescription
});
// TODO: 后续根据需求添加具体的处理逻辑
// 可能包括:
// 1. 在地图上显示航班相关信息
// 2. 发送通知到相关组件
// 3. 更新航班状态数据
// 4. 触发告警或提示
}
// 处理车辆状态更新消息
function handleVehicleStatusUpdate(payload) {
if (!payload || !payload.vehicleId) {
console.error('车辆状态更新消息格式错误:', payload);
return;
}
const { vehicleId, statusData } = payload;
// 检查是否存在该车辆
if (!vehicles.value[vehicleId]) {
console.warn(`未找到车辆 ${vehicleId},无法更新状态`);
return;
}
// 保存更新前的车辆详情用于比较
const previousVehicleData = { ...vehicles.value[vehicleId] };
// 更新车辆详细状态信息
if (statusData) {
// 车辆基本信息
if (statusData.vehicleInfo) {
const vehicleInfo = statusData.vehicleInfo;
vehicles.value[vehicleId] = {
...vehicles.value[vehicleId],
carId: vehicleInfo.vehicleId || vehicles.value[vehicleId].carId,
type: vehicleInfo.vehicleType || vehicles.value[vehicleId].type,
brand: vehicleInfo.brand || vehicles.value[vehicleId].brand,
organization: vehicleInfo.owningUnit || vehicles.value[vehicleId].organization
};
}
// 运行状态
if (statusData.operationalStatus) {
const ops = statusData.operationalStatus;
const operationalMode = ops.operationalMode || vehicles.value[vehicleId].routeStatus;
const systemHealth = ops.systemHealth;
let statusText = vehicles.value[vehicleId].status;
// 根据系统健康状态和运行模式确定状态文本
if (systemHealth === 'HEALTHY') {
statusText = operationalMode === 'AUTONOMOUS' ? '任务中' : '在线';
} else if (systemHealth === 'FAULT') {
statusText = '故障';
} else if (systemHealth === 'OFFLINE') {
statusText = '离线';
}
vehicles.value[vehicleId] = {
...vehicles.value[vehicleId],
routeStatus: operationalMode,
status: statusText,
chargeStatus: ops.powerStatus || vehicles.value[vehicleId].chargeStatus
};
}
// 运动状态
if (statusData.motionStatus) {
const motion = statusData.motionStatus;
if (motion.velocity) {
vehicles.value[vehicleId] = {
...vehicles.value[vehicleId],
speed: motion.velocity.speed !== undefined ? motion.velocity.speed : vehicles.value[vehicleId].speed
};
}
// 更新位置信息用于显示
if (motion.position) {
const { latitude, longitude } = motion.position;
vehicles.value[vehicleId] = {
...vehicles.value[vehicleId],
currentLocation: `(${latitude.toFixed(6)}, ${longitude.toFixed(6)})`
};
}
}
// 电池状态
if (statusData.batteryStatus?.mainBattery) {
const battery = statusData.batteryStatus.mainBattery;
vehicles.value[vehicleId] = {
...vehicles.value[vehicleId],
soc: battery.chargeLevel !== undefined ? `${battery.chargeLevel}%` : vehicles.value[vehicleId].soc,
voltage: battery.voltage !== undefined ? `${battery.voltage}V` : vehicles.value[vehicleId].voltage,
current: battery.current !== undefined ? `${battery.current}A` : vehicles.value[vehicleId].current,
battery: battery.chargeLevel !== undefined ? `${battery.chargeLevel}%` : vehicles.value[vehicleId].battery
};
}
// 通信状态
if (statusData.communicationStatus) {
const comm = statusData.communicationStatus;
vehicles.value[vehicleId] = {
...vehicles.value[vehicleId],
lastTime: comm.lastUpdateTime ? new Date(comm.lastUpdateTime).toLocaleString() :
new Date().toLocaleString() // 使用当前时间作为最后更新时间
};
}
// 任务上下文信息
if (statusData.missionContext?.currentMission) {
const mission = statusData.missionContext.currentMission;
vehicles.value[vehicleId] = {
...vehicles.value[vehicleId],
// 任务开始时间 - 从时间戳转换为可读格式
startTime: mission.startTime ?
new Date(mission.startTime).toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).replace(/\//g, '-').replace(/,/, '') :
vehicles.value[vehicleId].startTime,
// 总里程 - 从米转换为公里
totalDistance: mission.totalMileage !== undefined ?
`${(mission.totalMileage / 1000).toFixed(1)}km` :
vehicles.value[vehicleId].totalDistance,
// 任务进度
progress: mission.progress !== undefined ?
`${mission.progress.toFixed(1)}%` :
vehicles.value[vehicleId].progress,
// 任务ID和类型
missionId: mission.missionId || vehicles.value[vehicleId].missionId,
missionType: mission.missionType || vehicles.value[vehicleId].missionType
};
}
}
// 如果当前正在显示该车辆的详情弹窗,实时更新弹窗数据
if (vehicleDetailVisible.value && vehicleDetail.value.id === vehicleId) {
const updatedVehicle = vehicles.value[vehicleId];
vehicleDetail.value = {
...vehicleDetail.value,
id: vehicleId,
type: updatedVehicle.type || vehicleDetail.value.type,
status: updatedVehicle.status || vehicleDetail.value.status,
startTime: updatedVehicle.startTime || vehicleDetail.value.startTime,
currentLocation: updatedVehicle.currentLocation || vehicleDetail.value.currentLocation,
totalDistance: updatedVehicle.totalDistance || vehicleDetail.value.totalDistance,
battery: updatedVehicle.battery || vehicleDetail.value.battery,
manager: updatedVehicle.manager || updatedVehicle.location || vehicleDetail.value.manager,
phone: updatedVehicle.phone || updatedVehicle.contactPhone || vehicleDetail.value.phone
};
}
// 如果车辆详情有更新,则发射事件通知其他组件
const currentVehicleData = vehicles.value[vehicleId];
if (hasVehicleDetailsChanged(previousVehicleData, currentVehicleData)) {
emit('vehicle-details-updated', currentVehicleData);
}
}
// 处理路口红绿灯状态更新
function handleIntersectionTrafficLightStatus(payload) {
// 检查是否在父组件中存在更新红绿灯图层的方法
if (window.updateTrafficLightStatus) {
window.updateTrafficLightStatus(payload);
}
}
function handleTrafficLightWsMessage(payload) {
const sd = payload && payload.serviceData ? payload.serviceData : payload;
const phases = Array.isArray(sd && sd.phases) ? sd.phases : [];
let status = 'unknown';
if (phases.length > 0) {
const colors = phases.map(p => Number(p.phaseColor));
if (colors.includes(3)) status = 'red';
else if (colors.includes(4)) status = 'yellow';
else if (colors.includes(2)) status = 'yellow';
else if (colors.includes(1)) status = 'green';
else status = 'unknown';
} else {
const s = Number((sd && sd.trafficLightsStatus) ?? payload.trafficLightsStatus);
if (s === 3) status = 'red';
else if (s === 4) status = 'yellow';
else if (s === 2) status = 'yellow';
else if (s === 1) status = 'green';
}
if (window.updateTrafficLightStatus) {
window.updateTrafficLightStatus({ status });
}
}
// 处理位置更新消息
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;
const geoJsonObject = parseGeoJsonLike(routeGeometry);
if (geoJsonObject) {
updateAircraftRouteFromGeoJson(flightNo, geoJsonObject, routeType, routeStatus);
return;
}
// 解析LINESTRING格式的routeGeometry
const coordinates = parseLineStringGeometry(routeGeometry);
if (!coordinates || coordinates.length === 0) {
console.error('解析routeGeometry失败:', routeGeometry);
return;
}
// 更新或创建路线
updateAircraftRoute(flightNo, coordinates, routeType, routeStatus);
}
function parseGeoJsonLike(value) {
if (!value) return null
if (typeof value === 'object') {
const t = String(value?.type || '')
if (t === 'Feature' || t === 'FeatureCollection' || t === 'GeometryCollection') return value
return null
}
if (typeof value !== 'string') return null
const s = value.trim()
if (!s || (s[0] !== '{' && s[0] !== '[')) return null
try {
const obj = JSON.parse(s)
const t = String(obj?.type || '')
if (t === 'Feature' || t === 'FeatureCollection' || t === 'GeometryCollection') return obj
return null
} catch (e) {
return null
}
}
function removeAircraftRouteFeatures(flightNo) {
if (!aircraftRouteSource) return
const all = aircraftRouteSource.getFeatures()
all.forEach((f) => {
const id = String(f?.getId?.() || '')
const fFlight = f?.get?.('flightNo')
if (fFlight === flightNo || id === flightNo || (id && id.startsWith(`${flightNo}-`))) {
aircraftRouteSource.removeFeature(f)
}
})
}
function updateAircraftRouteFromGeoJson(flightNo, geoJsonObject, routeType, routeStatus) {
if (!aircraftRouteSource || !props.map) return
if (aircraftRouteTimers[flightNo]) {
clearTimeout(aircraftRouteTimers[flightNo])
delete aircraftRouteTimers[flightNo]
}
removeAircraftRouteFeatures(flightNo)
const viewProj = props.map.getView().getProjection()
const geojson = new GeoJSON()
const rawFeatures = geojson.readFeatures(geoJsonObject, {
dataProjection: viewProj,
featureProjection: viewProj
})
let dataProj = viewProj
const firstLine = rawFeatures.find((f) => f?.getGeometry?.()?.getType?.() === 'LineString')
const firstCoords = firstLine?.getGeometry?.()?.getCoordinates?.()
const firstCoord = Array.isArray(firstCoords) && firstCoords.length ? firstCoords[0] : null
if (Array.isArray(firstCoord) && firstCoord.length === 2) {
const x = Number(firstCoord[0])
const y = Number(firstCoord[1])
if (Number.isFinite(x) && Number.isFinite(y)) {
const looksLikeLngLat = Math.abs(x) <= 180 && Math.abs(y) <= 90
dataProj = looksLikeLngLat ? 'EPSG:4326' : 'EPSG:4528'
}
}
const features = dataProj === viewProj
? rawFeatures
: geojson.readFeatures(geoJsonObject, {
dataProjection: dataProj,
featureProjection: viewProj
})
let firstLineCoords = null
features.forEach((f, idx) => {
const existingId = f.getId()
if (existingId === undefined || existingId === null || String(existingId).trim() === '') {
f.setId(`${flightNo}-geo-${idx}`)
} else {
f.setId(`${flightNo}-geo-${existingId}`)
}
f.set('flightNo', flightNo)
if (routeType) f.set('routeType', routeType)
if (routeStatus) f.set('routeStatus', routeStatus)
if (f.getGeometry()?.getType?.() === 'LineString' && !firstLineCoords) {
firstLineCoords = f.getGeometry().getCoordinates()
f.setStyle(getRouteStyle(routeType, routeStatus))
}
aircraftRouteSource.addFeature(f)
})
if (firstLineCoords && firstLineCoords.length) {
aircraftRoutes.value[flightNo] = {
flightNo,
routeType,
routeStatus,
coordinates: firstLineCoords,
feature: null,
lastUpdateTime: Date.now()
}
aircraftRouteProgress[flightNo] = { maxIndexSeen: 0, endHitCount: 0, lastIndex: 0 }
}
if (vehicles.value[flightNo]) {
const currentVehicle = vehicles.value[flightNo]
const isAircraftVehicle = currentVehicle.isAircraft || currentVehicle.type?.toUpperCase() === 'AIRCRAFT'
if (isAircraftVehicle) {
vehicles.value[flightNo] = { ...currentVehicle, hasAircraftRoute: true }
const v = vehicles.value[flightNo]
if (v.feature) {
if (styleManager.value) {
setVehicleStyleRespectVisibility(flightNo, styleManager.value.getVehicleStyle(flightNo, v.speed, v.heading))
} else {
setVehicleStyleRespectVisibility(flightNo, getDefaultVehicleStyle(v))
}
}
}
}
aircraftRouteTimers[flightNo] = setTimeout(() => {
removeAircraftRoute(flightNo)
}, 300000)
}
// 解析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 computeClosestRouteIndex(routeCoords, positionCoord, startIndexHint = 0) {
if (!Array.isArray(routeCoords) || routeCoords.length === 0) return 0;
if (!positionCoord || positionCoord.length !== 2) return 0;
const startIndex = Math.max(0, Math.min(Math.floor(startIndexHint || 0), routeCoords.length - 1));
let bestIndex = startIndex;
let bestD2 = Infinity;
for (let i = startIndex; i < routeCoords.length; i++) {
const dx = routeCoords[i][0] - positionCoord[0];
const dy = routeCoords[i][1] - positionCoord[1];
const d2 = dx * dx + dy * dy;
if (d2 < bestD2) {
bestD2 = d2;
bestIndex = i;
}
}
return bestIndex;
}
function tryAutoRemoveAircraftRouteByPosition(flightNo, positionCoord) {
const route = aircraftRoutes.value?.[flightNo];
if (!route || !Array.isArray(route.coordinates) || route.coordinates.length < 2) return;
const end = route.coordinates[route.coordinates.length - 1];
const dx = end[0] - positionCoord[0];
const dy = end[1] - positionCoord[1];
const distToEnd = Math.sqrt(dx * dx + dy * dy);
const progress = aircraftRouteProgress[flightNo] || { maxIndexSeen: 0, endHitCount: 0, lastIndex: 0 };
const nearestIndex = computeClosestRouteIndex(route.coordinates, positionCoord, Math.max(0, progress.maxIndexSeen - 20));
progress.lastIndex = nearestIndex;
progress.maxIndexSeen = Math.max(progress.maxIndexSeen, nearestIndex);
const endIndexThreshold = Math.max(0, route.coordinates.length - 2);
const reachedRouteEnd = progress.maxIndexSeen >= endIndexThreshold;
const movedBeyondStart = progress.maxIndexSeen >= Math.min(3, endIndexThreshold);
const endDistanceThreshold = 20;
if (reachedRouteEnd && movedBeyondStart && distToEnd <= endDistanceThreshold) {
progress.endHitCount = (progress.endHitCount || 0) + 1;
} else {
progress.endHitCount = 0;
}
aircraftRouteProgress[flightNo] = progress;
if (progress.endHitCount >= 3) {
removeAircraftRoute(flightNo);
}
}
// 更新或创建飞机路线
function updateAircraftRoute(flightNo, coordinates, routeType, routeStatus) {
if (!aircraftRouteSource || !props.map) return;
console.log('原始航线坐标:', coordinates);
// 清除该航班之前的定时器(如果存在)
if (aircraftRouteTimers[flightNo]) {
clearTimeout(aircraftRouteTimers[flightNo]);
delete aircraftRouteTimers[flightNo];
}
const isLngLat4326 = Array.isArray(coordinates) && coordinates.length > 0 && coordinates.every(coord => {
if (!Array.isArray(coord) || coord.length !== 2) return false
const x = Number(coord[0])
const y = Number(coord[1])
return Number.isFinite(x) && Number.isFinite(y) && Math.abs(x) <= 180 && Math.abs(y) <= 90
})
const sourceProjection = isLngLat4326 ? 'EPSG:4326' : 'EPSG:4528'
const projectedCoords = coordinates.map(coord =>
transform(coord, sourceProjection, props.map.getView().getProjection())
)
console.log('处理后的坐标:', projectedCoords);
// 获取起点和终点坐标
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,
lastUpdateTime: Date.now() // 记录最后更新时间
};
aircraftRouteProgress[flightNo] = { maxIndexSeen: 0, endHitCount: 0, lastIndex: 0 };
} 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,
lastUpdateTime: Date.now() // 更新最后更新时间
};
if (aircraftRouteProgress[flightNo]) {
const p = aircraftRouteProgress[flightNo];
p.maxIndexSeen = Math.min(p.maxIndexSeen || 0, Math.max(0, projectedCoords.length - 1));
p.lastIndex = Math.min(p.lastIndex || 0, Math.max(0, projectedCoords.length - 1));
p.endHitCount = 0;
}
}
if (vehicles.value[flightNo]) {
const currentVehicle = vehicles.value[flightNo];
const isAircraftVehicle = currentVehicle.isAircraft || currentVehicle.type?.toUpperCase() === 'AIRCRAFT';
if (isAircraftVehicle) {
vehicles.value[flightNo] = {
...currentVehicle,
hasAircraftRoute: true
};
const v = vehicles.value[flightNo];
if (v.feature) {
if (styleManager.value) {
setVehicleStyleRespectVisibility(
flightNo,
styleManager.value.getVehicleStyle(flightNo, v.speed, v.heading)
);
} else {
setVehicleStyleRespectVisibility(flightNo, getDefaultVehicleStyle(v));
}
}
}
}
// 设置新的定时器如果在5分钟内没有收到新的更新消息则移除路线
// 这样可以防止路线永久显示,同时允许通过新消息来刷新显示时间
aircraftRouteTimers[flightNo] = setTimeout(() => {
console.log(`航班 ${flightNo} 路线超时5分钟内未收到更新消息移除路线`);
removeAircraftRoute(flightNo);
}, 300000); // 5分钟后移除
console.log(`航班 ${flightNo} 路线已更新,将在收到下次更新消息时刷新显示时间`);
}
// 根据路线类型和状态获取样式
function getRouteStyle(routeType, routeStatus) {
let color = '#3388ff'; // 默认蓝色
let width = 3;
let lineDash = undefined;
// 根据路线类型设置颜色
if (routeType === 'IN') {
color = '#292C38'; // 深蓝色表示滑入
} else if (routeType === 'OUT') {
color = '#27AE60'; // 蓝色表示滑出
} else if (routeType === 'TEST') {
color = '#FF8C00'; // 测试路由使用橙色
}
// // 根据路线状态设置线型
// 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 = '#27AE60'; // 绿色表示滑出
} else if (routeType === 'TEST') {
color = '#FF8C00'; // 测试路由点使用橙色
}
// 创建圆点样式
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;
// 清除该航班的定时器(如果存在)
if (aircraftRouteTimers[flightNo]) {
clearTimeout(aircraftRouteTimers[flightNo]);
delete aircraftRouteTimers[flightNo];
console.log(`已清除航班 ${flightNo} 的路线定时器`);
}
removeAircraftRouteFeatures(flightNo)
// 移除路线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];
}
if (aircraftRouteProgress[flightNo]) {
delete aircraftRouteProgress[flightNo];
}
if (vehicles.value[flightNo]) {
const currentVehicle = vehicles.value[flightNo];
const isAircraftVehicle = currentVehicle.isAircraft || currentVehicle.type?.toUpperCase() === 'AIRCRAFT';
if (isAircraftVehicle) {
vehicles.value[flightNo] = {
...currentVehicle,
hasAircraftRoute: false
};
const v = vehicles.value[flightNo];
if (v.feature) {
if (styleManager.value) {
setVehicleStyleRespectVisibility(
flightNo,
styleManager.value.getVehicleStyle(flightNo, v.speed, v.heading)
);
} else {
setVehicleStyleRespectVisibility(flightNo, getDefaultVehicleStyle(v));
}
}
}
}
console.log(`航班 ${flightNo} 的路线已完全移除`);
}
function handlePathConflictMessage(message) {
if (!pathConflictDisplayEnabled.value) {
clearPathConflictDisplay();
return;
}
const conflict = normalizePathConflictMessage(message);
if (!conflict) return;
if (conflict.kind === 'resume') {
const matchedConflicts = getPathConflictsToClear(conflict.key || conflict.vehicleName);
clearPathConflict(conflict.key || conflict.vehicleName);
const statusIds = new Set([conflict.vehicleName, conflict.aircraftName]);
matchedConflicts.forEach(item => {
statusIds.add(item.vehicleName);
statusIds.add(item.aircraftName);
});
statusIds.forEach(id => {
if (id) clearConflictStatus(id);
});
if (!currentPathConflict.value) clearAlertMessage();
return;
}
const previous = pathConflicts.value?.[conflict.key];
upsertPathConflict(conflict);
if (conflict.kind === 'alert') {
const alarmLevel = conflict.level === 'WARNING' ? 'medium' : 'high';
if (!previous || previous.kind !== 'alert' || previous.level !== conflict.level) {
addAlarm({
carId: conflict.vehicleName || '未知车辆',
carType: vehicles.value[conflict.vehicleName]?.type || '路径冲突',
time: `${formatTimeRange(new Date())} ${conflict.aircraftName || '航空器'}${conflict.vehicleName || '车辆'}`,
description: conflict.title,
level: alarmLevel,
type: 'car',
rawData: conflict.rawData
});
}
showAlert(`${conflict.title}${conflict.aircraftName || '航空器'}${conflict.vehicleName || '车辆'}`, conflict.level === 'WARNING' ? 'warning' : 'alarm', 0);
markConflictStatus(conflict.vehicleName, conflict.level === 'WARNING' ? 'warning' : 'alarm', conflict.title);
markConflictStatus(conflict.aircraftName, conflict.level === 'WARNING' ? 'warning' : 'alarm', conflict.title);
}
}
function upsertPathConflict(conflict) {
const key = conflict.key || `${Date.now()}`;
const next = {
...(pathConflicts.value[key] || {}),
...conflict,
key,
updatedAt: Date.now()
};
pathConflicts.value = {
...pathConflicts.value,
[key]: next
};
renderPathConflictFeature(next);
}
function renderPathConflictFeature(conflict) {
if (!pathConflictSource || !conflict.conflictPoint) return;
const latitude = Number(conflict.conflictPoint.latitude);
const longitude = Number(conflict.conflictPoint.longitude);
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return;
const coordinates = transform([longitude, latitude], 'EPSG:4326', 'EPSG:4528');
let feature = pathConflictSource.getFeatureById(conflict.key);
if (!feature) {
feature = new Feature({
geometry: new Point(coordinates),
conflictType: 'path_conflict'
});
feature.setId(conflict.key);
pathConflictSource.addFeature(feature);
} else {
feature.getGeometry().setCoordinates(coordinates);
}
feature.setStyle(getPathConflictStyle(conflict));
}
function clearPathConflict(key) {
const next = { ...(pathConflicts.value || {}) };
const keys = Object.keys(next).filter(itemKey => {
const item = next[itemKey];
return itemKey === key || item.vehicleName === key || item.aircraftName === key;
});
keys.forEach(itemKey => {
if (pathConflictSource) {
const feature = pathConflictSource.getFeatureById(itemKey);
if (feature) pathConflictSource.removeFeature(feature);
}
delete next[itemKey];
});
pathConflicts.value = next;
}
function getPathConflictsToClear(key) {
return Object.entries(pathConflicts.value || {})
.filter(([itemKey, item]) => itemKey === key || item.vehicleName === key || item.aircraftName === key)
.map(([, item]) => item);
}
function clearPathConflictDisplay() {
pathConflicts.value = {};
if (pathConflictSource) {
pathConflictSource.clear();
}
}
// 处理冲突告警和预警
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}可能发生冲突`;
const isAlert = (payload.alertType === 'CONFLICT_ALERT') || (payload.alert === true) || (payload.alertLevel === 'CRITICAL');
const isWarning = (payload.alertType === 'CONFLICT_WARNING') || (payload.warning === true);
if (!isAlert && isWarning) {
console.log('处理冲突预警:', vehicleId, otherVehicleId);
const warningMessage = `预警:${message}`;
showAlert(warningMessage, 'warning', 8000);
// 添加到告警列表
addAlarm({
carId: vehicleId,
carType: vehicles.value[vehicleId]?.type || '未知类型',
time: `${formatTimeRange(new Date())}${otherVehicleId}发生冲`,
description: '突预警',
level: 'medium',
type: 'car',
rawData: payload
});
// 冲突预警默认10秒无新消息自动清除
markConflictStatus(vehicleId, 'warning', message, 10000);
markConflictStatus(otherVehicleId, 'warning', message, 10000);
} else if (isAlert) {
console.log('处理冲突告警:', vehicleId, otherVehicleId);
const alertMessage = `⚠️ 告警:${message}`;
showAlert(alertMessage, 'alarm', 10000);
// 添加到告警列表
addAlarm({
carId: vehicleId,
carType: vehicles.value[vehicleId]?.type || '未知类型',
time: `${formatTimeRange(new Date())}${otherVehicleId}发生冲`,
description: '突告警',
level: 'high',
type: 'car',
rawData: payload
});
// 冲突告警默认15秒无新消息自动清除
markConflictStatus(vehicleId, 'alarm', message, 15000);
markConflictStatus(otherVehicleId, 'alarm', message, 15000);
} 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);
// 添加到告警列表,提供更详细的信息
addAlarm({
carId: vehicleId,
carType: vehicles.value[vehicleId]?.type || '未知类型',
time: `${formatTimeRange(new Date())}超速行驶`,
description: `,速度达到${actualValue}km/h`,
level: actualValue > limitValue * 1.5 ? 'high' : 'medium', // 超速50%以上为高级别告警
type: 'speed',
limitValue: limitValue,
actualValue: actualValue,
ruleName: ruleName,
rawData: payload
});
// 在地图上标记该车辆的超速状态
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) {
setVehicleStyleRespectVisibility(
vehicleId,
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);
// 然后创建新的超速状态标签
updateVehicleLabelIfAllowed(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);
// 添加到告警列表
addAlarm({
carId: vehicleId,
carType: vehicles.value[vehicleId]?.type || '未知类型',
time: `${formatTimeRange(new Date())}越界`,
description: '越界告警',
level: 'high',
type: 'report', // 修改类型为report与AlarmNotification.vue中的标签页类型匹配
rawData: payload
});
console.log('越界告警------------------------------------', vehicles.value[vehicleId].type);
// 在地图上标记该车辆的越界状态
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) {
setVehicleStyleRespectVisibility(
vehicleId,
styleManager.value.getVehicleStyle(
vehicleId,
vehicles.value[vehicleId].speed,
vehicles.value[vehicleId].heading
)
);
}
// 更新标签显示
if (vehicles.value[vehicleId].position && labelSystem.value) {
updateVehicleLabelIfAllowed(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) {
setVehicleStyleRespectVisibility(
vehicleId,
styleManager.value.getVehicleStyle(vehicleId, vehicle.speed, vehicle.heading)
);
}
// 更新标签显示为正常状态只显示车辆ID不显示0.00km/h
if (vehicle.position && labelSystem.value) {
// 先移除可能存在的旧标签,避免重复显示
labelSystem.value.removeVehicleLabel(vehicleId);
// 创建新的标准标签
updateVehicleLabelIfAllowed(vehicleId, vehicle.position, vehicle.speed > 0.1 ? vehicle.speed : 0);
}
console.log(`已成功清除车辆${vehicleId}的超速状态`);
}
// 获取车辆类型键值
function getVehicleTypeKey(vehicle) {
if (!vehicle) return 'UNMANNED_VEHICLE';
const override = vehicleDisplayStore.getVehicleTypeOverride(vehicle.id);
const vehicleType = String(override || vehicle.type || '').toUpperCase();
if (!vehicleType) return 'UNMANNED_VEHICLE';
// 根据车辆类型和特征确定分类键值
if (vehicleType === 'AIRCRAFT' || vehicleType === 'AIRCRAFT_IN' || vehicleType === 'AIRCRAFT_OUT' || vehicleType === 'HANGKONG') {
return 'AIRCRAFT';
} else if (vehicle.isAircraft) {
return 'AIRCRAFT';
} else if (vehicleType === 'UNMANNED_VEHICLE' || vehicleType === 'WUREN' || vehicle.isUnmannedVehicle) {
return 'UNMANNED_VEHICLE';
} else if (vehicleType === 'SPECIAL_VEHICLE' || vehicleType === 'TEQIN' || vehicle.isSpecialVehicle) {
return 'AIRPORT_VEHICLE';
} else if (vehicleType === 'SHUTTLE_VEHICLE' || vehicle.isShuttleVehicle) {
return 'SHUTTLE_VEHICLE';
}
// 默认返回无人车类型
return 'UNMANNED_VEHICLE';
}
// 仅当左侧类别的 showLabel 为真时才允许更新/创建标签
function isLabelAllowedForVehicle(vehicleId) {
const vehicle = vehicles.value[vehicleId];
if (!vehicle) return false;
const typeKey = getVehicleTypeKey(vehicle);
if (typeKey !== 'AIRCRAFT' && !vehicleDisplayStore.isVehicleEnabled(vehicleId)) return false;
const category = vehicleCategories.value?.[typeKey];
return !!(category && category.showLabel);
}
function updateVehicleLabelIfAllowed(vehicleId, coordinates, speed, alertInfo) {
if (!labelSystem.value || !labelSystem.value.updateVehicleLabel) return;
if (isLabelAllowedForVehicle(vehicleId)) {
labelSystem.value.updateVehicleLabel(vehicleId, coordinates, speed, alertInfo);
} else {
// 当不允许显示时,确保现有标签保持隐藏(若已存在)
if (labelSystem.value.setLabelVisibility) {
labelSystem.value.setLabelVisibility(vehicleId, false);
}
}
}
// 获取默认车辆样式
function getDefaultVehicleStyle(vehicle) {
if (!vehicle) return new Style({});
const typeUpper = String(vehicle.type || '').toUpperCase();
const isAircraft = vehicle.isAircraft || typeUpper === 'AIRCRAFT' || typeUpper === 'AIRCRAFT_IN' || typeUpper === 'AIRCRAFT_OUT' || typeUpper === 'HANGKONG';
const isUnmanned = vehicle.isUnmannedVehicle || typeUpper === 'UNMANNED_VEHICLE' || typeUpper === 'WUREN' || String(vehicle.type || '').includes('无人');
const iconSrc = isAircraft
? (vehicle.hasAircraftRoute ? aircraftRouteIcon : aircraftIcon)
: (isUnmanned ? carIcon : specialCar);
const heading = vehicle.heading || 0;
// 计算旋转角度
const rotationRad = ((heading - 72) * Math.PI) / 180;
return new Style({
image: new Icon({
src: iconSrc,
scale: isAircraft ? AIRCRAFT_ICON_SCALE : DEFAULT_VEHICLE_ICON_SCALE,
anchor: [0.5, 0.5],
rotation: rotationRad,
})
});
}
function setVehicleStyleRespectVisibility(vehicleId, style) {
const vehicle = vehicles.value[vehicleId];
if (!vehicle || !vehicle.feature) return;
const typeKey = getVehicleTypeKey(vehicle);
const enabled = typeKey === 'AIRCRAFT' ? true : vehicleDisplayStore.isVehicleEnabled(vehicleId);
const category = vehicleCategories.value?.[typeKey];
const visible = enabled && !!(category && category.visible);
vehicle.feature.setStyle(visible ? style : new Style({}));
if (!enabled && labelSystem.value?.setLabelVisibility) {
labelSystem.value.setLabelVisibility(vehicleId, false);
}
}
function applyVehicleStyleWithVisibility(vehicleId, style) {
setVehicleStyleRespectVisibility(vehicleId, style);
}
// 默认获取车辆样式函数(用于动画系统)
// function defaultGetVehicleStyle(vehicleId, speed, heading) {
// const vehicle = vehicles.value[vehicleId];
// if (!vehicle) return new Style({});
// return getDefaultVehicleStyle(vehicle);
// }
// 清除车辆告警状态(如果需要)
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', 'path_conflict_status', 'path_conflict_alert', '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];
});
// 清理所有飞机路线定时器
Object.keys(aircraftRouteTimers).forEach(flightNo => {
clearTimeout(aircraftRouteTimers[flightNo]);
delete aircraftRouteTimers[flightNo];
});
console.log('已清理所有飞机路线定时器');
// 清理所有冲突/违规状态定时器
const statusTimers = violationStatusTimers.value || {};
Object.keys(statusTimers).forEach((key) => {
clearTimeout(statusTimers[key]);
delete statusTimers[key];
});
violationStatusTimers.value = {};
// 移除图层
if (vehicleLayer && props.map) {
props.map.removeLayer(vehicleLayer);
vehicleLayer = null;
}
// 移除飞机路线图层
if (aircraftRouteLayer && props.map) {
props.map.removeLayer(aircraftRouteLayer);
aircraftRouteLayer = null;
}
if (pathConflictLayer && props.map) {
props.map.removeLayer(pathConflictLayer);
pathConflictLayer = null;
pathConflictSource = null;
}
// 清空飞机路线数据
aircraftRoutes.value = {};
pathConflicts.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(); // 创建飞机路线图层
createPathConflictLayer();
}
// 连接WebSocket
connectWebSocket();
const allowDebugInject =
import.meta.env.DEV ||
window.APP_CONFIG?.ENABLE_ROUTE_DEBUG === true ||
new URLSearchParams(window.location.search).has('debugRoute')
if (allowDebugInject) {
window.__injectWsMessage = (msg) => {
const raw = typeof msg === 'string' ? msg : JSON.stringify(msg)
handleWsMessage(raw)
}
window.__injectAircraftRouteUpdate = (payload, options = { fit: true }) => {
handleAircraftRouteUpdate(payload)
if (options && options.fit === false) return
try {
const flightNo = payload?.flightNo
const route = flightNo ? aircraftRoutes.value?.[flightNo] : null
if (route && route.feature && props.map) {
const extent = route.feature.getGeometry().getExtent()
props.map.getView().fit(extent, { padding: [50, 50, 50, 50], duration: 500 })
}
} catch (e) {}
}
}
// 设置定时发送心跳
const pingInterval = setInterval(() => {
if (wsService && wsConnected.value) {
sendPing();
}
}, 30000); // 每30秒发送一次心跳
// 组件卸载时清理资源
onUnmounted(() => {
clearInterval(pingInterval);
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
try {
delete window.__injectWsMessage
delete window.__injectAircraftRouteUpdate
} catch (e) {}
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(); // 创建飞机路线图层
createPathConflictLayer();
}
});
watch(pathConflictDisplayEnabled, (enabled) => {
if (!enabled) {
clearPathConflictDisplay();
clearAlertMessage();
}
});
// 向外暴露方法
defineExpose({
updateVehiclePosition,
wsConnected,
sendPing,
sendSubscribe,
vehicleCategories,
weatherStationVisible,
vehicleDetailVisible,
vehicleDetail,
alarmList, // 暴露告警列表
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 }) {
console.log(`VehicleMovementControl: 设置分类 ${type} 可见性`, { visible, showLabel });
if (vehicleCategories.value[type]) {
// 更新分类设置
vehicleCategories.value[type].visible = visible;
vehicleCategories.value[type].showLabel = showLabel;
console.log(`更新分类设置完成: ${type}`, vehicleCategories.value[type]);
// 立即应用更改 - 更新所有属于此类别的车辆
Object.values(vehicles.value).forEach(vehicle => {
let vehicleTypeKey = getVehicleTypeKey(vehicle);
// 如果车辆属于当前修改的类别
if (vehicleTypeKey === type) {
console.log(`更新车辆 ${vehicle.id} (${vehicleTypeKey}) 的显示状态:`, { visible, showLabel });
// 更新图标可见性
if (vehicle.feature) {
if (visible) {
// 显示图标
if (styleManager.value) {
setVehicleStyleRespectVisibility(
vehicle.id,
styleManager.value.getVehicleStyle(vehicle.id, vehicle.speed, vehicle.heading)
);
} else {
setVehicleStyleRespectVisibility(
vehicle.id,
getDefaultVehicleStyle(vehicle)
);
}
} else {
// 隐藏图标
vehicle.feature.setStyle(new Style({}));
}
}
// 控制文本标签可见性
if (labelSystem.value) {
if (labelSystem.value.setLabelVisibility) {
labelSystem.value.setLabelVisibility(vehicle.id, showLabel);
} else if (labelSystem.value.updateVehicleLabel) {
// 如果没有setLabelVisibility方法使用updateVehicleLabel
if (showLabel && vehicle.position) {
updateVehicleLabelIfAllowed(vehicle.id, vehicle.position, vehicle.speed);
} else {
labelSystem.value.removeVehicleLabel(vehicle.id);
}
}
}
}
});
}
},
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);
},
debugInjectAircraftRouteUpdate(payload) {
return handleAircraftRouteUpdate(payload)
},
// 告警通知相关方法
showAlarmNotification(message, type, duration) {
if (alarmNotification.value) {
alarmNotification.value.showNotification(message, type, duration);
}
},
hideAlarmNotification() {
if (alarmNotification.value) {
alarmNotification.value.hideNotification();
}
},
handleAlarmClose() {
// 告警通知组件关闭时,可以在这里执行一些清理或状态重置
console.log('告警通知组件已关闭');
},
addAlarm(alarm) {
return addAlarm(alarm);
},
updateAlarm(alarmId, updates) {
return updateAlarm(alarmId, updates);
},
removeAlarm(alarmId) {
return removeAlarm(alarmId);
},
clearAlarms() {
return clearAlarms();
},
updateAlarmNotification() {
return updateAlarmNotification();
},
toggleAlarmNotification() {
return toggleAlarmNotification();
},
handleAircraftRouteUpdate(payload) {
return handleAircraftRouteUpdate(payload);
},
drawTestRoute() {
const routes = [
{
flightNo: '805-807',
routeType: 'TEST',
routeStatus: 'ACTIVE',
points: [
[40508513.641064, 4024186.813497],
[40508714.127776, 4024250.934516],
[40508853.166318, 4024293.929307],
[40509022.478135, 4024346.688892]
]
},
{
flightNo: 'M4 829-826-K1',
routeType: 'TEST',
routeStatus: 'ACTIVE',
points: [
[40507849.913313, 4023625.932486],
[40507732.597485, 4023991.671027],
[40507723.998527, 4023986.908527],
[40507674.841898, 4024138.30738]
]
},
{
flightNo: 'M1 K2-1-1-1#消防',
routeType: 'TEST',
routeStatus: 'ACTIVE',
points: [
[40509113.197805, 4024029.951692],
[40508997.734226, 4024393.687528],
[40509007.523809, 4024395.275028],
[40508957.783071, 4024548.361738]
]
}
];
routes.forEach(r => {
const wktPoints = (r.points || []).map((p) => `${p[0]} ${p[1]}`).join(', ')
const payload = {
flightNo: r.flightNo,
routeType: r.routeType,
routeStatus: r.routeStatus,
routeGeometry: `LINESTRING(${wktPoints})`
};
handleAircraftRouteUpdate(payload);
});
}
});
// 添加一个默认的getVehicleStyle函数
function defaultGetVehicleStyle(id, _speed, heading) {
// 检查车辆类型
const vehicle = vehicles.value[id];
if (!vehicle) return createDefaultStyle(carIcon, heading);
// 根据车辆类型选择图标与VehicleStyleManager保持一致
if (vehicle.isAircraft) {
// 航空器使用aircraft.png图标
return createDefaultStyle(aircraftIcon, heading);
} else if (vehicle.type === '无人车' || vehicle.type === 'UNMANNED_VEHICLE') {
// 无人车使用noPeopleCar.svg图标
return createDefaultStyle(carIcon, heading);
} else {
// 其他车辆使用specialCar.svg图标
return createDefaultStyle(specialCar, 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: (iconSrc === aircraftIcon || iconSrc === aircraftRouteIcon) ? AIRCRAFT_ICON_SCALE : DEFAULT_VEHICLE_ICON_SCALE,
anchor: [0.5, 0.5],
rotation: rotationRad, // 应用计算后的旋转角度
})
});
}
</script>
<style scoped>
/* 告警按钮样式 */
.alarm-btn {
position: absolute;
top: 20px;
left: 20px;
width: 40px;
height: 40px;
background-color: rgba(41, 44, 56, 0.8);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: all 0.3s;
}
.alarm-btn:hover {
background-color: rgba(41, 44, 56, 1);
transform: scale(1.05);
}
.alarm-btn img {
width: 24px;
height: 24px;
}
.alarm-badge {
position: absolute;
top: -5px;
right: -5px;
background-color: #ff4d4f;
color: white;
font-size: 12px;
min-width: 18px;
height: 18px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
}
.path-conflict-card {
position: absolute;
left: 28px;
top: calc(23% + 160px);
width: 246px;
color: #fff;
z-index: 1000;
background: rgba(41, 44, 56, 0.95);
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 8px;
box-shadow: 0 10px 15px rgba(2, 2, 2, 0.25);
overflow: hidden;
}
.path-conflict-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 7px 10px;
font-size: 14px;
font-weight: 700;
background: rgba(52, 55, 68, 0.96);
border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.path-conflict-level {
padding: 1px 7px;
border-radius: 10px;
font-size: 12px;
background: rgba(59, 130, 246, 0.28);
}
.path-conflict-body {
padding: 8px 10px 10px;
font-size: 12px;
line-height: 1.8;
}
.path-conflict-muted,
.path-conflict-debug {
color: rgba(255, 255, 255, 0.78);
}
.path-conflict-reason {
color: rgba(255, 214, 165, 0.95);
}
.path-conflict-card.level-WARNING .path-conflict-level {
background: rgba(245, 158, 11, 0.35);
}
.path-conflict-card.level-CRITICAL .path-conflict-level,
.path-conflict-card.level-EMERGENCY .path-conflict-level {
background: rgba(239, 68, 68, 0.38);
}
</style>