CollisionAvoidance/tools/map_websocket.html

655 lines
23 KiB
HTML
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.

<!DOCTYPE html>
<html>
<head>
<title>机场车辆监控</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
}
.container {
display: flex;
gap: 20px;
}
#map {
height: 800px;
width: 60%;
border: 1px solid #ccc;
}
#messages {
width: 40%;
height: 800px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
font-family: monospace;
}
.controls {
margin-top: 10px;
}
.error { color: red; }
.success { color: green; }
.info { color: blue; }
.position { color: #666; }
.warning { color: #f90; }
.command { color: #800080; }
.vehicle-icon {
width: 20px;
height: 20px;
background-color: black;
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
border: 2px solid white;
}
.aircraft-icon {
width: 50px;
height: 50px;
background-color: rgba(128, 0, 128, 0.5);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
border: 2px solid white;
position: relative;
}
.aircraft-icon::after {
content: '';
position: absolute;
width: 6px;
height: 6px;
background-color: black;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.special-vehicle-icon {
width: 20px;
height: 20px;
background-color: orange;
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
border: 2px solid white;
}
.intersection-icon {
width: 30px;
height: 30px;
background-color: #666;
clip-path: polygon(40% 0%, 60% 0%, 60% 40%, 100% 40%, 100% 60%, 60% 60%, 60% 100%, 40% 100%, 40% 60%, 0% 60%, 0% 40%, 40% 40%);
border: 2px solid white;
}
.traffic-light {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid white;
z-index: 1000;
}
.traffic-light-red {
background-color: red;
}
.traffic-light-green {
background-color: green;
}
.countdown-label {
background: #000;
border: 1px solid #666;
border-radius: 2px;
font-family: "Digital-7", "DSEG7 Classic", Monaco, monospace;
font-weight: normal;
font-size: 12px;
text-align: center;
white-space: nowrap;
padding: 1px 2px;
line-height: 14px;
min-width: 28px;
box-shadow: 0 0 2px rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
height: 14px;
}
.countdown-red {
color: #ff3333;
}
.countdown-green {
color: #33ff33;
}
.distance-label {
background: none;
border: none;
color: #666;
font-size: 12px;
text-align: center;
white-space: nowrap;
}
.command-text {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 10px;
font-weight: bold;
pointer-events: none;
z-index: 1000;
}
</style>
</head>
<body>
<h2>机场车辆监控系统</h2>
<div class="container">
<div id="map"></div>
<div id="messages"></div>
</div>
<div class="controls">
<button onclick="connect()">连接</button>
<button onclick="disconnect()">断开</button>
<button onclick="clearMessages()">清空日志</button>
</div>
<script>
let ws = null;
const messagesDiv = document.getElementById('messages');
// 初始化地图
const map = L.map('map').setView([36.35305878, 120.08558121], 17);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// 定义路口坐标
const T1_INTERSECTION = {
latitude: 36.35496367,
longitude: 120.0868853
};
const T2_INTERSECTION = {
latitude: 36.35448347,
longitude: 120.08502054
};
const T3_INTERSECTION = {
latitude: 36.35406879,
longitude: 120.08341044
};
const T4_INTERSECTION = {
latitude: 36.35305878,
longitude: 120.08558121
};
const T6_INTERSECTION = {
latitude: 36.35074527,
longitude: 120.08649105
};
const T7_INTERSECTION = {
latitude: 36.35052372,
longitude: 120.08562915
};
const T8_INTERSECTION = {
latitude: 36.35004529,
longitude: 120.08676664
};
const T10_INTERSECTION = {
latitude: 36.34917893,
longitude: 120.08710569
};
const T11_INTERSECTION = {
latitude: 36.3509885,
longitude: 120.0873865
};
// 存储所有标记
const markers = new Map();
const trafficLights = new Map();
const intersections = new Map();
// 创建自定义图标
function createIcon(className, command = '') {
let size;
if (className.includes('aircraft')) {
size = [50, 50]; // 50米正方形
} else if (className.includes('vehicle')) {
size = [20, 20]; // 10米正方形
} else if (className.includes('traffic-light')) {
size = [12, 12]; // 10像素的红绿灯
} else {
size = [20, 20]; // 其他图标保持原样
}
// 如果有指令,创建带指令的图标
if (command && className === 'vehicle-icon') {
const html = `
<div style="width:${size[0]}px;height:${size[1]}px;position:relative;">
<div class="${className}"></div>
<div class="command-text">${command}</div>
</div>`;
return L.divIcon({
html: html,
className: '',
iconSize: size,
iconAnchor: [size[0]/2, size[1]/2]
});
}
// 没有指令时,创建普通图标
return L.divIcon({
className: className,
iconSize: size,
iconAnchor: [size[0]/2, size[1]/2]
});
}
function log(message, type = 'info') {
const div = document.createElement('div');
div.className = type;
div.textContent = `${new Date().toLocaleTimeString()} - ${message}`;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function clearMessages() {
messagesDiv.innerHTML = '';
}
function updatePosition(data) {
const id = data.object_id;
const position = [data.position.latitude, data.position.longitude];
let iconClass;
// 根据ID前缀确定图标类型
if (data.object_type === 'aircraft') {
iconClass = 'aircraft-icon'; // 六形
} else if (id.startsWith('TQ')) {
iconClass = 'special-vehicle-icon'; // 正方形
} else {
iconClass = 'vehicle-icon'; // 三角形
}
let marker = markers.get(id);
if (!marker) {
// 创建新标
marker = L.marker(position, {
icon: createIcon(iconClass)
}).addTo(map);
marker.bindTooltip(id); // 添加标签显示ID
markers.set(id, marker);
} else {
// 更新现有标记位置
marker.setLatLng(position);
}
// 更新标记旋转(根据航向)
if (data.heading !== undefined) {
marker.setRotationAngle(data.heading);
}
}
// 添加红绿灯状态和倒计时变量
let lastTrafficLightState = null;
let countdownInterval = null;
let countdown = 10; // 10秒倒计时
function updateTrafficLight(data) {
const id = data.id;
const position = [data.position.latitude, data.position.longitude];
const state = data.status === 0 ? 'red' : 'green';
// 检查是否是西路口TL001的红绿灯状态变化
if (id === 'TL001' && lastTrafficLightState !== state) {
lastTrafficLightState = state;
// 重置倒计时
countdown = 10;
// 清除现有的倒计时
if (countdownInterval) {
clearInterval(countdownInterval);
}
// 启动新的倒计时
countdownInterval = setInterval(() => {
countdown = Math.max(0, countdown - 1);
// 更新红绿灯标签显示
const light = trafficLights.get(id);
if (light) {
// 格式化倒计时为 0:SS 格式
const countdownStr = `0:${countdown.toString().padStart(2, '0')}`;
const label = L.divIcon({
className: `countdown-label countdown-${state}`,
html: countdownStr,
iconSize: [32, 16],
iconAnchor: [16, 30]
});
// 更新或创建倒计时标签
if (!light.countdownMarker) {
light.countdownMarker = L.marker(position, {
icon: label,
interactive: false
}).addTo(map);
} else {
light.countdownMarker.setIcon(label);
}
}
}, 1000);
}
let light = trafficLights.get(id);
if (!light) {
light = L.marker(position, {
icon: createIcon(`traffic-light traffic-light-${state}`)
}).addTo(map);
// 为西路口添加倒计时显示
if (id === 'TL001') {
const countdownStr = `0:${countdown.toString().padStart(2, '0')}`;
const label = L.divIcon({
className: `countdown-label countdown-${state}`,
html: countdownStr,
iconSize: [32, 16],
iconAnchor: [16, 30]
});
light.countdownMarker = L.marker(position, {
icon: label,
interactive: false
}).addTo(map);
}
trafficLights.set(id, light);
} else {
light.setIcon(createIcon(`traffic-light traffic-light-${state}`));
// 更新西路口的倒计时显示
if (id === 'TL001' && light.countdownMarker) {
const countdownStr = `0:${countdown.toString().padStart(2, '0')}`;
const label = L.divIcon({
className: `countdown-label countdown-${state}`,
html: countdownStr,
iconSize: [32, 16],
iconAnchor: [16, 30]
});
light.countdownMarker.setIcon(label);
}
}
}
function updateVehicleCommand(vehicleId, commandType) {
console.log('更新车辆指令:', vehicleId, commandType);
// 只处理无人车
if (!vehicleId.startsWith('QN')) {
return;
}
// 如果是 SIGNAL 指令,不更新显示
if (commandType === 'SIGNAL') {
console.log('忽略 SIGNAL 指令');
return;
}
// 获取指令字母
let commandText = '';
switch(commandType) {
case 'ALERT':
commandText = 'A';
break;
case 'WARNING':
commandText = 'W';
break;
case 'RESUME':
commandText = 'R';
break;
default:
commandText = '';
}
console.log('指令文本:', commandText);
// 更新图标
const marker = markers.get(vehicleId);
if (marker && commandText) {
console.log('设置新标:', vehicleId, commandText);
marker.setIcon(createIcon('vehicle-icon', commandText));
} else if (marker) {
marker.setIcon(createIcon('vehicle-icon'));
}
}
function connect() {
if (ws) {
log('已经连接,请先断开', 'error');
return;
}
try {
ws = new WebSocket('ws://localhost:8010');
ws.onopen = () => {
log('连接成功', 'success');
};
ws.onclose = () => {
log('连接关闭', 'info');
ws = null;
};
ws.onerror = (error) => {
log('发生错误: ' + error, 'error');
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
let type = 'info'; // 默认类型
let message = '';
switch (data.type) {
case 'position_update':
type = 'position';
updatePosition(data);
break;
case 'traffic_light_status':
type = 'info';
updateTrafficLight(data);
message = `红绿灯状态更新:\n信号灯: ${data.id}\n状态: ${data.status === 0 ? '红灯' : '绿灯'}`;
break;
case 'collision_warning':
type = 'warning';
break;
case 'vehicle_command':
type = 'command';
console.log('收到指令消息:', data); // 调试日志
updateVehicleCommand(data.vehicleId, data.commandType);
// 为控制指令添加中文描述
const commandTypes = {
'SIGNAL': '信号灯指令',
'ALERT': '告警指令',
'WARNING': '预警指令',
'RESUME': '恢复指令'
};
const reasons = {
'TRAFFIC_LIGHT': '红绿灯控制',
'AIRCRAFT_CROSSING': '航空器交叉',
'SPECIAL_VEHICLE': '特勤车辆',
'AIRCRAFT_PUSH': '航空器推出',
'RESUME_TRAFFIC': '恢复通行'
};
message = `收到车辆控制指令:\n车辆: ${data.vehicleId}\n` +
`指令类型: ${commandTypes[data.commandType] || data.commandType}\n` +
`原因: ${reasons[data.reason] || data.reason}\n` +
(data.targetLatitude !== undefined ? `目标位置: (${data.targetLatitude}, ${data.targetLongitude})\n` : '') +
(data.signalState ? `信号灯状态: ${data.signalState}\n` : '') +
(data.intersectionId ? `路口ID: ${data.intersectionId}\n` : '') +
`时间戳: ${new Date(data.timestamp/1000000).toLocaleString()}`;
break;
}
// 如果没有特定消息,使用格式化的数据
if (!message) {
message = '收到消息:\n' + JSON.stringify(data, null, 2);
}
log(message, type);
} catch (e) {
log('收到消息: ' + event.data, 'info');
}
};
} catch (error) {
log('连接失败: ' + error, 'error');
}
}
function disconnect() {
if (!ws) {
log('未连接', 'error');
return;
}
// 清除倒计时
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
lastTrafficLightState = null;
countdown = 10;
// 清除倒计时标记
trafficLights.forEach(light => {
if (light.countdownMarker) {
map.removeLayer(light.countdownMarker);
light.countdownMarker = null;
}
});
ws.close();
ws = null;
// 清除所有标记
markers.forEach(marker => map.removeLayer(marker));
markers.clear();
trafficLights.forEach(light => map.removeLayer(light));
trafficLights.clear();
}
// 添加道路刻度标记函数
function addRoadMarks(startPoint, endPoint) {
// 计算两点之间的距离(米)
const lat1 = startPoint[0];
const lon1 = startPoint[1];
const lat2 = endPoint[0];
const lon2 = endPoint[1];
// 计算道路角度(考虑经纬度投影)
const latMid = (lat1 + lat2) / 2; // 使用中点纬度来计算经度缩放
const lonScale = Math.cos(latMid * Math.PI / 180); // 经度缩放因子
const dx = (lon2 - lon1) * lonScale;
const dy = lat2 - lat1;
const angle = Math.atan2(dy, dx);
// 计算垂直于道路的方向(只在右侧显示刻度)
const perpAngle = angle + Math.PI / 2;
const markLength = 0.00005; // 保持您设置的较短刻度线长度
const offset = 0.00004; // 向右偏移一点,避免与道路重叠
// 计算总距离
const dist = Math.sqrt(dx * dx + dy * dy);
// 每50米一个刻度
const step = 0.0005; // 约50米
const steps = Math.floor(dist / step);
for (let i = 0; i <= steps; i++) {
// 计算刻度位置
const ratio = i / steps;
// 在经纬度坐标系中正插值
const pos = [
lat1 + dy * ratio,
lon1 + (dx / lonScale) * ratio
];
// 计算垂直偏移(考虑经纬度投影)
const offsetPos = [
pos[0] + Math.sin(perpAngle) * offset,
pos[1] + Math.cos(perpAngle) * offset / lonScale
];
// 计算刻度线终点(考虑经纬度投影)
const markEnd = [
offsetPos[0] + Math.sin(perpAngle) * markLength,
offsetPos[1] + Math.cos(perpAngle) * markLength / lonScale
];
// 添加刻度线
L.polyline([offsetPos, markEnd], {
color: '#666',
weight: 1.5
}).addTo(map);
// 添加距离标签
const distance = Math.round(i * 50);
if (distance > 0) {
const label = L.divIcon({
className: 'distance-label',
html: distance + 'm',
iconSize: [40, 20],
iconAnchor: [-5, 10]
});
L.marker(markEnd, {
icon: label,
interactive: false
}).addTo(map);
}
}
}
// 添加道路
// T2 到 T10主路
const mainRoadEW = L.polyline([
[T2_INTERSECTION.latitude, T2_INTERSECTION.longitude],
[T10_INTERSECTION.latitude, T10_INTERSECTION.longitude]
], {
color: '#999',
weight: 8
}).addTo(map);
// T1 到 T3道路
const westRoadNS = L.polyline([
[T1_INTERSECTION.latitude, T1_INTERSECTION.longitude],
[T3_INTERSECTION.latitude, T3_INTERSECTION.longitude]
], {
color: '#999',
weight: 8
}).addTo(map);
// T7 到 T11道路
const eastRoadNS = L.polyline([
[T7_INTERSECTION.latitude, T7_INTERSECTION.longitude],
[T11_INTERSECTION.latitude, T11_INTERSECTION.longitude]
], {
color: '#999',
weight: 8
}).addTo(map);
// 添加刻度标记
// T1 到 T3路刻度
addRoadMarks(
[T1_INTERSECTION.latitude, T1_INTERSECTION.longitude],
[T3_INTERSECTION.latitude, T3_INTERSECTION.longitude]
);
// T7 到 T11路刻度
addRoadMarks(
[T7_INTERSECTION.latitude, T7_INTERSECTION.longitude],
[T11_INTERSECTION.latitude, T11_INTERSECTION.longitude]
);
// T2 到 T10路刻度
addRoadMarks(
[T2_INTERSECTION.latitude, T2_INTERSECTION.longitude],
[T10_INTERSECTION.latitude, T10_INTERSECTION.longitude]
);
</script>
</body>
</html>