From 31304a5ff56c0f06590e487bf3f8f5441d2ce6f1 Mon Sep 17 00:00:00 2001 From: renna <576157508@qq.com> Date: Tue, 15 Jul 2025 17:46:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A3=9E=E6=9C=BA=E8=B7=AF?= =?UTF-8?q?=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VehicleMovementControlRefactored.vue | 362 +++++++++++++++++- src/utils/test_websocket.html | 25 +- 2 files changed, 379 insertions(+), 8 deletions(-) diff --git a/src/components/map/controls/VehicleMovementControlRefactored.vue b/src/components/map/controls/VehicleMovementControlRefactored.vue index fec2a11..83a4274 100644 --- a/src/components/map/controls/VehicleMovementControlRefactored.vue +++ b/src/components/map/controls/VehicleMovementControlRefactored.vue @@ -47,9 +47,10 @@ 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 } from 'ol/style'; +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'; @@ -132,6 +133,11 @@ 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: '滑入航空器' }, @@ -188,6 +194,26 @@ function createVehicleLayer() { 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) { @@ -248,9 +274,12 @@ function updateVehiclePosition(vehicleData) { const { object_id, object_type, position, heading, speed } = vehicleData; - // 计算正确的旋转角度,不再减去72度 - const rotationRad = (heading * Math.PI) / 180; - console.log(`车辆${object_id}的heading值: ${heading}, 计算的旋转角度: ${rotationRad} 弧度, ${rotationRad * 180 / Math.PI} 度`); + // 计算正确的旋转角度 + // 在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; @@ -463,6 +492,11 @@ function handleWsMessage(message) { console.log('收到车辆控制指令:', data.payload); break; + case 'aircraftRouteUpdate': + console.log('收到飞机路由更新:', data.data); + handleAircraftRouteUpdate(data.data); + break; + default: // 其他类型的消息可以根据需要处理 console.log(`未知消息类型: ${data.type}`, data); @@ -487,6 +521,269 @@ function handlePositionUpdate(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) { @@ -888,6 +1185,15 @@ function cleanup() { 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 => { @@ -907,6 +1213,7 @@ onMounted(() => { // 创建图层并连接WebSocket if (props.map) { createVehicleLayer(); + createAircraftRouteLayer(); // 创建飞机路线图层 } // 连接WebSocket @@ -964,6 +1271,7 @@ onDeactivated(() => { watch(() => props.map, (newMap) => { if (newMap) { createVehicleLayer(); + createAircraftRouteLayer(); // 创建飞机路线图层 } }); @@ -1084,6 +1392,42 @@ defineExpose({ 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); } }); @@ -1107,14 +1451,20 @@ function defaultGetVehicleStyle(id, _speed, heading) { function createDefaultStyle(iconSrc, heading) { // 确保heading是有效的数值并正确转换为弧度 const validHeading = heading !== undefined ? Number(heading) : 0; - const rotationRad = (validHeading * Math.PI) / 180; + + // 计算正确的旋转角度 + // 在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, // 正确应用旋转角度 + rotation: rotationRad, // 应用计算后的旋转角度 }) }); } diff --git a/src/utils/test_websocket.html b/src/utils/test_websocket.html index f5fd0c9..da2399f 100644 --- a/src/utils/test_websocket.html +++ b/src/utils/test_websocket.html @@ -1,3 +1,4 @@ + @@ -263,7 +264,12 @@ log('collisionLog', `碰撞预警: ${message.payload?.message || JSON.stringify(message.payload)}`, 'error'); break; case 'rule_violation': - log('collisionLog', `规则违规: ${message.payload?.ruleType || '未知规则'}`, 'warning'); + const vehicleLicense = message.payload?.vehicleLicense || '未知车辆'; + const ruleName = message.payload?.ruleName || '未知规则'; + const violationType = message.payload?.violationType || '未知类型'; + const alertLevel = message.payload?.alertLevel || '未知级别'; + const description = message.payload?.description || '无描述'; + log('collisionLog', `规则违规: ${vehicleLicense} - ${ruleName} (${violationType}/${alertLevel}): ${description}`, 'warning'); break; case 'rule_execution_status': log('collisionLog', `规则执行状态: ${message.payload?.status || '未知状态'}`, 'info'); @@ -277,6 +283,19 @@ case 'traffic_light_status': log('collisionLog', `红绿灯状态: ${message.payload?.intersection_id || '未知路口'}`, 'info'); break; + case 'aircraftRouteUpdate': + const flightNo = message.payload?.flightNo || '未知航班'; + const routeType = message.payload?.routeType || '未知路由'; + const routeStatus = message.payload?.status || '未知状态'; + log('collisionLog', `航空器路由更新: ${flightNo} - ${routeType} (${routeStatus})`, 'info'); + break; + case 'path_conflict_alert': + const object1 = message.payload?.object1Name || '未知对象1'; + const object2 = message.payload?.object2Name || '未知对象2'; + const conflictType = message.payload?.alertType || '未知类型'; + const conflictLevel = message.payload?.alertLevel || '未知级别'; + log('collisionLog', `路径冲突告警: ${object1} vs ${object2} (${conflictType}/${conflictLevel})`, 'error'); + break; default: log('collisionLog', `未知消息类型: ${message.type}`, 'info'); } @@ -318,4 +337,6 @@ }; - \ No newline at end of file + + + \ No newline at end of file