468 lines
16 KiB
HTML
468 lines
16 KiB
HTML
<!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: purple;
|
|
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
|
border: 2px solid white;
|
|
}
|
|
.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;
|
|
}
|
|
.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.361999, 120.088503], 17);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
maxZoom: 19,
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
// 定义路口坐标
|
|
const WEST_INTERSECTION = {
|
|
latitude: 36.361999,
|
|
longitude: 120.086003
|
|
};
|
|
const EAST_INTERSECTION = {
|
|
latitude: 36.361999,
|
|
longitude: 120.090003
|
|
};
|
|
|
|
// 存储所有标记
|
|
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);
|
|
}
|
|
}
|
|
|
|
function updateTrafficLight(data) {
|
|
const id = data.id;
|
|
const position = [data.position.latitude, data.position.longitude];
|
|
const state = data.status === 0 ? 'red' : 'green';
|
|
|
|
let light = trafficLights.get(id);
|
|
if (!light) {
|
|
light = L.marker(position, {
|
|
icon: createIcon(`traffic-light traffic-light-${state}`)
|
|
}).addTo(map);
|
|
light.bindTooltip(id);
|
|
trafficLights.set(id, light);
|
|
} else {
|
|
light.setIcon(createIcon(`traffic-light traffic-light-${state}`));
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
switch (data.type) {
|
|
case 'position_update':
|
|
type = 'position';
|
|
updatePosition(data);
|
|
break;
|
|
case 'traffic_light_status':
|
|
updateTrafficLight(data);
|
|
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;
|
|
}
|
|
|
|
const formattedData = JSON.stringify(data, null, 2);
|
|
let message = '收到消息:\n' + formattedData;
|
|
log(message, type);
|
|
} catch (e) {
|
|
log('收到消息: ' + event.data, 'info');
|
|
}
|
|
};
|
|
} catch (error) {
|
|
log('连接失败: ' + error, 'error');
|
|
}
|
|
}
|
|
|
|
function disconnect() {
|
|
if (!ws) {
|
|
log('未连接', 'error');
|
|
return;
|
|
}
|
|
|
|
ws.close();
|
|
ws = null;
|
|
|
|
// 清除所有标记
|
|
markers.forEach(marker => map.removeLayer(marker));
|
|
markers.clear();
|
|
trafficLights.forEach(light => map.removeLayer(light));
|
|
trafficLights.clear();
|
|
}
|
|
|
|
// 添加道路刻度标记函数
|
|
function addRoadMarks(startPoint, endPoint, isLatitude = false) {
|
|
const start = isLatitude ? startPoint[0] : startPoint[1];
|
|
const end = isLatitude ? endPoint[0] : endPoint[1];
|
|
const fixed = isLatitude ? startPoint[1] : startPoint[0];
|
|
const step = 0.0005; // 约50米
|
|
const direction = start < end ? 1 : -1;
|
|
|
|
for (let i = 0; Math.abs(i) <= Math.abs(end - start); i += step * direction) {
|
|
const position = isLatitude ?
|
|
[start + i, fixed] :
|
|
[fixed, start + i];
|
|
|
|
// 添加刻度线
|
|
const markLine = isLatitude ?
|
|
[[position[0], position[1] - 0.0001], [position[0], position[1] + 0.0001]] :
|
|
[[position[0] - 0.0001, position[1]], [position[0] + 0.0001, position[1]]];
|
|
|
|
L.polyline(markLine, {
|
|
color: '#666',
|
|
weight: 2
|
|
}).addTo(map);
|
|
|
|
// 添加距离标签(以米为单位)
|
|
const distance = Math.abs(Math.round(i / step) * 50);
|
|
if (distance > 0) { // 只显示非零距离
|
|
const label = L.divIcon({
|
|
className: 'distance-label',
|
|
html: distance + 'm',
|
|
iconSize: [40, 20],
|
|
iconAnchor: [20, 10]
|
|
});
|
|
|
|
L.marker(position, {
|
|
icon: label,
|
|
interactive: false
|
|
}).addTo(map);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 添加道路
|
|
// 东西向主路
|
|
const mainRoadEW = L.polyline([
|
|
[WEST_INTERSECTION.latitude, WEST_INTERSECTION.longitude - 0.002],
|
|
[EAST_INTERSECTION.latitude, EAST_INTERSECTION.longitude + 0.002]
|
|
], {
|
|
color: '#999',
|
|
weight: 8
|
|
}).addTo(map);
|
|
|
|
// 西路口南北向道路
|
|
const westRoadNS = L.polyline([
|
|
[WEST_INTERSECTION.latitude + 0.002, WEST_INTERSECTION.longitude],
|
|
[WEST_INTERSECTION.latitude - 0.002, WEST_INTERSECTION.longitude]
|
|
], {
|
|
color: '#999',
|
|
weight: 8
|
|
}).addTo(map);
|
|
|
|
// 东路口南北向道路
|
|
const eastRoadNS = L.polyline([
|
|
[EAST_INTERSECTION.latitude + 0.002, EAST_INTERSECTION.longitude],
|
|
[EAST_INTERSECTION.latitude - 0.002, EAST_INTERSECTION.longitude]
|
|
], {
|
|
color: '#999',
|
|
weight: 8
|
|
}).addTo(map);
|
|
|
|
// 添加刻度标记
|
|
// 西路口南北向刻度
|
|
addRoadMarks(
|
|
[WEST_INTERSECTION.latitude - 0.002, WEST_INTERSECTION.longitude],
|
|
[WEST_INTERSECTION.latitude + 0.002, WEST_INTERSECTION.longitude],
|
|
true
|
|
);
|
|
|
|
// 东路口南北向刻度
|
|
addRoadMarks(
|
|
[EAST_INTERSECTION.latitude - 0.002, EAST_INTERSECTION.longitude],
|
|
[EAST_INTERSECTION.latitude + 0.002, EAST_INTERSECTION.longitude],
|
|
true
|
|
);
|
|
|
|
// 东西向刻度
|
|
addRoadMarks(
|
|
[WEST_INTERSECTION.latitude, WEST_INTERSECTION.longitude - 0.002],
|
|
[EAST_INTERSECTION.latitude, EAST_INTERSECTION.longitude + 0.002],
|
|
false
|
|
);
|
|
</script>
|
|
</body>
|
|
</html> |