Initial commit

This commit is contained in:
sladro 2026-01-27 15:24:05 +08:00
commit 1fc14d7438
93 changed files with 16791 additions and 0 deletions

14
.clang-format Normal file
View File

@ -0,0 +1,14 @@
# 精简版 K&R 风格配置
BasedOnStyle: LLVM
BreakBeforeBraces: Linux
IndentWidth: 4
AllowShortFunctionsOnASingleLine: Empty
# 显式禁用所有大括号换行
BraceWrapping:
AfterClass: false
AfterStruct: false
AfterUnion: false
AfterFunction: false
AfterNamespace: false
AfterControlStatement: Never

22
.clangd Normal file
View File

@ -0,0 +1,22 @@
CompileFlags:
Add:
- "-I${workspaceFolder}/src"
- "-I/opt/homebrew/include"
- "-I/opt/homebrew/Cellar/boost/1.86.0_2/include"
- "-I/opt/homebrew/Cellar/nlohmann-json/3.11.3/include"
Remove:
- "-W*"
- "-std=*"
Compiler: clang++
Diagnostics:
ClangTidy:
Add:
- modernize*
- performance*
Remove:
- modernize-use-trailing-return-type
UnusedIncludes: Strict
Index:
Background: Build

3
.cursorignore Normal file
View File

@ -0,0 +1,3 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
.cursorignore
build/

44
.cursorrules Normal file
View File

@ -0,0 +1,44 @@
请遵循:
- 最小化修改代码,不要修改任何与解决当前问题无关的代码(包括注释),一定不能删除任何其他的代码
- 使用源代码中的格式化方式和注释风格
- 日志的字符串格式采用拼接方式,不要使用{}占位符
请遵循以下 Markdown 规范:
1. 标题层级
- 文档标题使用一级标题 (#)
- 主要章节使用二级标题 (##)
- 子章节使用三级及以下标题
- 标题和内容之间空一行
- 标题层级不跳跃使用
2. 列表格式
- 无序列表统一使用 -
- 列表项前后空一行
- 列表嵌套使用两个空格缩进
3. 代码块
- 使用 ```language 指定语言
- 代码块前后空一行
- 代码添加必要的注释
4. 内容格式
- 中英文之间加空格
- 使用半角标点符号
- 代码、参数等使用反引号包裹
- 重要内容使用加粗或斜体
5. 文档结构
- 章节层次分明
- 内容组织有序
- 格式统一一致
请遵循如下 C++ 规范:
- 使用 C++17 标准
- 使用 CMake 3.14 及以上版本
- 使用 nlohmann_json 3.11.3 版本
- 使用 FetchContent 管理第三方库
- 使用 Google Test 进行单元测试
- 使用 Google Mock 进行单元测试
- 使用 Google Benchmark 进行性能测试
- 在头文件中定义函数,在源文件中实现函数

47
.gitignore vendored Normal file
View File

@ -0,0 +1,47 @@
# Build directories
build/
out/
logs/
cmake-build-*/
# IDE directories
.idea/
.vscode/
*.swp
*.swo
# Compiled files
*.o
*.obj
*.exe
*.dll
*.so
*.dylib
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
# Python cache files
__pycache__/
*.py[cod]
pyvenv.cfg
# macOS system files
.DS_Store
.AppleDouble
.LSOverride
# Log files
*.log
# Dependency directories
/vendor/
/third_party/
# Test generated files
Testing/
assistant_snippet*

197
CMakeLists.txt Normal file
View File

@ -0,0 +1,197 @@
cmake_minimum_required(VERSION 3.14...3.27)
#
if(NOT APPLE)
set(CMAKE_C_COMPILER "/usr/local/bin/gcc")
set(CMAKE_CXX_COMPILER "/usr/local/bin/g++")
endif()
# CMake
cmake_policy(SET CMP0074 NEW) #
# FetchContent
include(FetchContent)
# compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(collision_avoidance)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
#
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
message(FATAL_ERROR "GCC 版本需要 >= 7.0 以支持 C++17")
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
message(FATAL_ERROR "Clang 版本需要 >= 5.0 以支持 C++17")
endif()
endif()
#
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# Boost
set(Boost_NO_WARN_NEW_VERSIONS 1)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF)
if(NOT APPLE)
# CentOS 使 Boost 1.69
set(BOOST_ROOT "/usr/include/boost169")
set(BOOST_LIBRARYDIR "/usr/lib64/boost169")
else()
# macOS 使 Homebrew Boost
set(BOOST_ROOT "/opt/homebrew/Cellar/boost/1.87.0")
set(BOOST_LIBRARYDIR "/opt/homebrew/Cellar/boost/1.87.0/lib")
endif()
# CMake find_package(Boost)
if(POLICY CMP0167)
cmake_policy(SET CMP0167 OLD) # 使 FindBoost
endif()
find_package(Boost 1.69.0 REQUIRED COMPONENTS
system
filesystem
thread
chrono
date_time
atomic
regex # regex
)
# libcurl
find_package(CURL REQUIRED)
# JSON
if(APPLE)
# macOS 使 FetchContent
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(nlohmann_json)
else()
# CentOS 使
find_package(nlohmann_json REQUIRED)
#
if(NOT TARGET nlohmann_json::nlohmann_json)
message(FATAL_ERROR "系统未安装正确版本的 nlohmann_json")
endif()
# CMake
include_directories(${nlohmann_json_INCLUDE_DIRS})
endif()
#
set(LIB_SOURCES
src/collector/DataCollector.cpp
src/collector/DataSourceConfig.cpp
src/core/System.cpp
src/detector/CollisionDetector.cpp
src/detector/SafetyZone.cpp
src/network/HTTPDataSource.cpp
src/network/WebSocketServer.cpp
src/network/HTTPClient.cpp
src/network/TrafficLightHttpServer.cpp
src/spatial/CoordinateConverter.cpp
src/types/BasicTypes.cpp
src/types/VehicleData.cpp
src/vehicle/ControllableVehicles.cpp
src/config/AirportBounds.cpp
src/config/SystemConfig.cpp
src/config/IntersectionConfig.cpp
src/detector/TrafficLightDetector.cpp
)
#
add_library(${PROJECT_NAME}_lib ${LIB_SOURCES})
target_include_directories(${PROJECT_NAME}_lib
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${Boost_INCLUDE_DIRS}
${CURL_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME}_lib
PUBLIC
Boost::system
Boost::filesystem
Boost::thread
Boost::chrono
Boost::date_time
Boost::atomic
Boost::regex
nlohmann_json::nlohmann_json
CURL::libcurl
Threads::Threads
-pthread
)
#
add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_lib)
#
if(APPLE)
option(BUILD_TESTS "Build test cases" ON)
else()
option(BUILD_TESTS "Build test cases" OFF)
endif()
#
if(BUILD_TESTS AND EXISTS "${CMAKE_SOURCE_DIR}/tests")
if(APPLE)
# macOS 使 FetchContent googletest
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.15.2
)
FetchContent_MakeAvailable(googletest)
else()
# CentOS 使 FetchContent googletest
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.15.2
)
FetchContent_MakeAvailable(googletest)
endif()
# tests
add_subdirectory(tests)
endif()
#
message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message(STATUS "Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}")
message(STATUS "Boost_LIBRARIES: ${Boost_LIBRARIES}")
message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}")
#
configure_file(${CMAKE_SOURCE_DIR}/config/system_config.json ${CMAKE_BINARY_DIR}/config/system_config.json COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/config/intersections.json ${CMAKE_BINARY_DIR}/config/intersections.json COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/config/airport_bounds.json ${CMAKE_BINARY_DIR}/config/airport_bounds.json COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/config/unmanned_vehicles.json ${CMAKE_BINARY_DIR}/config/unmanned_vehicles.json COPYONLY)
# Threads
find_package(Threads REQUIRED)
if(NOT APPLE)
target_link_libraries(collision_avoidance
PRIVATE
${PROJECT_NAME}_lib
)
endif()

208
README.md Normal file
View File

@ -0,0 +1,208 @@
# 机场地面碰撞预警系统(测试平台)
## 项目简介
该系统用于监测和预警机场地面航空器与车辆之间的潜在碰撞风险。通过实时采集和分析位置数据,为机场运营提供安全保障。
本系统运行在机场测试平台中,用于测试和验证碰撞预警系统的功能可行性。
## 功能特性
- 实时数据采集:获取航空器和车辆的位置信息
- 碰撞风险检测:基于安全距离进行碰撞风险评估
- 运动参数计算:自动计算速度和航向信息
- 坐标转换:支持地理坐标和平面坐标转换
- 日志记录:详细的运行日志和警告信息
## 系统架构
- 数据采集模块:从数据源获取位置信息
- 碰撞检测模块:分析潜在的碰撞风险
- 坐标转换模块:处理不同坐标系统
- 核心数据类型:定义基础的数据结构
- 性能测试模块:使用 Google Benchmark 进行性能评估
## 开发环境
- C++17
- CMake 3.14+
- Boost 1.86.0
- nlohmann_json 3.11.3
- Google Test
- Google Mock
- Google Benchmark
## 构建说明
```bash
# 创建构建目录
mkdir build && cd build
# 配置项目(启用测试)
cmake -DBUILD_TESTING=ON ..
# 编译
cmake --build .
# 运行单元测试
ctest --output-on-failure
# 运行性能测试
./bin/benchmark_tests
```
## 测试框架
- 单元测试:使用 Google Test 框架
- Mock 测试:使用 Google Mock 进行模拟
- 性能测试:使用 Google Benchmark
- 测试覆盖:
- 基础数据类型
- 碰撞检测逻辑
- 数据采集功能
- HTTP 数据源
- 性能基准测试
## 目录结构
```mermaid
graph TD
A[CollisionAvoidance] --> B[src]
A --> C[tests]
A --> D[tools]
A --> E[docs]
A --> F[build]
A --> G[benchmarks]
B --> BA[collector]
B --> BB[detector]
B --> BC[network]
B --> BD[spatial]
B --> BE[types]
B --> BF[utils]
B --> BG[core]
BA --> BAA[DataCollector.h/cpp]
BA --> BAB[DataSource.h]
BB --> BBA[CollisionDetector.h/cpp]
BC --> BCA[HTTPDataSource.h/cpp]
BC --> BCB[ConnectionConfig.h]
BD --> BDA[CoordinateConverter.h/cpp]
BE --> BEA[BasicTypes.h/cpp]
BF --> BFA[Logger.h/cpp]
BG --> BGA[System.h/cpp]
C --> CA[BasicTypesTest.cpp]
C --> CB[CollisionDetectorTest.cpp]
C --> CC[DataCollectorTest.cpp]
C --> CD[HTTPDataSourceTest.cpp]
G --> GA[CollisionDetectorBenchmark.cpp]
G --> GB[CoordinateConverterBenchmark.cpp]
D --> DA[mock_server.py]
```
## 依赖管理
项目使用 CMake 的 FetchContent 模块管理第三方依赖:
```cmake
include(FetchContent)
FetchContent_Declare(
json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)
FetchContent_MakeAvailable(json)
```
## 配置说明
- 数据源配置:
- 主机localhost
- 端口8081
- API路径
- /api/getCurrentFlightPositions
- /api/getCurrentVehiclePositions
- 安全参数:
- 水平安全距离50米
- 垂直安全距离50米
- 最大速度限制:
- 航空器100米/秒
- 车辆30米/秒
## 开发团队
- 项目负责人:[田建勇]
- 开发人员:[赵豪、陈横、刘青宇]
## 许可证
[License Type]
## 版本历史
- v1.1.0 (2024-03-20)
- 添加 Google Benchmark 性能测试
- 升级 CMake 最低版本至 3.14
- 规范化第三方库版本管理
- 添加 FetchContent 依赖管理
- v1.0.0 (2024-11-15)
- 初始版本发布
- 基本功能实现
- 单元测试框架搭建
## Mock 服务
用于模拟机场场面的移动物体(航空器和车辆),提供以下功能:
- 航空器位置更新
- 车辆位置更新
- 红绿灯状态更新
- 车辆控制指令响应
### API 接口
- GET /api/getCurrentFlightPositions - 获取航空器位置
- GET /api/getCurrentVehiclePositions - 获取车辆位置
- GET /api/getTrafficLightSignals - 获取红绿灯状态
- POST /api/vehicle/command - 发送车辆控制指令
### 运行方式
```bash
cd tools
python mock_server.py
```
服务默认监听:
- HTTP 服务:<http://localhost:8081>
### 模拟场景
- 航空器T7 -> T11到达后返回起点
- 无人车1 (QN001)T1 -> T2 -> T4到达后返回起点
- 无人车2 (QN002)T12 -> T8到达后返回起点
- 特勤车 (TQ001)T4 -> T2 -> T3到达后返回起点
### 红绿灯控制
- 西路口 (TL001)每10秒切换一次
- 东路口 (TL002):根据航空器位置自动控制
### 车辆控制指令优先级
1. ALERT (5) - 告警指令
2. RED (4) - 红灯指令
3. WARNING (3) - 预警指令
4. GREEN (2) - 绿灯状态
5. RESUME (1) - 恢复指令

7
compile_commands.json Normal file
View File

@ -0,0 +1,7 @@
{
"commands": [
"cd build",
"cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..",
"ln -s build/compile_commands.json ."
]
}

147
config/airport_bounds.json Normal file
View File

@ -0,0 +1,147 @@
{
"airport": {
"rotation_angle": 68.53,
"reference_point": {
"x": 0.0,
"y": 0.0
},
"bounds": {
"x": -100,
"y": -200,
"width": 800,
"height": 400
}
},
"areas": {
"runway": {
"bounds": {
"x": -101,
"y": -201,
"width": 1,
"height": 1
},
"config": {
"collision_radius": {
"aircraft": 100.0,
"special": 50.0,
"unmanned": 25.0
},
"height_threshold": 15.0,
"warning_zone_radius": {
"aircraft": 200.0,
"special": 100.0,
"unmanned": 50.0
},
"alert_zone_radius": {
"aircraft": 100.0,
"special": 50.0,
"unmanned": 25.0
}
}
},
"taxiway": {
"bounds": {
"x": -101,
"y": -201,
"width": 1,
"height": 1
},
"config": {
"collision_radius": {
"aircraft": 50.0,
"special": 50.0,
"unmanned": 25.0
},
"height_threshold": 10.0,
"warning_zone_radius": {
"aircraft": 100.0,
"special": 100.0,
"unmanned": 50.0
},
"alert_zone_radius": {
"aircraft": 50.0,
"special": 50.0,
"unmanned": 25.0
}
}
},
"gate": {
"bounds": {
"x": -101,
"y": -201,
"width": 1,
"height": 1
},
"config": {
"collision_radius": {
"aircraft": 40.0,
"special": 50.0,
"unmanned": 25.0
},
"height_threshold": 5.0,
"warning_zone_radius": {
"aircraft": 80.0,
"special": 100.0,
"unmanned": 50.0
},
"alert_zone_radius": {
"aircraft": 40.0,
"special": 50.0,
"unmanned": 25.0
}
}
},
"service": {
"bounds": {
"x": -101,
"y": -201,
"width": 1,
"height": 1
},
"config": {
"collision_radius": {
"aircraft": 30.0,
"special": 50.0,
"unmanned": 25.0
},
"height_threshold": 5.0,
"warning_zone_radius": {
"aircraft": 60.0,
"special": 100.0,
"unmanned": 50.0
},
"alert_zone_radius": {
"aircraft": 30.0,
"special": 50.0,
"unmanned": 25.0
}
}
},
"test_zone": {
"bounds": {
"x": -100,
"y": -200,
"width": 800,
"height": 400
},
"config": {
"collision_radius": {
"aircraft": 30.0,
"special": 15.0,
"unmanned": 15.0
},
"height_threshold": 10.0,
"warning_zone_radius": {
"aircraft": 70.0,
"special": 30.0,
"unmanned": 30.0
},
"alert_zone_radius": {
"aircraft": 35.0,
"special": 15.0,
"unmanned": 15.0
}
}
}
}
}

34
config/intersections.json Normal file
View File

@ -0,0 +1,34 @@
{
"intersections": [
{
"id": "T2路口",
"name": "无人车与特勤车交叉路口",
"trafficLightId": "TL001",
"position": {
"longitude": 120.08502054,
"latitude": 36.35448347,
"altitude": 9.543
},
"width": 20.0,
"safetyZone": {
"aircraftRadius": 50.0,
"vehicleRadius": 50.0
}
},
{
"id": "T6路口",
"name": "无人车与飞机交叉路口",
"trafficLightId": "TL002",
"position": {
"longitude": 120.08649105,
"latitude": 36.35074527,
"altitude": 9.778
},
"width": 30.0,
"safetyZone": {
"aircraftRadius": 50.0,
"vehicleRadius": 50.0
}
}
]
}

165
config/system_config.json Normal file
View File

@ -0,0 +1,165 @@
{
"airport": {
"name": "青岛胶东国际机场",
"iata": "TAO",
"icao": "ZSQD",
"reference_point": {
"latitude": 36.35448347,
"longitude": 120.08502054
},
"coordinate_points": [
{
"point": "T1",
"longitude": 120.0868853,
"latitude": 36.35496367
},
{
"point": "T2",
"longitude": 120.08502054,
"latitude": 36.35448347
},
{
"point": "T3",
"longitude": 120.08341044,
"latitude": 36.35406879
},
{
"point": "T4",
"longitude": 120.08558121,
"latitude": 36.35305878
},
{
"point": "T5",
"longitude": 120.08400957,
"latitude": 36.35265197
},
{
"point": "T6",
"longitude": 120.08649105,
"latitude": 36.35074527
},
{
"point": "T7",
"longitude": 120.08562915,
"latitude": 36.35052372
},
{
"point": "T8",
"longitude": 120.08676664,
"latitude": 36.35004529
},
{
"point": "T9",
"longitude": 120.08520616,
"latitude": 36.34964473
},
{
"point": "T10",
"longitude": 120.08710569,
"latitude": 36.34917893
},
{
"point": "T11",
"longitude": 120.0873865,
"latitude": 36.3509885
},
{
"point": "T12",
"longitude": 120.08603613,
"latitude": 36.35190217
},
{
"point": "T13",
"longitude": 120.08509148,
"latitude": 36.35041247
}
]
},
"data_source": {
"position": {
"host": "localhost",
"port": 8081,
"aircraft_path": "/openApi/getCurrentFlightPositions",
"vehicle_path": "/openApi/getCurrentVehiclePositions",
"refresh_interval_ms": 1000,
"auth": {
"username": "dianxin",
"password": "dianxin@123",
"auth_path": "/login",
"auth_required": true
},
"timeout_ms": 5000,
"read_timeout_ms": 2000
},
"unmanned_vehicle": {
"host": "localhost",
"port": 8081,
"location_path": "/api/VehicleLocationInfo",
"status_path": "/api/VehicleStateInfo",
"command_path": "/api/VehicleCommandInfo",
"refresh_interval_ms": 1000,
"auth": {
"username": "dianxin",
"password": "dianxin@123",
"auth_path": "/api/login",
"auth_required": false
},
"timeout_ms": 5000,
"read_timeout_ms": 2000
},
"traffic_light": {
"host": "localhost",
"port": 8081,
"signal_path": "/openApi/getTrafficLightSignals",
"refresh_interval_ms": 1000,
"auth": {
"username": "dianxin",
"password": "dianxin@123",
"auth_path": "/api/login",
"auth_required": false
},
"timeout_ms": 5000,
"read_timeout_ms": 2000
}
},
"warning": {
"warning_interval_ms": 1000,
"log_interval_ms": 3000
},
"websocket": {
"port": 8010,
"max_connections": 100,
"ping_interval_ms": 30000,
"position_update": {
"aircraft_interval_ms": 300,
"vehicle_interval_ms": 500,
"traffic_light_interval_ms": 1000
}
},
"collision_detection": {
"update_interval_ms": 200,
"prediction": {
"time_window": 20.0,
"vehicle_size": 20.0,
"aircraft_size": 60.0,
"min_unmanned_speed": 1.0
}
},
"logging": {
"level": "debug",
"file": "logs/system.log",
"max_size_mb": 10,
"max_files": 5,
"console_output": true
},
"debug": {
"enable_mock_data": false,
"save_raw_data": false,
"profile_performance": false
},
"traffic_light_server": {
"port": 8082,
"max_connections": 100
},
"simulated_mobile_light_target_intersection_id": "T2路口"
}

View File

@ -0,0 +1,22 @@
{
"vehicles": [
{
"vehicleNo": "QN001",
"type": "UNMANNED",
"ip": "localhost",
"port": 8081
},
{
"vehicleNo": "QN002",
"type": "UNMANNED",
"ip": "localhost",
"port": 8081
},
{
"vehicleNo": "TQ001",
"type": "SPECIAL",
"ip": "localhost",
"port": 8081
}
]
}

View File

@ -0,0 +1,83 @@
# 机场区域配置说明文档
本文档详细说明了 `airport_bounds.json` 配置文件中各参数的含义和用途。
## 整体机场边界 (airport.bounds)
- `x`: 机场区域左上角 X 坐标(米)
- `y`: 机场区域左上角 Y 坐标(米)
- `width`: 机场总宽度(米)
- `height`: 机场总高度(米)
## 功能区域 (areas)
### 跑道区域 (runway)
- 位置参数 (bounds)
- `x`: 跑道起始 X 坐标(米)
- `y`: 跑道起始 Y 坐标(米)
- `width`: 跑道宽度(米)
- `height`: 跑道高度(米)
- 安全配置 (config)
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
- `height_threshold`: 高度阈值(米)
### 滑行道区域 (taxiway)
- 位置参数 (bounds)
- `x`: 滑行道起始 X 坐标(米)
- `y`: 滑行道起始 Y 坐标(米)
- `width`: 滑行道宽度(米)
- `height`: 滑行道高度(米)
- 安全配置 (config)
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
- `height_threshold`: 高度阈值(米)
### 停机位区域 (gate)
- 位置参数 (bounds)
- `x`: 停机位起始 X 坐标(米)
- `y`: 停机位起始 Y 坐标(米)
- `width`: 停机位区域宽度(米)
- `height`: 停机位区域高度(米)
- 安全配置 (config)
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
- `height_threshold`: 高度阈值(米)
### 服务区域 (service)
- 位置参数 (bounds)
- `x`: 服务区起始 X 坐标(米)
- `y`: 服务区起始 Y 坐标(米)
- `width`: 服务区宽度(米)
- `height`: 服务区高度(米)
- 安全配置 (config)
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
- `height_threshold`: 高度阈值(米)
## 参数说明
### 碰撞检测半径
- 跑道区域:使用最大的碰撞检测半径,确保最高的安全性
- 滑行道:使用中等的碰撞检测半径
- 停机位:使用较小的碰撞检测半径
- 服务区:使用最小的碰撞检测半径,因为车速较慢且司机更警觉
### 高度阈值
- 跑道区域15米考虑到起降阶段
- 滑行道10米考虑到滑行阶段
- 停机位和服务区5米主要考虑地面操作
## 注意事项
1. 所有距离单位均为米
2. 坐标系统使用左下角为原点
3. 各区域的参数需要根据实际机场情况调整
4. 碰撞检测半径应考虑车辆和航空器的实际尺寸
5. 高度阈值应考虑实际运营需求和安全裕度

366
docs/configuration_guide.md Normal file
View File

@ -0,0 +1,366 @@
# 机场无人车冲突预警系统配置指南
## 目录
- [机场无人车冲突预警系统配置指南](#机场无人车冲突预警系统配置指南)
- [目录](#目录)
- [一、机场基础信息配置](#一机场基础信息配置)
- [参数说明](#参数说明)
- [配置说明](#配置说明)
- [二、区域边界配置](#二区域边界配置)
- [区域参数规范表](#区域参数规范表)
- [参数说明](#参数说明-1)
- [三、交叉路口配置](#三交叉路口配置)
- [参数说明](#参数说明-2)
- [配置原则](#配置原则)
- [四、数据接口配置](#四数据接口配置)
- [接口参数示例](#接口参数示例)
- [参数说明](#参数说明-3)
- [五、无人车配置](#五无人车配置)
- [参数说明](#参数说明-4)
- [六、预警参数配置](#六预警参数配置)
- [参数说明](#参数说明-5)
- [冲突预警和告警参数](#冲突预警和告警参数)
- [参数说明](#参数说明-6)
- [告警阈值](#告警阈值)
- [七、调试与日志](#七调试与日志)
- [参数说明](#参数说明-7)
- [八、配置验证流程](#八配置验证流程)
- [文档版本](#文档版本)
---
## 一、机场基础信息配置
文件路径:`config/system_config.json`
```json
{
"airport": {
"name": "青岛胶东国际机场",
"iata": "TAO",
"icao": "ZSQD",
"reference_point": {
"latitude": 36.35448347,
"longitude": 120.08502054
},
"coordinate_points": [
{"point": "T1", "longitude": 120.0868853, "latitude": 36.35496367},
{"point": "T2", "longitude": 120.08502054, "latitude": 36.35448347}
]
}
}
```
### 参数说明
- **name**:机场名称
- **iata**:机场三字代码
- **icao**:机场四字代码
- **reference_point**:参考点
- **latitude**:参考点纬度
- **longitude**:参考点经度
- **coordinate_points**:坐标点
- **point**:坐标点名称
- **longitude**:坐标点经度
- **latitude**:坐标点纬度
### 配置说明
- **参考点设置**:选择塔台或机场中心点作为坐标系原点
- **坐标精度**经纬度建议保留8位小数约1cm精度
- **命名规范**
- IATA代码使用3位大写字母
- 坐标点按T1-T13顺序编号
---
## 二、区域边界配置
文件路径:`config/airport_bounds.json`
### 区域参数规范表
| 区域类型 | 边界坐标x(米) | 边界坐标y(米) | 区域宽度 | 区域高度 |
| -------- | ------------- | ------------- | -------- | -------- |
| 测试区 | -100 | -200 | 800 | 400 |
```json
"test_zone": {
"bounds": {
"x": -100,
"y": -200,
"width": 800,
"height": 400
}
```
### 参数说明
- **bounds**:区域边界
- **x**区域左上角x坐标
- **y**区域左上角y坐标
- **width**:区域宽度
- **height**:区域高度
---
## 三、交叉路口配置
文件路径:`config/intersections.json`
```json
{
"id": "T2路口",
"name": "无人车与特勤车交叉路口",
"trafficLightId": "TL001",
"position": {
"longitude": 120.08502054,
"latitude": 36.35448347,
"altitude": 9.543
},
"width": 20.0,
"safetyZone": {
"aircraftRadius": 50.0,
"vehicleRadius": 50.0
}
}
```
### 参数说明
- **id**路口ID
- **name**:路口名称
- **trafficLightId**交通灯ID
- **position**:路口位置
- **width**:路口宽度
- **safetyZone**:安全区
- **aircraftRadius**:飞机安全区半径
- **vehicleRadius**:车辆安全区半径
### 配置原则
1. 路口ID按"T"+数字编号如T1-T13
2. 安全区半径建议值:
- 飞机翼展×1.5
- 车辆车长×2.0
---
## 四、数据接口配置
文件路径:`config/system_config.json`
### 接口参数示例
```json
"data_source": {
"position": {
"host": "localhost",
"port": 8081,
"aircraft_path": "/openApi/getCurrentFlightPositions",
"vehicle_path": "/openApi/getCurrentVehiclePositions",
"refresh_interval_ms": 1000,
"auth": {
"username": "dianxin",
"password": "dianxin@123",
"auth_path": "/login",
"auth_required": true
},
"timeout_ms": 5000,
"read_timeout_ms": 2000
},
"unmanned_vehicle": {
"host": "localhost",
"port": 8081,
"location_path": "/api/VehicleLocationInfo",
"status_path": "/api/VehicleStateInfo",
"command_path": "/api/VehicleCommandInfo",
"refresh_interval_ms": 1000,
"auth": {
"username": "dianxin",
"password": "dianxin@123",
"auth_path": "/api/login",
"auth_required": false
},
"timeout_ms": 5000,
"read_timeout_ms": 2000
},
"traffic_light": {
"host": "localhost",
"port": 8081,
"signal_path": "/openApi/getTrafficLightSignals",
"refresh_interval_ms": 1000,
"auth": {
"username": "dianxin",
"password": "dianxin@123",
"auth_path": "/api/login",
"auth_required": false
},
"timeout_ms": 5000,
"read_timeout_ms": 2000
}
}
```
### 参数说明
- **position**:位置接口
- **host**数据源IP地址
- **port**:数据源端口号
- **aircraft_path**:航空器数据接口路径
- **vehicle_path**:车辆数据接口路径
- **refresh_interval_ms**:数据刷新间隔时间(ms)
- **auth**:认证信息
- **username**:用户名
- **password**:密码
- **auth_path**:认证路径
- **auth_required**:是否需要认证
- **timeout_ms**:请求超时时间(ms)
- **read_timeout_ms**:读取超时时间(ms)
- **unmanned_vehicle**:无人车接口
- **host**无人车IP地址
- **port**:无人车端口号
- **location_path**:无人车位置接口路径
- **status_path**:无人车状态接口路径
- **command_path**:无人车命令接口路径
- **refresh_interval_ms**:数据刷新间隔时间(ms)
- **auth**:认证信息
- **username**:用户名
- **password**:密码
- **auth_path**:认证路径
- **auth_required**:是否需要认证
- **timeout_ms**:请求超时时间(ms)
- **read_timeout_ms**:读取超时时间(ms)
- **traffic_light**:交通灯接口
- **host**交通灯IP地址
- **port**:交通灯端口号
- **signal_path**:交通灯信号接口路径
- **refresh_interval_ms**:数据刷新间隔时间(ms)
- **auth**:认证信息
- **username**:用户名
- **password**:密码
- **auth_path**:认证路径
- **auth_required**:是否需要认证
- **timeout_ms**:请求超时时间(ms)
- **read_timeout_ms**:读取超时时间(ms)
---
## 五、无人车配置
文件路径:`config/unmanned_vehicles.json`
```json
"vehicles": [
{
"vehicleNo": "QN001",
"type": "UNMANNED",
"ip": "localhost",
"port": 8081
},
{
"vehicleNo": "QN002",
"type": "UNMANNED",
"ip": "localhost",
"port": 8081
}
]
```
### 参数说明
- **vehicleNo**:无人车编号
- **type**:无人车类型
- **ip**无人车IP地址
- **port**:无人车端口号
---
## 六、预警参数配置
###冲突预测和航空器和车辆尺寸:
文件路径:`config/system_config.json`
```json
"collision_detection": {
"update_interval_ms": 200,
"prediction": {
"time_window": 20.0,
"vehicle_size": 20.0,
"aircraft_size": 60.0,
"min_unmanned_speed": 1.0
}
}
```
### 参数说明
- **update_interval_ms**:冲突检测更新间隔时间(ms)
- **prediction**:冲突预测模型参数
- **time_window**:预测时间窗口(s)
- **vehicle_size**:车辆尺寸(m)
- **aircraft_size**:航空器尺寸(m)
- **min_unmanned_speed**:无人车最小速度(m/s)
### 冲突预警和告警参数
文件路径:`config/airport_bounds.json`
```json
"warning_zone_radius": {
"aircraft": 70.0,
"special": 30.0,
"unmanned": 30.0
},
"alert_zone_radius": {
"aircraft": 35.0,
"special": 15.0,
"unmanned": 15.0
}
```
### 参数说明
- **warning_zone_radius**:预警区半径 = 预警距离阈值 + 飞机尺寸/2 + 车辆尺寸/2
- **alert_zone_radius**:告警区半径 = 告警距离阈值 + 飞机尺寸/2 + 车辆尺寸/2
### 告警阈值
| 告警级别 | 飞机距离阈值(m) | 车辆距离阈值(m) |
| -------- | --------------- | --------------- |
| 预警 | 60 | 30 |
| 告警 | 30 | 15 |
---
## 七、调试与日志
文件路径:`config/logging_config.json`
```json
"logging": {
"level": "debug",
"file": "logs/system.log",
"max_size_mb": 10,
"max_files": 5,
"console_output": true
}
```
### 参数说明
- **level**:日志级别
- **file**:日志文件路径
- **max_size_mb**:日志文件最大大小(MB)
- **max_files**:日志文件最大数量
- **console_output**:是否输出到控制台
---
## 八、配置验证流程
1. 基础配置检查
```bash
# 检查配置文件语法
python3 -m json.tool config/system_config.json
```
2. 接口连通性测试
```bash
curl -X POST http://localhost:8081/openApi/getCurrentFlightPositions
```
3. 实时监控
```bash
tail -f /opt/collision_avoidance/logs/system.log | grep "WARNING"
```
## 文档版本
- 版本号1.0.0
- 更新日期2025-02-06
- 更新内容:初始版本

Binary file not shown.

View File

@ -0,0 +1,197 @@
# 机场地面冲突检测算法方案
## 1. 算法概述
机场地面冲突检测系统采用多层次检测策略,结合不同算法的优势,实现快速、准确、可靠的冲突检测。
## 2. 常用冲突检测算法
### 2.1 基于几何的检测算法
#### 2.1.1 CPA (Closest Point of Approach) 算法
- 原理:
- 计算两个移动物体之间的最近接近点
- 预测未来一段时间内的最小距离
- 判断是否小于安全阈值
- 优点:
- 计算简单快速
- 适合实时系统
- 易于实现
- 缺点:
- 不考虑路网约束
- 预测精度有限
#### 2.1.2 安全区域重叠检测
- 原理:
- 为每个物体定义安全区域(通常是椭圆或矩形)
- 检测安全区域是否重叠
- 考虑速度向量进行预测
- 优点:
- 直观易理解
- 可以考虑物体实际尺寸
- 适合不同类型交通工具
### 2.2 基于时空的检测算法
#### 2.2.1 时空立方体算法
- 原理:
- 构建 4D 时空轨迹x, y, z, t
- 检测时空轨迹的交叉
- 计算到达同一位置的时间差
- 优点:
- 预测性强
- 可以处理复杂场景
- 考虑时间维度
#### 2.2.2 网格化检测算法
- 原理:
- 将场地划分为网格
- 预测物体在未来时间点占用的网格
- 检测网格占用冲突
- 优点:
- 计算效率高
- 易于并行处理
- 适合大规模场景
### 2.3 基于概率的检测算法
#### 2.3.1 Monte Carlo 模拟
- 原理:
- 考虑位置和速度的不确定性
- 多次随机采样模拟
- 计算碰撞概率
- 优点:
- 考虑不确定性
- 结果更可靠
- 适合复杂环境
#### 2.3.2 贝叶斯预测模型
- 原理:
- 建立运动状态概率模型
- 实时更新碰撞概率
- 动态调整预警阈值
- 优点:
- 可以学习历史数据
- 适应性强
- 预测准确度高
## 3. 实现方案
### 3.1 多层次检测策略
采用三层检测机制:
1. 第一层:快速检测
```cpp
// 使用简单的 CPA 算法进行快速筛查
struct CPAResult {
double min_distance; // 最小距离
double time_to_cpa; // 到达最近点的时间
Point cpa_point; // 最近接近点
};
CPAResult calculateCPA(const Vehicle& v1, const Vehicle& v2, double prediction_time) {
// 计算两个物体的最近接近点
// 返回最小距离和时间信息
}
```
2. 第二层:精确检测
```cpp
// 使用安全区域重叠检测进行精确判断
struct SafetyZone {
Point center;
double length;
double width;
double orientation;
};
bool checkSafetyZoneOverlap(const SafetyZone& zone1, const SafetyZone& zone2) {
// 检查两个安全区域是否重叠
// 考虑物体的实际尺寸和方向
}
```
3. 第三层:预测检测
```cpp
// 使用时空网格进行路径预测
struct TimeSpaceGrid {
int x, y; // 空间坐标
double time; // 时间戳
std::vector<int> occupants; // 占用该格子的物体ID
};
bool checkPathConflict(const Vehicle& v1, const Vehicle& v2,
const std::vector<TimeSpaceGrid>& grids) {
// 检查未来时间窗口内的路径冲突
// 考虑路网约束
}
```
### 3.2 综合判断
```cpp
struct ConflictResult {
bool has_conflict; // 是否存在冲突
double risk_level; // 风险等级
double time_to_conflict; // 到冲突的时间
std::string conflict_type; // 冲突类型
};
ConflictResult detectConflict(const Vehicle& v1, const Vehicle& v2) {
// 1. 快速 CPA 检测
// 2. 如果可能存在冲突,进行安全区域检测
// 3. 对于高风险情况,进行时空路径预测
// 4. 综合三层结果,返回最终判断
}
```
## 4. 实现步骤
1. 第一阶段:基础功能实现
- 实现 CPA 算法
- 实现基本的安全区域检测
- 设计基础数据结构
2. 第二阶段:增强功能
- 添加时空网格检测
- 实现路径预测
- 优化性能
3. 第三阶段:高级特性
- 添加概率模型
- 实现自适应阈值
- 系统集成测试
## 5. 注意事项
1. 性能要求
- 实时响应:检测延迟不超过 100ms
- CPU 占用率控制在 50% 以下
- 内存使用不超过 1GB
2. 可靠性要求
- 误报率控制在 1% 以下
- 漏报率控制在 0.1% 以下
- 系统可用性 99.99%
3. 扩展性要求
- 支持新增检测算法
- 支持参数配置调整
- 支持不同类型交通工具

172
docs/deployment_guide.md Normal file
View File

@ -0,0 +1,172 @@
# 部署指南
本文档详细说明了如何在 CentOS 系统上部署碰撞避免系统。支持在线和离线两种部署方式。
## 目录
- [系统要求](#系统要求)
- [在线部署](#在线部署)
- [离线部署](#离线部署)
- [部署后配置](#部署后配置)
- [常见问题](#常见问题)
## 系统要求
- CentOS 7 或更高版本
- 至少 2GB 可用内存
- 至少 10GB 可用磁盘空间
- root 权限
- Python 3.6 或更高版本
## 在线部署
如果服务器可以访问互联网,可以直接使用在线部署方式。
1. 获取项目代码
```bash
git clone [项目仓库地址]
cd CollisionAvoidance
```
2. 执行部署脚本
```bash
sudo ./scripts/deploy.sh
```
## 离线部署
如果服务器无法访问互联网,需要使用离线部署方式。
### 准备阶段(在有网络的环境中进行)
1. 在有网络的 CentOS 环境中,获取项目代码
```bash
git clone [项目仓库地址]
cd CollisionAvoidance
```
2. 执行离线包准备脚本
```bash
./scripts/prepare_offline_packages.sh
```
脚本会自动完成:
- 下载所需的系统依赖包
- 下载 Python 依赖包
- 创建完整的项目归档文件
### 部署步骤
1. 将生成的归档文件传输到目标服务器
```bash
scp ../CollisionAvoidance.tar.gz root@target-server:/tmp/
```
2. 在目标服务器上解压
```bash
cd /tmp
tar xzf CollisionAvoidance.tar.gz
cd CollisionAvoidance
```
3. 删除 32 位依赖包
> 注意:由于系统是 64 位架构,我们需要删除所有 32 位i686的依赖包以避免安装冲突。这些包是在准备离线包时自动下载的但在 64 位系统上不需要。
```bash
# 删除所有 i686 架构的包
cd packages
rm -f *i686.rpm
cd ..
```
4. 执行部署脚本
```bash
sudo ./scripts/deploy.sh
```
## 部署后配置
### 服务管理
- 启动服务:`systemctl start collision-avoidance`
- 停止服务:`systemctl stop collision-avoidance`
- 重启服务:`systemctl restart collision-avoidance`
- 查看状态:`systemctl status collision-avoidance`
- 查看日志:`journalctl -u collision-avoidance -f`
### 配置文件
- 配置文件位置:`/etc/collision_avoidance/`
- WebSocket 服务端口8010
- Mock Server 端口8081
### 防火墙配置
如果使用 firewalld
```bash
# 开放 WebSocket 端口
firewall-cmd --permanent --add-port=8010/tcp
# 开放 Mock Server 端口
firewall-cmd --permanent --add-port=8081/tcp
firewall-cmd --reload
```
## 常见问题
### 1. 服务启动失败
- 检查服务日志:`journalctl -u collision-avoidance -n 50`
- 确认配置文件权限正确
- 验证端口 8010 和 8081 是否被占用
### 2. Mock Server 启动失<E58AA8><E5A4B1>
- 检查 Python 和依赖包是否正确安装
- 确认端口 8081 未被占用
- 检查 Mock Server 日志输出
### 3. 防火墙配置
- 如果使用其他防火墙,需要手动开放 8010 和 8081 端口
- 确认防火墙规则是否生效:`firewall-cmd --list-all`
### 4. 依赖包安装问题
- 如果遇到依赖包冲突,检查是否有 i686 架构的包未被删除
- 使用 `rpm -qa | grep i686` 命令检查系统中的 32 位包
- 如果发现 i686 包,使用 `rpm -e` 命令删除它们
## 卸载
如需卸载系统:
```bash
sudo ./scripts/uninstall.sh
```
卸载脚本会:
- 停止并禁用服务
- 删除系统服务配置
- 删除程序文件
- 删除配置文件
- 关闭防火墙端口
## 技术支持
如遇到问题,请:
1. 查看服务日志
2. 检查系统日志
3. 检查 Mock Server 日志
4. 联系技术支持团队

1070
docs/design.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,232 @@
# Docker 部署设计文档
本文档详细说明碰撞避免系统的 Docker 部署方案,包括测试环境和生产环境的部署流程。
## 环境说明
### 测试环境
- CentOS 7
- 可以访问互联网
- 可以安装软件包
- 内存 >= 4GB
- 磁盘空间 >= 20GB
### 生产环境
- CentOS 7
- 不能访问互联网
- 不能在线安装软件包
- 内存 >= 8GB
- 磁盘空间 >= 50GB
## 测试环境工作
### 1. 环境准备
```bash
# 安装 Docker
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
# 安装 Docker Compose
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
```
### 2. 镜像构建与测试
1. 构建基础镜像
2. 运行自动化测试
3. 验证功能完整性
4. 检查资源使用情况
5. 验证日志收集
6. 测试健康检查机制
### 3. 准备生产部署包
1. 导出 Docker 镜像
```bash
# 保存基础镜像
docker save centos:7 > centos7-base.tar
# 保存应用镜像
docker save collision-avoidance:latest > collision-avoidance.tar
```
2. 准备离线安装包
```bash
# 创建部署包目录
mkdir -p offline-package
cd offline-package
# 下载 Docker 离线安装包
yum install -y yum-utils
yumdownloader --resolve docker-ce docker-ce-cli containerd.io
# 下载 Docker Compose 二进制文件
wget https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64
```
3. 创建部署脚本
```bash
# 打包所有文件
tar czf docker-deploy-package.tar.gz \
centos7-base.tar \
collision-avoidance.tar \
*.rpm \
docker-compose-Linux-x86_64 \
scripts/
```
## 生产环境工作
### 1. 离线安装 Docker
```bash
# 解压部署包
tar xzf docker-deploy-package.tar.gz
# 安装 Docker RPM 包
rpm -ivh *.rpm
# 安装 Docker Compose
cp docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# 启动 Docker 服务
systemctl enable docker
systemctl start docker
```
### 2. 加载 Docker 镜像
```bash
# 加载基础镜像
docker load < centos7-base.tar
# 加载应用镜像
docker load < collision-avoidance.tar
```
### 3. 部署配置
1. 创建数据目录
```bash
mkdir -p /data/collision-avoidance/{config,logs}
```
2. 配置文件准备
```bash
cp config/* /data/collision-avoidance/config/
```
### 4. 启动<E590AF><E58AA8><EFBFBD>
```bash
docker-compose up -d
```
## 部署后验证
### 1. 基础检查
- 容器状态
- 端口可用性
- 日志输出
- 资源使用情况
### 2. 功能验证
- WebSocket 连接测试
- Mock Server 功能测试
- 碰撞检测功能测试
- 数据持久化验证
### 3. 监控检查
- 健康检查状态
- 资源监控指标
- 日志收集状态
## 回滚方案
### 1. 准备回滚镜像
- 在测试环境保留上一个稳定版本镜像
- 将回滚镜像包含在部署包中
### 2. 回滚步骤
```bash
# 停止当前服务
docker-compose down
# 加载回滚镜像
docker load < collision-avoidance-rollback.tar
# 修改镜像标签
docker tag collision-avoidance-rollback:latest collision-avoidance:latest
# 重启服务
docker-compose up -d
```
## 注意事项
### 1. 安全考虑
- 使用非 root 用户运行容器
- 配置文件权限控制
- 网络访问限制
- 资源限制设置
### 2. 性能优化
- 容器资源限制配置
- 日志轮转策略
- 数据持久化方案
### 3. 运维建议
- 定期备份配置文件
- 监控告警配置
- 日志管理策略
- 定期检查磁盘使用情况
## 故障处理
### 1. 常见问题
- 容器启动失败
- 端口冲突
- 资源不足
- 日志异常
### 2. 处理方法
- 检查容器日志
- 验证配置文件
- 检查<E6A380><E69FA5>源使用
- 查看系统日志
## 维护计划
### 1. 日常维护
- 日志清理
- 磁盘空间检查
- 性能监控
- 配置备份
### 2. 定期更新
- 安全补丁更新
- 基础镜像更新
- 应用版本更新

384
docs/mock_server.md Normal file
View File

@ -0,0 +1,384 @@
# Mock 服务设计与使用说明
## 1. 概述
Mock 服务用于模拟机场场面的移动物体(航空器和车辆),提供位置更新和响应控制指令的功能。服务包含两个主要部分:
- HTTP API 服务:提供位置查询和车辆控制接口
- 位置更新模拟:定期更新各个移动物体的位置
## 2. 移动物体配置
### 2.1 航空器配置
```python
{
"flightNo": "AC001", # 航班号
"latitude": 36.362647780875804, # 纬度
"longitude": 120.088303, # 经度
"direction": { # 使用方向向量
"lat": initial_target_lat,
"lon": initial_target_lon
},
"speed": 50.0 # 滑行速度 50km/h
}
```
### 2.2 车辆配置
```python
{
"vehicleNo": "QN001", # 无人车1西路口
"latitude": WEST_INTERSECTION["latitude"], # 纬度
"longitude": WEST_INTERSECTION["longitude"], # 经度
"direction": 1, # 1表示向东-1表示向西
"speed": 36.0 # 行驶速度 36km/h
}
```
### 2.3 关键坐标点
- T1 (120.0868853, 36.35496367): 无人车1起点
- T2 (120.08502054, 36.35448347): 冲突点1西路口
- T3 (120.08341044, 36.35406879): 特勤车终点
- T4 (120.08558121, 36.35305878): 无人车1终点/无人车2起点/特勤车起点
- T5 (120.08400957, 36.35265197)
- T6 (120.08649105, 36.35074527): 冲突点2东路口
- T7 (120.08562915, 36.35052372): 航空器1终点
- T8 (120.08676664, 36.35004529): 无人车2终点
- T9 (120.08520616, 36.34964473)
- T10 (120.08710569, 36.34917893)
- T11 (120.0873865, 36.3509885): 航空器1终点
- T12 (120.08603613, 36.35190217): QN002新起点距T6路口150米
## 3. 运动模式
### 3.1 航空器运动
- 沿跑道南北方向移动
- 到达边界时自动调头
- 保持恒定速度 50km/h
### 3.2 无人车运动
- QN001
1. T1 -> T2西路口
2. T2 -> T4
3. 到达T4后返回T1
- QN002
1. T12 -> T8
2. 到达T8后返回T12
- TQ001特勤车
1. T4 -> T2西路口
2. T2 -> T3
3. 到达T3后返回T4
### 3.3 配置参数
- 距离阈值:
- 300米DIST_300M
- 200米DIST_200M
- 150米DIST_150M
- 100米DIST_100M
- 50米DIST_50M
- 时间配置:
- 返回等待时间10秒
- 位置更新间隔1秒
- 红绿灯切换间隔10秒
- 速度配置:
- 默认车辆速度18.0 km/h
- 紧急制动减速度80%/更新
- 正常制动减速度20%/更新
## 4. HTTP API 接口
### 4.1 航空器位置查询接口
```http
GET /api/getCurrentFlightPositions
```
返回所有移动物体的当前位置信息:
```json
[
{
"flightNo": "AC001",
"latitude": 36.362647780875804,
"longitude": 120.088303,
"direction": {
"lat": 0.707,
"lon": 0.707
},
"speed": 36.0,
"time": 1733741411167
}
]
```
### 4.2 车辆位置查询接口
```http
GET /api/getCurrentVehiclePositions
```
返回所有车辆的当前位置信息:
```json
[
{
"vehicleNo": "QN001",
"latitude": 36.362448155990975,
"longitude": 120.08844920958369,
"direction": -1,
"speed": 18.0,
"time": 1733741411168,
"phase": 0
}
]
```
### 4.3 红绿灯状态查询接口
```http
GET /api/getTrafficLightSignals
```
返回所有红绿灯的当前状态:
```json
[
{
"id": "TL001",
"state": 1,
"timestamp": 1733741411167,
"intersection": "INT001",
"position": {
"latitude": 36.35448347,
"longitude": 120.08502054
}
}
]
```
### 4.4 车辆控制接口
```http
POST /api/vehicle/command
Content-Type: application/json
```
请求格式:
```json
{
"vehicleId": "QN001",
"type": "SIGNAL", // SIGNAL, ALERT, WARNING, RESUME
"reason": "TRAFFIC_LIGHT", // TRAFFIC_LIGHT, AIRCRAFT_CROSSING, SPECIAL_VEHICLE, AIRCRAFT_PUSH, RESUME_TRAFFIC
"timestamp": 1733741411167,
"signalState": "RED", // 可选,仅当 type 为 SIGNAL 时需要
"targetPosition": { // 可选
"x": 100.0,
"y": 200.0
},
"speed": 0.0, // 可选
"distance": 0.0 // 可选
}
```
响应格式:
```json
{
"status": "ok", // ok 或 error
"message": "Command executed successfully"
}
```
## 5. 指令优先级
车辆控制指令按以下优先级处理:
1. ALERT (5):告警指令,最高优先级
- 用于紧急情况,如碰撞风险
- 可以覆盖所有其他指令
- 收到后车辆立即停止
2. RED (4):红灯指令,次高优先级
- 用于红绿灯控制
- 可以覆盖预警、绿灯和恢复指令
- 红灯时车辆停止,绿灯时可能恢复(取决于是否有更高优先级指令)
3. WARNING (3):预警指令,中等优先级
- 用于提前预警可能的风险
- 可以覆盖绿灯和恢复指令
- 收到后车辆减速或停止
4. GREEN (2):绿灯状态
- 系统内部状态,不是外部指令
- 可以覆盖恢复指令
- 只有在没有更高优先级指令时才生效
5. RESUME (1):恢复指令,最低优先级
- 用于恢复正常行驶
- 不能覆盖其他指令
- 只有在没有任何其他指令时才生效
### 5.1 优先级规则
1. 高优先级指令可以覆盖低优先级指令
2. 相同优先级的新指令可以覆盖旧指令
3. 低优先级指令不能覆盖高优先级指令
4. 恢复行驶需要满足个条件:
- 收到 RESUME 指令
- 没有任何更高优先级的指令
### 5.2 状态转换
1. 收到告警指令 (ALERT)
- 立即停止
- 忽略所有低优先级指令
- 只能通过 RESUME 指令恢复(且无其他高优先级指令)
2. 收到红灯指令 (RED)
- 红灯:停止
- 绿灯如果没有更高优先级指令ALERT则恢复行驶
3. 收到预警指令 (WARNING)
- 减速或停止
- 可被告警或红灯指令覆盖
- 可通过绿灯或恢复指令解除(如无更高优先级指令)
4. 遇到绿灯 (GREEN)
- 如果没有 ALERT、SIGNAL 或 WARNING 指令,则恢复行驶
- 否则保持当前状态
5. 收到恢复指令 (RESUME)
- 检查是否有任何更高优先级指令
- 如果没有,则恢复正常行驶速度
- 如果有,则保持当前状态
### 5.3 使用示例
1. 告警指令覆盖其他指令:
```json
{
"vehicleId": "QN001",
"type": "ALERT",
"reason": "COLLISION_RISK",
"timestamp": 1733741411167
}
```
2. 红灯指令(但不会覆盖告警):
```json
{
"vehicleId": "QN001",
"type": "SIGNAL",
"reason": "TRAFFIC_LIGHT",
"signalState": "RED",
"timestamp": 1733741411167
}
```
3. 预警指令(可被告警和红灯覆盖):
```json
{
"vehicleId": "QN001",
"type": "WARNING",
"reason": "APPROACHING_INTERSECTION",
"timestamp": 1733741411167
}
```
4. 恢复指令(需要无更高优先级指令):
```json
{
"vehicleId": "QN001",
"type": "RESUME",
"reason": "RESUME_TRAFFIC",
"timestamp": 1733741411167
}
```
## 6. 使用示例
### 6.1 发送红绿灯指令
```bash
curl -X POST http://localhost:8081/api/vehicle/command \
-H "Content-Type: application/json" \
-d '{
"vehicleId": "QN001",
"type": "SIGNAL",
"reason": "TRAFFIC_LIGHT",
"timestamp": 1733741411167,
"signalState": "RED"
}'
```
### 6.2 发送碰撞预警指令
```bash
curl -X POST http://localhost:8081/api/vehicle/command \
-H "Content-Type: application/json" \
-d '{
"vehicleId": "QN001",
"type": "WARNING",
"reason": "AIRCRAFT_CROSSING",
"timestamp": 1733741411167,
"distance": 25.5
}'
```
### 6.3 发送恢复指令
```bash
curl -X POST http://localhost:8081/api/vehicle/command \
-H "Content-Type: application/json" \
-d '{
"vehicleId": "QN001",
"type": "RESUME",
"reason": "RESUME_TRAFFIC",
"timestamp": 1733741411167
}'
```
## 7. 启动服务
```bash
# 安装依赖
pip install flask
# 启动服务
python tools/mock_server.py
```
服务默认监听在:
- HTTP 服务:<http://localhost:8081>
## 8. 注意事项
1. 位置更新
- 位置更新频率1秒一次
- 坐标系统WGS84
- 速度单位km/h内部计算使用 m/s
2. 车辆控制
- 车辆收到停止指令后会立即停止位置更新
- 只有收到 RESUME 指令且没有更高优先级的指令时才会恢复运动
- 每个指令都必须包含时间戳
3. 错误处理
- 无效的车辆ID返回 404 错误
- 无效的指令格式:返回 400 错误
- 服务器内部错误:返回 500 错误

225
docs/official_api.md Normal file
View File

@ -0,0 +1,225 @@
# 数据接口对接方案
## 第1章 位置数据接口对接方案
### 1.1 登录认证
1. 登录接口:<http://IP:端口/login>
2. 请求方式post
3. 参数username、password
4. 示例:<http://127.0.0.1:8080/login?username=XXXX&password=XXXX>
5、返回值 data 为返回的鉴权token后续接口需要再header中携带data所有的数据是一个token不要截断
示例:{
    "status": 200,
    "msg": "登入成功",
    "data": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY"
}
### 1.2 航空器位置数据接入
数据来源:接入并转发从空管接收到的融合数据
1. 接口地址:<http://IP:端口/openApi/getCurrentFlightPositions>
2. 请求方式get需要在 Header 中携带认证信息,字段名为 Authorization值为认证接口返回的token
3. 返回格式:以 JSON 格式返回数据一次请求返回List集合对象
4. 数据结构:
| 序号 | 字段 | 描述 | 字段类型 | 是否必填 |
|-----|------|------|----------|----------|
| 1 | flightNo | 航班号 | String | 是 |
| 2 | longitude | 经度 | double | 是 |
| 3 | latitude | 纬度 | double | 是 |
| 4 | time | 时间戳UTC 时间) | long | 是 |
| 5 | altitude | 海拔高度 | double | 否 |
| 6 | trackNumber | 航迹号 | long | 否 |
### 1.3 车辆位置数据接入
数据来源:仅传递目前机场已接入的车辆位置数据
1. 接口地址:<http://IP:端口/openApi/getCurrentVehiclePositions>
2. 请求方式get需要在 Header 中携带认证信息,字段名为 Authorization值为认证接口返回的token
3. 返回格式:以 JSON 格式返回数据一次请求返回List集合对象
4. 数据结构:
| 序号 | 字段 | 描述 | 字段类型 | 是否必填 |
|-----|------|------|----------|----------|
| 1 | vehicleNo | 车牌号 | String | 是 |
| 2 | longitude | 经度 | double | 是 |
| 3 | latitude | 纬度 | double | 是 |
| 4 | time | 时间戳 | long | 是 |
| 5 | direction | 方向 | double | 否 |
| 6 | speed | 速度 | double | 否 |
## 第2章 无人车控制接口对接方案
### 2.1 无人车控制指令
2.1.1 接口地址: <http://127.0.0.1:31140/api/VehicleCommandInfo>
2.1.2 请求方法POST
2.1.3 请求参数:
| 字段名称 | 类型 | 是否必填 | 说明 |
|---------|------|----------|------|
| transId | string | 是 | 消息唯一 id消息的唯一标识符 |
| timestamp | long | 是 | 时间戳 |
| vehicleID | string | 是 | 车辆 ID |
| commandType | string | 是 | 指令类型ALERT告警指令SIGNAL信号灯指令WARNING预警指令RESUME恢复指令 |
| commandReason | string | 是 | 指令原因TRAFFIC_LIGHT红绿灯控制AIRCRAFT_CROSSING航空器交叉SPECIAL_VEHICLE特勤车辆AIRCRAFT_PUSH航空器推出RESUME_TRAFFIC恢复通行 |
| signalState | string | 否 | 信号灯状态(仅当 commandType 为 SIGNAL 时有效RED红灯GREEN绿灯YELLOW黄灯 |
| intersectionId | string | 否 | 路口 ID仅当 commandType 为 SIGNAL 时有效) |
| latitude | double | 是 | 目标位置纬度(路口/航空器/特勤车) |
| longitude | double | 是 | 目标位置经度(路口/航空器/特勤车) |
| relativeSpeed | double | 否 | 相对速度(仅当 commandType 为 ALERT/WARNING 时有效) |
| relativeMotionX | double | 否 | 相对运动 X 分量(仅当 commandType 为 ALERT/WARNING 时有效) |
| relativeMotionY | double | 否 | 相对运动 Y 分量(仅当 commandType 为 ALERT/WARNING 时有效) |
| minDistance | double | 否 | 最小距离(仅当 commandType 为 ALERT/WARNING 时有效) |
示例:
requestData:
{
"messageUniqueId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
"timestamp": 1736175610000,
"vehicleID": "A001",
"commandType": "SIGNAL",
"commandReason": "TRAFFIC_LIGHT",
"signalState":"RED",
"intersectionId":"002",
"latitude": 343.23,
"longitude": 343.23,
"relativeSpeed": 3,
"relativeMotionX": 2002.12,
"relativeMotionY":100.12,
"minDistance":10.5
}
返回值:
| 字段名 | 类型 | 是否必须 | 描述 |
|---------|------|----------|------|
| transId | string | 是 | 消息唯一id消息的唯一标识符与请求id一致 |
| timestamp | long | 是 | 时间戳 |
| code | int | 是 | 接口返回的状态码200 请求成功400 请求失败并在msg内返回原因 |
| msg | string | 是 | 接口成功/失败的原因或者附加提示信息 |
示例:
responseData:
{
"code": 200,
"msg": "success",
"transId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
"timestamp": 1736175610
}
### 2.2 无人车位置上报
1. 接口地址: <http://127.0.0.1:31140/api/VehicleLocationInfo>
2. 请求方法GET
3. 返回值(以 List 数据返回,一次请求返回集合对象):
| 字段名称 | 类型 | 是否必填 | 说明 |
|---------|------|----------|------|
| transId | string | 是 | 消息唯一 id消息的唯一标识符 |
| timestamp | long | 是 | 时间戳UTC 时间,单位:毫秒) |
| vehicleID | string | 是 | 车辆 ID |
| latitude | double | 是 | 纬度 |
| longitude | double | 是 | 经度 |
| speed | double | 是 | 速度单位m/s |
| direction | double | 是 | 车头航向角,正东为 0 度(弧度) |
示例:
requestData:
[
{
"transId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
"timestamp": 1736175610000,
"vehicleID": "AT001",
"latitude": 123.112,
"longitude": 78.331,
"speed": 3.2,
"direction": 1.57
}
]
### 2.3 无人车状态上报
1. 接口地址: <http://127.0.0.1:31140/api/VehicleStateInfo>
2. 请求方法POST
3. 请求参数:
| 字段名称 | 类型 | 是否必填 | 说明 |
|---------|------|----------|------|
| transId | string | 是 | 消息唯一 id消息的唯一标识符 |
| timestamp | long | 是 | 时间戳UTC 时间,单位:毫秒) |
| vehicleID | string | 是 | 车辆 ID |
| isSingle | boolean | 是 | True单个车辆False所有车辆 |
示例:
requestData:
{
"transId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
"timestamp": 1736175610000,
"vehicleID": "AT001",
"isSingle": true
}
4. 返回值(以 List 数据返回,一次请求返回集合对象):
| 字段名称 | 类型 | 是否必填 | 说明 |
|---------|------|----------|------|
| transId | string | 是 | 消息唯一 id消息的唯一标识符 |
| timestamp | long | 是 | 时间戳UTC 时间,单位:毫秒) |
| vehicleID | string | 是 | 车辆 ID |
| loginState | boolean | 是 | 登录状态True登录False未登录 |
| faultInfo | list | 是 | 故障信息,以列表返回,可能存在多个 |
| activeSafety | boolean | 是 | 车辆最小风险策略触发主动安全True触发False未触发 |
| RC | boolean | 是 | 被接管或干预相关信息是否被远控RemoteControlTrue车辆在遥控器远控模式False车辆处于自动驾驶模式 |
| Command | int | 是 | 接收的远程指令信息0恢复1急停2缓停 |
| airportInfo | list | 否 | 机场特殊要求的其他信息 |
| vehicleMode | int | 是 | 无人设备控制模式底盘控制模式1:手动司机驾驶2:自动3:遥控器4:远程5故障等待 |
| gearState | int | 是 | 车辆当前档位1:N2:D3:P4:R, 5: 未知 |
| chassisReady | boolean | 是 | 底盘是否准备就绪True车辆发控制指令就可以走false: 其他 |
| collisionStatus | boolean | 否 | 防撞梁是否触发true触发false未触发 |
| clearance | int | 是 | 0关闭1开启示廓灯 |
| turnSignalStstus | int | 是 | 转向灯状态0off , 1 : trun left , 2 : trun right, 3: 双闪 |
| pointCloud | list | 否 | 点云数据字节流每个点的长度现在是12每个坐标为float长度4|
示例:
responseData:
[
{
"transId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
"timestamp": 1736175610000,
"vehicleID": "AT001",
"loginStatus":true,
"faultInfo":[],
"activeSafety":false,
"RC":false,
"Command":0,
"airportInfo":[],
"vehicleMode": 2,
"gearState": 2,
"chassisRaedy":true,
"collisionStatus":false,
"clearance":0,
"turnSignalStstus":0,
"pointCloud":[]
}
]

View File

@ -0,0 +1,340 @@
# 碰撞检测系统性能测试报告
## 1. 测试环境
### 1.1 硬件环境
- CPU: Apple M3 Pro
- 内存: 16GB
- 存储: SSD
### 1.2 软件环境
- 操作系统: macOS Sonoma 14.1
- 编译器: Apple clang version 15.0.0
- 编译选项: -O3 优化
- Boost版本: 1.86.0
- CMake版本: 3.14+
## 2. 测试场景
### 2.1 数据规模
- 航空器数量: 150架
- 车辆数量: 300辆
- 总物体数: 450个
### 2.2 空间分布
- 跑道区域: 5架航空器
- 滑行道区域: 5架航空器
- 停机位区域: 100架航空器180辆车辆
- 服务区域: 40架航空器120辆车辆
### 2.3 区域范围
- 跑道区域: 3600m × 60m
- 滑行道区域: 3600m × 60m
- 停机位区域: 1500m × 1000m
- 服务区域: 2000m × 1000m
## 3. 测试结果
### 3.1 性能指标
- 总处理时间: 4.987毫秒
- 平均每个物体处理时间: 11微秒
- 内存使用峰值: < 1MB
### 3.2 碰撞检测结果
- 检测到的碰撞总数: 36个
- 区域分布:
- 停机位区域: 30个 (83.3%)
- 服务区域: 4个 (11.1%)
- 滑行道区域: 2个 (5.6%)
- 跑道区域: 0个
### 3.3 风险等级分布
- 严重风险: 12个 (33.3%)
- 高风险: 14个 (38.9%)
- 中等风险: 8个 (22.2%)
- 低风险: 2个 (5.6%)
### 3.4 空间优化效果
- 四叉树容量: 8个物体/节点
- 机场边界: 4000m × 2000m
- 空间索引效率: O(log n)
- 总体复杂度: O(m * log n)
## 4. 结论分析
### 4.1 性能表现
- 系统能够在5毫秒内完成453个物体的碰撞检测
- 空间索引优化效果显著
- 处理时间远低于100毫秒的实时性要求
- 平均每个物体处理时间稳定在11微秒以内
### 4.2 检测结果
- 碰撞数量分布合理
- 停机位区域碰撞占比83.3%,反映了实际情况
- 服务区碰撞占比11.1%,空间利用合理
- 滑行道碰撞占比5.6%,需要关注
- 跑道无碰撞,符合安全要求
### 4.3 风险等级分析
- 严重风险和高风险占比72.2%,需要重点关注
- 中等风险占比22.2%,在合理范围内
- 低风险占比5.6%,说明预警及时
### 4.4 优化效果
- 区域尺寸调整后,碰撞检测更准确
- 物体分布更接近实际情况
- 风险等级划分合理
- 处理时间稳定可控
## 5. 边界条件测试
### 5.1 距离边界
- 最小检测距离: 7.5米(车辆碰撞半径的一半)
- 最大检测距离: 100米跑道区域航空器阈值
- 临界距离测试: 通过
### 5.2 速度边界
- 静止物体: 正确处理
- 最大速度: 70 m/s航空器
- 相对静止: 正确识别
### 5.3 空间边界
- 区域边界重叠: 正确处理
- 区域切换: 平滑过渡
- 机场边界: 正确约束
## 6. 性能瓶颈分析
### 6.1 主要耗时
1. 四叉树更新: 15%
2. 空间查询: 25%
3. 距离计算: 35%
4. 相对运动分析: 25%
### 6.2 优化空间
1. 四叉树容量参数调优
2. 并行处理大规模数据
3. SIMD指令优化距离计算
4. 缓存历史碰撞结果
## 7. 结论与建议
### 7.1 性能结论
- 系统满足实时性要求(<10ms
- 内存使用合理
- 算法复杂度符合预期
- 空间索引效果显著
### 7.2 改进建议
1. 引入多线程处理
2. 优化四叉树参数
3. 添加预测性碰撞检测
4. 实现增量更新机制
5. 添加碰撞风险等级分类
### 7.3 后续测试建议
1. 进行持续运行测试(>24小时
2. 测试极端天气条件下的表现
3. 测试高并发场景
4. 进行内存泄漏测试
5. 进行载均衡测试
## 8. 附录
### 8.1 测试代码片段
```cpp
// 大规模碰撞检测性能测试
TEST_F(CollisionDetectorTest, LargeScaleCollisionDetection) {
std::srand(std::time(nullptr));
// 生成100架航空器
std::vector<Aircraft> aircraft;
for (int i = 0; i < 100; ++i) {
Aircraft a;
a.id = "FL" + std::to_string(i + 1);
a.position = generateRandomPosition();
a.speed = generateRandomSpeed(true);
a.heading = generateRandomHeading();
aircraft.push_back(a);
}
// 生成200辆车
std::vector<Vehicle> vehicles;
for (int i = 0; i < 200; ++i) {
Vehicle v;
v.id = "VH" + std::to_string(i + 1);
v.position = generateRandomPosition();
v.speed = generateRandomSpeed(false);
v.heading = generateRandomHeading();
vehicles.push_back(v);
}
auto start = std::chrono::high_resolution_clock::now();
detector->updateTraffic(aircraft, vehicles);
auto collisions = detector->detectCollisions();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>
(std::chrono::high_resolution_clock::now() - start);
// 验证结果...
}
```
### 8.2 测试数据分布
- 位置分布: 均匀随机
- 速度分布: 线性随机
- 向分布: 均匀随机
- 碰撞分布: 自然随机
## 9. 碰撞检测详细配置
### 9.1 告警阈值配置
#### 9.1.1 直接告警阈值(距离小于阈值的一半)
- 航空器与车辆:
- 跑道50米
- 滑行道25米
- 停机位20米
- 服务区15米
- 车辆之间:
- 跑道25米
- 滑行道15米
- 停机位10米
- 服务区7.5米
#### 9.1.2 相对运动告警阈值
- 航空器与车辆:
- 跑道100米
- 滑行道50米
- 停机位40米
- 服务区30米
- 车辆之间:
- 跑道50米
- 滑行道30米
- 停机位20米
- 服务区15米
### 9.2 算法实现细节
#### 9.2.1 空间索引实现
```cpp
class QuadTree {
Bounds bounds_; // 区域边界
int capacity_; // 节点容量8个物体/节点)
std::vector<T> items_; // 物体列表
bool divided_ = false; // 是否已分割
std::unique_ptr<QuadTree> northwest_; // 四个子节点
std::unique_ptr<QuadTree> northeast_;
std::unique_ptr<QuadTree> southwest_;
std::unique_ptr<QuadTree> southeast_;
};
```
#### 9.2.2 碰撞检测实现
```cpp
bool CollisionDetector::checkAircraftVehicleCollision(const Aircraft& aircraft,
const Vehicle& vehicle) const {
const auto& areaConfig = getCollisionParams(aircraft.position);
// 计算平面距离
double dx = aircraft.position.x - vehicle.position.x;
double dy = aircraft.position.y - vehicle.position.y;
double distance = std::sqrt(dx*dx + dy*dy);
// 直接告警条件
if (distance < areaConfig.aircraftGroundRadius * 0.5) {
return true;
}
// 相对运动检测
if (distance < areaConfig.aircraftGroundRadius) {
// 计算相对速度
double vax = aircraft.speed * std::cos((90 - aircraft.heading) * M_PI / 180.0);
double vay = aircraft.speed * std::sin((90 - aircraft.heading) * M_PI / 180.0);
double vvx = vehicle.speed * std::cos((90 - vehicle.heading) * M_PI / 180.0);
double vvy = vehicle.speed * std::sin((90 - vehicle.heading) * M_PI / 180.0);
double vx = vax - vvx;
double vy = vay - vvy;
// 计算相对运动
double relativeMotion = dx*vx + dy*vy;
return relativeMotion < 0; // 物体正在接近
}
return false;
}
```
### 9.3 性能优化策略
#### 9.3.1 空间查询优化
1. 四叉树空间分区:
- 将机场空间划分为四个子区域
- 递归分割直到达到容量限制
- 快速定位邻近物体
2. 邻近查询实现:
```cpp
std::vector<T> queryNearby(const Vector2D& point, double radius) const {
Bounds range{
point.x - radius,
point.y - radius,
radius * 2,
radius * 2
};
return queryRange(range);
}
```
#### 9.3.2 计算优化
1. 距离计算:
- 使用平方距离比较,避免开方运算
- 只在必要时计算精确距离
2. 相对运动计算:
- 使用航向角的正弦和余弦值
- 考虑数学坐标系的转换
### 9.4 内存优化
1. 数据结构:
- 使用智能指针管理四叉树节点
- 避免不必要的数据复制
- 合理设置容器初始容量
2. 缓存策略:
- 缓存航空器数据
- 使用四叉树索引车辆数据
- 及时清理无效数据

60
docs/requirements.md Normal file
View File

@ -0,0 +1,60 @@
# 需求文档
## 1. 需求概述
## 2. 功能需求
### 2.1 航空器、车辆位置显示
### 2.2 冲突检测
1. 路径检测
2. 距离检测
3. 路口检测
### 2.3 无人车辆控制
1. 无人车控制
1. 控制指令
1. 冲突预警
2. 冲突告警
3. 安全停靠
2.
### 2.4 红绿灯信号处理
### 2.5 数据采集
1. 航空器和车辆位置数据
1. 数据来源:
1. 航空器ADS-B
2. 车辆1.8G北斗
2. 数据格式:
参考 official_api.md
3. 采集频率:
1. 航空器3Hz
2. 车辆3Hz
2. 红绿灯信号
1. 数据来源:
1. 红绿灯5G
2. 数据格式:
参考 official_api.md
3. 采集频率:
1. 红绿灯1Hz
3. 无人车状态
1. 数据来源:
1. 无人车5G
2. 数据格式:
参考 official_api.md
3. 采集频率:
1. 无人车1Hz
### 2.5 数据处理
### 2.6 数据存储
## 3. 非功能需求
## 4. 约束
## 5. 其他

203
docs/warning_strategy.md Normal file
View File

@ -0,0 +1,203 @@
# 机场场面告警策略文档
## 1. 控制指令定义
系统对无人车的控制分为以下指令类型:
```cpp
// 指令类型
enum class CommandType {
ALERT, // 告警指令
SIGNAL, // 信号灯指令
WARNING, // 预警指令
RESUME // 恢复指令
};
// 指令原因
enum class CommandReason {
TRAFFIC_LIGHT, // 红绿灯控制
AIRCRAFT_CROSSING, // 航空器交叉
SPECIAL_VEHICLE, // 特勤车辆
AIRCRAFT_PUSH, // 航空器推出
RESUME_TRAFFIC // 恢复通行
};
// 信号灯状态
enum class SignalState {
RED, // 红灯
GREEN // 绿灯
};
```
### 1.1 信号灯指令SIGNAL
- **目的**:响应交通信号灯
- **触发条件**:接收到红绿灯状态变化
- **执行动作**:根据信号灯状态停车或通行
- **优先级**4最高优先级
- **原因**TRAFFIC_LIGHT
### 1.2 告警指令ALERT
- **目的**:应对紧急情况
- **触发条件**:检测到直接碰撞风险
- **执行动作**:强制停车或紧急避让
- **优先级**3
- **原因**AIRCRAFT_CROSSING, SPECIAL_VEHICLE, AIRCRAFT_PUSH
### 1.3 预警指令WARNING
- **目的**:提前告知可能的风险
- **触发条件**:进入监测区域但尚未到达关键区域
- **执行动作**:降低速度,提高警惕
- **优先级**2
- **原因**AIRCRAFT_CROSSING, SPECIAL_VEHICLE, AIRCRAFT_PUSH
### 1.4 恢复指令RESUME
- **目的**:恢复到正常行驶状态
- **触发条件**:航空器或特勤车辆通过后,距离路口 50 m后
- **执行动作**:恢复到正常行驶状态
- **优先级**1最低优先级
- **原因**RESUME_TRAFFIC
## 2. 红绿灯条件下的告警策略
### 2.1 适用场景
- 无人车接近带有红绿灯的交叉路口时的行为控制
- 主要针对机场内各主要道路交叉口
### 2.2 告警条件
1. **距离阈值**
- 车辆进入路口 200 米范围内开始监测
- 在距离路口 50 米处为停车点
2. **信号灯状态响应**
```cpp
struct VehicleCommand {
std::string vehicleId; // 车辆ID
CommandType type; // 指令类型SIGNAL
CommandReason reason; // 指令原因TRAFFIC_LIGHT
SignalState signalState; // 信号灯状态
std::string intersectionId; // 路口ID
double latitude; // 路口纬度
double longitude; // 路口经度
};
```
## 3. 航空器或特勤车辆,和无人车垂直经过交叉路口的告警策略
### 3.1 适用场景
- 滑行道与车行道的交叉路口
- 航空器或特勤车辆,和无人车垂直交叉,预判可能冲突的情况
### 3.2 告警条件
1. **距离监测**
- 航空器或特勤车辆通过路口无人车距离路口阈值100 米
2. **控制策略**
```cpp
struct VehicleCommand {
std::string vehicleId; // 车辆ID
CommandType type; // 指令类型WARNING/ALERT
CommandReason reason; // 指令原因AIRCRAFT_CROSSING/SPECIAL_VEHICLE
double latitude; // 目标位置纬度
double longitude; // 目标位置经度
double relativeSpeed; // 相对速度
double relativeMotionX; // 相对运动 X 分量
double relativeMotionY; // 相对运动 Y 分量
double minDistance; // 最小距离
};
```
## 4. 航空器出库时的告警策略
### 4.1 适用场景
- 航空器从机库或维修区驶出
- 与服务区道路的交叉路口
### 4.2 告警条件
1. **距离监测**
- 航空器通过路口无人车距离路口阈值100 米
2. **控制策略**
```cpp
struct VehicleCommand {
std::string vehicleId; // 车辆ID
CommandType type; // 指令类型WARNING/ALERT
CommandReason reason; // 指令原因AIRCRAFT_PUSH
double latitude; // 目标位置纬度
double longitude; // 目标位置经度
double relativeSpeed; // 相对速度
double relativeMotionX; // 相对运动 X 分量
double relativeMotionY; // 相对运动 Y 分量
double minDistance; // 最小距离
};
```
## 5. 通用要求
### 5.1 指令优先级处理
1. SIGNAL 指令(优先级 4
- 立即执行
- 覆盖其他所有指令
- 红绿灯控制具有最高优先级,确保交通安全
2. ALERT 指令(优先级 3
- 可被 SIGNAL 指令覆盖
- 优先于 WARNING 指令
- 用于碰撞风险等紧急情况
3. WARNING 指令(优先级 2
- 可被 SIGNAL 和 ALERT 指令覆盖
- 用于预防性控制
- 在无更高优先级指令时执行
4. RESUME 指令(优先级 1
- 可被其他指令覆盖
- 用于恢复到正常行驶状态
### 5.2 系统响应时间
- 告警信号处理延迟 < 100ms
- 控制指令执行延迟 < 200ms
- 状态反馈延迟 < 300ms
### 5.3 降级处理
- 在通信中断时,无人车应自动降速或停车
- 在传感器失效时,扩大安全距离
### 5.4 提前量
- 无人车在距离路口 110 米处开始监测,提前 10 米发送 WARNING 指令
- 无人车在距离路口 60 米处开始监测,提前 10 米发送 ALERT 指令
### 5.5 指令格式
```cpp
struct VehicleCommand {
std::string vehicleId; // 车辆ID
CommandType type; // 指令类型
CommandReason reason; // 指令原因
uint64_t timestamp; // 时间戳
SignalState signalState; // 信号灯状态(仅当 type 为 SIGNAL 时有效)
std::string intersectionId; // 路口ID仅当 type 为 SIGNAL 时有效)
double latitude; // 目标位置纬度(路口/航空器/特勤车)
double longitude; // 目标位置经度(路口/航空器/特勤车)
double relativeSpeed; // 相对速度(仅当 type 为 ALERT/WARNING 时有效)
double relativeMotionX; // 相对运动 X 分量(仅当 type 为 ALERT/WARNING 时有效)
double relativeMotionY; // 相对运动 Y 分量(仅当 type 为 ALERT/WARNING 时有效)
double minDistance; // 最小距离(仅当 type 为 ALERT/WARNING 时有效)
};
```

View File

@ -0,0 +1,142 @@
#!/bin/bash
# Color definitions for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Get script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")"
PROJECT_NAME="collision_avoidance"
# Generate version number based on current time
VERSION=$(date +"%Y%m%d_%H%M%S")
log_info "Building version: ${VERSION}"
# Create temp directory
TEMP_DIR="${PROJECT_ROOT}/temp_build"
rm -rf "${TEMP_DIR}"
mkdir -p "${TEMP_DIR}/bin"
mkdir -p "${TEMP_DIR}/lib"
# Build the project
log_info "Building project..."
cd "${PROJECT_ROOT}"
rm -rf build
mkdir build
cd build
# Configure and build
cmake3 -DCMAKE_BUILD_TYPE=Release ..
if [ $? -ne 0 ]; then
log_error "CMake configuration failed"
exit 1
fi
make -j$(nproc)
if [ $? -ne 0 ]; then
log_error "Build failed"
exit 1
fi
# Copy executable
log_info "Copying executable..."
cp "bin/${PROJECT_NAME}" "${TEMP_DIR}/bin/" || {
log_error "Failed to copy executable"
exit 1
}
# --- BEGIN: Copy Libraries ---
log_info "Copying required runtime libraries to ${TEMP_DIR}/lib/ ..."
GCC_RUNTIME_LIB_PATH="/usr/local/lib64"
if [ -f "${GCC_RUNTIME_LIB_PATH}/libstdc++.so.6" ]; then
cp -Lv "${GCC_RUNTIME_LIB_PATH}/libstdc++.so.6" "${TEMP_DIR}/lib/"
log_info "Copied libstdc++.so.6 (and its target file)"
else
log_warn "libstdc++.so.6 not found in ${GCC_RUNTIME_LIB_PATH}"
fi
if [ -f "${GCC_RUNTIME_LIB_PATH}/libgcc_s.so.1" ]; then
cp -Lv "${GCC_RUNTIME_LIB_PATH}/libgcc_s.so.1" "${TEMP_DIR}/lib/"
log_info "Copied libgcc_s.so.1 (and its target file)"
else
log_warn "libgcc_s.so.1 not found in ${GCC_RUNTIME_LIB_PATH}"
fi
# Modified Boost library copying
ACTUAL_BOOST_LIB_PATH="/usr/lib64" # Actual .so.1.69.0 files are in /usr/lib64
BOOST_VERSIONED_FILES=(
"libboost_system.so.1.69.0"
"libboost_filesystem.so.1.69.0"
"libboost_thread.so.1.69.0"
"libboost_chrono.so.1.69.0"
"libboost_date_time.so.1.69.0"
"libboost_atomic.so.1.69.0"
"libboost_regex.so.1.69.0"
)
log_info "Attempting to copy Boost libraries from ${ACTUAL_BOOST_LIB_PATH}..."
for boost_file in "${BOOST_VERSIONED_FILES[@]}"; do
if [ -f "${ACTUAL_BOOST_LIB_PATH}/${boost_file}" ]; then
cp -v "${ACTUAL_BOOST_LIB_PATH}/${boost_file}" "${TEMP_DIR}/lib/"
log_info "Copied ${boost_file}"
else
log_warn "Boost library ${boost_file} not found in ${ACTUAL_BOOST_LIB_PATH}"
fi
done
# --- END: Copy Libraries ---
# Generate version file
echo "${VERSION}" > "${TEMP_DIR}/bin/version.txt"
# Generate checksum
cd "${TEMP_DIR}/bin"
sha256sum "${PROJECT_NAME}" > "${PROJECT_NAME}.sha256"
# Create deployment archive
cd "${TEMP_DIR}"
PACKAGE_NAME="${PROJECT_NAME}_${VERSION}.tar.gz"
tar czf "${PROJECT_ROOT}/${PACKAGE_NAME}" bin/ lib/
# Cleanup
cd "${PROJECT_ROOT}"
rm -rf "${TEMP_DIR}"
log_info "Package created: ${PACKAGE_NAME}"
log_info "Package contents:"
log_info " - Executable binary (in bin/)"
log_info " - Runtime libraries (in lib/)"
log_info " - Version file (in bin/)"
log_info " - Checksum file (in bin/)"
# Print instructions
echo
log_info "To deploy:"
echo "1. Copy ${PACKAGE_NAME} to production environment"
echo "2. Extract: tar xzf ${PACKAGE_NAME}"
echo " This will create 'bin/' and 'lib/' directories."
echo "3. Verify checksum: cd bin && sha256sum -c ${PROJECT_NAME}.sha256 && cd .."
echo "4. Stop service: systemctl stop collision-avoidance"
echo "5. Backup old binary and libraries (optional but recommended)"
echo " e.g., mv /opt/collision_avoidance/bin/${PROJECT_NAME} /opt/collision_avoidance/bin/${PROJECT_NAME}.bak"
echo " e.g., rsync -ab /opt/collision_avoidance/lib/ /opt/collision_avoidance/lib_bak/"
echo "6. Copy new binary: cp bin/${PROJECT_NAME} /opt/collision_avoidance/bin/"
echo "7. Copy new libraries: cp lib/* /opt/collision_avoidance/lib/"
echo "8. Start service: systemctl start collision-avoidance"
echo "9. Check status: systemctl status collision-avoidance"

256
scripts/deploy.sh Normal file
View File

@ -0,0 +1,256 @@
#!/bin/bash
# Set up logging
LOGFILE="/tmp/deploy.log"
# Color definitions for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
echo "[INFO] $1" >> "$LOGFILE"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
echo "[WARN] $1" >> "$LOGFILE"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
echo "[ERROR] $1" >> "$LOGFILE"
}
# Check if running as root
if [ "$EUID" -ne 0 ]; then
log_error "Please run as root"
exit 1
fi
# Get script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")"
# Install RPM packages
log_info "Installing RPM packages..."
if [ ! -d "${PROJECT_ROOT}/rpm" ]; then
log_error "RPM directory not found: ${PROJECT_ROOT}/rpm"
exit 1
fi
cd "${PROJECT_ROOT}/rpm"
# Install Python3 and its dependencies
if ! ls python3*.rpm >/dev/null 2>&1; then
log_error "Python3 RPM packages not found"
exit 1
fi
rpm -Uvh --nodeps python3*.rpm
# Install Python packages from wheel files
log_info "Installing Python packages..."
if [ ! -d "${PROJECT_ROOT}/python" ]; then
log_error "Python packages directory not found: ${PROJECT_ROOT}/python"
exit 1
fi
# Install wheel files in correct order
log_info "Installing Python packages..."
cd "${PROJECT_ROOT}/python"
for pkg in *.whl; do
if [ ! -f "$pkg" ]; then
log_error "Required package not found: $pkg"
exit 1
fi
log_info "Installing $pkg..."
pip3 install --no-index "$pkg"
done
# Verify Python packages
log_info "Verifying Python packages..."
python3 -c "import flask; import werkzeug; import jinja2; import markupsafe" || {
log_error "Failed to verify Python packages"
exit 1
}
# Create work directory
WORK_DIR="/opt/collision_avoidance"
log_info "Creating work directory: $WORK_DIR"
mkdir -p $WORK_DIR
mkdir -p $WORK_DIR/bin
mkdir -p $WORK_DIR/lib
mkdir -p $WORK_DIR/tools
mkdir -p $WORK_DIR/config
mkdir -p $WORK_DIR/logs
# Set directory permissions
log_info "Setting directory permissions..."
chmod 755 $WORK_DIR
chmod 755 $WORK_DIR/bin
chmod 755 $WORK_DIR/lib
chmod 755 $WORK_DIR/tools
chmod 755 $WORK_DIR/config
chmod 755 $WORK_DIR/logs
chown -R root:root $WORK_DIR
# Copy files
log_info "Copying files..."
# Check SELinux status
log_info "Checking SELinux status..."
if command -v getenforce &> /dev/null; then
selinux_status=$(getenforce)
log_info "SELinux status: $selinux_status"
if [ "$selinux_status" = "Enforcing" ]; then
log_info "Setting SELinux contexts..."
# 设置用目录的 SELinux 上下文
log_info "Setting context for application directories..."
chcon -R -t usr_t $WORK_DIR
chcon -t bin_t $WORK_DIR/bin
chcon -t lib_t $WORK_DIR/lib
chcon -t etc_t $WORK_DIR/config
# 恢复默认上下文
log_info "Restoring default contexts..."
restorecon -Rv $WORK_DIR
fi
else
log_warn "SELinux tools not found"
fi
for dir in bin lib python tools config; do
if [ ! -d "${PROJECT_ROOT}/${dir}" ]; then
log_error "Directory not found: ${PROJECT_ROOT}/${dir}"
exit 1
fi
done
# Copy executable
log_info "Copying executable..."
if [ "$EXECUTABLE_COPIED" != "1" ]; then
if ! cp -v "${PROJECT_ROOT}/bin/"* "$WORK_DIR/bin/"; then
log_error "Failed to copy executable"
exit 1
fi
fi
# Copy libraries
log_info "Copying libraries..."
if ! cp -v "${PROJECT_ROOT}/lib/"* "$WORK_DIR/lib/"; then
log_error "Failed to copy libraries"
exit 1
fi
# Copy tools
log_info "Copying tools..."
if ! cp -v "${PROJECT_ROOT}/tools/"* "$WORK_DIR/tools/"; then
log_error "Failed to copy tools"
exit 1
fi
# Create config directory
CONFIG_DIR="$WORK_DIR/config"
log_info "Creating config directory: $CONFIG_DIR"
mkdir -p $CONFIG_DIR
log_info "Copying configuration files..."
for config_file in system_config.json vehicle_control.json airport_bounds.json intersections.json; do
if [ ! -f "${PROJECT_ROOT}/config/$config_file" ]; then
log_error "Config file not found: ${PROJECT_ROOT}/config/$config_file"
exit 1
fi
if ! cp -v "${PROJECT_ROOT}/config/$config_file" "$CONFIG_DIR/"; then
log_error "Failed to copy config file: $config_file"
exit 1
fi
done
# 设置配置文件权限
log_info "Setting config file permissions..."
chmod 644 "$CONFIG_DIR"/*.json
chown -R root:root "$CONFIG_DIR"
# 验证配置文件
log_info "Verifying config files..."
if ! ls -l "$CONFIG_DIR"/*.json; then
log_error "No config files found in $CONFIG_DIR"
exit 1
fi
# Create service file
log_info "Creating system service..."
cat > /etc/systemd/system/collision-avoidance.service << EOL
[Unit]
Description=Collision Avoidance Service
After=network.target
[Service]
Type=simple
ExecStart=$WORK_DIR/bin/collision_avoidance
WorkingDirectory=$WORK_DIR
User=root
Restart=always
RestartSec=3
Environment=CONFIG_PATH=$WORK_DIR/config
Environment=LD_LIBRARY_PATH=$WORK_DIR/lib
StandardOutput=append:$WORK_DIR/logs/collision-avoidance.log
StandardError=append:$WORK_DIR/logs/collision-avoidance.error.log
[Install]
WantedBy=multi-user.target
EOL
# Configure firewall
log_info "Configuring firewall..."
if command -v firewall-cmd &> /dev/null; then
if ! systemctl is-active --quiet firewalld; then
log_info "Starting firewalld service..."
systemctl start firewalld
fi
firewall-cmd --permanent --add-port=8010/tcp
firewall-cmd --permanent --add-port=8081/tcp
firewall-cmd --reload
else
log_warn "firewalld not detected, please configure firewall manually"
fi
# Start service
log_info "Starting service..."
systemctl daemon-reload
systemctl enable collision-avoidance
systemctl start collision-avoidance
# Check service status
if systemctl is-active --quiet collision-avoidance; then
log_info "Service started successfully"
else
log_error "Service failed to start, check logs"
journalctl -u collision-avoidance -n 50
exit 1
fi
# Display service status
log_info "Deployment completed, service status:"
systemctl status collision-avoidance
# Display usage instructions
echo -e "\n${GREEN}Deployment completed!${NC}"
echo "Use these commands to manage the service:"
echo " Start service: systemctl start collision-avoidance"
echo " Stop service: systemctl stop collision-avoidance"
echo " Restart service: systemctl restart collision-avoidance"
echo " Check status: systemctl status collision-avoidance"
echo " View logs: journalctl -u collision-avoidance -f"
echo -e "\nConfig files location: ${CONFIG_DIR}"
echo "WebSocket port: 8010"
echo "Mock Server port: 8081"
# 创建日志文件
log_info "Creating log files..."
touch $WORK_DIR/logs/collision-avoidance.log
touch $WORK_DIR/logs/collision-avoidance.error.log
chmod 644 $WORK_DIR/logs/*.log
chown root:root $WORK_DIR/logs/*.log

166
scripts/prepare_deploy.sh Normal file
View File

@ -0,0 +1,166 @@
#!/bin/bash
# Color definitions for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Get script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")"
PROJECT_NAME="$(basename "${PROJECT_ROOT}")"
# Create deploy directory structure
DEPLOY_DIR="${PROJECT_ROOT}/deploy"
log_info "Creating deploy directory: ${DEPLOY_DIR}"
rm -rf "${DEPLOY_DIR}"
mkdir -p "${DEPLOY_DIR}/bin"
mkdir -p "${DEPLOY_DIR}/lib"
mkdir -p "${DEPLOY_DIR}/python"
mkdir -p "${DEPLOY_DIR}/config"
mkdir -p "${DEPLOY_DIR}/rpm"
mkdir -p "${DEPLOY_DIR}/tools"
mkdir -p "${DEPLOY_DIR}/scripts"
# Download Python3 RPM packages
log_info "Downloading Python3 RPM packages..."
cd "${DEPLOY_DIR}/rpm"
# Install required tools
log_info "Installing required tools..."
if ! command -v repotrack &> /dev/null; then
sudo yum install -y yum-utils
fi
# Install build dependencies
log_info "Installing build dependencies..."
sudo yum groupinstall -y "Development Tools"
sudo yum install -y epel-release
sudo yum install -y cmake3 boost-devel openssl-devel python3-devel libcurl-devel
# Install nlohmann-json from EPEL
log_info "Installing nlohmann-json..."
sudo yum install -y nlohmann-json-devel
# Install GCC 11
log_info "Installing GCC 11..."
sudo yum install -y centos-release-scl
sudo yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++
# Enable GCC 11
log_info "Enabling GCC 11..."
source /opt/rh/devtoolset-11/enable
# Download Python3 and dependencies
log_info "Downloading Python3 and dependencies..."
repotrack python3 python3-pip python3-devel python3-flask python3-werkzeug python3-jinja2
# Build the project
log_info "Building project..."
cd "${PROJECT_ROOT}"
rm -rf build
mkdir build
cd build
cmake3 ..
make -j4
if [ $? -ne 0 ]; then
log_error "Build failed"
exit 1
fi
# Copy executable
log_info "Copying executable..."
cp "bin/collision_avoidance" "${DEPLOY_DIR}/bin/" || {
log_error "Failed to copy executable"
exit 1
}
# Copy required libraries
log_info "Copying required libraries..."
LIBS=(
"/usr/lib64/libboost_system.so.1.69.0"
"/usr/lib64/libboost_filesystem.so.1.69.0"
"/usr/lib64/libboost_thread.so.1.69.0"
"/usr/lib64/libssl.so.1.0.2k"
"/usr/lib64/libcrypto.so.1.0.2k"
)
for lib in "${LIBS[@]}"; do
if [[ "$lib" == /* ]]; then
# 对于完整路径的库文件,直接复制
if [ -f "$lib" ]; then
cp "$lib" "${DEPLOY_DIR}/lib/"
log_info "Copied $(basename $lib)"
else
log_warn "Library $lib not found"
fi
else
# 对于其他库文件,使用 ldd 查找
lib_path=$(ldd "${DEPLOY_DIR}/bin/collision_avoidance" | grep "$lib" | awk '{print $3}')
if [ -n "$lib_path" ]; then
cp "$lib_path" "${DEPLOY_DIR}/lib/"
log_info "Copied $lib"
else
log_warn "Library $lib not found"
fi
fi
done
# Copy Python dependencies
log_info "Copying Python dependencies..."
mkdir -p "${DEPLOY_DIR}/python"
cd "${DEPLOY_DIR}/python"
# 下载所有依赖包及其依赖
pip3 download \
flask==2.0.1 werkzeug==2.0.1 click==8.0.1 \
itsdangerous==2.0.1 Jinja2==3.0.1 MarkupSafe==2.0.1
# Copy configuration files
log_info "Copying configuration files..."
cp "${PROJECT_ROOT}/config/airport_bounds.json" "${DEPLOY_DIR}/config/"
cp "${PROJECT_ROOT}/config/intersections.json" "${DEPLOY_DIR}/config/"
cp "${PROJECT_ROOT}/config/system_config.json" "${DEPLOY_DIR}/config/"
cp "${PROJECT_ROOT}/config/vehicle_control.json" "${DEPLOY_DIR}/config/"
# Copy tools
log_info "Copying tools..."
cp "${PROJECT_ROOT}/tools/mock_server.py" "${DEPLOY_DIR}/tools/"
cp "${PROJECT_ROOT}/tools/map_websocket.html" "${DEPLOY_DIR}/tools/"
cp "${PROJECT_ROOT}/tools/test_websocket.html" "${DEPLOY_DIR}/tools/"
# Copy deployment scripts
log_info "Copying deployment scripts..."
cp "${PROJECT_ROOT}/scripts/deploy.sh" "${DEPLOY_DIR}/scripts/"
cp "${PROJECT_ROOT}/scripts/uninstall.sh" "${DEPLOY_DIR}/scripts/"
chmod +x "${DEPLOY_DIR}/scripts/"*.sh
# Create deployment archive
log_info "Creating deployment archive..."
cd "${PROJECT_ROOT}"
tar czf "${PROJECT_NAME}_deploy.tar.gz" deploy/
log_info "Deployment package created: ${PROJECT_NAME}_deploy.tar.gz"
echo "Package contents:"
echo " - Executable binary"
echo " - Required runtime libraries"
echo " - Python3 RPM packages"
echo " - Python dependencies"
echo " - Configuration files"
echo " - Tools"
echo " - Deployment scripts"

56
scripts/uninstall.sh Normal file
View File

@ -0,0 +1,56 @@
#!/bin/bash
# Color definitions for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if running as root
if [ "$EUID" -ne 0 ]; then
log_error "Please run as root"
exit 1
fi
# Stop and disable service
log_info "Stopping service..."
systemctl stop collision-avoidance
systemctl disable collision-avoidance
# Remove service file
log_info "Removing service file..."
rm -f /etc/systemd/system/collision-avoidance.service
systemctl daemon-reload
# Remove files
log_info "Removing files..."
rm -rf /opt/collision_avoidance
# Remove Python packages
log_info "Removing Python packages..."
pip3 uninstall -y flask werkzeug click itsdangerous Jinja2 MarkupSafe
# Remove firewall rules
log_info "Removing firewall rules..."
if command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --remove-port=8010/tcp
firewall-cmd --permanent --remove-port=8081/tcp
firewall-cmd --reload
else
log_warn "firewalld not detected, please remove firewall rules manually"
fi
log_info "Uninstallation completed"

View File

@ -0,0 +1,471 @@
#include "collector/DataCollector.h"
#include "network/HTTPDataSource.h"
#include "utils/Logger.h"
#include <chrono>
#include <fstream>
#include <mutex>
#include <nlohmann/json.hpp>
DataCollector::DataCollector(const AirportBounds& bounds)
: last_position_fetch_(std::chrono::steady_clock::now()),
last_unmanned_fetch_(std::chrono::steady_clock::now()),
last_traffic_light_fetch_(std::chrono::steady_clock::now()),
last_warning_time_(std::chrono::steady_clock::now()),
airportBounds_(bounds) {}
DataCollector::~DataCollector() {
stop();
}
bool DataCollector::initialize(const DataSourceConfig& dataSourceConfig,
const WarnConfig& warnConfig) {
if (!dataSource_) {
dataSourceConfig_ = dataSourceConfig;
dataSource_ = std::make_shared<HTTPDataSource>(dataSourceConfig_);
warnConfig_ = warnConfig;
last_position_fetch_ = std::chrono::steady_clock::now();
last_unmanned_fetch_ = std::chrono::steady_clock::now();
last_traffic_light_fetch_ = std::chrono::steady_clock::now();
last_warning_time_ = std::chrono::steady_clock::now();
}
return true;
}
void DataCollector::start() {
if (running_) {
return;
}
if (!dataSource_) {
Logger::error("No data source available");
return;
}
running_ = true;
last_position_fetch_ = std::chrono::steady_clock::now();
last_unmanned_fetch_ = std::chrono::steady_clock::now();
last_traffic_light_fetch_ = std::chrono::steady_clock::now();
// 尝试连接,但即使失败也继续运行
if (!dataSource_->connect()) {
Logger::error("Failed to connect to data source");
// 立即检查超时,这样如果一开始就连不上,会马上发出告警
checkTimeout();
}
// 启动三个独立的采集线程
positionThread_ = std::thread(&DataCollector::positionLoop, this);
unmannedThread_ = std::thread(&DataCollector::unmannedLoop, this);
trafficLightThread_ = std::thread(&DataCollector::trafficLightLoop, this);
}
void DataCollector::stop() {
if (!running_) {
return;
}
running_ = false;
// 等待所有线程结束
if (positionThread_.joinable()) {
positionThread_.join();
}
if (unmannedThread_.joinable()) {
unmannedThread_.join();
}
if (trafficLightThread_.joinable()) {
trafficLightThread_.join();
}
if (dataSource_) {
dataSource_->disconnect();
}
}
void DataCollector::positionLoop() {
Logger::debug("位置数据采集循环启动,刷新间隔: ",
dataSourceConfig_.position.refresh_interval_ms, "ms");
auto next_collection_time = std::chrono::steady_clock::now();
while (running_) {
bool success = fetchPositionData();
if (!success) {
Logger::warning("位置数据采集失败");
checkTimeout();
}
// 计算下一次采集时间
next_collection_time += std::chrono::milliseconds(
dataSourceConfig_.position.refresh_interval_ms);
auto now = std::chrono::steady_clock::now();
if (next_collection_time > now) {
auto wait_time =
std::chrono::duration_cast<std::chrono::milliseconds>(
next_collection_time - now)
.count();
if (wait_time > 0) {
std::this_thread::sleep_for(
std::chrono::milliseconds(wait_time));
}
} else {
// 如果已经超过了下一次采集时间,立即重置
next_collection_time = std::chrono::steady_clock::now();
}
}
}
void DataCollector::unmannedLoop() {
Logger::debug("无人车状态采集循环启动,刷新间隔: ",
dataSourceConfig_.vehicle.refresh_interval_ms, "ms");
auto next_collection_time = std::chrono::steady_clock::now();
while (running_) {
bool success = fetchUnmannedVehicleData();
if (!success) {
Logger::warning("无人车状态采集失败");
checkTimeout();
}
// 计算下一次采集时间
next_collection_time += std::chrono::milliseconds(
dataSourceConfig_.vehicle.refresh_interval_ms);
auto now = std::chrono::steady_clock::now();
if (next_collection_time > now) {
auto wait_time =
std::chrono::duration_cast<std::chrono::milliseconds>(
next_collection_time - now)
.count();
if (wait_time > 0) {
std::this_thread::sleep_for(
std::chrono::milliseconds(wait_time));
}
} else {
// 如果已经超过了下一次采集时间,立即重置
next_collection_time = std::chrono::steady_clock::now();
}
}
}
void DataCollector::trafficLightLoop() {
Logger::debug("红绿灯数据采集循环启动,刷新间隔: ",
dataSourceConfig_.traffic_light.refresh_interval_ms, "ms");
auto next_collection_time = std::chrono::steady_clock::now();
while (running_) {
bool success = fetchTrafficLightData();
if (!success) {
Logger::warning("红绿灯数据采集失败");
checkTimeout();
}
// 计算下一次采集时间
next_collection_time += std::chrono::milliseconds(
dataSourceConfig_.traffic_light.refresh_interval_ms);
auto now = std::chrono::steady_clock::now();
if (next_collection_time > now) {
auto wait_time =
std::chrono::duration_cast<std::chrono::milliseconds>(
next_collection_time - now)
.count();
if (wait_time > 0) {
std::this_thread::sleep_for(
std::chrono::milliseconds(wait_time));
}
} else {
// 如果已经超过了下一次采集时间,立即重置
next_collection_time = std::chrono::steady_clock::now();
}
}
}
void DataCollector::checkTimeout() {
auto now = std::chrono::steady_clock::now();
// 检查位置数据超时
auto position_elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_position_fetch_)
.count();
if (position_elapsed > dataSourceConfig_.position.timeout_ms) {
sendTimeoutWarning("position", position_elapsed);
}
// 检查无人车数据超时
auto unmanned_elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_unmanned_fetch_)
.count();
if (unmanned_elapsed > dataSourceConfig_.vehicle.timeout_ms) {
sendTimeoutWarning("unmanned", unmanned_elapsed);
}
// 检查红绿灯数据超时
auto traffic_light_elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_traffic_light_fetch_)
.count();
if (traffic_light_elapsed > dataSourceConfig_.traffic_light.timeout_ms) {
sendTimeoutWarning("traffic_light", traffic_light_elapsed);
}
}
void DataCollector::resetTimeout(const std::string& source) {
auto now = std::chrono::steady_clock::now();
if (source == "position") {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_position_fetch_)
.count();
if (elapsed > dataSourceConfig_.position.timeout_ms) {
Logger::info("Position data source connection restored after ",
elapsed, "ms timeout");
}
last_position_fetch_ = now;
} else if (source == "unmanned") {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_unmanned_fetch_)
.count();
if (elapsed > dataSourceConfig_.vehicle.timeout_ms) {
Logger::info(
"Unmanned vehicle data source connection restored after ",
elapsed, "ms timeout");
}
last_unmanned_fetch_ = now;
} else if (source == "traffic_light") {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_traffic_light_fetch_)
.count();
if (elapsed > dataSourceConfig_.traffic_light.timeout_ms) {
Logger::info("Traffic light data source connection restored after ",
elapsed, "ms timeout");
}
last_traffic_light_fetch_ = now;
}
}
void DataCollector::sendTimeoutWarning(const std::string& source,
int64_t elapsed_ms) {
auto now = std::chrono::steady_clock::now();
auto warning_elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_warning_time_)
.count();
if (warning_elapsed >= warnConfig_.warning_interval_ms) {
if (!system_) {
Logger::debug("System not set, skipping timeout warning");
return;
}
network::TimeoutWarningMessage msg;
msg.source = source;
msg.elapsed_ms = elapsed_ms;
// 根据数据源类型设置超时阈值
int64_t timeout_ms = 0;
if (source == "position") {
timeout_ms = dataSourceConfig_.position.timeout_ms;
} else if (source == "unmanned") {
timeout_ms = dataSourceConfig_.vehicle.timeout_ms;
} else if (source == "traffic_light") {
timeout_ms = dataSourceConfig_.traffic_light.timeout_ms;
}
msg.is_read_timeout = (elapsed_ms > timeout_ms);
system_->broadcastTimeoutWarning(msg);
last_warning_time_ = now;
}
}
void DataCollector::filterPositionData(std::vector<Aircraft>& aircraft,
std::vector<Vehicle>& vehicles) {
size_t originalAircraftCount = aircraft.size();
size_t originalVehicleCount = vehicles.size();
// 过滤掉不在机场区域内的航空器数据
auto aircraftIt = std::remove_if(
aircraft.begin(), aircraft.end(), [this](const Aircraft& a) {
return !airportBounds_.isPointInBounds(a.position);
});
aircraft.erase(aircraftIt, aircraft.end());
// 过滤掉不在机场区域内的车辆数据
auto vehicleIt = std::remove_if(
vehicles.begin(), vehicles.end(), [this](const Vehicle& v) {
return !airportBounds_.isPointInBounds(v.position);
});
vehicles.erase(vehicleIt, vehicles.end());
// 记录过滤结果
size_t filteredAircraftCount = originalAircraftCount - aircraft.size();
size_t filteredVehicleCount = originalVehicleCount - vehicles.size();
if (filteredAircraftCount > 0 || filteredVehicleCount > 0) {
Logger::info("数据过滤结果: 过滤掉 ", filteredAircraftCount,
" 架航空器, ", filteredVehicleCount, " 辆车辆");
}
}
bool DataCollector::fetchPositionData() {
std::vector<Aircraft> newAircraft;
std::vector<Vehicle> newVehicles;
if (!dataSource_) {
Logger::error("No data source available");
return false;
}
bool success = dataSource_->fetchPositionAircraftData(newAircraft) &&
dataSource_->fetchPositionVehicleData(newVehicles);
if (success) {
std::lock_guard<std::mutex> lock(cacheMutex_);
// 更新运动信息
for (auto& a : newAircraft) {
auto it = lastAircraftPositions_.find(a.id);
if (it != lastAircraftPositions_.end()) {
// 复制历史记录
a.copyHistoryFrom(it->second);
}
// 更新运动信息
a.updateMotion(a.geo, a.timestamp);
lastAircraftPositions_[a.id] = a;
// 更新时间戳
lastAircraftTimestamp_ = a.timestamp;
}
// 更新运动信息
for (auto& v : newVehicles) {
auto it = lastVehiclePositions_.find(v.id);
if (it != lastVehiclePositions_.end()) {
// 复制历史记录
v.copyHistoryFrom(it->second);
}
// 更新运动信息
v.updateMotion(v.geo, v.timestamp);
lastVehiclePositions_[v.id] = v;
// 更新时间戳
lastVehicleTimestamp_ = v.timestamp;
}
// 转换坐标系
for (auto& a : newAircraft) {
// 1. 先转换为世界坐标
a.position = CoordinateConverter::instance().toLocalXY(
a.geo.latitude, a.geo.longitude);
// 2. 再转换为机场坐标
a.position = airportBounds_.toAirportCoordinate(a.position);
}
for (auto& v : newVehicles) {
// 1. 先转换为世界坐标
v.position = CoordinateConverter::instance().toLocalXY(
v.geo.latitude, v.geo.longitude);
// 2. 再转换为机场坐标
v.position = airportBounds_.toAirportCoordinate(v.position);
}
// 过滤数据
filterPositionData(newAircraft, newVehicles);
aircraftCache_ = std::move(newAircraft);
vehicleCache_ = std::move(newVehicles);
resetTimeout("position");
}
return success;
}
bool DataCollector::fetchUnmannedVehicleData() {
if (!dataSource_) {
Logger::error("数据源未初始化");
return false;
}
bool any_success = false;
bool any_failure = false;
auto& vehicles = ControllableVehicles::getInstance().getVehicles();
for (const auto& vehicle : vehicles) {
if (vehicle.type == "UNMANNED") {
std::string status;
if (dataSource_->fetchUnmannedVehicleStatus(vehicle.vehicleNo,
status)) {
any_success = true;
} else {
any_failure = true;
Logger::error("获取无人车状态失败, vehicleNo: " +
vehicle.vehicleNo);
}
}
}
if (any_success) {
resetTimeout("unmanned");
// 如果有部分成功,就返回 true
return true;
}
// 只有全部失败才返回 false
return !any_failure;
}
bool DataCollector::fetchTrafficLightData() {
std::vector<TrafficLightSignal> newSignals;
if (!dataSource_) {
Logger::error("No data source available");
return false;
}
bool success = dataSource_->fetchTrafficLightSignals(newSignals);
if (success) {
std::lock_guard<std::mutex> lock(cacheMutex_);
trafficLightCache_ = std::move(newSignals);
// 更新时间戳
if (!trafficLightCache_.empty()) {
lastTrafficLightTimestamp_ = trafficLightCache_[0].timestamp;
}
resetTimeout("traffic_light");
}
return success;
}
std::vector<TrafficLightSignal> DataCollector::getTrafficLightSignals() {
std::lock_guard<std::mutex> lock(cacheMutex_);
return trafficLightCache_;
}
bool DataCollector::fetchTrafficLightSignals(
std::vector<TrafficLightSignal>& signals) {
if (!dataSource_) {
return false;
}
return dataSource_->fetchTrafficLightSignals(signals);
}
std::vector<Aircraft> DataCollector::getAircraftData() {
std::lock_guard<std::mutex> lock(cacheMutex_);
return aircraftCache_;
}
std::vector<Vehicle> DataCollector::getVehicleData() {
std::lock_guard<std::mutex> lock(cacheMutex_);
return vehicleCache_;
}
bool DataCollector::fetchUnmannedVehicleStatus(const std::string& vehicle_id,
std::string& status) {
if (!dataSource_) {
Logger::error("No data source available");
return false;
}
return dataSource_->fetchUnmannedVehicleStatus(vehicle_id, status);
}

View File

@ -0,0 +1,117 @@
#ifndef AIRPORT_COLLECTOR_DATA_COLLECTOR_H
#define AIRPORT_COLLECTOR_DATA_COLLECTOR_H
#include <memory>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <map>
#include "DataSource.h"
#include "DataSourceConfig.h"
#include "types/BasicTypes.h"
#include "config/WarnConfig.h"
#include "types/TrafficLightTypes.h"
#include "vehicle/ControllableVehicles.h"
#include "config/AirportBounds.h"
// 前向声明
class System;
class DataCollector {
public:
DataCollector(const AirportBounds& bounds);
~DataCollector();
bool initialize(const DataSourceConfig& dataSourceConfig, const WarnConfig& warnConfig);
void start();
void stop();
// 获取最新数据的接口
std::tuple<std::vector<Aircraft>, std::vector<Vehicle>, std::vector<TrafficLightSignal>>
getLatestData() const {
std::lock_guard<std::mutex> lock(cacheMutex_);
return std::make_tuple(aircraftCache_, vehicleCache_, trafficLightCache_);
}
// 获取数据的最后更新时间
std::tuple<int64_t, int64_t, int64_t> getLastUpdateTimestamps() const {
std::lock_guard<std::mutex> lock(cacheMutex_);
return {lastAircraftTimestamp_, lastVehicleTimestamp_, lastTrafficLightTimestamp_};
}
// 获取单个数据的接口
std::vector<Aircraft> getAircraftData();
std::vector<Vehicle> getVehicleData();
std::vector<TrafficLightSignal> getTrafficLightSignals();
bool fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals);
bool fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status);
bool fetchPositionData(); // 获取位置数据
void setSystem(std::shared_ptr<System> system) { system_ = system; }
void setDataSource(std::shared_ptr<DataSource> source) { dataSource_ = source; }
// 获取数据的引用访问接口
const std::vector<Aircraft>& getAircraftRef() const {
std::lock_guard<std::mutex> lock(cacheMutex_);
return aircraftCache_;
}
const std::vector<Vehicle>& getVehicleRef() const {
std::lock_guard<std::mutex> lock(cacheMutex_);
return vehicleCache_;
}
private:
std::shared_ptr<DataSource> dataSource_;
std::shared_ptr<System> system_;
DataSourceConfig dataSourceConfig_;
WarnConfig warnConfig_;
const AirportBounds& airportBounds_;
// 三个独立的采集线程
std::thread positionThread_; // 位置数据采集线程
std::thread unmannedThread_; // 无人车状态采集线程
std::thread trafficLightThread_; // 红绿灯数据采集线程
std::atomic<bool> running_{false};
std::atomic<uint64_t> loopCount_{0};
// 缓存当前数据
mutable std::mutex cacheMutex_;
std::vector<Aircraft> aircraftCache_;
std::vector<Vehicle> vehicleCache_;
std::vector<TrafficLightSignal> trafficLightCache_;
// 保存上一次位置信息(用于计算速度)
std::map<std::string, Aircraft> lastAircraftPositions_;
std::map<std::string, Vehicle> lastVehiclePositions_;
// 数据时间戳
std::atomic<int64_t> lastAircraftTimestamp_{0};
std::atomic<int64_t> lastVehicleTimestamp_{0};
std::atomic<int64_t> lastTrafficLightTimestamp_{0};
// 超时检测相关
std::chrono::steady_clock::time_point last_position_fetch_; // 位置数据最后获取时间
std::chrono::steady_clock::time_point last_unmanned_fetch_; // 无人车状态最后获取时间
std::chrono::steady_clock::time_point last_traffic_light_fetch_; // 红绿灯数据最后获取时间
std::chrono::steady_clock::time_point last_warning_time_;
std::chrono::steady_clock::time_point last_log_time_;
void checkTimeout();
void resetTimeout(const std::string& source);
void sendTimeoutWarning(const std::string& source, int64_t elapsed_ms);
// 三个独立的采集循环
void positionLoop(); // 位置数据采集循环
void unmannedLoop(); // 无人车状态采集循环
void trafficLightLoop(); // 红绿灯数据采集循环
bool fetchUnmannedVehicleData(); // 获取无人车状态数据
bool fetchTrafficLightData(); // 获取红绿灯数据
// 数据过滤方法
void filterPositionData(std::vector<Aircraft>& aircraft, std::vector<Vehicle>& vehicles);
};
#endif // AIRPORT_COLLECTOR_DATA_COLLECTOR_H

View File

@ -0,0 +1,38 @@
#ifndef AIRPORT_COLLECTOR_DATA_SOURCE_H
#define AIRPORT_COLLECTOR_DATA_SOURCE_H
#include "types/BasicTypes.h"
#include "types/TrafficLightTypes.h"
#include "types/VehicleCommand.h"
#include <optional>
#include <vector>
#include <string>
class DataSource {
public:
// 数据源类型
enum class DataSourceType {
POSITION, // 位置数据接口
UNMANNED, // 无人车接口
TRAFFIC_LIGHT // 红绿灯接口
};
virtual ~DataSource() = default;
virtual bool connect() = 0;
virtual void disconnect() = 0;
virtual bool isAvailable() const = 0;
// 位置数据接口
virtual bool fetchPositionAircraftData(std::vector<Aircraft>& aircraft) = 0;
virtual bool fetchPositionVehicleData(std::vector<Vehicle>& vehicles) = 0;
// 无人车接口
virtual bool sendUnmannedVehicleCommand(const std::string& vehicle_id, const VehicleCommand& command) = 0;
virtual bool fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status) = 0;
// 红绿灯接口
virtual bool fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals) = 0;
};
#endif // AIRPORT_COLLECTOR_DATA_SOURCE_H

View File

@ -0,0 +1,72 @@
#include "DataSourceConfig.h"
#include "config/SystemConfig.h"
#include "utils/Logger.h"
DataSourceConfig DataSourceConfig::fromSystemConfig(const SystemConfig& config) {
DataSourceConfig dataSourceConfig;
// 转换位置数据源配置
dataSourceConfig.position.host = config.data_source.position.host;
dataSourceConfig.position.port = config.data_source.position.port;
dataSourceConfig.position.aircraft_path =
config.data_source.position.aircraft_path;
dataSourceConfig.position.vehicle_path =
config.data_source.position.vehicle_path;
dataSourceConfig.position.refresh_interval_ms =
config.data_source.position.refresh_interval_ms;
dataSourceConfig.position.timeout_ms =
config.data_source.position.timeout_ms;
dataSourceConfig.position.read_timeout_ms =
config.data_source.position.read_timeout_ms;
dataSourceConfig.position.auth.username =
config.data_source.position.auth.username;
dataSourceConfig.position.auth.password =
config.data_source.position.auth.password;
dataSourceConfig.position.auth.auth_path =
config.data_source.position.auth.auth_path;
dataSourceConfig.position.auth.auth_required =
config.data_source.position.auth.auth_required;
// 转换无人车数据源配置
dataSourceConfig.vehicle.host = config.data_source.vehicle.host;
dataSourceConfig.vehicle.port = config.data_source.vehicle.port;
dataSourceConfig.vehicle.status_path =
config.data_source.vehicle.status_path;
dataSourceConfig.vehicle.command_path =
config.data_source.vehicle.command_path;
dataSourceConfig.vehicle.refresh_interval_ms =
config.data_source.vehicle.refresh_interval_ms;
dataSourceConfig.vehicle.timeout_ms = config.data_source.vehicle.timeout_ms;
dataSourceConfig.vehicle.read_timeout_ms =
config.data_source.vehicle.read_timeout_ms;
dataSourceConfig.vehicle.auth.username =
config.data_source.vehicle.auth.username;
dataSourceConfig.vehicle.auth.password =
config.data_source.vehicle.auth.password;
dataSourceConfig.vehicle.auth.auth_path =
config.data_source.vehicle.auth.auth_path;
dataSourceConfig.vehicle.auth.auth_required =
config.data_source.vehicle.auth.auth_required;
// 转换红绿灯数据源配置
dataSourceConfig.traffic_light.host = config.data_source.traffic_light.host;
dataSourceConfig.traffic_light.port = config.data_source.traffic_light.port;
dataSourceConfig.traffic_light.signal_path =
config.data_source.traffic_light.signal_path;
dataSourceConfig.traffic_light.refresh_interval_ms =
config.data_source.traffic_light.refresh_interval_ms;
dataSourceConfig.traffic_light.timeout_ms =
config.data_source.traffic_light.timeout_ms;
dataSourceConfig.traffic_light.read_timeout_ms =
config.data_source.traffic_light.read_timeout_ms;
dataSourceConfig.traffic_light.auth.username =
config.data_source.traffic_light.auth.username;
dataSourceConfig.traffic_light.auth.password =
config.data_source.traffic_light.auth.password;
dataSourceConfig.traffic_light.auth.auth_path =
config.data_source.traffic_light.auth.auth_path;
dataSourceConfig.traffic_light.auth.auth_required =
config.data_source.traffic_light.auth.auth_required;
return dataSourceConfig;
}

View File

@ -0,0 +1,171 @@
#ifndef AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H
#define AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// 前向声明
class SystemConfig;
// 认证配置
struct AuthConfig {
std::string username; // 认证用户名
std::string password; // 认证密码
std::string auth_path; // 认证接口路径
bool auth_required = false; // 是否需要认证
};
// AuthConfig 的 JSON 序列化/反序列化函数
inline void to_json(json& j, const AuthConfig& a) {
j = json{
{"username", a.username},
{"password", a.password},
{"auth_path", a.auth_path},
{"auth_required", a.auth_required}
};
}
inline void from_json(const json& j, AuthConfig& a) {
j.at("username").get_to(a.username);
j.at("password").get_to(a.password);
j.at("auth_path").get_to(a.auth_path);
j.at("auth_required").get_to(a.auth_required);
}
// 位置数据源配置
struct PositionDataConfig {
std::string host; // 服务器地址
int port; // 服务器端口
std::string aircraft_path; // 航空器位置接口
std::string vehicle_path; // 车辆位置接口
int refresh_interval_ms; // 位置数据刷新间隔
int timeout_ms; // 连接超时时间
int read_timeout_ms; // 读取超时时间
AuthConfig auth; // 认证配置
};
// PositionDataConfig 的 JSON 序列化/反序列化函数
inline void to_json(json& j, const PositionDataConfig& p) {
j = json{
{"host", p.host},
{"port", p.port},
{"aircraft_path", p.aircraft_path},
{"vehicle_path", p.vehicle_path},
{"refresh_interval_ms", p.refresh_interval_ms},
{"timeout_ms", p.timeout_ms},
{"read_timeout_ms", p.read_timeout_ms},
{"auth", p.auth}
};
}
inline void from_json(const json& j, PositionDataConfig& p) {
j.at("host").get_to(p.host);
j.at("port").get_to(p.port);
j.at("aircraft_path").get_to(p.aircraft_path);
j.at("vehicle_path").get_to(p.vehicle_path);
j.at("refresh_interval_ms").get_to(p.refresh_interval_ms);
j.at("timeout_ms").get_to(p.timeout_ms);
j.at("read_timeout_ms").get_to(p.read_timeout_ms);
j.at("auth").get_to(p.auth);
}
// 无人车数据源配置
struct UnmannedVehicleConfig {
std::string host; // 服务器地址
int port; // 服务器端口
std::string status_path; // 无人车状态接口
std::string command_path; // 无人车指令接口
int refresh_interval_ms; // 状态数据刷新间隔
int timeout_ms; // 连接超时时间
int read_timeout_ms; // 读取超时时间
AuthConfig auth; // 认证配置
};
// UnmannedVehicleConfig 的 JSON 序列化/反序列化函数
inline void to_json(json& j, const UnmannedVehicleConfig& u) {
j = json{
{"host", u.host},
{"port", u.port},
{"status_path", u.status_path},
{"command_path", u.command_path},
{"refresh_interval_ms", u.refresh_interval_ms},
{"timeout_ms", u.timeout_ms},
{"read_timeout_ms", u.read_timeout_ms},
{"auth", u.auth}
};
}
inline void from_json(const json& j, UnmannedVehicleConfig& u) {
j.at("host").get_to(u.host);
j.at("port").get_to(u.port);
j.at("status_path").get_to(u.status_path);
j.at("command_path").get_to(u.command_path);
j.at("refresh_interval_ms").get_to(u.refresh_interval_ms);
j.at("timeout_ms").get_to(u.timeout_ms);
j.at("read_timeout_ms").get_to(u.read_timeout_ms);
j.at("auth").get_to(u.auth);
}
// 红绿灯数据源配置
struct TrafficLightConfig {
std::string host; // 服务器地址
int port; // 服务器端口
std::string signal_path; // 红绿灯信号接口
int refresh_interval_ms; // 信号数据刷新间隔
int timeout_ms; // 连接超时时间
int read_timeout_ms; // 读取超时时间
AuthConfig auth; // 认证配置
};
// TrafficLightConfig 的 JSON 序列化/反序列化函数
inline void to_json(json& j, const TrafficLightConfig& t) {
j = json{
{"host", t.host},
{"port", t.port},
{"signal_path", t.signal_path},
{"refresh_interval_ms", t.refresh_interval_ms},
{"timeout_ms", t.timeout_ms},
{"read_timeout_ms", t.read_timeout_ms},
{"auth", t.auth}
};
}
inline void from_json(const json& j, TrafficLightConfig& t) {
j.at("host").get_to(t.host);
j.at("port").get_to(t.port);
j.at("signal_path").get_to(t.signal_path);
j.at("refresh_interval_ms").get_to(t.refresh_interval_ms);
j.at("timeout_ms").get_to(t.timeout_ms);
j.at("read_timeout_ms").get_to(t.read_timeout_ms);
j.at("auth").get_to(t.auth);
}
// 数据源配置
struct DataSourceConfig {
PositionDataConfig position; // 位置数据源配置
UnmannedVehicleConfig vehicle; // 无人车数据源配置
TrafficLightConfig traffic_light; // 红绿灯数据源配置
// 从系统配置创建数据源配置的静态工厂方法
static DataSourceConfig fromSystemConfig(const SystemConfig& system_config);
};
// DataSourceConfig 的 JSON 序列化/反序列化函数
inline void to_json(json& j, const DataSourceConfig& d) {
j = json{
{"position", d.position},
{"unmanned_vehicle", d.vehicle},
{"traffic_light", d.traffic_light}
};
}
inline void from_json(const json& j, DataSourceConfig& d) {
j.at("position").get_to(d.position);
j.at("unmanned_vehicle").get_to(d.vehicle);
j.at("traffic_light").get_to(d.traffic_light);
}
#endif

View File

@ -0,0 +1,179 @@
#include "AirportBounds.h"
#include "utils/Logger.h"
#include <cmath>
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
AirportBounds::AirportBounds(const std::string& configFile) {
// 如果配置文件路径为空,不加载配置
if (!configFile.empty()) {
loadConfig(configFile);
}
}
void AirportBounds::loadConfig(const std::string& configFile) {
// 检查文件是否存在
if (!std::filesystem::exists(configFile)) {
Logger::error("配置文件不存在: ", configFile);
Logger::error("当前工作目录: ",
std::filesystem::current_path().string());
throw std::runtime_error("配置文件不存在: " + configFile);
}
std::ifstream file(configFile);
if (!file.is_open()) {
throw std::runtime_error("无法打开配置文件: " + configFile);
}
try {
json j;
file >> j;
// 检查必要的字段是否存在
if (!j.contains("airport") || !j["airport"].contains("bounds")) {
throw std::runtime_error("配置文件缺少 airport.bounds 字段");
}
// 加载机场旋转角度(度)和参考点
auto& airport = j["airport"];
if (airport.contains("rotation_angle")) {
// 将角度转换为弧度
rotationAngle_ =
airport["rotation_angle"].get<double>() * M_PI / 180.0;
}
if (airport.contains("reference_point")) {
referencePoint_.x = airport["reference_point"]["x"].get<double>();
referencePoint_.y = airport["reference_point"]["y"].get<double>();
}
// 加载机场边界
auto& airportBounds = j["airport"]["bounds"];
airportBounds_ = { airportBounds["x"].get<double>(),
airportBounds["y"].get<double>(),
airportBounds["width"].get<double>(),
airportBounds["height"].get<double>() };
// 检查 areas 字段
if (!j.contains("areas")) {
throw std::runtime_error("配置文件缺少 areas 字段");
}
// 加载各区域配置
for (const auto& [key, value] : j["areas"].items()) {
AreaType type;
if (key == "runway")
type = AreaType::RUNWAY;
else if (key == "taxiway")
type = AreaType::TAXIWAY;
else if (key == "gate")
type = AreaType::GATE;
else if (key == "service")
type = AreaType::SERVICE;
else if (key == "test_zone")
type = AreaType::TEST_ZONE;
else
continue;
// 检查必要的字段
if (!value.contains("bounds") || !value.contains("config")) {
throw std::runtime_error("区域 " + key + " 缺少必要的字段");
}
// 加载区域边界
auto& bounds = value["bounds"];
areaBounds_[type] = {
bounds["x"].get<double>(), bounds["y"].get<double>(),
bounds["width"].get<double>(), bounds["height"].get<double>() };
// 加载区域配置
auto& config = value["config"];
auto& collision_radius = config["collision_radius"];
auto& warning_zone_radius = config["warning_zone_radius"];
auto& alert_zone_radius = config["alert_zone_radius"];
RadiusConfig collision_config = {
collision_radius["aircraft"].get<double>(),
collision_radius["special"].get<double>(),
collision_radius["unmanned"].get<double>() };
RadiusConfig warning_config = {
warning_zone_radius["aircraft"].get<double>(),
warning_zone_radius["special"].get<double>(),
warning_zone_radius["unmanned"].get<double>() };
RadiusConfig alert_config = {
alert_zone_radius["aircraft"].get<double>(),
alert_zone_radius["special"].get<double>(),
alert_zone_radius["unmanned"].get<double>() };
areaConfigs_[type] = { collision_config,
config["height_threshold"].get<double>(),
warning_config, alert_config };
}
} catch (const json::exception& e) {
Logger::error("JSON解析错误: ", e.what());
throw;
} catch (const std::exception& e) {
Logger::error("加载配置文件失败: ", e.what());
throw;
}
}
Vector2D AirportBounds::toAirportCoordinate(const Vector2D& point) const {
// 1. 计算相对于参考点的偏移
double dx = point.x - referencePoint_.x;
double dy = point.y - referencePoint_.y;
// 2. 应用旋转变换
double cos_angle = std::cos(rotationAngle_);
double sin_angle = std::sin(rotationAngle_);
// 3. 返回机场坐标系中的坐标(逆时针旋转)
Vector2D result{ dx * cos_angle - dy * sin_angle,
dx * sin_angle + dy * cos_angle };
return result;
}
AreaType AirportBounds::getAreaType(const Vector2D& position) const {
// 按优先级检查位置所在区域
for (const auto& [type, bounds] : areaBounds_) {
if (bounds.contains(position)) {
return type;
}
}
// 默认返回测试区
return AreaType::TEST_ZONE;
}
const AreaConfig& AirportBounds::getAreaConfig(AreaType type) const {
auto it = areaConfigs_.find(type);
if (it == areaConfigs_.end()) {
throw std::runtime_error("Invalid area type");
}
return it->second;
}
bool AirportBounds::isPointInBounds(const Vector2D& position) const {
// 在机场坐标系中判断是否在边界内
bool result = airportBounds_.contains(position);
return result;
}
bool AirportBounds::isPointInArea(const Vector2D& position,
AreaType areaType) const {
// 获取区域定义
auto it = areaBounds_.find(areaType);
if (it == areaBounds_.end()) {
return false;
}
// 在机场坐标系中判断是否在区域内
return it->second.contains(position);
}

View File

@ -0,0 +1,58 @@
#ifndef AIRPORT_BOUNDS_H
#define AIRPORT_BOUNDS_H
#include <string>
#include <vector>
#include <stdexcept>
#include "types/BasicTypes.h"
#include "spatial/QuadTree.h"
#include "AreaConfig.h"
#include <unordered_map>
// 机场区域定义
class AirportBounds {
public:
explicit AirportBounds(const std::string& configFile = "");
virtual ~AirportBounds() = default;
// 获取点所在的区域类型
virtual AreaType getAreaType(const Vector2D& position) const;
// 获取区域配置
virtual const AreaConfig& getAreaConfig(AreaType type) const;
// 获取整个机场边界
virtual const Bounds& getAirportBounds() const { return airportBounds_; }
// 获取特定区域的边界
virtual const Bounds& getAreaBounds(AreaType type) const {
auto it = areaBounds_.find(type);
if (it == areaBounds_.end()) {
throw std::runtime_error("Invalid area type");
}
return it->second;
}
// 判断点是否在机场边界内
virtual bool isPointInBounds(const Vector2D& worldPoint) const;
// 判断点是否在指定区域内(跑道、滑行道等)
virtual bool isPointInArea(const Vector2D& worldPoint, AreaType areaType) const;
// 将点从世界坐标系转换到机场坐标系
// 输入:世界坐标系中的点 (x, y)
// 输出:机场坐标系中的点,原点为机场参考点,方向为机场旋转后的方向
Vector2D toAirportCoordinate(const Vector2D& point) const;
protected:
Bounds airportBounds_; // 整个机场边界
std::unordered_map<AreaType, Bounds> areaBounds_; // 各区域边界
std::unordered_map<AreaType, AreaConfig> areaConfigs_; // 各区域配置
// 从配置文件加载数据
virtual void loadConfig(const std::string& configFile);
double rotationAngle_ = 0.0; // 机场与正北方向的夹角(弧度)
Vector2D referencePoint_; // 机场参考点(旋转中心)
};
#endif // AIRPORT_BOUNDS_H

53
src/config/AreaConfig.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef AIRPORT_CONFIG_AREA_CONFIG_H
#define AIRPORT_CONFIG_AREA_CONFIG_H
#include "types/BasicTypes.h"
// 区域类型
enum class AreaType {
RUNWAY, // 跑道
TAXIWAY, // 滑行道
GATE, // 停机位
SERVICE, // 服务区
TEST_ZONE // 测试区
};
// 半径配置
struct RadiusConfig {
double aircraft; // 航空器半径
double special; // 特勤车半径
double unmanned; // 无人车半径
double getRadius(MovingObjectType type) const {
switch (type) {
case MovingObjectType::AIRCRAFT: return aircraft;
case MovingObjectType::SPECIAL: return special;
case MovingObjectType::UNMANNED: return unmanned;
}
return special; // 默认返回特勤车半径
}
};
// 区域配置
struct AreaConfig {
RadiusConfig collision_radius; // 碰撞半径
double height_threshold; // 高度阈值
RadiusConfig warning_zone_radius; // 预警区域半径
RadiusConfig alert_zone_radius; // 告警区域半径
// 获取碰撞阈值
struct CollisionThresholds {
double warning; // 预警距离
double critical; // 告警距离
};
// 根据物体类型获取对应的阈值
CollisionThresholds getThresholds(MovingObjectType type1, MovingObjectType type2) const {
return {
warning_zone_radius.getRadius(type1) + warning_zone_radius.getRadius(type2),
alert_zone_radius.getRadius(type1) + alert_zone_radius.getRadius(type2)
};
}
};
#endif // AIRPORT_CONFIG_AREA_CONFIG_H

View File

@ -0,0 +1,78 @@
#include "config/IntersectionConfig.h"
#include "utils/Logger.h"
#include <nlohmann/json.hpp>
#include <fstream>
#include <stdexcept>
#include <algorithm>
IntersectionConfig IntersectionConfig::load(const std::string& configFile) {
IntersectionConfig config;
std::ifstream file(configFile);
if (!file.is_open()) {
throw std::runtime_error("Failed to open intersection config file: " + configFile);
}
try {
nlohmann::json jsonConfig;
file >> jsonConfig;
for (const auto& item : jsonConfig["intersections"]) {
Intersection info;
info.id = item["id"].get<std::string>();
info.name = item["name"].get<std::string>();
info.trafficLightId = item["trafficLightId"].get<std::string>();
// 加载位置信息,检查是否为 null
if (item["position"]["longitude"].is_null() ||
item["position"]["latitude"].is_null() ||
item["position"]["altitude"].is_null()) {
throw std::runtime_error("Position values cannot be null for intersection: " + info.id);
}
info.position.longitude = item["position"]["longitude"].get<double>();
info.position.latitude = item["position"]["latitude"].get<double>();
info.position.altitude = item["position"]["altitude"].get<double>();
// 加载路口宽度和安全区配置,检查是否为 null
if (item["width"].is_null() ||
item["safetyZone"]["aircraftRadius"].is_null() ||
item["safetyZone"]["vehicleRadius"].is_null()) {
throw std::runtime_error("Width or safety zone values cannot be null for intersection: " + info.id);
}
info.width = item["width"].get<double>();
info.safetyZone.aircraftRadius = item["safetyZone"]["aircraftRadius"].get<double>();
info.safetyZone.vehicleRadius = item["safetyZone"]["vehicleRadius"].get<double>();
config.intersections_.push_back(info);
Logger::debug("Loaded intersection: id=", info.id,
", name=", info.name,
", trafficLightId=", info.trafficLightId,
", width=", info.width,
", aircraftRadius=", info.safetyZone.aircraftRadius,
", vehicleRadius=", info.safetyZone.vehicleRadius);
}
Logger::info("Loaded ", config.intersections_.size(), " intersections");
} catch (const std::exception& e) {
throw std::runtime_error("Failed to parse intersection config: " + std::string(e.what()));
}
return config;
}
const Intersection* IntersectionConfig::findByTrafficLightId(const std::string& trafficLightId) const {
auto iter = std::find_if(intersections_.begin(), intersections_.end(),
[&](const Intersection& info) {
return info.trafficLightId == trafficLightId;
});
return iter != intersections_.end() ? &(*iter) : nullptr;
}
const Intersection* IntersectionConfig::findById(const std::string& intersectionId) const {
auto iter = std::find_if(intersections_.begin(), intersections_.end(),
[&](const Intersection& info) {
return info.id == intersectionId;
});
return iter != intersections_.end() ? &(*iter) : nullptr;
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <vector>
#include "types/BasicTypes.h"
struct IntersectionPosition {
double longitude;
double latitude;
double altitude;
};
// 路口安全区配置
struct SafetyZoneConfig {
double aircraftRadius; // 航空器安全区半径
double vehicleRadius; // 特勤车安全区半径
};
struct Intersection {
std::string id;
std::string name;
std::string trafficLightId;
IntersectionPosition position;
double width; // 路口宽度,单位:米
SafetyZoneConfig safetyZone; // 安全区配置
};
class IntersectionConfig {
public:
static IntersectionConfig load(const std::string& configFile);
// 根据红绿灯ID查找路口
const Intersection* findByTrafficLightId(const std::string& trafficLightId) const;
// 根据路口ID查找路口
const Intersection* findById(const std::string& intersectionId) const;
// 获取所有路口配置
const std::vector<Intersection>& getIntersections() const { return intersections_; }
private:
std::vector<Intersection> intersections_;
};

View File

@ -0,0 +1,21 @@
#include "SystemConfig.h"
#include "utils/Logger.h"
#include <fstream>
void SystemConfig::load(const std::string& filename) {
try {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("Failed to open config file: " + filename);
}
nlohmann::json j;
file >> j;
nlohmann::from_json(j, *this);
Logger::info("Loaded system configuration from ", filename);
} catch (const std::exception& e) {
throw std::runtime_error("Failed to load config: " +
std::string(e.what()));
}
}

293
src/config/SystemConfig.h Normal file
View File

@ -0,0 +1,293 @@
#pragma once
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "collector/DataSourceConfig.h"
using json = nlohmann::json;
class System; // 前向声明
// 基础结构体
struct CoordinatePoint {
std::string point;
double latitude;
double longitude;
};
// JSON 序列化/反序列化函数
inline void to_json(json& j, const CoordinatePoint& p) {
j = json{
{"point", p.point},
{"latitude", p.latitude},
{"longitude", p.longitude}
};
}
inline void from_json(const json& j, CoordinatePoint& p) {
j.at("point").get_to(p.point);
j.at("latitude").get_to(p.latitude);
j.at("longitude").get_to(p.longitude);
}
// SystemConfig 类定义
class SystemConfig {
public:
struct Airport {
struct ReferencePoint {
double latitude;
double longitude;
} reference_point;
std::string name;
std::string iata;
std::string icao;
std::vector<CoordinatePoint> coordinate_points;
} airport;
DataSourceConfig data_source; // 使用 DataSourceConfig 替换原有的 DataSource 结构
struct WebSocket {
struct PositionUpdate {
int aircraft_interval_ms;
int vehicle_interval_ms;
int traffic_light_interval_ms;
} position_update;
uint16_t port;
int max_connections;
int ping_interval_ms;
} websocket;
struct CollisionDetection {
struct Prediction {
double time_window;
double vehicle_size;
double aircraft_size;
double min_unmanned_speed;
} prediction;
int update_interval_ms;
} collision_detection;
struct Logging {
std::string level;
std::string file;
int max_size_mb;
int max_files;
bool console_output;
} logging;
struct Debug {
bool enable_mock_data;
bool save_raw_data;
bool profile_performance;
} debug;
struct Warning {
int warning_interval_ms;
int log_interval_ms;
} warning;
// 新增: 红绿灯 HTTP 服务器配置
struct TrafficLightServerConfig {
uint16_t port = 8082; // Default port if not specified in config
int max_connections = 100;
// Can add other relevant configs like timeout etc.
} traffic_light_server;
// 新增: 模拟的移动红绿灯目标路口 ID
std::string simulated_mobile_light_target_intersection_id = "T2路口"; // Default value
static SystemConfig& instance() {
static SystemConfig instance;
return instance;
}
void load(const std::string& filename);
SystemConfig(const SystemConfig&) = delete;
SystemConfig& operator=(const SystemConfig&) = delete;
friend class System;
private:
SystemConfig() = default;
};
// 为每个嵌套结构体定义 JSON 序列化/反序列化函数
inline void to_json(json& j, const SystemConfig::Airport::ReferencePoint& p) {
j = json{
{"latitude", p.latitude},
{"longitude", p.longitude}
};
}
inline void from_json(const json& j, SystemConfig::Airport::ReferencePoint& p) {
j.at("latitude").get_to(p.latitude);
j.at("longitude").get_to(p.longitude);
}
inline void to_json(json& j, const SystemConfig::Airport& p) {
j = json{
{"name", p.name},
{"iata", p.iata},
{"icao", p.icao},
{"reference_point", p.reference_point},
{"coordinate_points", p.coordinate_points}
};
}
inline void from_json(const json& j, SystemConfig::Airport& p) {
j.at("name").get_to(p.name);
j.at("iata").get_to(p.iata);
j.at("icao").get_to(p.icao);
j.at("reference_point").get_to(p.reference_point);
j.at("coordinate_points").get_to(p.coordinate_points);
}
inline void to_json(json& j, const SystemConfig::WebSocket::PositionUpdate& p) {
j = json{
{"aircraft_interval_ms", p.aircraft_interval_ms},
{"vehicle_interval_ms", p.vehicle_interval_ms},
{"traffic_light_interval_ms", p.traffic_light_interval_ms}
};
}
inline void from_json(const json& j, SystemConfig::WebSocket::PositionUpdate& p) {
j.at("aircraft_interval_ms").get_to(p.aircraft_interval_ms);
j.at("vehicle_interval_ms").get_to(p.vehicle_interval_ms);
j.at("traffic_light_interval_ms").get_to(p.traffic_light_interval_ms);
}
inline void to_json(json& j, const SystemConfig::WebSocket& p) {
j = json{
{"port", p.port},
{"max_connections", p.max_connections},
{"ping_interval_ms", p.ping_interval_ms},
{"position_update", p.position_update}
};
}
inline void from_json(const json& j, SystemConfig::WebSocket& p) {
j.at("port").get_to(p.port);
j.at("max_connections").get_to(p.max_connections);
j.at("ping_interval_ms").get_to(p.ping_interval_ms);
j.at("position_update").get_to(p.position_update);
}
inline void to_json(json& j, const SystemConfig::CollisionDetection::Prediction& p) {
j = json{
{"time_window", p.time_window},
{"vehicle_size", p.vehicle_size},
{"aircraft_size", p.aircraft_size},
{"min_unmanned_speed", p.min_unmanned_speed}
};
}
inline void from_json(const json& j, SystemConfig::CollisionDetection::Prediction& p) {
j.at("time_window").get_to(p.time_window);
j.at("vehicle_size").get_to(p.vehicle_size);
j.at("aircraft_size").get_to(p.aircraft_size);
j.at("min_unmanned_speed").get_to(p.min_unmanned_speed);
}
inline void to_json(json& j, const SystemConfig::CollisionDetection& p) {
j = json{
{"update_interval_ms", p.update_interval_ms},
{"prediction", p.prediction}
};
}
inline void from_json(const json& j, SystemConfig::CollisionDetection& p) {
j.at("update_interval_ms").get_to(p.update_interval_ms);
j.at("prediction").get_to(p.prediction);
}
inline void to_json(json& j, const SystemConfig::Logging& p) {
j = json{
{"level", p.level},
{"file", p.file},
{"max_size_mb", p.max_size_mb},
{"max_files", p.max_files},
{"console_output", p.console_output}
};
}
inline void from_json(const json& j, SystemConfig::Logging& p) {
j.at("level").get_to(p.level);
j.at("file").get_to(p.file);
j.at("max_size_mb").get_to(p.max_size_mb);
j.at("max_files").get_to(p.max_files);
j.at("console_output").get_to(p.console_output);
}
inline void to_json(json& j, const SystemConfig::Debug& p) {
j = json{
{"enable_mock_data", p.enable_mock_data},
{"save_raw_data", p.save_raw_data},
{"profile_performance", p.profile_performance}
};
}
inline void from_json(const json& j, SystemConfig::Debug& p) {
j.at("enable_mock_data").get_to(p.enable_mock_data);
j.at("save_raw_data").get_to(p.save_raw_data);
j.at("profile_performance").get_to(p.profile_performance);
}
inline void to_json(json& j, const SystemConfig::Warning& p) {
j = json{
{"warning_interval_ms", p.warning_interval_ms},
{"log_interval_ms", p.log_interval_ms}
};
}
inline void from_json(const json& j, SystemConfig::Warning& p) {
j.at("warning_interval_ms").get_to(p.warning_interval_ms);
j.at("log_interval_ms").get_to(p.log_interval_ms);
}
// 新增: TrafficLightServerConfig 的 JSON 序列化/反序列化函数
inline void to_json(json& j, const SystemConfig::TrafficLightServerConfig& p) {
j = json{
{"port", p.port},
{"max_connections", p.max_connections}
// Serialize other fields if added
};
}
inline void from_json(const json& j, SystemConfig::TrafficLightServerConfig& p) {
// Use .value() to provide default values if keys are missing
p.port = j.value("port", static_cast<uint16_t>(8082));
p.max_connections = j.value("max_connections", 100);
// Deserialize other fields if added
}
inline void to_json(json& j, const SystemConfig& config) {
j = json{
{"airport", config.airport},
{"data_source", config.data_source},
{"websocket", config.websocket},
{"collision_detection", config.collision_detection},
{"logging", config.logging},
{"debug", config.debug},
{"warning", config.warning},
{"traffic_light_server", config.traffic_light_server},
{"simulated_mobile_light_target_intersection_id", config.simulated_mobile_light_target_intersection_id}
};
}
inline void from_json(const json& j, SystemConfig& config) {
j.at("airport").get_to(config.airport);
j.at("data_source").get_to(config.data_source);
j.at("websocket").get_to(config.websocket);
j.at("collision_detection").get_to(config.collision_detection);
j.at("logging").get_to(config.logging);
j.at("debug").get_to(config.debug);
j.at("warning").get_to(config.warning);
config.traffic_light_server = j.value("traffic_light_server", SystemConfig::TrafficLightServerConfig{});
config.simulated_mobile_light_target_intersection_id = j.value("simulated_mobile_light_target_intersection_id", std::string("T2路口"));
}

11
src/config/WarnConfig.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef AIRPORT_NETWORK_WARN_CONFIG_H
#define AIRPORT_NETWORK_WARN_CONFIG_H
#include <string>
struct WarnConfig {
int warning_interval_ms;
int log_interval_ms;
};
#endif // AIRPORT_NETWORK_WARN_CONFIG_H

856
src/core/System.cpp Normal file
View File

@ -0,0 +1,856 @@
#include <csignal>
#include <typeinfo>
#include <chrono>
#include <cmath>
#include "System.h"
#include "utils/Logger.h"
#include "collector/DataCollector.h"
#include "spatial/CoordinateConverter.h"
#include "network/TrafficLightHttpServer.h"
#include "config/SystemConfig.h"
#include "types/TrafficLightTypes.h"
#include "network/WebSocketServer.h"
#include <nlohmann/json.hpp>
System* System::instance_ = nullptr;
System::System()
: controllableVehicles_(ControllableVehicles::getInstance()) {
instance_ = this;
std::signal(SIGINT, signalHandler);
std::signal(SIGTERM, signalHandler);
// 使用单例模式获取配置实例
auto& config = SystemConfig::instance();
}
System::~System() {
stop();
instance_ = nullptr;
}
void System::signalHandler(int signal) {
Logger::info("Received signal: ", signal);
if (instance_) {
instance_->stop();
}
std::exit(0);
}
bool System::initialize() {
try {
// 配置已在 main 中加载,这里直接使用
const auto& system_config = SystemConfig::instance();
// 初始化全局坐标转换器
CoordinateConverter::instance().setReferencePoint(
system_config.airport.reference_point.latitude,
system_config.airport.reference_point.longitude);
// 加载路口配置
intersection_config_ = IntersectionConfig::load("config/intersections.json");
Logger::info("Loaded {} intersections", intersection_config_.getIntersections().size());
// 初始化 WebSocket 服务器
ws_server_ = std::make_unique<network::WebSocketServer>(system_config.websocket.port);
ws_thread_ = std::thread([this]() { ws_server_->start(); });
Logger::info("WebSocket server initialized on port ", system_config.websocket.port);
// 新增: 初始化并启动红绿灯 HTTP 服务器
try {
traffic_light_http_server_ = std::make_unique<network::TrafficLightHttpServer>(
system_config.traffic_light_server.port,
system_config.traffic_light_server.max_connections,
".",
*this
);
// Start server in its own thread (start itself handles creating internal threads for io_context)
traffic_light_http_server_thread_ = std::thread([this]() {
try {
traffic_light_http_server_->start(2); // Use 2 threads for IO context
} catch (const std::exception& e) {
Logger::error("TrafficLightHttpServer thread failed to start: ", e.what());
}
});
Logger::info("Traffic light HTTP server initialized on port ", system_config.traffic_light_server.port);
} catch (const std::exception& e) {
Logger::error("Failed to initialize Traffic Light HTTP Server: ", e.what());
// Decide if this failure is critical
return false; // Example: treat as critical
}
// 加载机场区域配置
airportBounds_ = std::make_unique<AirportBounds>("config/airport_bounds.json");
// 初始化冲突检测器
collisionDetector_ = std::make_unique<CollisionDetector>(*airportBounds_, controllableVehicles_);
// 初始化红绿灯检测器
trafficLightDetector_ = std::make_unique<TrafficLightDetector>(intersection_config_, controllableVehicles_, *this);
// 初始化安全区
initializeSafetyZones();
// 加载告警配置
WarnConfig warnConfig{
system_config.warning.warning_interval_ms,
system_config.warning.log_interval_ms };
// 创建数据采集器并初始化
dataCollector_ = std::make_unique<DataCollector>(*airportBounds_);
return dataCollector_->initialize(system_config.data_source, warnConfig);
} catch (const std::exception& e) {
Logger::error("Failed to initialize system: ", e.what());
return false;
}
}
void System::initializeSafetyZones() {
// 清空现有的安全区
safetyZones_.clear();
// 获取所有路口配置
const auto& intersections = intersection_config_.getIntersections();
// 为每个路口创建安全区
for (const auto& intersection : intersections) {
// 获取路口中心点的地理坐标
Vector2D center = CoordinateConverter::instance().toLocalXY(
intersection.position.latitude,
intersection.position.longitude);
// 创建安全区
auto safetyZone = std::make_unique<SafetyZone>(
center,
intersection.safetyZone.aircraftRadius, // 飞机安全区半径
intersection.safetyZone.vehicleRadius // 特勤车安全区半径
);
Logger::debug("创建路口安全区: id=", intersection.id,
", center=(", center.x, ",", center.y, ")",
", aircraftRadius=", intersection.safetyZone.aircraftRadius,
", vehicleRadius=", intersection.safetyZone.vehicleRadius);
// 保存安全区
safetyZones_[intersection.id] = std::move(safetyZone);
}
}
void System::start() {
if (running_) {
return;
}
running_ = true;
dataCollector_->start();
processThread_ = std::thread(&System::processLoop, this);
Logger::info("System processing loop started");
}
void System::stop() {
Logger::info("Stopping system...");
running_ = false;
// Stop data collector first
if (dataCollector_) {
dataCollector_->stop();
}
// Stop WebSocket server
if (ws_server_) {
ws_server_->stop();
}
if (ws_thread_.joinable()) {
ws_thread_.join();
}
// 新增: Stop Traffic Light HTTP Server
if (traffic_light_http_server_) {
traffic_light_http_server_->stop();
}
if (traffic_light_http_server_thread_.joinable()) {
traffic_light_http_server_thread_.join();
}
// Stop processing loop
if (processThread_.joinable()) {
processThread_.join();
}
Logger::info("System fully stopped.");
}
void System::processLoop() {
while (running_) {
try {
auto now = std::chrono::steady_clock::now();
// 获取最新数据和时间戳
std::tie(latest_aircraft_, latest_vehicles_, latest_traffic_lights_) = dataCollector_->getLatestData();
auto [aircraft_ts, vehicle_ts, traffic_light_ts] = dataCollector_->getLastUpdateTimestamps();
// 检查数据更新
bool has_new_data = false;
if (aircraft_ts > last_aircraft_timestamp) {
has_new_data = true;
last_aircraft_timestamp = aircraft_ts;
Logger::debug("航空器数据已更新,新时间戳: ", aircraft_ts);
}
if (vehicle_ts > last_vehicle_timestamp) {
has_new_data = true;
last_vehicle_timestamp = vehicle_ts;
Logger::debug("车辆数据已更新,新时间戳: ", vehicle_ts);
}
if (traffic_light_ts > last_traffic_light_timestamp) {
has_new_data = true;
last_traffic_light_timestamp = traffic_light_ts;
Logger::debug("红绿灯数据已更新,新时间戳: ", traffic_light_ts);
}
if (has_new_data) {
// 使用成员变量的引用构建 objects 向量
std::vector<MovingObject*> objects;
for (auto& ac : latest_aircraft_) {
objects.push_back(&ac);
}
for (auto& veh : latest_vehicles_) {
const auto* config = controllableVehicles_.findVehicle(veh.vehicleNo);
if (config) {
veh.type = config->type == "UNMANNED" ? MovingObjectType::UNMANNED : MovingObjectType::SPECIAL;
veh.isControllable = config->type == "UNMANNED";
}
objects.push_back(&veh);
}
// 创建当前周期的风险管理器
std::unordered_map<std::string, RiskLevel> vehicleMaxRiskLevels; // 统一记录所有车辆的最高风险等级
std::vector<CollisionRisk> detectedRisks; // 统一收集所有检测到的风险
// 检查安全区更新
auto safety_zone_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_safety_zone_update)
.count();
if (safety_zone_elapsed >= SystemConfig::instance().collision_detection.update_interval_ms) {
updateSafetyZoneStates(objects);
Logger::debug("开始检查安全区冲突...");
// 记录安全区风险检测前的风险数量
size_t beforeSafetyZone = detectedRisks.size();
checkUnmannedVehicleSafetyZones(latest_vehicles_, objects, vehicleMaxRiskLevels, detectedRisks);
Logger::debug("安全区检测新增风险数量: ", detectedRisks.size() - beforeSafetyZone);
last_safety_zone_update = now;
}
// 检查和处理常规碰撞风险
collisionDetector_->updateTraffic(latest_aircraft_, latest_vehicles_);
auto collisionRisks = collisionDetector_->detectCollisions();
Logger::debug("碰撞检测器检测到风险数量: ", collisionRisks.size());
for (const auto& risk : collisionRisks) {
Logger::debug("碰撞风险: id1=", risk.id1,
", id2=", risk.id2,
", level=", static_cast<int>(risk.level),
", distance=", risk.distance);
}
// 合并风险
size_t beforeMerge = detectedRisks.size();
detectedRisks.insert(detectedRisks.end(), collisionRisks.begin(), collisionRisks.end());
Logger::debug("合并后新增风险数量: ", detectedRisks.size() - beforeMerge);
// 统一处理所有风险
processCollisions(detectedRisks, vehicleMaxRiskLevels);
// 广播位置更新
for (const auto& ac : latest_aircraft_) {
broadcastPositionUpdate(ac);
}
for (const auto& veh : latest_vehicles_) {
broadcastPositionUpdate(veh);
}
// 处理红绿灯信号
for (const auto& signal : latest_traffic_lights_) {
broadcastTrafficLightStatus(signal);
trafficLightDetector_->processSignal(signal, latest_vehicles_);
}
}
// 等待下一次处理
std::this_thread::sleep_for(std::chrono::milliseconds(
SystemConfig::instance().collision_detection.update_interval_ms));
} catch (const std::exception& e) {
Logger::error("处理循环发生错误: ", e.what());
}
}
}
void System::checkUnmannedVehicleSafetyZones(
const std::vector<Vehicle>& vehicles,
const std::vector<MovingObject*>& objects,
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels,
std::vector<CollisionRisk>& detectedRisks) {
// 遍历所有无人车
for (const auto& vehicle : vehicles) {
// 遍历所有路口安全区
for (const auto& [intersectionId, zone] : safetyZones_) {
if (zone->getState() == SafetyZoneState::INACTIVE) {
continue;
}
// 计算无人车到安全区中心的距离
double dx = vehicle.position.x - zone->getCenter().x;
double dy = vehicle.position.y - zone->getCenter().y;
double distance = std::sqrt(dx * dx + dy * dy);
double zoneRadius = zone->getCurrentRadius();
RiskLevel riskLevel = RiskLevel::NONE;
if (distance <= zoneRadius) {
riskLevel = RiskLevel::CRITICAL;
} else if (distance <= 2 * zoneRadius) {
riskLevel = RiskLevel::WARNING;
}
if (riskLevel != RiskLevel::NONE) {
// 查找触发安全区的目标物体
for (const auto* obj : objects) {
// 添加判断:跳过与自己的碰撞检测
if (obj->id == vehicle.id) {
continue;
}
if ((obj->isAircraft() || obj->isSpecialVehicle()) &&
zone->isObjectInZone(*obj)) {
// 创建风险对象
CollisionRisk risk;
risk.id1 = vehicle.id;
risk.id2 = obj->id;
risk.level = riskLevel;
risk.distance = distance;
risk.minDistance = distance;
risk.relativeSpeed = std::sqrt(
std::pow(obj->speed * std::cos(obj->heading) -
vehicle.speed * std::cos(vehicle.heading),
2) +
std::pow(obj->speed * std::sin(obj->heading) -
vehicle.speed * std::sin(vehicle.heading),
2));
risk.relativeMotion = {
obj->position.x - vehicle.position.x,
obj->position.y - vehicle.position.y };
detectedRisks.push_back(risk);
// 更新风险级别
auto& currentRisk = vehicleMaxRiskLevels[vehicle.id];
currentRisk = std::max(currentRisk, riskLevel);
Logger::debug("安全区风险: id1=", risk.id1,
", id2=", risk.id2,
", level=", static_cast<int>(risk.level),
", distance=", risk.distance);
break;
}
}
}
}
}
}
void System::processCollisions(
const std::vector<CollisionRisk>& detectedRisks,
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels) {
// 记录当前有风险的可控车辆
std::unordered_set<std::string> currentVehiclesWithRisk;
// 修改日志输出,显示更多信息
Logger::debug("开始处理碰撞风险,风险数量: ", detectedRisks.size());
for (const auto& risk : detectedRisks) {
Logger::debug("待处理风险: id1=", risk.id1,
", id2=", risk.id2,
", level=", static_cast<int>(risk.level),
", distance=", risk.distance,
", minDistance=", risk.minDistance);
}
// 处理当前的碰撞风险
for (const auto& risk : detectedRisks) {
// 处理 id1 和 id2 中的可控车辆
bool id1_controllable = controllableVehicles_.isControllable(risk.id1);
bool id2_controllable = controllableVehicles_.isControllable(risk.id2);
auto processVehicle = [&](const std::string& vehicleId, const std::string& otherId) {
// 发送指令
VehicleCommand cmd;
cmd.vehicleId = vehicleId;
cmd.type = risk.level == RiskLevel::CRITICAL ? CommandType::ALERT : CommandType::WARNING;
cmd.reason = otherId.substr(0, 2) == "AC" ? CommandReason::AIRCRAFT_CROSSING : CommandReason::SPECIAL_VEHICLE;
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
// 设置目标位置
const MovingObject* target = findVehicle(otherId);
if (target) {
cmd.latitude = target->geo.latitude;
cmd.longitude = target->geo.longitude;
cmd.relativeSpeed = risk.relativeSpeed;
cmd.relativeMotionX = risk.relativeMotion.x;
cmd.relativeMotionY = risk.relativeMotion.y;
cmd.minDistance = risk.minDistance;
}
Logger::debug("准备发送指令: 车辆=", vehicleId,
", 类型=", cmd.type == CommandType::ALERT ? "ALERT" : "WARNING",
", 原因=", cmd.reason == CommandReason::AIRCRAFT_CROSSING ? "AIRCRAFT_CROSSING" : "SPECIAL_VEHICLE",
", 距离=", risk.distance,
", 风险等级=", static_cast<int>(risk.level));
broadcastVehicleCommand(cmd);
controllableVehicles_.sendCommand(vehicleId, cmd);
// 更新风险记录
currentVehiclesWithRisk.insert(vehicleId);
vehicleMaxRiskLevels[vehicleId] = risk.level;
Logger::info("碰撞风险: 车辆=", vehicleId,
", 目标=", otherId,
", 距离=", risk.distance, "m",
", 最小距离=", risk.minDistance, "m",
", 相对速度=", risk.relativeSpeed, "m/s");
};
// 为每个可控车辆处理风险
if (id1_controllable) {
processVehicle(risk.id1, risk.id2);
}
if (id2_controllable) {
processVehicle(risk.id2, risk.id1);
}
// 广播碰撞预警消息
broadcastCollisionWarning(risk);
}
// 处理恢复指令
for (const auto& vehicleId : lastVehiclesWithRisk_) {
if (currentVehiclesWithRisk.find(vehicleId) == currentVehiclesWithRisk.end()) {
auto riskIt = vehicleMaxRiskLevels.find(vehicleId);
if (riskIt == vehicleMaxRiskLevels.end() || riskIt->second == RiskLevel::NONE) {
VehicleCommand cmd;
cmd.vehicleId = vehicleId;
cmd.type = CommandType::RESUME;
cmd.reason = CommandReason::RESUME_TRAFFIC;
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
broadcastVehicleCommand(cmd);
controllableVehicles_.sendCommand(vehicleId, cmd);
Logger::info("发送恢复指令到车辆: ", vehicleId);
}
}
}
// 更新风险车辆列表
lastVehiclesWithRisk_ = std::move(currentVehiclesWithRisk);
}
void System::broadcastPositionUpdate(const MovingObject& obj) {
if (!ws_server_) {
return;
}
network::PositionUpdateMessage msg;
msg.objectId = obj.id;
msg.objectType = (typeid(obj) == typeid(Aircraft)) ? "aircraft" : "vehicle";
msg.timestamp = obj.timestamp;
msg.longitude = obj.geo.longitude;
msg.latitude = obj.geo.latitude;
msg.heading = obj.heading;
msg.speed = obj.speed;
nlohmann::json j = {
{"type", "position_update"},
{"object_id", msg.objectId},
{"object_type", msg.objectType},
{"timestamp", msg.timestamp},
{"position", {{"longitude", msg.longitude}, {"latitude", msg.latitude}}},
{"heading", msg.heading},
{"speed", msg.speed} };
ws_server_->broadcast(j.dump());
Logger::debug("广播位置更新: id=", msg.objectId,
" type=", msg.objectType,
" pos=(", msg.longitude, ",", msg.latitude, ")",
" heading=", msg.heading,
" speed=", msg.speed,
" timestamp=", msg.timestamp);
}
// 新增辅助函数:将 SignalStatus 转换为字符串
std::string signalStatusToString(SignalStatus status) {
switch (status) {
case SignalStatus::RED: return "RED";
case SignalStatus::GREEN: return "GREEN";
case SignalStatus::YELLOW: return "YELLOW";
default: return "UNKNOWN";
}
}
void System::broadcastTrafficLightStatus(const TrafficLightSignal& signal) {
if (!ws_server_) {
return;
}
// 查找路口信息
const Intersection* intersection = intersection_config_.findByTrafficLightId(signal.trafficLightId);
nlohmann::json j = {
{"type", "traffic_light_status"},
{"id", signal.trafficLightId},
// 修改: 将 status 改为一个包含 ns 和 ew 状态的对象
{"status", {
{"ns", signalStatusToString(signal.ns_status)},
{"ew", signalStatusToString(signal.ew_status)}
}},
{"timestamp", signal.timestamp}
};
// 添加路口信息
if (intersection) {
j["intersection"] = intersection->id;
j["position"] = {
{"longitude", intersection->position.longitude},
{"latitude", intersection->position.latitude} };
}
ws_server_->broadcast(j.dump());
// 修改日志记录
Logger::debug("广播红绿灯状态: id=", signal.trafficLightId,
", NS_Status=", signalStatusToString(signal.ns_status),
", EW_Status=", signalStatusToString(signal.ew_status),
", intersection=", intersection ? intersection->id : "",
", timestamp=", signal.timestamp);
}
std::string getRiskLevelString(RiskLevel level) {
switch (level) {
case RiskLevel::CRITICAL:
return "critical";
case RiskLevel::WARNING:
return "warning";
default:
return "none";
}
}
void System::broadcastCollisionWarning(const CollisionRisk& risk) {
if (!ws_server_) {
return;
}
// 构造冲突预警消息
nlohmann::json j = {
{"type", "collision_warning"},
{"id1", risk.id1},
{"id2", risk.id2},
{"distance", risk.distance},
{"relativeSpeed", risk.relativeSpeed},
{"warningLevel", getRiskLevelString(risk.level)},
{"timestamp", std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count()} };
// 广播冲突预警消息
ws_server_->broadcast(j.dump());
Logger::debug("广播冲突预警: id1=", risk.id1, " id2=", risk.id2,
" distance=", risk.distance, "m",
" relativeSpeed=", risk.relativeSpeed, "m/s",
" level=", getRiskLevelString(risk.level));
}
void System::broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning) {
if (ws_server_) {
ws_server_->broadcast(warning.toJson().dump());
// 根据超时时间记录不同级的日志
if (warning.elapsed_ms > 30000) { // 30秒以上
Logger::error("Severe timeout: ", warning.source, " - ",
warning.elapsed_ms, "ms without response");
} else {
Logger::warning("Connection timeout: ", warning.source, " - ",
warning.elapsed_ms, "ms without response");
}
}
}
void System::broadcastVehicleCommand(const VehicleCommand& cmd) {
if (!ws_server_) {
return;
}
nlohmann::json j = {
{"type", "vehicle_command"},
{"vehicleId", cmd.vehicleId},
{"commandType", [&]() {
switch (cmd.type) {
case CommandType::SIGNAL:
return "SIGNAL";
case CommandType::ALERT:
return "ALERT";
case CommandType::WARNING:
return "WARNING";
case CommandType::RESUME:
return "RESUME";
case CommandType::PARKING:
return "PARKING";
default:
return "UNKNOWN";
}
}()},
{"reason", [&]() {
switch (cmd.reason) {
case CommandReason::TRAFFIC_LIGHT:
return "TRAFFIC_LIGHT";
case CommandReason::AIRCRAFT_CROSSING:
return "AIRCRAFT_CROSSING";
case CommandReason::SPECIAL_VEHICLE:
return "SPECIAL_VEHICLE";
case CommandReason::AIRCRAFT_PUSH:
return "AIRCRAFT_PUSH";
case CommandReason::RESUME_TRAFFIC:
return "RESUME_TRAFFIC";
case CommandReason::PARKING_SIDE:
return "PARKING_SIDE";
default:
return "UNKNOWN";
}
}()},
{"timestamp", cmd.timestamp} };
// 添加可选字段
if (cmd.type == CommandType::SIGNAL) {
j["signalState"] = cmd.signalState == SignalState::RED ? "RED" : "GREEN";
j["intersectionId"] = cmd.intersectionId;
}
// 添目标位置(对于所有非 RESUME 类型的指令)
if (cmd.type != CommandType::RESUME) {
j["targetLatitude"] = cmd.latitude;
j["targetLongitude"] = cmd.longitude;
}
ws_server_->broadcast(j.dump());
}
const MovingObject* System::findVehicle(const std::string& vehicleId) const {
if (!dataCollector_) {
return nullptr;
}
// 获取数据引用注意DataCollector 内部会加锁)
const auto& aircrafts = dataCollector_->getAircraftRef();
const auto& vehicles = dataCollector_->getVehicleRef();
// 在航空器中查找
for (const auto& aircraft : aircrafts) {
if (aircraft.flightNo == vehicleId) {
return &aircraft;
}
}
// 在车辆中查找
for (const auto& vehicle : vehicles) {
if (vehicle.vehicleNo == vehicleId) {
return &vehicle;
}
}
return nullptr;
}
void System::updateSafetyZoneStates(const std::vector<MovingObject*>& objects) {
// 重置所有安全区状态为未激活,同时重置类型
for (auto& [id, zone] : safetyZones_) {
zone->setState(SafetyZoneState::INACTIVE);
zone->resetType(); // 重置安全区类型
}
// 检查所有移动物体
for (const auto& obj : objects) {
Logger::debug("检查移动物体: id=", obj->id,
", type=", obj->type == MovingObjectType::UNMANNED ? "无人车" : (obj->type == MovingObjectType::SPECIAL ? "特勤车" : "飞机"),
", isAircraft=", obj->isAircraft(),
", isSpecialVehicle=", obj->isSpecialVehicle(),
", isUnmannedVehicle=", obj->isUnmannedVehicle());
// 只检查飞机和特勤车
if (obj->isAircraft() || obj->isSpecialVehicle()) {
checkSafetyZoneIntrusion(*obj);
}
}
}
void System::checkSafetyZoneIntrusion(const MovingObject& obj) {
Logger::debug("检查安全区入侵: id=", obj.id,
", type=", obj.isAircraft() ? "飞机" : (obj.isSpecialVehicle() ? "特勤车" : "其他"),
", position=(", obj.position.x, ",", obj.position.y, ")");
// 检查每个安全区
for (auto& [id, zone] : safetyZones_) {
// 先检查是否在安全区内
if (zone->isObjectInZone(obj)) {
// 如果在区内,尝试激活安全区
if (zone->tryActivate(obj)) {
Logger::debug("目标 ", obj.id, " 进入路口 ", id, " 安全区, 类型: ",
zone->getType() == SafetyZoneType::AIRCRAFT ? "飞机" : "特勤车",
", 半径: ", zone->getCurrentRadius(),
", 中心点=(", zone->getCenter().x, ",", zone->getCenter().y, ")");
}
}
}
}
bool System::handleSafetyZoneRisk(const Vehicle& vehicle,
const SafetyZone* zone,
const std::vector<MovingObject*>& objects,
double distance,
const std::string& intersectionId,
CommandType cmdType,
const std::string& riskLevel) {
// 检查是否为无人车
if (!controllableVehicles_.isControllable(vehicle.id)) {
return false;
}
VehicleCommand cmd;
cmd.vehicleId = vehicle.id;
cmd.type = cmdType;
cmd.reason = zone->getType() == SafetyZoneType::AIRCRAFT ? CommandReason::AIRCRAFT_CROSSING : CommandReason::SPECIAL_VEHICLE;
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
// 查找触发安全区的目标物体
const MovingObject* target = nullptr;
for (const auto& obj : objects) {
if ((obj->isAircraft() || obj->isSpecialVehicle()) &&
zone->isObjectInZone(*obj)) {
target = obj;
break;
}
}
// 如果找到目标物体,添加相关信息
if (target) {
cmd.latitude = target->geo.latitude;
cmd.longitude = target->geo.longitude;
// 计算相对距离
double rel_dx = target->position.x - vehicle.position.x;
double rel_dy = target->position.y - vehicle.position.y;
// 计算相对速度
cmd.relativeSpeed = std::sqrt(
(target->position.x - vehicle.position.x) * (target->position.x - vehicle.position.x) +
(target->position.y - vehicle.position.y) * (target->position.y - vehicle.position.y));
cmd.relativeMotionX = rel_dx;
cmd.relativeMotionY = rel_dy;
cmd.minDistance = distance;
Logger::debug("安全区冲突目标信息: id=", target->id,
", 相对距离=(", rel_dx, ",", rel_dy, ")",
", 相对速度=", cmd.relativeSpeed,
", 最小距离=", distance);
}
broadcastVehicleCommand(cmd);
controllableVehicles_.sendCommand(vehicle.id, cmd);
CollisionRisk risk;
risk.id1 = vehicle.id;
risk.id2 = target ? target->id : "";
risk.level = cmdType == CommandType::ALERT ? RiskLevel::CRITICAL : RiskLevel::WARNING;
risk.distance = distance;
risk.relativeSpeed = cmd.relativeSpeed;
risk.relativeMotion = { cmd.relativeMotionX, cmd.relativeMotionY };
risk.zoneType = WarningZoneType::WARNING;
broadcastCollisionWarning(risk);
return true;
}
void System::processPushedTrafficLightData(const nlohmann::json& di_data) {
try {
const auto& config = SystemConfig::instance();
const std::string targetIntersectionId = config.simulated_mobile_light_target_intersection_id;
const Intersection* targetIntersection = intersection_config_.findById(targetIntersectionId);
if (!targetIntersection) {
Logger::warning("Configured target intersection ID not found in intersection config: ", targetIntersectionId);
return;
}
SignalStatus ns_status = SignalStatus::UNKNOWN;
SignalStatus ew_status = SignalStatus::UNKNOWN;
if (di_data.contains("DI-13") && di_data.at("DI-13").get<int>() == 1) {
ns_status = SignalStatus::GREEN;
} else if (di_data.contains("DI-12") && di_data.at("DI-12").get<int>() == 1) {
ns_status = SignalStatus::YELLOW;
} else if (di_data.contains("DI-11") && di_data.at("DI-11").get<int>() == 1) {
ns_status = SignalStatus::RED;
} else {
Logger::warning("Received DI data has unknown or invalid N/S state: ", di_data.dump());
}
if (di_data.contains("DI-16") && di_data.at("DI-16").get<int>() == 1) {
ew_status = SignalStatus::GREEN;
} else if (di_data.contains("DI-15") && di_data.at("DI-15").get<int>() == 1) {
ew_status = SignalStatus::YELLOW;
} else if (di_data.contains("DI-14") && di_data.at("DI-14").get<int>() == 1) {
ew_status = SignalStatus::RED;
} else {
Logger::warning("Received DI data has unknown or invalid E/W state: ", di_data.dump());
}
TrafficLightSignal signal;
signal.trafficLightId = "DI_PUSHED_" + targetIntersectionId;
signal.ns_status = ns_status;
signal.ew_status = ew_status;
signal.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
if (ws_server_) {
nlohmann::json messageJson;
messageJson["type"] = "intersection_traffic_light_status";
messageJson["intersection_id"] = targetIntersection->id;
messageJson["position"] = {
{"latitude", targetIntersection->position.latitude},
{"longitude", targetIntersection->position.longitude}
};
messageJson["ns_status"] = static_cast<int>(signal.ns_status);
messageJson["ew_status"] = static_cast<int>(signal.ew_status);
messageJson["timestamp"] = signal.timestamp;
ws_server_->broadcast(messageJson.dump());
Logger::debug("广播路口交通灯状态: ", messageJson.dump());
} else {
Logger::warning("WebSocket server is not available, cannot broadcast intersection status.");
}
} catch (const std::exception& e) {
Logger::error("处理推送的交通信号灯数据时出错: ", e.what());
}
}

126
src/core/System.h Normal file
View File

@ -0,0 +1,126 @@
#pragma once
#include <memory>
#include <thread>
#include <string>
#include <atomic>
#include <unordered_map>
#include <unordered_set>
#include "types/BasicTypes.h"
#include "detector/CollisionDetector.h"
#include "detector/TrafficLightDetector.h"
#include "detector/SafetyZone.h"
#include "config/AirportBounds.h"
#include "vehicle/ControllableVehicles.h"
#include "network/WebSocketServer.h"
#include "network/MessageTypes.h"
#include "config/IntersectionConfig.h"
#include "network/TrafficLightHttpServer.h"
#include <nlohmann/json_fwd.hpp>
// 前向声明
class DataCollector;
class SystemConfig; // 添加前向声明
class System {
public:
System();
virtual ~System();
bool initialize();
void start();
void stop();
static System* instance() { return instance_; }
static void signalHandler(int signal);
// 广播消息给客户端
virtual void broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning);
void broadcastPositionUpdate(const MovingObject& obj);
void broadcastCollisionWarning(const CollisionRisk& risk);
void broadcastVehicleCommand(const VehicleCommand& cmd);
void broadcastTrafficLightStatus(const TrafficLightSignal& signal);
const SystemConfig& getSystemConfig() const { return SystemConfig::instance(); }
const IntersectionConfig& getIntersectionConfig() const { return intersection_config_; }
// 新增: 处理推送的红绿灯数据 (改回接收 json 对象)
void processPushedTrafficLightData(const nlohmann::json& di_data);
// 处理来自数据源的交通信号灯状态
void processTrafficLightStatus(const TrafficLightSignal& signal);
private:
void processLoop();
void processCollisions(const std::vector<CollisionRisk>& detectedRisks,
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels);
// 初始化安全区
void initializeSafetyZones();
// 更新安全区状态
void updateSafetyZoneStates(const std::vector<MovingObject*>& objects);
// 检查目标是否进入安全区
void checkSafetyZoneIntrusion(const MovingObject& obj);
// 检查无人车与安全区的冲突
void checkUnmannedVehicleSafetyZones(const std::vector<Vehicle>& vehicles,
const std::vector<MovingObject*>& objects,
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels,
std::vector<CollisionRisk>& detectedRisks);
// 处理安全区风险
bool handleSafetyZoneRisk(const Vehicle& vehicle,
const SafetyZone* zone,
const std::vector<MovingObject*>& objects,
double distance,
const std::string& intersectionId,
CommandType cmdType,
const std::string& riskLevel);
// 记录上一次有安全区风险的无人车
std::unordered_map<std::string, std::string> lastVehiclesWithSafetyZoneRisk_; // <vehicleId, intersectionId>
std::atomic<bool> running_{false};
std::thread processThread_;
std::unique_ptr<AirportBounds> airportBounds_;
ControllableVehicles& controllableVehicles_{ControllableVehicles::getInstance()};
std::unique_ptr<CollisionDetector> collisionDetector_;
std::unique_ptr<TrafficLightDetector> trafficLightDetector_;
std::unique_ptr<DataCollector> dataCollector_;
// WebSocket 服务器
std::unique_ptr<network::WebSocketServer> ws_server_;
std::thread ws_thread_;
// 新增: 红绿灯 HTTP 服务器
std::unique_ptr<network::TrafficLightHttpServer> traffic_light_http_server_;
std::thread traffic_light_http_server_thread_;
// 路口配置
IntersectionConfig intersection_config_;
// 安全区管理
std::unordered_map<std::string, std::unique_ptr<SafetyZone>> safetyZones_;
static System* instance_;
// 记录上一次有风险的车辆列表
std::unordered_set<std::string> lastVehiclesWithRisk_;
// 时间戳
int64_t last_aircraft_timestamp{0};
int64_t last_vehicle_timestamp{0};
int64_t last_traffic_light_timestamp{0};
std::chrono::steady_clock::time_point last_safety_zone_update;
// 辅助函数
const MovingObject* findVehicle(const std::string& vehicleId) const;
// 存储最新数据的成员变量
std::vector<Aircraft> latest_aircraft_;
std::vector<Vehicle> latest_vehicles_;
std::vector<TrafficLightSignal> latest_traffic_lights_;
};

View File

@ -0,0 +1,729 @@
#include "CollisionDetector.h"
#include "config/SystemConfig.h"
#include "utils/Logger.h"
#include <cmath>
#include <unordered_set>
CollisionDetector::CollisionDetector(const AirportBounds& bounds, const ControllableVehicles& controllableVehicles)
: airportBounds_(bounds),
vehicleTree_(bounds.getAirportBounds(), 8), // 使用机场总边界初始化四叉树
controllableVehicles_(&controllableVehicles) {
// 记录初始化信息
const auto& airportBounds = bounds.getAirportBounds();
}
void CollisionDetector::updateTraffic(const std::vector<Aircraft>& aircraft,
const std::vector<Vehicle>& vehicles) {
Logger::debug("更新交通数据: 航空器数量=", aircraft.size(),
", 车辆数量=", vehicles.size());
// 更新航空器数据
aircraftData_ = aircraft;
// 更新车辆四叉树
vehicleTree_.clear();
size_t validVehicles = 0;
for (const auto& vehicle : vehicles) {
// 首先检查车辆是否在机场边界内
if (!airportBounds_.isPointInBounds(vehicle.position)) {
Logger::error("车辆在机场边界外: id=", vehicle.id, ", position=(",
vehicle.position.x, ",", vehicle.position.y, ")");
continue; // 跳过边界外的车辆
}
// 根据配置设置车辆类型
Vehicle updatedVehicle = vehicle;
const auto* config =
controllableVehicles_->findVehicle(vehicle.vehicleNo);
if (config) {
if (config->type == "UNMANNED") {
updatedVehicle.type = MovingObjectType::UNMANNED;
updatedVehicle.isControllable = true;
} else if (config->type == "SPECIAL") {
updatedVehicle.type = MovingObjectType::SPECIAL;
updatedVehicle.isControllable = false;
}
} else {
updatedVehicle.type = MovingObjectType::SPECIAL;
updatedVehicle.isControllable = false;
}
// 插入四叉树
try {
Logger::debug("尝试插入车辆: id=", updatedVehicle.id,
", position=(", updatedVehicle.position.x, ",",
updatedVehicle.position.y, ")", ", type=",
updatedVehicle.isControllable ? "UNMANNED"
: "SPECIAL");
vehicleTree_.insert(updatedVehicle);
++validVehicles;
Logger::debug("成功插入车辆: id=", updatedVehicle.id);
} catch (const std::exception& e) {
Logger::error("无法插入车辆到四叉树: id=", updatedVehicle.id,
", error=", e.what());
}
}
// 记录更新后的状态
auto bounds = vehicleTree_.getBounds();
Logger::debug("四叉树边界: min=(", bounds.x, ",", bounds.y, "), max=(",
bounds.x + bounds.width, ",", bounds.y + bounds.height, ")");
auto allVehicles = vehicleTree_.queryRange(bounds);
for (const auto& vehicle : allVehicles) {
Logger::debug(
"查询到车辆: id=", vehicle.id, ", position=(", vehicle.position.x,
",", vehicle.position.y, ")",
", type=", vehicle.isControllable ? "UNMANNED" : "SPECIAL");
}
Logger::debug("交通数据更新完成: 有效车辆数量=", validVehicles,
", 四叉树中的车辆数量=", allVehicles.size());
}
std::vector<CollisionRisk> CollisionDetector::detectCollisions() {
std::vector<CollisionRisk> risks;
// 获取配置参数
const auto& predictionConfig =
SystemConfig::instance().collision_detection.prediction;
// 获取所有车辆
auto allVehicles = vehicleTree_.queryRange(vehicleTree_.getBounds());
// 可控车辆(无人车)
std::vector<Vehicle> unmannedVehicles;
for (const auto& vehicle : allVehicles) {
if (vehicle.isControllable) {
unmannedVehicles.push_back(vehicle);
}
}
// 记录当前检测到的冲突对
std::unordered_set<std::pair<std::string, std::string>, CollisionPairHash>
currentCollisions;
// 检测可控车辆与航空器的碰撞
for (const auto& aircraft : aircraftData_) {
for (const auto& vehicle : unmannedVehicles) {
// 获取冲突记录键
auto collisionKey = getCollisionKey(aircraft.id, vehicle.id);
// 获取区域配置
const auto& areaConfig = getCollisionParams(aircraft.position);
double safeDistance = areaConfig.warning_zone_radius.aircraft +
areaConfig.warning_zone_radius.unmanned;
// 检查是否存在未解除的冲突记录
auto it = collisionRecords_.find(collisionKey);
bool hasUnresolvedConflict =
(it != collisionRecords_.end() && !it->second.resolved);
if (hasUnresolvedConflict) {
Logger::debug("存在未解除的冲突记录: obj1=", aircraft.id,
", obj2=", vehicle.id, ", maxLevel=",
static_cast<int>(it->second.maxLevel),
", collisionPoint=(", it->second.collisionPoint.x,
",", it->second.collisionPoint.y, ")");
}
// 检测碰撞
auto collisionResult =
checkCollision(aircraft, vehicle, predictionConfig.time_window);
// 计算相对运动
MovementVector av(aircraft.speed, aircraft.heading);
MovementVector vv(vehicle.speed, vehicle.heading);
double vx = av.vx - vv.vx;
double vy = av.vy - vv.vy;
double relativeSpeed = std::sqrt(vx * vx + vy * vy);
// 根据相对运动方向调整相对速度符号
if (collisionResult.timeToCollision > 0) {
relativeSpeed = -relativeSpeed;
}
// 评估风险等级
auto [level, zoneType] =
evaluateRisk(collisionResult, aircraft.position, aircraft.type,
vehicle.type);
if (level != RiskLevel::NONE) {
// 记录或更新冲突信息
auto& record = collisionRecords_[collisionKey];
if (!record.resolved) {
// 更新已存在的冲突记录
record.maxLevel = std::max(record.maxLevel, level);
record.collisionPoint =
collisionResult.collisionPoint; // 更新冲突点
Logger::debug("更新冲突记录: obj1=", aircraft.id,
", obj2=", vehicle.id, ", maxLevel=",
static_cast<int>(record.maxLevel),
", collisionPoint=(", record.collisionPoint.x,
",", record.collisionPoint.y, ")");
} else {
// 创建新的冲突记录
record = { collisionResult
.collisionPoint, // 使用当前检测到的冲突点
std::chrono::system_clock::now()
.time_since_epoch()
.count(),
level, false };
Logger::debug("创建新的冲突记录: obj1=", aircraft.id,
", obj2=", vehicle.id,
", level=", static_cast<int>(level),
", collisionPoint=(",
collisionResult.collisionPoint.x, ",",
collisionResult.collisionPoint.y, ")");
}
// 添加到当前冲突集合
currentCollisions.insert(collisionKey);
// 添加到风险列表
risks.push_back({ aircraft.id,
vehicle.id,
level,
collisionResult.minDistance,
collisionResult.minDistance,
relativeSpeed,
{vx, vy},
zoneType,
collisionResult.timeToCollision,
collisionResult.collisionPoint });
Logger::debug(
"检测到碰撞风险: obj1=", aircraft.id, ", obj2=", vehicle.id,
", minDistance=", collisionResult.minDistance, "m",
", timeToCollision=", collisionResult.timeToCollision, "s",
", level=", static_cast<int>(level));
} else if (hasUnresolvedConflict) {
// 检查是否满足解除条件
if (checkCollisionResolved(aircraft, vehicle, it->second,
safeDistance)) {
it->second.resolved = true;
Logger::debug("冲突已解除: obj1=", aircraft.id,
", obj2=", vehicle.id);
} else {
// 虽然当前没有检测到风险,但由于未满足解除条件,继续保持原有风险等级
currentCollisions.insert(collisionKey);
risks.push_back({ aircraft.id,
vehicle.id,
it->second.maxLevel,
collisionResult.minDistance,
collisionResult.minDistance,
relativeSpeed,
{vx, vy},
zoneType,
collisionResult.timeToCollision,
it->second.collisionPoint });
Logger::debug(
"保持原有风险等级: obj1=", aircraft.id,
", obj2=", vehicle.id,
", maxLevel=", static_cast<int>(it->second.maxLevel),
", minDistance=", collisionResult.minDistance, "m",
", timeToCollision=", collisionResult.timeToCollision,
"s");
}
}
}
}
// 清理已解除的冲突记录
for (auto it = collisionRecords_.begin(); it != collisionRecords_.end();) {
if (it->second.resolved) {
it = collisionRecords_.erase(it);
} else {
++it;
}
}
return risks;
}
bool CollisionDetector::checkCollisionResolved(const MovingObject& obj1,
const MovingObject& obj2,
const CollisionRecord& record,
double safeDistance) const {
// 计算航空器与冲突点的距离
double dist1 =
std::sqrt(std::pow(obj1.position.x - record.collisionPoint.x, 2) +
std::pow(obj1.position.y - record.collisionPoint.y, 2));
double dist2 =
std::sqrt(std::pow(obj2.position.x - record.collisionPoint.x, 2) +
std::pow(obj2.position.y - record.collisionPoint.y, 2));
// 计算冲突点相对于航空器的位置向量与航空器航向的夹角
double dx =
record.collisionPoint.x - obj1.position.x; // 冲突点相对于航空器的向量
double dy = record.collisionPoint.y - obj1.position.y;
double pointAngle =
std::atan2(dy, dx) * 180.0 / M_PI; // 冲突点相对于航空器的角度
double angleDiff =
normalizeAngle(pointAngle - (90.0 - obj1.heading)); // 修正航向角
Logger::debug("检查冲突解除条件: 航空器位置=(", obj1.position.x, ",",
obj1.position.y, "), 冲突点=(", record.collisionPoint.x, ",",
record.collisionPoint.y, "), 航向=", obj1.heading,
", 冲突点角度=", pointAngle, ", 角度差=", angleDiff,
", 距离=", dist1);
// 判断航空器是否已经通过交叉点
if (angleDiff > 90 && angleDiff < 270) {
// 航空器已经通过交叉点,只要航空器离开冲突点的距离大于安全距离就可以解除
Logger::debug("航空器已通过交叉点,航空器距离=", dist1,
"m, 安全距离=", safeDistance, "m");
return dist1 >= safeDistance;
} else {
// 航空器还未通过交叉点,需要两者都在安全距离外且预计不会碰撞
auto collisionResult =
checkCollision(obj1, obj2, 30.0); // 使用30秒的预测窗口
bool willCollide = collisionResult.willCollide;
bool bothOutside = (dist1 >= safeDistance && dist2 >= safeDistance);
Logger::debug("航空器未通过交叉点,航空器距离=", dist1,
"m, 无人车距离=", dist2, "m, 安全距离=", safeDistance,
"m, 预测碰撞=", willCollide ? "" : "");
return !willCollide && bothOutside;
}
}
// 统一的风险评估函数
std::pair<RiskLevel, WarningZoneType> CollisionDetector::evaluateRisk(
const CollisionResult& collisionResult, const Vector2D& position,
MovingObjectType type1, MovingObjectType type2) const {
// 取区域配置
const auto& areaConfig = getCollisionParams(position);
auto thresholds = areaConfig.getThresholds(type1, type2);
RiskLevel level = RiskLevel::NONE;
WarningZoneType zoneType = WarningZoneType::NONE;
// 如果预测到碰撞,根据当前距离判断风险等级
if (collisionResult.willCollide) {
// 当前距离小于警报距离 -> 危险等级
if (collisionResult.distance <= thresholds.critical) {
level = RiskLevel::CRITICAL;
zoneType = WarningZoneType::DANGER;
}
// 当前距离在预警范围内 -> 预警等级
else if (collisionResult.distance <= thresholds.warning) {
level = RiskLevel::WARNING;
zoneType = WarningZoneType::WARNING;
}
}
// 修改日志,添加物体类型信息
Logger::debug("风险评估: 物体1类型=", static_cast<int>(type1),
", 物体2类型=", static_cast<int>(type2),
", 当前距离=", collisionResult.distance,
"m, 预测最小距离=", collisionResult.minDistance,
"m, 预警阈值=", thresholds.warning,
"m, 警报阈值=", thresholds.critical,
"m, 预测碰撞=", collisionResult.willCollide ? "" : "",
", 风险等级=", static_cast<int>(level),
", 区域类型=", static_cast<int>(zoneType));
return { level, zoneType };
}
// 统一的碰撞检测函数
CollisionResult CollisionDetector::checkCollision(const MovingObject& obj1,
const MovingObject& obj2,
double timeWindow) const {
// 添加物体信息日志
Logger::debug("检查碰撞: obj1[", obj1.id, "]=(", obj1.position.x, ",",
obj1.position.y, "), obj2[", obj2.id, "]=(", obj2.position.x,
",", obj2.position.y, ")");
// 获取区域配置
const auto& areaConfig = getCollisionParams(obj1.position);
// 获取各自的碰撞半径和警告半径
double radius1 = areaConfig.collision_radius.getRadius(obj1.type);
double radius2 = areaConfig.collision_radius.getRadius(obj2.type);
double warning_radius1 =
areaConfig.warning_zone_radius.getRadius(obj1.type);
double warning_radius2 =
areaConfig.warning_zone_radius.getRadius(obj2.type);
// 计算当前距离
double dx = obj2.position.x - obj1.position.x;
double dy = obj2.position.y - obj1.position.y;
double current_distance = std::sqrt(dx * dx + dy * dy);
// 如果是静止的无人车,使用基础速度
double speed1_calc = obj1.speed;
double speed2_calc = obj2.speed;
if (obj1.type == MovingObjectType::UNMANNED && speed1_calc < 0.1) {
speed1_calc = SystemConfig::instance()
.collision_detection.prediction
.min_unmanned_speed; // 使用配置的最低速度
}
if (obj2.type == MovingObjectType::UNMANNED && speed2_calc < 0.1) {
speed2_calc = SystemConfig::instance()
.collision_detection.prediction
.min_unmanned_speed; // 使用配置的最低速度
}
// 计算速度分量
double vx1 =
speed1_calc * std::cos((90.0 - obj1.heading) * M_PI / 180.0); // x 方向
double vy1 =
speed1_calc * std::sin((90.0 - obj1.heading) * M_PI / 180.0); // y 方向
double vx2 = speed2_calc * std::cos((90.0 - obj2.heading) * M_PI / 180.0);
double vy2 = speed2_calc * std::sin((90.0 - obj2.heading) * M_PI / 180.0);
// 如果当前距离大于警告区域,直接返回不会碰撞
if (current_distance > (warning_radius1 + warning_radius2)) {
CollisionResult result;
result.willCollide = false;
result.minDistance = current_distance;
result.distance = current_distance;
return result;
}
// 否则进行碰撞检测
return predictCircleBasedCollision(obj1.position, radius1, obj1.speed,
obj1.heading, obj2.position, radius2,
obj2.speed, obj2.heading, timeWindow);
}
CollisionResult CollisionDetector::predictCircleBasedCollision(
const Vector2D& pos1, double radius1, double speed1, double heading1,
const Vector2D& pos2, double radius2, double speed2, double heading2,
double timeWindow) const {
CollisionResult result;
// 计算安全距离(两个圆的半径之和)
double safe_distance = radius1 + radius2;
// 1. 计算当前距离
double dx = pos2.x - pos1.x;
double dy = pos2.y - pos1.y;
double current_distance = std::sqrt(dx * dx + dy * dy);
// 初始化最小距离和当前距离
result.minDistance = current_distance;
result.distance = current_distance;
result.timeToMinDistance = 0.0;
// 2. 计算速度分量
double vx1 = speed1 * std::sin((90.0 - heading1) * M_PI / 180.0);
double vy1 = speed1 * std::cos((90.0 - heading1) * M_PI / 180.0);
double vx2 = speed2 * std::sin((90.0 - heading2) * M_PI / 180.0);
double vy2 = speed2 * std::cos((90.0 - heading2) * M_PI / 180.0);
// 3. 计算相对运动
double rel_vx = vx2 - vx1;
double rel_vy = vy2 - vy1;
double rel_speed = std::sqrt(rel_vx * rel_vx + rel_vy * rel_vy);
// 计算相对运动方向与连线方向的夹角
double motion_angle = std::atan2(rel_vy, rel_vx);
double connection_angle = std::atan2(dy, dx);
double angle = std::abs(motion_angle - connection_angle);
if (angle > M_PI) {
angle = 2 * M_PI - angle; // 确保夹角不超过180度
}
// 4. 确定碰撞类型
if (speed1 < 0.1 && speed2 < 0.1) {
result.type = CollisionType::STATIC;
if (current_distance <= safe_distance) {
result.willCollide = true;
result.timeToCollision = 0.0;
result.minDistance = current_distance;
result.timeToMinDistance = 0.0;
result.collisionPoint = { (pos1.x + pos2.x) / 2.0,
(pos1.y + pos2.y) / 2.0 };
result.object1State = CollisionObjectState(pos1, speed1, heading1);
result.object2State = CollisionObjectState(pos2, speed2, heading2);
}
return result;
}
double angle_diff = getAngleDifference(heading1, heading2);
if (angle_diff > 150.0) {
result.type = CollisionType::HEAD_ON;
// 对于相向运动,直接计算碰撞时间
double closing_speed = speed1 + speed2; // 相对接近速度是两个速度之和
if (closing_speed > 0.1) { // 避免除以零
double collision_distance = current_distance - safe_distance;
if (collision_distance <= 0) {
result.willCollide = true;
result.timeToCollision = 0.0;
result.minDistance = current_distance;
result.timeToMinDistance = 0.0;
result.collisionPoint = { (pos1.x + pos2.x) / 2.0,
(pos1.y + pos2.y) / 2.0 };
result.object1State =
CollisionObjectState(pos1, speed1, heading1);
result.object2State =
CollisionObjectState(pos2, speed2, heading2);
} else {
result.timeToCollision = collision_distance / closing_speed;
result.willCollide = result.timeToCollision <= timeWindow;
if (result.willCollide) {
result.minDistance = safe_distance;
result.timeToMinDistance = result.timeToCollision;
// 计算碰撞时两个物体的实际位置
double move_distance1 = speed1 * result.timeToCollision;
double move_distance2 = speed2 * result.timeToCollision;
// 计算物体1的碰撞位置向前移动到碰撞点前方
Vector2D collision1 = {
pos1.x +
move_distance1 * std::sin(heading1 * M_PI / 180.0),
pos1.y +
move_distance1 * std::cos(heading1 * M_PI / 180.0) };
// 计算物体2的碰撞位置也是向前移动
Vector2D collision2 = {
pos2.x +
move_distance2 * std::sin(heading2 * M_PI / 180.0),
pos2.y +
move_distance2 * std::cos(heading2 * M_PI / 180.0) };
// 碰撞点两个物体的中点
result.collisionPoint = {
(collision1.x + collision2.x) / 2.0,
(collision1.y + collision2.y) / 2.0 };
result.object1State =
CollisionObjectState(collision1, speed1, heading1);
result.object2State =
CollisionObjectState(collision2, speed2, heading2);
}
}
return result;
}
} else if (angle_diff > 15.0 &&
angle_diff < 165.0) { // 放宽交叉路径的角度范围
result.type = CollisionType::CROSSING;
Logger::debug("交叉路径检测: angle_diff=", angle_diff, ", pos1=(",
pos1.x, ",", pos1.y, ")", ", pos2=(", pos2.x, ",", pos2.y,
")", ", v1=(", vx1, ",", vy1, ")", ", v2=(", vx2, ",",
vy2, ")", ", current_distance=", current_distance,
", safe_distance=", safe_distance);
// 计算行列式 (v1 x v2)
double det = vx1 * (-vy2) - (-vx2) * vy1;
// 计算 det1 = (P2-P1) x (-v2)
double det1 = dx * (-vy2) - (-vx2) * dy;
// 计算 det2 = v1 x (P2-P1)
double det2 = vx1 * dy - dx * vy1;
// 计算时间
double t1 = det1 / det; // 物体1的时间
double t2 = det2 / det; // 物体2的时间
Logger::debug("交叉路径检测: det1=", det1, ", det2=", det2,
", det=", det, ", t1=", t1, ", t2=", t2);
// 计算相交点
Vector2D intersection = { pos1.x + vx1 * t1, pos1.y + vy1 * t1 };
Logger::debug("交叉路径检测: intersection=(", intersection.x, ",",
intersection.y, ")", ", t1=", t1, ", t2=", t2);
// 检查时间条件
double first_arrival_time = std::min(t1, t2);
double first_arrival_speed = (t1 < t2) ? speed1 : speed2;
double time_threshold = safe_distance / first_arrival_speed;
bool valid_time =
(t1 >= 0 && t2 >= 0 && std::max(t1, t2) <= timeWindow &&
std::abs(t1 - t2) <= time_threshold);
// 如果满足所有条件,则预测为碰撞
if (valid_time) {
// 计算实际碰撞点(在交点之前)
double collision_distance = safe_distance;
double collision_time =
first_arrival_time - collision_distance / first_arrival_speed;
if (collision_time < 0) {
collision_time = 0;
}
// 根据碰撞时间计算两个物体的位置
Vector2D pos1_at_collision = { pos1.x + vx1 * collision_time,
pos1.y + vy1 * collision_time };
Vector2D pos2_at_collision = { pos2.x + vx2 * collision_time,
pos2.y + vy2 * collision_time };
// 碰撞点在两个物体位置的中点
result.collisionPoint = {
(pos1_at_collision.x + pos2_at_collision.x) / 2.0,
(pos1_at_collision.y + pos2_at_collision.y) / 2.0 };
Logger::debug("碰撞时间计算: first_arrival_time=",
first_arrival_time, ", safe_distance=", safe_distance,
", collision_distance=", collision_distance,
", collision_time=", collision_time);
result.willCollide = true;
result.timeToCollision = collision_time;
result.timeToMinDistance = collision_time;
result.minDistance = safe_distance;
// 更新物体状态
result.object1State =
CollisionObjectState(pos1_at_collision, speed1, heading1);
result.object2State =
CollisionObjectState(pos2_at_collision, speed2, heading2);
Logger::debug("交叉路径碰撞: collision_time=", collision_time,
", collision1=(", pos1_at_collision.x, ",",
pos1_at_collision.y, ")", ", collision2=(",
pos2_at_collision.x, ",", pos2_at_collision.y, ")",
", collision_point=(", result.collisionPoint.x, ",",
result.collisionPoint.y, ")");
return result;
}
} else {
result.type = CollisionType::PARALLEL;
// 平行运动判断:
// 1. 航向差很小小于30度
// 2. 横向距离大于安全距离
// 3. 相对速度很小(说明速度接近)
if (angle_diff < 30.0) {
// 计算垂直于运动方向的向距离
// 航向角转换为学坐标系中的旋转角度(逆时针为正)
double rotation_angle = (90.0 - heading1) * M_PI / 180.0;
// 使用标准的二维坐标旋转式
double dx_rotated =
dx * std::cos(rotation_angle) - dy * std::sin(rotation_angle);
double dy_rotated =
dx * std::sin(rotation_angle) + dy * std::cos(rotation_angle);
// 横向距离就是旋转后的y坐标的绝对值
double lateral_distance = std::abs(dy_rotated);
Logger::debug("平行运动检测: angle_diff=", angle_diff,
", heading1=", heading1,
", rotation_angle=", rotation_angle * 180.0 / M_PI,
", dx=", dx, ", dy=", dy, ", dx_rotated=", dx_rotated,
", dy_rotated=", dy_rotated,
", lateral_distance=", lateral_distance,
", safe_distance=", safe_distance,
", rel_speed=", rel_speed);
// 如果横向距离大于等于安全距离,且相对速度很小,则不会碰撞
if (lateral_distance >= safe_distance && rel_speed < 1.0) {
result.willCollide = false;
return result;
}
}
}
// 5. 在时间窗口内预测碰撞
const int STEPS = 120; // 增加采样点数以提高精度
double dt = timeWindow / STEPS; // 时间步长
// 新计算速度分量(修正计算方式)
double vx1_sample =
speed1 * std::cos((90.0 - heading1) * M_PI / 180.0); // x 方向
double vy1_sample =
speed1 * std::sin((90.0 - heading1) * M_PI / 180.0); // y 方向
double vx2_sample = speed2 * std::cos((90.0 - heading2) * M_PI / 180.0);
double vy2_sample = speed2 * std::sin((90.0 - heading2) * M_PI / 180.0);
// 如果当前已经碰撞
if (current_distance <= safe_distance) {
result.willCollide = true;
result.timeToCollision = 0.0;
result.minDistance = current_distance;
result.timeToMinDistance = 0.0;
result.collisionPoint = { (pos1.x + pos2.x) / 2.0,
(pos1.y + pos2.y) / 2.0 };
result.object1State = CollisionObjectState(pos1, speed1, heading1);
result.object2State = CollisionObjectState(pos2, speed2, heading2);
return result;
}
// 在时间窗口内采样检查
double prev_distance = current_distance;
Logger::debug("开始时间窗口采样: STEPS=", STEPS, ", dt=", dt,
", timeWindow=", timeWindow,
", safe_distance=", safe_distance,
", current_distance=", current_distance);
for (int i = 1; i <= STEPS; ++i) {
double t = i * dt;
// 计算t时的位置使用修正后的速度分量
Vector2D future1 = { pos1.x + vx1_sample * t, pos1.y + vy1_sample * t };
Vector2D future2 = { pos2.x + vx2_sample * t, pos2.y + vy2_sample * t };
// 计算t时的距离
double dx_t = future2.x - future1.x;
double dy_t = future2.y - future1.y;
double distance =
std::sqrt(dx_t * dx_t + dy_t * dy_t); // 统一使用实际距离
// 更新小距离
if (distance < result.minDistance) {
result.minDistance = distance;
result.timeToMinDistance = t;
// Logger::debug(
// "更新最小距离: min_distance=", distance,
// ", time_to_min=", t
// );
}
// 检查是否会碰撞
if (distance <= safe_distance) {
result.willCollide = true;
// 使用当前距离和安全距离做插值,提高精确度
double progress =
(distance - safe_distance) / (prev_distance - safe_distance);
double t_interp = t - dt * progress;
result.timeToCollision = t_interp;
Logger::debug("找到碰撞: t=", t, ", distance=", distance,
", prev_distance=", prev_distance, ", dt=", dt,
", t_interp=", t_interp, ", future1=(", future1.x,
",", future1.y, ")", ", future2=(", future2.x, ",",
future2.y, ")");
// 使用插值时间计算更精确的碰撞点
Vector2D interp1 = { pos1.x + vx1_sample * t_interp,
pos1.y + vy1_sample * t_interp };
Vector2D interp2 = { pos2.x + vx2_sample * t_interp,
pos2.y + vy2_sample * t_interp };
result.collisionPoint = { (interp1.x + interp2.x) / 2.0,
(interp1.y + interp2.y) / 2.0 };
result.object1State =
CollisionObjectState(interp1, speed1, heading1);
result.object2State =
CollisionObjectState(interp2, speed2, heading2);
break;
}
prev_distance = distance;
// 如果相对速度很小,且距离增加,可以提前退出
if (rel_speed < 0.1 && i > 1) {
if (distance > prev_distance) {
break;
}
}
}
return result;
}

View File

@ -0,0 +1,153 @@
#ifndef AIRPORT_DETECTOR_COLLISION_DETECTOR_H
#define AIRPORT_DETECTOR_COLLISION_DETECTOR_H
#include "types/BasicTypes.h"
#include "spatial/QuadTree.h"
#include "config/AirportBounds.h"
#include "vehicle/ControllableVehicles.h"
#include "config/SystemConfig.h"
#include "CollisionTypes.h"
#include "utils/Logger.h"
#include <vector>
#include <unordered_map>
// 碰撞风险等级
enum class RiskLevel {
NONE = 0, // 无风险
WARNING = 1, // 预警:进入预警区域
CRITICAL = 2 // 告警:进入危险区域
};
// 预警区域类型
enum class WarningZoneType {
NONE = 0, // 无风险区域
WARNING = 1, // 预警区域
DANGER = 2 // 危险区域
};
// 碰撞风险信息
struct CollisionRisk {
std::string id1, id2; // 碰撞物体的ID
RiskLevel level; // 风险等级
double distance; // 当前距离
double minDistance; // 预测的最小距离(两个中心点之间的距离)
double relativeSpeed; // 相对速度
Vector2D relativeMotion; // 相对运动向量
WarningZoneType zoneType; // 预警区域类型
double timeToCollision; // 预计碰撞时间(秒)
Vector2D collisionPoint; // 预计碰撞点
};
// 冲突记录信息
struct CollisionRecord {
Vector2D collisionPoint; // 冲突点位置
int64_t detectedTime; // 检测到冲突的时间
RiskLevel maxLevel; // 历史最高风险等级
bool resolved; // 是否已解除
};
// 冲突对象对的哈希函数
struct CollisionPairHash {
std::size_t operator()(const std::pair<std::string, std::string>& p) const {
return std::hash<std::string>()(p.first + p.second);
}
};
class CollisionDetector {
public:
CollisionDetector(const AirportBounds& bounds, const ControllableVehicles& controllableVehicles);
// 更新交通数据
void updateTraffic(const std::vector<Aircraft>& aircraft,
const std::vector<Vehicle>& vehicles);
// 检测所有可能的碰撞
std::vector<CollisionRisk> detectCollisions();
// 统一的碰撞检测函数
CollisionResult checkCollision(
const MovingObject& obj1,
const MovingObject& obj2,
double timeWindow = 30.0) const;
private:
const AirportBounds& airportBounds_;
QuadTree<Vehicle> vehicleTree_;
std::vector<Aircraft> aircraftData_;
const ControllableVehicles* controllableVehicles_;
// 冲突记录映射表:<(id1,id2), CollisionRecord>
std::unordered_map<std::pair<std::string, std::string>, CollisionRecord, CollisionPairHash> collisionRecords_;
// 将角度标准化到[0, 360)范围
static double normalizeAngle(double angle) {
while (angle >= 360.0) angle -= 360.0;
while (angle < 0.0) angle += 360.0;
return angle;
}
// 计算两个角度之间的最小差值(范围[0, 180]
static double getAngleDifference(double angle1, double angle2) {
angle1 = normalizeAngle(angle1);
angle2 = normalizeAngle(angle2);
double diff = std::abs(angle1 - angle2);
return std::min(diff, 360.0 - diff);
}
// 根据区域获取碰撞检测参数
AreaConfig getCollisionParams(const Vector2D& position) const {
auto areaType = airportBounds_.getAreaType(position);
return airportBounds_.getAreaConfig(areaType);
}
// 统一的风险评估函数
std::pair<RiskLevel, WarningZoneType> evaluateRisk(
const CollisionResult& collisionResult,
const Vector2D& position,
MovingObjectType type1,
MovingObjectType type2) const;
// 检查是否满足冲突解除条件
bool checkCollisionResolved(const MovingObject& obj1,
const MovingObject& obj2,
const CollisionRecord& record,
double safeDistance) const;
// 获取冲突记录的键
std::pair<std::string, std::string> getCollisionKey(const std::string& id1,
const std::string& id2) const {
return id1 < id2 ? std::make_pair(id1, id2) : std::make_pair(id2, id1);
}
// 计算相对运动
struct MovementVector {
double vx, vy;
MovementVector(double speed, double heading) {
// 标准方位角正北为0度顺时针增加
double radians = heading * M_PI / 180.0;
// 计算速度分量
vx = speed * std::sin(radians); // 东向分量
vy = speed * std::cos(radians); // 北向分量
}
};
// 预测位置的辅助函数
Vector2D predictPosition(const Vector2D& position, double speed, double heading, double t) const {
double vx = speed * std::sin(heading * M_PI / 180.0);
double vy = speed * std::cos(heading * M_PI / 180.0);
return {
position.x + vx * t,
position.y + vy * t
};
}
// 基于圆形的碰撞检测核心函数
CollisionResult predictCircleBasedCollision(
const Vector2D& pos1, double radius1, double speed1, double heading1,
const Vector2D& pos2, double radius2, double speed2, double heading2,
double timeWindow) const;
};
#endif // AIRPORT_DETECTOR_COLLISION_DETECTOR_H

View File

@ -0,0 +1,47 @@
#pragma once
#include "types/BasicTypes.h"
#include <limits>
#include <cmath>
// 碰撞类型
enum class CollisionType {
HEAD_ON, // 相向碰撞
CROSSING, // 交叉碰撞
STATIC, // 静态碰撞
PARALLEL // 平行运动
};
// 碰撞时刻的物体状态
struct CollisionObjectState {
Vector2D position; // 位置
double speed; // 速度
double heading; // 航向
CollisionObjectState() : speed(0.0), heading(0.0) {}
CollisionObjectState(const Vector2D& pos, double spd, double hdg)
: position(pos), speed(spd), heading(hdg) {}
};
// 碰撞预测结果
struct CollisionResult {
bool willCollide; // 是否会碰撞
double timeToCollision; // 碰撞时间(从当前时刻开始的秒数)
Vector2D collisionPoint; // 碰撞点位置
double minDistance; // 最小距离
double distance; // 当前距离
double timeToMinDistance; // 到达最小距离的时间(从当前时刻开始的秒数)
CollisionType type; // 碰撞类型
CollisionObjectState object1State; // 物体1在碰撞时刻的状态
CollisionObjectState object2State; // 物体2在碰撞时刻的状态
// 构造函数
CollisionResult() :
willCollide(false),
timeToCollision(std::numeric_limits<double>::infinity()),
collisionPoint({0, 0}),
minDistance(std::numeric_limits<double>::infinity()),
distance(std::numeric_limits<double>::infinity()),
timeToMinDistance(std::numeric_limits<double>::infinity()),
type(CollisionType::STATIC) {}
};

View File

@ -0,0 +1,117 @@
#ifndef AIRPORT_DETECTOR_RECTANGLE_COLLISION_H
#define AIRPORT_DETECTOR_RECTANGLE_COLLISION_H
#include "types/BasicTypes.h"
#include <vector>
#include <cmath>
#include <iostream>
// 碰撞检测结果
struct RectangleCollisionResult {
bool willCollide = false; // 是否会发生碰撞
double timeToCollision = 0.0; // 到碰撞的时间
Vector2D collisionPoint = {0, 0}; // 碰撞点
double minDistance = 0.0; // 最小距离
};
// 正方形表示
struct Rectangle {
Vector2D center; // 中心点
double size; // 边长
Vector2D velocity; // 速度向量
double heading; // 朝向(角度)
// 构造函数
Rectangle(const Vector2D& c, double s, const Vector2D& v, double h = 0.0)
: center(c), size(s), velocity(v), heading(h) {
}
// 默认构造函数
Rectangle() : center({0, 0}), size(0), velocity({0, 0}), heading(0) {}
// 获取正方形的AABB轴对齐包围盒
// 正方形不需要考虑朝向因为旋转后的正方形AABB保持不变
void getAABB(double& x_min, double& x_max, double& y_min, double& y_max) const {
double half_size = size / 2.0;
x_min = center.x - half_size;
x_max = center.x + half_size;
y_min = center.y - half_size;
y_max = center.y + half_size;
}
// 预测t时刻的位置
Rectangle predictPosition(double t) const {
Rectangle future = *this;
future.center.x += velocity.x * t;
future.center.y += velocity.y * t;
return future;
}
};
// 预测两个矩形在给定时间窗口内是否会发生碰撞
inline bool predictRectangleBasedCollision(const Rectangle& rect1, const Rectangle& rect2, double timeWindow) {
// 1. 首先检查当前是否碰撞
double x1_min, x1_max, y1_min, y1_max;
double x2_min, x2_max, y2_min, y2_max;
rect1.getAABB(x1_min, x1_max, y1_min, y1_max);
rect2.getAABB(x2_min, x2_max, y2_min, y2_max);
// 检查当前是否重叠
if (x1_max >= x2_min && x2_max >= x1_min &&
y1_max >= y2_min && y2_max >= y1_min) {
return true;
}
// 2. 检查未来一段时间内是否会碰撞
// 计算相对速度
double rel_vx = rect1.velocity.x - rect2.velocity.x;
double rel_vy = rect1.velocity.y - rect2.velocity.y;
double rel_speed = std::sqrt(rel_vx*rel_vx + rel_vy*rel_vy);
// 如果相对速度接近0使用当前距离判断
if (rel_speed < 0.1) {
double dx = rect1.center.x - rect2.center.x;
double dy = rect1.center.y - rect2.center.y;
double distance = std::sqrt(dx*dx + dy*dy);
double safe_distance = rect1.size/2.0 + rect2.size/2.0; // 两个正方形从中心到边缘的距离之和
if (distance <= safe_distance) {
return true;
}
return false;
}
// 3. 在时间窗口内采样检查
const int STEPS = 60;
double dt = timeWindow / STEPS;
for (int i = 1; i <= STEPS; ++i) {
double t = i * dt;
Rectangle future1 = rect1.predictPosition(t);
Rectangle future2 = rect2.predictPosition(t);
future1.getAABB(x1_min, x1_max, y1_min, y1_max);
future2.getAABB(x2_min, x2_max, y2_min, y2_max);
// 检查是否重叠
if (x1_max >= x2_min && x2_max >= x1_min &&
y1_max >= y2_min && y2_max >= y1_min) {
return true;
}
// 计算中心点距离
double dx = (future1.center.x - future2.center.x);
double dy = (future1.center.y - future2.center.y);
double distance = std::sqrt(dx*dx + dy*dy);
double safe_distance = rect1.size/2.0 + rect2.size/2.0; // 两个正方形从中心到边缘的距离之和
if (distance <= safe_distance) {
return true;
}
}
return false;
}
#endif // AIRPORT_DETECTOR_RECTANGLE_COLLISION_H

126
src/detector/SafetyZone.cpp Normal file
View File

@ -0,0 +1,126 @@
#include "SafetyZone.h"
#include "utils/Logger.h"
#include "config/SystemConfig.h"
#include <cmath>
SafetyZone::SafetyZone(const Vector2D& center, double aircraftRadius, double specialVehicleRadius)
: center_(center)
, aircraftRadius_(aircraftRadius)
, specialVehicleRadius_(specialVehicleRadius)
, currentRadius_(0.0)
, state_(SafetyZoneState::INACTIVE)
, type_(SafetyZoneType::NONE) {
Logger::debug("创建安全区: center=(", center_.x, ",", center_.y,
"), aircraftRadius=", aircraftRadius_,
", specialVehicleRadius=", specialVehicleRadius_);
}
bool SafetyZone::isInZone(const Vector2D& point) const {
// 如果未设置类型,直接返回 false
if (type_ == SafetyZoneType::NONE) {
return false;
}
// 计算点到中心的距离
double dx = point.x - center_.x;
double dy = point.y - center_.y;
double distance = std::sqrt(dx * dx + dy * dy);
// 使用当前设置的安全区半径
return distance <= currentRadius_;
}
void SafetyZone::resetType() {
type_ = SafetyZoneType::NONE;
currentRadius_ = 0.0;
Logger::debug("重置安全区类型为 NONE");
}
double SafetyZone::getObjectRadius(const MovingObject& object) const {
const auto& config = SystemConfig::instance().collision_detection.prediction;
if (object.isAircraft()) {
double radius = config.aircraft_size / 2.0;
Logger::debug("目标 ", object.id, " 是飞机,尺寸半径: ", radius);
return radius;
} else if (object.isSpecialVehicle()) {
double radius = config.vehicle_size / 2.0;
Logger::debug("目标 ", object.id, " 是特勤车,尺寸半径: ", radius);
return radius;
}
return 0.0;
}
bool SafetyZone::isTypeMatched(const MovingObject& object) const {
// 如果安全区类型未设置,返回 true
if (type_ == SafetyZoneType::NONE) {
return true;
}
// 如果是无人车,返回 true
if (object.isUnmannedVehicle()) {
return true;
}
// 如果安全区类型已设置,检查是否匹配
if ((type_ == SafetyZoneType::AIRCRAFT && !object.isAircraft()) ||
(type_ == SafetyZoneType::VEHICLE && !object.isSpecialVehicle())) {
Logger::debug("目标 ", object.id, " 类型与安全区类型不匹配,当前安全区类型: ",
type_ == SafetyZoneType::AIRCRAFT ? "飞机" : "特勤车");
return false;
}
return true;
}
bool SafetyZone::isObjectInZone(const MovingObject& object) const {
// 计算点到中心的距离
double dx = object.position.x - center_.x;
double dy = object.position.y - center_.y;
double distance = std::sqrt(dx * dx + dy * dy);
// 如果安全区类型未设置,使用对应的临时半径进行检查
double checkRadius = (type_ == SafetyZoneType::NONE)
? (object.isAircraft() ? aircraftRadius_ : specialVehicleRadius_)
: currentRadius_;
Logger::debug("检查目标 ", object.id, " 是否在安全区内: ",
"dx=", dx, ", dy=", dy, ", distance=", distance,
", checkRadius=", checkRadius,
", type=", type_ == SafetyZoneType::NONE ? "NONE" :
(type_ == SafetyZoneType::AIRCRAFT ? "AIRCRAFT" : "VEHICLE"),
", center=(", center_.x, ",", center_.y, ")",
", object=(", object.position.x, ",", object.position.y, ")");
return distance <= checkRadius;
}
bool SafetyZone::tryActivate(const MovingObject& object) {
// 如果是无人车,不激活安全区
if (object.isUnmannedVehicle()) {
Logger::debug("目标 ", object.id, " 是无人车,不激活安全区");
return false;
}
// 如果类型已设置且不匹配,不激活
if (!isTypeMatched(object)) {
return false;
}
// 检查是否在区内
bool inZone = isObjectInZone(object);
if (!inZone) {
return false;
}
// 如果安全区类型未设置,设置新的类型和半径
if (type_ == SafetyZoneType::NONE) {
type_ = object.isAircraft() ? SafetyZoneType::AIRCRAFT : SafetyZoneType::VEHICLE;
currentRadius_ = object.isAircraft() ? aircraftRadius_ : specialVehicleRadius_;
Logger::debug("安全区设置为", object.isAircraft() ? "飞机" : "特勤车", "类型,半径: ", currentRadius_);
}
// 激活安全区
state_ = SafetyZoneState::ACTIVE;
return true;
}

66
src/detector/SafetyZone.h Normal file
View File

@ -0,0 +1,66 @@
#pragma once
#include "types/BasicTypes.h"
// 安全区状态
enum class SafetyZoneState {
INACTIVE, // 未激活
ACTIVE // 已激活
};
// 安全区类型
enum class SafetyZoneType {
NONE, // 未设置
AIRCRAFT, // 飞机安全区
VEHICLE // 特勤车安全区
};
// 安全区定义
class SafetyZone {
public:
// 构造函数
SafetyZone(const Vector2D& center, double aircraftSize, double specialVehicleSize);
// 获取状态
SafetyZoneState getState() const { return state_; }
SafetyZoneType getType() const { return type_; }
// 设置状态
void setState(SafetyZoneState state) { state_ = state; }
// 重置类型为 NONE
void resetType();
// 检查点是否在安全区内
bool isInZone(const Vector2D& point) const;
// 检查目标是否在安全区内 - 纯检测函数,不修改状态
bool isObjectInZone(const MovingObject& object) const;
// 尝试更新安全区状态 - 处理状态更新
bool tryActivate(const MovingObject& object);
// 获取中心点
const Vector2D& getCenter() const { return center_; }
// 获取安全区尺寸配置
double getAircraftRadius() const { return aircraftRadius_; }
double getSpecialVehicleRadius() const { return specialVehicleRadius_; }
// 获取当前安全区半径
double getCurrentRadius() const { return currentRadius_; }
private:
Vector2D center_; // 安全区中心点
double aircraftRadius_; // 飞机安全区半径
double specialVehicleRadius_; // 特勤车安全区半径
double currentRadius_; // 当前安全区半径
SafetyZoneState state_; // 当前状态
SafetyZoneType type_; // 当前类型
// 检查物体类型是否匹配当前安全区类型
bool isTypeMatched(const MovingObject& object) const;
// 获取物体的有效半径
double getObjectRadius(const MovingObject& object) const;
};

View File

@ -0,0 +1,72 @@
#include "detector/TrafficLightDetector.h"
#include "utils/Logger.h"
#include "types/BasicTypes.h"
#include "core/System.h"
#include <chrono>
TrafficLightDetector::TrafficLightDetector(const IntersectionConfig& intersectionConfig,
ControllableVehicles& controllableVehicles,
System& system)
: intersection_config_(intersectionConfig)
, controllable_vehicles_(controllableVehicles)
, system_(system) {}
void TrafficLightDetector::processSignal(const TrafficLightSignal& signal,
const std::vector<Vehicle>& vehicles) {
// 根据红绿灯ID查找对应的路口
const Intersection* intersection = intersection_config_.findByTrafficLightId(signal.trafficLightId);
if (!intersection) {
Logger::warning("未找到红绿灯对应的路口: ", signal.trafficLightId);
return;
}
// 保存当前处理的信号
current_signal_ = signal;
// 检查每个车辆
for (const auto& vehicle : vehicles) {
if (controllable_vehicles_.isControllable(vehicle.vehicleNo)) {
// 根据信号灯状态发送指令
// FIXME: 临时修改 - 仅根据南北向状态发送指令,未考虑车辆方向和东西向状态。
// 需要根据车辆行驶方向判断应该参考 ns_status 还是 ew_status。
switch (signal.ns_status) {
case SignalStatus::RED:
sendSignalCommand(vehicle.vehicleNo, SignalState::RED);
break;
case SignalStatus::GREEN:
sendSignalCommand(vehicle.vehicleNo, SignalState::GREEN);
break;
case SignalStatus::YELLOW:
sendSignalCommand(vehicle.vehicleNo, SignalState::YELLOW);
break;
default:
Logger::warning("未知的信号灯状态");
break;
}
}
}
}
void TrafficLightDetector::sendSignalCommand(const std::string& vehicleNo, SignalState state) {
VehicleCommand cmd;
cmd.vehicleId = vehicleNo;
cmd.type = CommandType::SIGNAL;
cmd.reason = CommandReason::TRAFFIC_LIGHT;
cmd.signalState = state;
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
// 添加路口信息
const Intersection* intersection = intersection_config_.findByTrafficLightId(current_signal_.trafficLightId);
if (intersection) {
cmd.intersectionId = intersection->id;
cmd.latitude = intersection->position.latitude;
cmd.longitude = intersection->position.longitude;
}
controllable_vehicles_.sendCommand(vehicleNo, cmd);
system_.broadcastVehicleCommand(cmd);
Logger::debug("发送信号灯指令到车辆: ", vehicleNo,
" 路口: ", cmd.intersectionId,
" 状态: ", state == SignalState::RED ? "红灯" : state == SignalState::GREEN ? "绿灯" : "黄灯");
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "config/IntersectionConfig.h"
#include "vehicle/ControllableVehicles.h"
#include "types/TrafficLightTypes.h"
#include <vector>
class System;
class TrafficLightDetector {
public:
TrafficLightDetector(const IntersectionConfig& intersectionConfig,
ControllableVehicles& controllableVehicles,
System& system);
void processSignal(const TrafficLightSignal& signal,
const std::vector<Vehicle>& vehicles);
private:
const IntersectionConfig& intersection_config_;
ControllableVehicles& controllable_vehicles_;
System& system_;
TrafficLightSignal current_signal_;
// 发送信号灯指令
void sendSignalCommand(const std::string& vehicleNo, SignalState state);
};

67
src/main.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "core/System.h"
#include "utils/Logger.h"
#include "config/SystemConfig.h"
#include <iostream>
#include <thread>
#include <csignal>
#include <filesystem>
volatile sig_atomic_t running = true;
void signalHandler(int signum) {
std::cout << "Interrupt signal (" << signum << ") received.\n";
running = false;
}
LogLevel getLogLevelFromString(const std::string& level) {
if (level == "debug") return LogLevel::DEBUG;
if (level == "info") return LogLevel::INFO;
if (level == "warning") return LogLevel::WARNING;
if (level == "error") return LogLevel::ERROR;
return LogLevel::INFO; // 默认级别
}
int main(int argc, char* argv[]) {
try {
// 设置信号处理
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
// 创建日志目录
std::filesystem::create_directories("logs");
// 加载系统配置
SystemConfig::instance().load("config/system_config.json");
// 初始化日志系统
Logger::initialize(SystemConfig::instance().logging.file, getLogLevelFromString(SystemConfig::instance().logging.level));
Logger::info("Starting system...");
Logger::debug("Log level set to: ", SystemConfig::instance().logging.level);
// 初始化系统
System system;
if (!system.initialize()) {
Logger::error("Failed to initialize system");
return 1;
}
// 启动系统
system.start();
Logger::info("System running...");
// 主循环
while (running) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// 停止系统
system.stop();
Logger::info("System stopped");
return 0;
}
catch (const std::exception& e) {
std::cerr << "Fatal error: " << e.what() << std::endl;
return 1;
}
}

View File

@ -0,0 +1,168 @@
#include "mock/MockDataService.h"
#include <format>
#include <sstream>
#include <iomanip>
MockDataService::MockDataService(const AirportBounds& bounds)
: bounds_(bounds)
{
}
std::vector<Aircraft> MockDataService::generateAircraftData() {
std::vector<Aircraft> aircraft;
// 生成跑道上的航空器
for (int i = 0; i < 5; ++i) {
Aircraft a;
a.flightNo = generateAircraftId();
a.position = generatePosition(AreaType::RUNWAY);
a.speed = generateSpeed(AreaType::RUNWAY);
a.heading = generateHeading(AreaType::RUNWAY);
a.altitude = generateAltitude(AreaType::RUNWAY);
aircraft.push_back(a);
}
// 生成滑行道上的航空器
for (int i = 0; i < 10; ++i) {
Aircraft a;
a.flightNo = generateAircraftId();
a.position = generatePosition(AreaType::TAXIWAY);
a.speed = generateSpeed(AreaType::TAXIWAY);
a.heading = generateHeading(AreaType::TAXIWAY);
a.altitude = generateAltitude(AreaType::TAXIWAY);
aircraft.push_back(a);
}
// 生成停机位的航空器
for (int i = 0; i < 15; ++i) {
Aircraft a;
a.flightNo = generateAircraftId();
a.position = generatePosition(AreaType::GATE);
a.speed = generateSpeed(AreaType::GATE);
a.heading = generateHeading(AreaType::GATE);
a.altitude = generateAltitude(AreaType::GATE);
aircraft.push_back(a);
}
return aircraft;
}
std::vector<Vehicle> MockDataService::generateVehicleData() {
std::vector<Vehicle> vehicles;
// 生成服务区的车辆
for (int i = 0; i < 20; ++i) {
Vehicle v;
v.vehicleNo = generateVehicleId();
v.position = generatePosition(AreaType::SERVICE);
v.speed = generateSpeed(AreaType::SERVICE);
v.heading = generateHeading(AreaType::SERVICE);
vehicles.push_back(v);
}
// 生成停机位的车辆
for (int i = 0; i < 15; ++i) {
Vehicle v;
v.vehicleNo = generateVehicleId();
v.position = generatePosition(AreaType::GATE);
v.speed = generateSpeed(AreaType::GATE);
v.heading = generateHeading(AreaType::GATE);
vehicles.push_back(v);
}
return vehicles;
}
Vector2D MockDataService::generatePosition(AreaType areaType) {
const auto& config = bounds_.getAreaConfig(areaType);
const auto& areaBounds = bounds_.getAreaBounds(areaType);
std::uniform_real_distribution<double> x_dist(areaBounds.x,
areaBounds.x + areaBounds.width);
std::uniform_real_distribution<double> y_dist(areaBounds.y,
areaBounds.y + areaBounds.height);
return {x_dist(rng_), y_dist(rng_)};
}
double MockDataService::generateSpeed(AreaType areaType) {
// 根据区域类型返回合适的速度范围
switch (areaType) {
case AreaType::RUNWAY:
return std::uniform_real_distribution<double>{30.0, 80.0}(rng_);
case AreaType::TAXIWAY:
return std::uniform_real_distribution<double>{5.0, 15.0}(rng_);
case AreaType::GATE:
return std::uniform_real_distribution<double>{0.0, 5.0}(rng_);
case AreaType::SERVICE:
return std::uniform_real_distribution<double>{0.0, 10.0}(rng_);
default:
return 0.0;
}
}
double MockDataService::generateHeading(AreaType areaType) {
// 根据区域类型返回合适的航向范围
switch (areaType) {
case AreaType::RUNWAY:
// 跑道通常只有两个方向
return std::uniform_int_distribution<int>{0, 1}(rng_) * 180.0;
default:
// 其他区域可以是任意方向
return std::uniform_real_distribution<double>{0.0, 360.0}(rng_);
}
}
double MockDataService::generateAltitude(AreaType areaType) {
// 根据区域类型返回合适的高度范围
switch (areaType) {
case AreaType::RUNWAY:
return std::uniform_real_distribution<double>{0.0, 15.0}(rng_);
case AreaType::TAXIWAY:
return std::uniform_real_distribution<double>{0.0, 5.0}(rng_);
default:
return 0.0;
}
}
std::string MockDataService::generateAircraftId() {
std::stringstream ss;
ss << "AC" << std::setw(4) << std::setfill('0') << ++aircraftCounter_;
return ss.str();
}
std::string MockDataService::generateVehicleId() {
std::stringstream ss;
ss << "VH" << std::setw(4) << std::setfill('0') << ++vehicleCounter_;
return ss.str();
}
std::vector<TrafficLightSignal> MockDataService::generateTrafficLightData() {
std::vector<TrafficLightSignal> signals;
// 生成 5-10 个红绿灯信号
std::uniform_int_distribution<> numSignals(5, 10);
int count = numSignals(rng_);
for (int i = 0; i < count; ++i) {
TrafficLightSignal signal;
signal.trafficLightId = generateTrafficLightId();
signal.status = generateTrafficLightState();
signal.timestamp = std::chrono::system_clock::now().time_since_epoch().count();
signals.push_back(signal);
}
return signals;
}
std::string MockDataService::generateTrafficLightId() {
std::stringstream ss;
ss << "TL" << std::setw(4) << std::setfill('0') << ++trafficLightCounter_;
return ss.str();
}
SignalStatus MockDataService::generateTrafficLightState() {
std::uniform_int_distribution<> dist(0, 2);
int state = dist(rng_);
return static_cast<SignalStatus>(state);
}

View File

@ -0,0 +1,43 @@
#ifndef AIRPORT_MOCK_MOCKDATASERVICE_H
#define AIRPORT_MOCK_MOCKDATASERVICE_H
#include "types/BasicTypes.h"
#include "config/AirportBounds.h"
#include "types/TrafficLightTypes.h"
#include <vector>
#include <random>
class MockDataService {
public:
explicit MockDataService(const AirportBounds& bounds);
std::vector<Aircraft> generateAircraftData();
std::vector<Vehicle> generateVehicleData();
std::vector<TrafficLightSignal> generateTrafficLightData();
private:
const AirportBounds& bounds_;
std::mt19937 rng_{std::random_device{}()};
// 在指定区域内生成随机位置
Vector2D generatePosition(AreaType areaType);
// 根据区域生成合适的速度和航向
double generateSpeed(AreaType areaType);
double generateHeading(AreaType areaType);
double generateAltitude(AreaType areaType);
// 生成ID
std::string generateAircraftId();
std::string generateVehicleId();
std::string generateTrafficLightId();
// 生成红绿灯状态
SignalStatus generateTrafficLightState();
int aircraftCounter_ = 0;
int vehicleCounter_ = 0;
int trafficLightCounter_ = 0;
};
#endif // AIRPORT_MOCK_MOCKDATASERVICE_H

142
src/network/HTTPClient.cpp Normal file
View File

@ -0,0 +1,142 @@
#include "HTTPClient.h"
#include "utils/Logger.h"
#include <sstream>
namespace {
std::string getSignalStateString(SignalState state) {
switch (state) {
case SignalState::RED:
return "RED";
case SignalState::YELLOW:
return "YELLOW";
case SignalState::GREEN:
return "GREEN";
default:
return "UNKNOWN";
}
}
} // anonymous namespace
HTTPClient::HTTPClient() {
curl_global_init(CURL_GLOBAL_ALL);
curl_ = curl_easy_init();
if (!curl_) {
throw std::runtime_error("Failed to initialize CURL");
}
}
HTTPClient::~HTTPClient() {
if (curl_) {
curl_easy_cleanup(curl_);
}
curl_global_cleanup();
}
size_t HTTPClient::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t total_size = size * nmemb;
std::string* response = static_cast<std::string*>(userp);
response->append(static_cast<char*>(contents), total_size);
return total_size;
}
bool HTTPClient::sendCommand(const std::string& ip, int port, const VehicleCommand& command) const {
if (!curl_) {
Logger::error("CURL not initialized");
return false;
}
// 构造请求URL
std::stringstream url;
url << "http://" << ip << ":" << port << "/api/VehicleCommandInfo";
// 生成消息唯一 id
std::stringstream transId;
transId << std::hex << std::chrono::system_clock::now().time_since_epoch().count();
// 构造请求体(必填字段)
nlohmann::json request = {
{"transId", transId.str()},
{"timestamp", command.timestamp},
{"vehicleID", command.vehicleId},
{"commandType", [&]() {
switch (command.type) {
case CommandType::SIGNAL: return "SIGNAL";
case CommandType::ALERT: return "ALERT";
case CommandType::WARNING: return "WARNING";
case CommandType::RESUME: return "RESUME";
case CommandType::PARKING: return "PARKING";
default: return "UNKNOWN";
}
}()},
{"commandReason", [&]() {
switch (command.reason) {
case CommandReason::TRAFFIC_LIGHT: return "TRAFFIC_LIGHT";
case CommandReason::AIRCRAFT_CROSSING: return "AIRCRAFT_CROSSING";
case CommandReason::SPECIAL_VEHICLE: return "SPECIAL_VEHICLE";
case CommandReason::AIRCRAFT_PUSH: return "AIRCRAFT_PUSH";
case CommandReason::RESUME_TRAFFIC: return "RESUME_TRAFFIC";
case CommandReason::PARKING_SIDE: return "PARKING_SIDE";
default: return "UNKNOWN";
}
}()},
{"latitude", command.latitude},
{"longitude", command.longitude},
{"signalState", getSignalStateString(command.signalState)},
{"intersectionId", command.intersectionId},
{"relativeSpeed", command.relativeSpeed},
{"relativeMotionX", command.relativeMotionX},
{"relativeMotionY", command.relativeMotionY},
{"minDistance", command.minDistance}
};
std::string request_body = request.dump();
response_buffer_.clear();
// 设置CURL选项
curl_easy_setopt(curl_, CURLOPT_URL, url.str().c_str());
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, request_body.c_str());
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response_buffer_);
// 设置请求头
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers);
// 发送请求
CURLcode res = curl_easy_perform(curl_);
curl_slist_free_all(headers);
if (res != CURLE_OK) {
Logger::error("Failed to send command: ", curl_easy_strerror(res));
return false;
}
// 检查响应状态码
long http_code = 0;
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code == 200) {
try {
// 解析响应
nlohmann::json response = nlohmann::json::parse(response_buffer_);
int code = response["code"].get<int>();
std::string msg = response["msg"].get<std::string>();
if (code == 200) {
Logger::debug("Command sent successfully: ", request_body);
return true;
} else {
Logger::error("Command failed with code: ", code, " message: ", msg);
return false;
}
} catch (const std::exception& e) {
Logger::error("Failed to parse response: ", e.what());
return false;
}
} else {
Logger::error("Command failed with HTTP code: ", http_code, " response: ", response_buffer_);
return false;
}
}

20
src/network/HTTPClient.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <curl/curl.h>
#include "types/VehicleCommand.h"
#include "nlohmann/json.hpp"
class HTTPClient {
public:
HTTPClient();
~HTTPClient();
// 发送控制指令
bool sendCommand(const std::string& ip, int port, const VehicleCommand& command) const;
private:
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp);
CURL* curl_;
mutable std::string response_buffer_;
};

View File

@ -0,0 +1,605 @@
#include "HTTPDataSource.h"
#include "HTTPClient.h"
#include "utils/Logger.h"
#include <nlohmann/json.hpp>
#include <sstream>
using json = nlohmann::json;
size_t HTTPDataSource::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t total_size = size * nmemb;
std::string* response = static_cast<std::string*>(userp);
response->append(static_cast<char*>(contents), total_size);
return total_size;
}
size_t HTTPDataSource::DebugCallback(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) {
return 0;
}
HTTPDataSource::HTTPDataSource(const DataSourceConfig& config)
: config_(config)
, last_health_check_(std::chrono::steady_clock::now()) {
curl_ = curl_easy_init();
if (!curl_) {
Logger::error("Failed to initialize CURL");
}
}
HTTPDataSource::~HTTPDataSource() {
disconnect();
if (curl_) {
curl_easy_cleanup(curl_);
}
}
bool HTTPDataSource::connect() {
if (!curl_) {
curl_ = curl_easy_init();
if (!curl_) {
Logger::error("Failed to initialize CURL");
return false;
}
}
return true;
}
void HTTPDataSource::disconnect() {
if (curl_) {
curl_easy_cleanup(curl_);
curl_ = nullptr;
}
}
bool HTTPDataSource::isAvailable() const {
return curl_ != nullptr;
}
bool HTTPDataSource::tryReconnect() {
if (is_reconnecting_) {
return false;
}
is_reconnecting_ = true;
try {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - last_connect_attempt_).count();
if (elapsed < config_.position.timeout_ms) {
is_reconnecting_ = false;
return false;
}
last_connect_attempt_ = now;
for (int retry = 0; retry < MAX_RETRIES; ++retry) {
if (connect()) {
is_reconnecting_ = false;
return true;
}
if (retry < MAX_RETRIES - 1) {
std::this_thread::sleep_for(std::chrono::milliseconds(config_.position.timeout_ms / 3));
}
}
Logger::error("Failed to reconnect after ", MAX_RETRIES, " attempts");
is_reconnecting_ = false;
return false;
} catch (const std::exception& e) {
Logger::error("Error during reconnection: ", e.what());
is_reconnecting_ = false;
return false;
}
}
bool HTTPDataSource::checkConnectionHealth() {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
now - last_health_check_).count();
if (elapsed < HEALTH_CHECK_INTERVAL) {
return true;
}
last_health_check_ = now;
return curl_ != nullptr;
}
bool HTTPDataSource::ensureConnected() {
if (isAvailable() && checkConnectionHealth()) {
return true;
}
return tryReconnect();
}
bool HTTPDataSource::sendRequest(const std::string& url, const AuthState* auth_state,
std::string& response, HttpMethod method, const std::string& body) {
if (!curl_) {
Logger::error("CURL not initialized");
return false;
}
curl_easy_reset(curl_);
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, config_.position.timeout_ms / 1000);
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, config_.position.timeout_ms / 1000);
curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L);
if (method == HttpMethod::GET) {
curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1L);
curl_easy_setopt(curl_, CURLOPT_POST, 0L);
} else if (method == HttpMethod::POST) {
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
curl_easy_setopt(curl_, CURLOPT_HTTPGET, 0L);
if (!body.empty()) {
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, body.c_str());
} else {
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, "");
}
}
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Accept: application/json");
headers = curl_slist_append(headers, "Cache-Control: no-cache");
headers = curl_slist_append(headers, "Expect:");
if (method == HttpMethod::POST) {
headers = curl_slist_append(headers, "Content-Type: application/json");
}
if (auth_state && auth_state->is_authenticated && !auth_state->token.empty()) {
std::string auth_header = "Authorization: " + auth_state->token;
headers = curl_slist_append(headers, auth_header.c_str());
}
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers);
response.clear();
CURLcode res = curl_easy_perform(curl_);
curl_slist_free_all(headers);
if (res != CURLE_OK) {
Logger::error("请求失败: ", curl_easy_strerror(res));
return false;
}
long http_code = 0;
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 200) {
Logger::error("HTTP 请求失败,状态码: ", http_code);
return false;
}
return true;
}
bool HTTPDataSource::fetchPositionAircraftData(std::vector<Aircraft>& aircraft) {
std::lock_guard<std::mutex> lock(mutex_);
if (!ensureConnected()) {
Logger::error("连接失败");
return false;
}
if (!ensureAuthenticated(config_.position.auth,
config_.position.host,
config_.position.port,
position_auth_,
DataSourceType::POSITION)) {
Logger::error("认证失败");
return false;
}
std::string url = "http://" + config_.position.host + ":" +
std::to_string(config_.position.port) +
config_.position.aircraft_path;
std::string response;
if (!sendRequest(url, &position_auth_, response, HttpMethod::GET)) {
Logger::error("请求失败");
return false;
}
return parsePositionAircraftResponse(response, aircraft);
}
bool HTTPDataSource::fetchPositionVehicleData(std::vector<Vehicle>& vehicles) {
std::lock_guard<std::mutex> lock(mutex_);
if (!ensureConnected()) {
return false;
}
if (!ensureAuthenticated(config_.position.auth,
config_.position.host,
config_.position.port,
position_auth_,
DataSourceType::POSITION)) {
return false;
}
std::string url = "http://" + config_.position.host + ":" +
std::to_string(config_.position.port) +
config_.position.vehicle_path;
std::string response;
if (!sendRequest(url, &position_auth_, response, HttpMethod::GET)) {
return false;
}
return parsePositionVehicleResponse(response, vehicles);
}
bool HTTPDataSource::fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals) {
std::lock_guard<std::mutex> lock(mutex_);
if (!ensureConnected()) {
return false;
}
if (!ensureAuthenticated(config_.traffic_light.auth,
config_.traffic_light.host,
config_.traffic_light.port,
traffic_light_auth_,
DataSourceType::TRAFFIC_LIGHT)) {
return false;
}
std::string url = "http://" + config_.traffic_light.host + ":" +
std::to_string(config_.traffic_light.port) +
config_.traffic_light.signal_path;
std::string response;
if (!sendRequest(url, &traffic_light_auth_, response, HttpMethod::GET)) {
return false;
}
return parseTrafficLightResponse(response, signals);
}
std::string getSignalStateString(SignalState state) {
switch (state) {
case SignalState::RED:
return "RED";
case SignalState::YELLOW:
return "YELLOW";
case SignalState::GREEN:
return "GREEN";
default:
return "GREEN"; // 默认为绿灯
}
}
bool HTTPDataSource::sendUnmannedVehicleCommand(const std::string& vehicle_id, const VehicleCommand& command) {
std::lock_guard<std::mutex> lock(mutex_);
if (!ensureConnected()) {
return false;
}
if (!ensureAuthenticated(config_.vehicle.auth,
config_.vehicle.host,
config_.vehicle.port,
unmanned_vehicle_auth_,
DataSourceType::UNMANNED)) {
return false;
}
std::string url = "http://" + config_.vehicle.host + ":" +
std::to_string(config_.vehicle.port) +
config_.vehicle.command_path;
std::stringstream transId;
transId << std::hex << std::chrono::system_clock::now().time_since_epoch().count();
nlohmann::json request = {
{"transId", transId.str()},
{"timestamp", command.timestamp},
{"vehicleID", command.vehicleId},
{"commandType", [&]() {
switch (command.type) {
case CommandType::SIGNAL: return "SIGNAL";
case CommandType::ALERT: return "ALERT";
case CommandType::WARNING: return "WARNING";
case CommandType::RESUME: return "RESUME";
default: return "UNKNOWN";
}
}()},
{"commandReason", [&]() {
switch (command.reason) {
case CommandReason::TRAFFIC_LIGHT: return "TRAFFIC_LIGHT";
case CommandReason::AIRCRAFT_CROSSING: return "AIRCRAFT_CROSSING";
case CommandReason::SPECIAL_VEHICLE: return "SPECIAL_VEHICLE";
case CommandReason::AIRCRAFT_PUSH: return "AIRCRAFT_PUSH";
case CommandReason::RESUME_TRAFFIC: return "RESUME_TRAFFIC";
default: return "UNKNOWN";
}
}()},
{"latitude", command.latitude},
{"longitude", command.longitude},
{"signalState", getSignalStateString(command.signalState)},
{"intersectionId", command.intersectionId}
};
if (command.type == CommandType::ALERT || command.type == CommandType::WARNING) {
request["relativeSpeed"] = command.relativeSpeed;
request["relativeMotionX"] = command.relativeMotionX;
request["relativeMotionY"] = command.relativeMotionY;
request["minDistance"] = command.minDistance;
}
std::string response;
if (!sendRequest(url, &unmanned_vehicle_auth_, response, HttpMethod::POST, request.dump())) {
return false;
}
try {
nlohmann::json j = nlohmann::json::parse(response);
if (!j.contains("code") || !j.contains("msg")) {
Logger::error("Invalid command response format");
return false;
}
if (j["code"].get<int>() != 200) {
Logger::error("Command failed: ", j["msg"].get<std::string>());
return false;
}
return true;
} catch (const std::exception& e) {
Logger::error("Failed to parse command response: ", e.what());
return false;
}
}
bool HTTPDataSource::fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status) {
std::lock_guard<std::mutex> lock(mutex_);
if (!ensureConnected()) {
return false;
}
if (!ensureAuthenticated(config_.vehicle.auth,
config_.vehicle.host,
config_.vehicle.port,
unmanned_vehicle_auth_,
DataSourceType::UNMANNED)) {
return false;
}
std::string url = "http://" + config_.vehicle.host + ":" +
std::to_string(config_.vehicle.port) +
config_.vehicle.status_path + "?vehicleId=" + vehicle_id;
std::string response;
if (!sendRequest(url, &unmanned_vehicle_auth_, response, HttpMethod::GET)) {
return false;
}
try {
nlohmann::json j = nlohmann::json::parse(response);
if (!j.contains("status") || !j.contains("data")) {
Logger::error("Invalid vehicle status response format");
return false;
}
if (j["status"].get<int>() != 200) {
Logger::error("Failed to get vehicle status");
return false;
}
status = j["data"].dump();
return true;
} catch (const std::exception& e) {
Logger::error("Failed to parse vehicle status response: ", e.what());
return false;
}
}
bool HTTPDataSource::authenticatePosition(const AuthConfig& auth_config,
const std::string& host, int port, AuthState& auth_state) {
if (!auth_config.auth_required) {
auth_state.is_authenticated = true;
return true;
}
std::lock_guard<std::mutex> lock(auth_mutex_);
if (!curl_) {
Logger::error("CURL not initialized");
return false;
}
std::string url = "http://" + host + ":" + std::to_string(port) +
auth_config.auth_path + "?username=" + auth_config.username +
"&password=" + auth_config.password;
std::string response;
if (!sendRequest(url, nullptr, response, HttpMethod::POST, "")) {
Logger::error("认证请求失败");
return false;
}
try {
nlohmann::json j = nlohmann::json::parse(response);
if (!j.contains("status") || !j.contains("msg") || !j.contains("data")) {
Logger::error("认证响应缺少必需字段");
return false;
}
if (j["status"].get<int>() != 200) {
Logger::error("认证失败: ", j["msg"].get<std::string>());
return false;
}
std::string token = j["data"].get<std::string>();
if (token.substr(0, 7) != "Bearer ") {
token = "Bearer " + token;
}
auth_state.token = token;
auth_state.is_authenticated = true;
auth_state.last_auth_time = std::chrono::steady_clock::now();
return true;
} catch (const std::exception& e) {
Logger::error("解析认证响应失败: ", e.what(), ", 响应内容: ", response);
return false;
}
}
bool HTTPDataSource::authenticateUnmanned(const AuthConfig& auth_config,
const std::string& host, int port, AuthState& auth_state) {
auth_state.is_authenticated = true;
return true;
}
bool HTTPDataSource::authenticateTrafficLight(const AuthConfig& auth_config,
const std::string& host, int port, AuthState& auth_state) {
auth_state.is_authenticated = true;
return true;
}
bool HTTPDataSource::ensureAuthenticated(const AuthConfig& auth_config, const std::string& host,
int port, AuthState& auth_state, DataSourceType type) {
if (!auth_config.auth_required) {
return true;
}
if (!auth_state.is_authenticated) {
switch (type) {
case DataSourceType::POSITION:
return authenticatePosition(auth_config, host, port, auth_state);
case DataSourceType::UNMANNED:
return authenticateUnmanned(auth_config, host, port, auth_state);
case DataSourceType::TRAFFIC_LIGHT:
return authenticateTrafficLight(auth_config, host, port, auth_state);
default:
return false;
}
}
return true;
}
bool HTTPDataSource::parsePositionAircraftResponse(const std::string& response, std::vector<Aircraft>& aircraft) {
try {
nlohmann::json j = nlohmann::json::parse(response);
if (!j.contains("status") || !j.contains("data")) {
Logger::error("Invalid aircraft response format");
return false;
}
if (j["status"].get<int>() != 200) {
Logger::error("Failed to get aircraft data: ", j["msg"].get<std::string>());
return false;
}
const auto& data = j["data"];
aircraft.clear();
for (const auto& item : data) {
Aircraft ac;
ac.flightNo = item["flightNo"].get<std::string>();
ac.id = ac.flightNo;
ac.geo.longitude = item["longitude"].get<double>();
ac.geo.latitude = item["latitude"].get<double>();
ac.timestamp = item["time"].get<uint64_t>();
if (item.contains("altitude")) {
ac.altitude = item["altitude"].get<double>();
}
if (item.contains("trackNumber")) {
ac.trackNumber = item["trackNumber"].get<int64_t>();
}
aircraft.push_back(ac);
}
return true;
} catch (const std::exception& e) {
Logger::error("Failed to parse aircraft response: ", e.what());
return false;
}
}
bool HTTPDataSource::parsePositionVehicleResponse(const std::string& response, std::vector<Vehicle>& vehicles) {
try {
nlohmann::json j = nlohmann::json::parse(response);
if (!j.contains("status") || !j.contains("data")) {
Logger::error("Invalid vehicle response format");
return false;
}
if (j["status"].get<int>() != 200) {
Logger::error("Failed to get vehicle data: ", j["msg"].get<std::string>());
return false;
}
const auto& data = j["data"];
vehicles.clear();
for (const auto& item : data) {
Vehicle vehicle;
vehicle.vehicleNo = item["vehicleNo"].get<std::string>();
vehicle.id = vehicle.vehicleNo;
vehicle.geo.longitude = item["longitude"].get<double>();
vehicle.geo.latitude = item["latitude"].get<double>();
vehicle.timestamp = item["time"].get<uint64_t>();
if (item.contains("direction")) {
vehicle.heading = item["direction"].get<double>();
}
if (item.contains("speed")) {
vehicle.speed = item["speed"].get<double>();
}
vehicles.push_back(vehicle);
}
return true;
} catch (const std::exception& e) {
Logger::error("Failed to parse vehicle response: ", e.what());
return false;
}
}
bool HTTPDataSource::parseTrafficLightResponse(const std::string& response, std::vector<TrafficLightSignal>& signals) {
try {
nlohmann::json j = nlohmann::json::parse(response);
if (!j.contains("status") || !j.contains("data")) {
Logger::error("Invalid traffic light response format");
return false;
}
if (j["status"].get<int>() != 200) {
Logger::error("Failed to get traffic light data: ", j["msg"].get<std::string>());
return false;
}
const auto& data = j["data"];
signals.clear();
for (const auto& item : data) {
TrafficLightSignal signal;
signal.trafficLightId = item["id"].get<std::string>();
signal.ns_status = [&]() {
int state = item["ns_status"].get<int>();
switch (state) {
case 0: return SignalStatus::RED;
case 1: return SignalStatus::GREEN;
case 2: return SignalStatus::YELLOW;
default: return SignalStatus::UNKNOWN;
}
}();
signal.timestamp = item["timestamp"].get<uint64_t>();
signal.intersectionId = item["intersection"].get<std::string>();
signal.latitude = item["position"]["latitude"].get<double>();
signal.longitude = item["position"]["longitude"].get<double>();
signals.push_back(signal);
}
return true;
} catch (const std::exception& e) {
Logger::error("Failed to parse traffic light response: ", e.what());
return false;
}
}

View File

@ -0,0 +1,96 @@
#ifndef AIRPORT_NETWORK_HTTP_DATA_SOURCE_H
#define AIRPORT_NETWORK_HTTP_DATA_SOURCE_H
#include "collector/DataSource.h"
#include "spatial/CoordinateConverter.h"
#include <string>
#include "collector/DataSourceConfig.h"
#include <mutex>
#include <atomic>
#include <chrono>
#include "core/System.h"
#include <curl/curl.h>
#include "types/VehicleCommand.h"
class HTTPDataSource : public DataSource {
public:
enum class HttpMethod {
GET,
POST
};
explicit HTTPDataSource(const DataSourceConfig& config);
~HTTPDataSource() override;
const DataSourceConfig& getConfig() const { return config_; }
DataSourceConfig config_;
bool connect() override;
void disconnect() override;
bool isAvailable() const override;
// 位置数据接口
bool fetchPositionAircraftData(std::vector<Aircraft>& aircraft) override;
bool fetchPositionVehicleData(std::vector<Vehicle>& vehicles) override;
// 无人车接口
bool sendUnmannedVehicleCommand(const std::string& vehicle_id, const VehicleCommand& command) override;
bool fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status) override;
// 红绿灯接口
bool fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals) override;
private:
std::mutex mutex_;
CURL* curl_;
std::chrono::steady_clock::time_point last_connect_attempt_;
std::chrono::steady_clock::time_point last_health_check_; // 上次健康检查时间
static constexpr int MAX_RETRIES = 3; // 单次连接最大重试次数
static constexpr int HEALTH_CHECK_INTERVAL = 60; // 健康检查间隔(秒)
std::atomic<bool> is_reconnecting_{false}; // 重连状态标志
// 每个数据源的认证状态
struct AuthState {
bool is_authenticated = false;
std::string token;
std::chrono::steady_clock::time_point last_auth_time;
};
AuthState position_auth_; // 位置数据源认证状态
AuthState unmanned_vehicle_auth_; // 无人车数据源认证状态
AuthState traffic_light_auth_; // 红绿灯数据源认证状态
std::mutex auth_mutex_; // 认证相关的互斥锁
// 认证相关方法
bool ensureAuthenticated(const AuthConfig& auth_config, const std::string& host,
int port, AuthState& auth_state, DataSourceType type);
bool tryReconnect();
bool ensureConnected();
bool checkConnectionHealth(); // 检查连接健康状况
bool sendRequest(const std::string& url, const AuthState* auth_state,
std::string& response, HttpMethod method = HttpMethod::GET,
const std::string& body = "");
// 位置数据响应解析
bool parsePositionAircraftResponse(const std::string& response, std::vector<Aircraft>& aircraft);
bool parsePositionVehicleResponse(const std::string& response, std::vector<Vehicle>& vehicles);
// 红绿灯响应解析
bool parseTrafficLightResponse(const std::string& response, std::vector<TrafficLightSignal>& signals);
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp);
static size_t DebugCallback(CURL* handle, curl_infotype type, char* data, size_t size, void* userp);
// 不同数据源的认证方法
bool authenticatePosition(const AuthConfig& auth_config, const std::string& host,
int port, AuthState& auth_state);
bool authenticateUnmanned(const AuthConfig& auth_config, const std::string& host,
int port, AuthState& auth_state);
bool authenticateTrafficLight(const AuthConfig& auth_config, const std::string& host,
int port, AuthState& auth_state);
};
#endif // AIRPORT_NETWORK_HTTP_DATA_SOURCE_H

View File

@ -0,0 +1,71 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace network {
// 位置更新消息
struct PositionUpdateMessage {
std::string type = "position_update";
std::string objectId;
std::string objectType; // "aircraft" 或 "vehicle"
double longitude;
double latitude;
double heading;
double speed;
uint64_t timestamp;
nlohmann::json toJson() const {
return {
{"type", type},
{"objectId", objectId},
{"objectType", objectType},
{"longitude", longitude},
{"latitude", latitude},
{"heading", heading},
{"speed", speed},
{"timestamp", timestamp}
};
}
};
// 碰撞告警消息
struct CollisionWarningMessage {
std::string type = "collision_warning";
std::string id1;
std::string id2;
std::string warningLevel; // "low", "medium", "high"
double distance; // 当前距离
double relativeSpeed; // 相对速度
double threshold; // 预警距离阈值
uint64_t timestamp;
nlohmann::json toJson() const {
return {
{"type", type},
{"id1", id1},
{"id2", id2},
{"warningLevel", warningLevel},
{"distance", distance},
{"relativeSpeed", relativeSpeed},
{"threshold", threshold},
{"timestamp", timestamp}
};
}
};
struct TimeoutWarningMessage {
std::string source; // 超时来源
int64_t elapsed_ms; // 超时时长
bool is_read_timeout; // 是否是读取超时
nlohmann::json toJson() const {
return {
{"source", source},
{"elapsed_ms", elapsed_ms},
{"is_read_timeout", is_read_timeout}
};
}
};
} // namespace network

View File

@ -0,0 +1,17 @@
#ifndef AIRPORT_NETWORK_INTERFACE_H
#define AIRPORT_NETWORK_INTERFACE_H
#include <string>
#include <memory>
#include "types/VehicleCommand.h"
class NetworkInterface {
public:
virtual ~NetworkInterface() = default;
virtual bool connect() = 0;
virtual void disconnect() = 0;
virtual bool send(const VehicleCommand& cmd) = 0;
virtual bool isConnected() const = 0;
};
#endif // AIRPORT_NETWORK_INTERFACE_H

View File

@ -0,0 +1,377 @@
#include "TrafficLightHttpServer.h"
#include "utils/Logger.h"
#include "core/System.h"
#include <nlohmann/json.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <vector>
#include <memory>
#include <string>
namespace network {
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;
using json = nlohmann::json;
// --- Forward Declarations within namespace ---
// 不再需要,因为类定义直接在下面
// class TrafficLightSession;
// class TrafficLightListener;
//---------------- Helper Functions for HTTP Responses ------------------
// Returns a bad request response
http::response<http::string_body> bad_request(const http::request<http::string_body>& req, beast::string_view why) {
http::response<http::string_body> res{ http::status::bad_request, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = std::string(why);
res.prepare_payload();
return res;
}
// Returns a not found response
http::response<http::string_body> not_found(const http::request<http::string_body>& req) {
http::response<http::string_body> res{ http::status::not_found, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "The resource '" + std::string(req.target()) + "' was not found.";
res.prepare_payload();
return res;
}
// Returns a server error response
http::response<http::string_body> server_error(const http::request<http::string_body>& req, beast::string_view what) {
http::response<http::string_body> res{ http::status::internal_server_error, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "An error occurred: '" + std::string(what) + "'";
res.prepare_payload();
return res;
}
// Returns a method not allowed response
http::response<http::string_body> method_not_allowed(const http::request<http::string_body>& req) {
http::response<http::string_body> res{ http::status::method_not_allowed, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.set(http::field::allow, "POST");
res.keep_alive(req.keep_alive());
res.body() = "Method Not Allowed. Allowed methods: POST";
res.prepare_payload();
return res;
}
// Returns a success response (200 OK)
http::response<http::string_body> ok_response(const http::request<http::string_body>& req, const std::string& message = "OK") {
http::response<http::string_body> res{ http::status::ok, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = "{\"status\": \"success\", \"message\": \"" + message + "\"}";
res.prepare_payload();
return res;
}
//---------------- Internal Session Class (Definition & Implementation) ----------------------
// Handles an HTTP server connection. Defined entirely within the .cpp file.
class TrafficLightSession : public std::enable_shared_from_this<TrafficLightSession>
{
tcp::socket socket_;
beast::flat_buffer buffer_;
std::string const& doc_root_; // Keep for potential future use
http::request<http::string_body> req_;
// Strand to ensure sequential execution of handlers for this session.
net::strand<net::io_context::executor_type> strand_;
net::io_context& ioc_; // 新增: 持有 io_context 引用
System& system_; // 新增: System 引用
public:
// Take ownership of the socket
explicit TrafficLightSession(tcp::socket&& socket, std::string const& doc_root, net::io_context& ioc, System& system_ref)
: socket_(std::move(socket)),
doc_root_(doc_root),
strand_(ioc.get_executor()),
ioc_(ioc),
system_(system_ref) {}
void run() {
net::dispatch(strand_, [self = shared_from_this()]() {
self->do_read();
});
}
private:
void do_read() {
req_ = {}; // 清空请求对象以便重用
http::async_read(socket_, buffer_, req_,
net::bind_executor(strand_,
[self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
self->on_read(ec, bytes_transferred);
}
));
}
void on_read(beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
// Connection closed by peer
if (ec == http::error::end_of_stream) return do_close();
if (ec) return Logger::error("Session Read Error: ", ec.message());
handle_request();
}
void handle_request() {
// Respond to HEAD request
if (req_.method() == http::verb::head) {
auto res = ok_response(req_, "");
res.body() = ""; // No body for HEAD
return send_response(std::move(res));
}
// Only allow POST requests to the specific endpoint
if (req_.method() != http::verb::post) {
return send_response(method_not_allowed(req_));
}
if (req_.target() != "/trafficlight") {
return send_response(not_found(req_));
}
// Attempt to parse the JSON body
try {
json body_json = json::parse(req_.body());
// Log the received data
Logger::info("接收到红绿灯数据:");
std::string log_msg = "红绿灯状态: ";
bool first = true;
for (const auto& key : { "DI-11", "DI-12", "DI-13", "DI-14", "DI-15", "DI-16" }) {
if (body_json.contains(key)) {
if (!first) log_msg += ", ";
log_msg += key;
log_msg += ":";
// Safely convert JSON value to string, handling potential non-string types
try {
log_msg += body_json[key].dump();
} catch (const json::type_error& te) {
log_msg += "[invalid_type]";
Logger::warning("JSON value for key ", key, " is not easily dumpable: ", te.what());
}
first = false;
}
}
if (first) { // If no relevant keys were found
log_msg += body_json.dump(); // Log the full body
}
Logger::info(log_msg);
// TODO: Integrate data processing (e.g., pass to DataCollector)
// For now, just logging and sending OK.
system_.processPushedTrafficLightData(body_json);
return send_response(ok_response(req_, "Data received and processed"));
} catch (const json::parse_error& e) {
Logger::error("JSON Parsing Error: ", e.what(), ", Body: ", req_.body());
return send_response(bad_request(req_, "Invalid JSON format"));
} catch (const std::exception& e) {
Logger::error("Error processing request: ", e.what());
return send_response(server_error(req_, "Internal server error processing request"));
}
}
void send_response(http::response<http::string_body>&& res) {
auto sp = std::make_shared<http::response<http::string_body>>(std::move(res));
http::async_write(socket_, *sp,
net::bind_executor(strand_,
[self = shared_from_this(), sp](beast::error_code ec, std::size_t bytes) {
self->on_write(sp->need_eof(), ec, bytes);
}
));
}
void on_write(bool close, beast::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec) return Logger::error("Session Write Error: ", ec.message());
if (close) return do_close();
do_read();
}
void do_close() {
beast::error_code ec;
socket_.shutdown(tcp::socket::shutdown_send, ec);
}
};
//---------------- Internal Listener Class (Definition & Implementation) ---------------------
// Accepts incoming connections and launches sessions. Defined entirely within the .cpp file.
class TrafficLightListener : public std::enable_shared_from_this<TrafficLightListener>
{
net::io_context& ioc_;
tcp::acceptor acceptor_;
std::string const& doc_root_;
int max_connections_; // 保留这个,用于拒绝连接
System& system_; // 新增: System 引用
public:
TrafficLightListener(
net::io_context& ioc,
tcp::endpoint endpoint,
int max_connections,
std::string const& doc_root,
System& system_ref)
: ioc_(ioc),
acceptor_(ioc),
doc_root_(doc_root),
max_connections_(max_connections),
system_(system_ref) {
beast::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if (ec) { Logger::error("Listener Open Error: ", ec.message()); throw beast::system_error{ ec }; }
// Allow address reuse
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
if (ec) { Logger::error("Listener Set Option Error: ", ec.message()); throw beast::system_error{ ec }; }
// Bind to the server address
acceptor_.bind(endpoint, ec);
if (ec) { Logger::error("Listener Bind Error: ", ec.message()); throw beast::system_error{ ec }; }
// Start listening for connections
acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec) { Logger::error("Listener Listen Error: ", ec.message()); throw beast::system_error{ ec }; }
}
void run() {
do_accept();
}
void stop() {
beast::error_code ec_cancel, ec_close;
// 取消所有挂起的异步接受操作
acceptor_.cancel(ec_cancel);
// 关闭 acceptor
acceptor_.close(ec_close);
if (ec_cancel) Logger::warning("Listener cancel error: ", ec_cancel.message());
if (ec_close) Logger::warning("Listener close error: ", ec_close.message());
}
private:
void do_accept() {
acceptor_.async_accept(
net::bind_executor(acceptor_.get_executor(),
[self = shared_from_this()](beast::error_code ec, tcp::socket socket) { // socket 按值传递
if (ec) {
if (ec == net::error::operation_aborted) {
return; // 服务器停止,不再接受
}
Logger::error("Listener Accept Error: ", ec.message());
// 对于其他错误,我们仍然尝试继续接受,除非 acceptor 关闭
} else {
Logger::info("Accepted connection from ", socket.remote_endpoint());
std::make_shared<TrafficLightSession>(
std::move(socket), // 从 lambda 的参数 socket 移动
self->doc_root_, // 访问 listener 的成员
self->ioc_, // 访问 listener 的成员
self->system_ // 访问 listener 的成员
)->run();
}
if (self->acceptor_.is_open()) {
self->do_accept(); // 继续接受下一个连接
}
}
)
);
}
};
//---------------- TrafficLightHttpServer Implementation -------------------
TrafficLightHttpServer::TrafficLightHttpServer(uint16_t port, int max_connections, const std::string& doc_root, System& system_ref)
: port_(port),
doc_root_(doc_root),
max_connections_(max_connections),
ioc_(1),
system_(system_ref) {}
TrafficLightHttpServer::~TrafficLightHttpServer() {
if (running_.load()) {
stop();
}
}
void TrafficLightHttpServer::start(int num_threads) {
if (running_.exchange(true)) {
Logger::warning("TrafficLightHttpServer already started.");
return;
}
// ... (确保 num_threads >= 1)
auto const address = net::ip::make_address("0.0.0.0");
auto const port = port_;
try {
listener_ = std::make_shared<TrafficLightListener>(
ioc_,
tcp::endpoint{ address, port },
max_connections_,
doc_root_,
system_
);
listener_->run();
Logger::info("TrafficLightHttpServer starting on ", address.to_string(), ":", port, " with ", num_threads, " threads.");
threads_.reserve(num_threads);
for (int i = 0; i < num_threads; ++i) {
threads_.emplace_back([this] { run_ioc(); });
}
} catch (const std::exception& e) {
Logger::error("Failed to start TrafficLightHttpServer: ", e.what());
running_ = false;
throw;
}
}
void TrafficLightHttpServer::stop() {
if (!running_.exchange(false)) { return; }
Logger::info("Stopping TrafficLightHttpServer...");
net::post(ioc_, [this]() {
if (listener_) {
listener_->stop();
listener_.reset();
}
});
ioc_.stop();
for (auto& thread : threads_) {
if (thread.joinable()) { thread.join(); }
}
threads_.clear();
Logger::info("TrafficLightHttpServer stopped.");
}
void TrafficLightHttpServer::run_ioc() {
Logger::debug("TrafficLightHttpServer I/O thread started.");
try {
ioc_.run();
} catch (const std::exception& e) {
Logger::error("Exception in TrafficLightHttpServer I/O thread: ", e.what());
} catch (...) {
Logger::error("Unknown exception in TrafficLightHttpServer I/O thread.");
}
Logger::debug("TrafficLightHttpServer I/O thread finished.");
}
} // namespace network

View File

@ -0,0 +1,64 @@
#pragma once
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/config.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
#include <cstdint> // For uint16_t
#include <nlohmann/json.hpp>
// Forward declare Logger if header is heavy, or include if needed often
// #include "utils/Logger.h"
// 前向声明 System 类
class System;
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
using json = nlohmann::json;
namespace network {
// 前向声明内部类 (如果 TrafficLightHttpServer 需要引用它)
class TrafficLightListener;
// class TrafficLightSession; // Session 通常由 Listener 创建和管理Server 可能不需要直接引用
// Main server class (只保留这个类的声明)
class TrafficLightHttpServer
{
uint16_t port_;
std::string doc_root_;
int max_connections_;
net::io_context ioc_;
// 使用指向内部 Listener 实现的指针
std::shared_ptr<TrafficLightListener> listener_;
std::vector<std::thread> threads_;
std::atomic<bool> running_{ false };
System& system_; // 新增: System 引用
public:
TrafficLightHttpServer(uint16_t port, int max_connections, const std::string& doc_root, System& system_ref);
~TrafficLightHttpServer();
TrafficLightHttpServer(const TrafficLightHttpServer&) = delete;
TrafficLightHttpServer& operator=(const TrafficLightHttpServer&) = delete;
void start(int num_threads = 1);
void stop();
private:
void run_ioc();
};
}

View File

@ -0,0 +1,141 @@
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include "network/WebSocketServer.h"
#include "utils/Logger.h"
#include <nlohmann/json.hpp>
namespace network {
WebSocketServer::WebSocketServer(uint16_t port)
: acceptor_(ioc_, { boost::asio::ip::tcp::v4(), port }) {}
WebSocketServer::~WebSocketServer() {
// 关闭所有连接
std::lock_guard<std::mutex> lock(sessions_mutex_);
for (auto& session : sessions_) {
try {
session.lock()->close(boost::beast::websocket::close_code::normal);
} catch (...) {
// 忽略关闭时的错误
}
}
sessions_.clear();
// 停止 io_context
ioc_.stop();
}
void WebSocketServer::start() {
Logger::info("WebSocket 服务器启动,监听端口: ", acceptor_.local_endpoint().port());
handleAccept();
ioc_.run();
}
void WebSocketServer::broadcast(const std::string& message) {
std::lock_guard<std::mutex> lock(sessions_mutex_);
auto it = sessions_.begin();
int successCount = 0;
int failCount = 0;
while (it != sessions_.end()) {
if (auto session = it->lock()) { // 获取 shared_ptr
try {
session->write(boost::asio::buffer(message));
++successCount;
++it;
} catch (...) {
Logger::warning("广播消息到客户端失败");
++failCount;
it = sessions_.erase(it); // 移除失败的会话
}
} else {
Logger::debug("移除失效的会话");
it = sessions_.erase(it); // 移除已失效的会话
}
}
Logger::debug("广播消息完成: ", successCount, " 个成功, ", failCount, " 个失败, 当前共 ", sessions_.size(), " 个连接");
}
void WebSocketServer::handleAccept() {
acceptor_.async_accept(
[this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
if (!ec) {
Logger::info("接受新的 WebSocket 连接: ", socket.remote_endpoint().address().to_string());
// 创建新的 WebSocket 会话
auto ws = std::make_shared<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>(std::move(socket));
// 异步完成 WebSocket 握手
ws->async_accept(
[this, ws](boost::system::error_code ec) {
if (!ec) {
// 握手成功,保存会话并开始读取消息
{
std::lock_guard<std::mutex> lock(sessions_mutex_);
sessions_.push_back(ws); // 存储 weak_ptr
Logger::info("WebSocket 握手成功,当前连接数: ", sessions_.size());
}
doRead(ws);
} else {
Logger::error("WebSocket 握手失败: ", ec.message());
}
});
} else {
Logger::error("接受 WebSocket 连接失败: ", ec.message());
}
// 继续接受新的连接
handleAccept();
});
}
void WebSocketServer::doRead(std::shared_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> ws) {
// 为每个会话创建一个专用的缓冲区
auto buffer = std::make_shared<boost::beast::multi_buffer>();
// 异步读取消息
ws->async_read(
*buffer,
[this, ws, buffer = buffer](boost::system::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
// 成功读取消息,继续读取下一条
buffer->consume(buffer->size()); // 清空缓冲区
doRead(ws);
} else {
// 发生错误或连接关闭,移除会话
std::lock_guard<std::mutex> lock(sessions_mutex_);
auto it = std::find_if(sessions_.begin(), sessions_.end(),
[ws](const std::weak_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>& weak) {
return !weak.expired() && weak.lock() == ws;
});
if (it != sessions_.end()) {
sessions_.erase(it);
}
}
});
}
void WebSocketServer::stop() {
running_ = false;
Logger::info("正在停止 WebSocket 服务器...");
ioc_.stop(); // 停止 io_context
// 关闭所有连接
std::lock_guard<std::mutex> lock(sessions_mutex_);
for (auto& session : sessions_) {
if (auto ws = session.lock()) {
try {
ws->close(boost::beast::websocket::close_code::normal);
Logger::debug("关闭 WebSocket 连接");
} catch (...) {
Logger::warning("关闭 WebSocket 连接时发生错误");
}
}
}
sessions_.clear();
Logger::info("WebSocket 服务器已停止");
}
} // namespace network

View File

@ -0,0 +1,32 @@
#pragma once
#include <boost/beast/websocket.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <string>
#include <vector>
#include <mutex>
#include <atomic>
namespace network {
class WebSocketServer {
public:
WebSocketServer(uint16_t port);
~WebSocketServer();
void start();
void broadcast(const std::string& message);
void stop();
private:
void handleAccept();
void doRead(std::shared_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> ws);
boost::asio::io_context ioc_;
boost::asio::ip::tcp::acceptor acceptor_;
std::vector<std::weak_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>> sessions_;
std::mutex sessions_mutex_;
std::atomic<bool> running_{true};
};
} // namespace network

View File

@ -0,0 +1,52 @@
#include "spatial/CoordinateConverter.h"
#include "config/SystemConfig.h"
#include "utils/Logger.h"
#include <cmath>
// 地球半径(米)
const double EARTH_RADIUS = 6378137.0;
CoordinateConverter::CoordinateConverter() noexcept {
const auto& refPoint = SystemConfig::instance().airport.reference_point;
setReferencePoint(refPoint.latitude, refPoint.longitude);
}
void CoordinateConverter::setReferencePoint(double lat, double lon) noexcept {
ref_lat_ = lat * M_PI / 180.0; // 转换为弧度
ref_lon_ = lon * M_PI / 180.0;
cos_ref_lat_ = std::cos(ref_lat_); // 预计算参考点的余弦值
}
Vector2D CoordinateConverter::toLocalXY(double lat, double lon) const noexcept {
// 将经纬度转换为弧度
double lat_rad = lat * M_PI / 180.0;
double lon_rad = lon * M_PI / 180.0;
// 计算相对于参考点的经纬度差(弧度)
double d_lon = lon_rad - ref_lon_;
double d_lat = lat_rad - ref_lat_;
// 计算东西方向距离x和南北方向距离y
Vector2D result;
result.x = EARTH_RADIUS * cos_ref_lat_ * d_lon; // 使用预计算的余弦值
result.y = EARTH_RADIUS * d_lat;
return result;
}
GeoPosition CoordinateConverter::toLatLon(const Vector2D& pos) const noexcept {
// 计算经纬度差(弧度)
double d_lon = pos.x / (EARTH_RADIUS * cos_ref_lat_); // 使用预计算的余弦值
double d_lat = pos.y / EARTH_RADIUS;
// 计算实际经纬度(弧度)
double lon_rad = d_lon + ref_lon_;
double lat_rad = d_lat + ref_lat_;
// 转换为度
GeoPosition result;
result.latitude = lat_rad * 180.0 / M_PI;
result.longitude = lon_rad * 180.0 / M_PI;
return result;
}

View File

@ -0,0 +1,39 @@
#ifndef AIRPORT_SPATIAL_COORDINATE_CONVERTER_H
#define AIRPORT_SPATIAL_COORDINATE_CONVERTER_H
#include "types/BasicTypes.h"
#include <cmath>
#include <utility>
class CoordinateConverter {
public:
// 删除拷贝构造和赋值操作
CoordinateConverter(const CoordinateConverter&) = delete;
CoordinateConverter& operator=(const CoordinateConverter&) = delete;
// 获取全局单例
static CoordinateConverter& instance() {
static CoordinateConverter instance;
return instance;
}
// 设置参考点(经纬度)
void setReferencePoint(double lat, double lon) noexcept;
// 将经纬度转换为本地平面坐标系(米)
Vector2D toLocalXY(double lat, double lon) const noexcept;
// 将本地平面坐标转换为经纬度
GeoPosition toLatLon(const Vector2D& pos) const noexcept;
private:
// 私有构造函数
CoordinateConverter() noexcept;
static constexpr double EARTH_RADIUS = 6378137.0; // WGS84椭球体赤道半径
double ref_lat_{0.0}; // 参考点纬度(弧度)
double ref_lon_{0.0}; // 参考点经度(弧度)
double cos_ref_lat_{1.0}; // 参考点纬度的余弦值(预计算)
};
#endif // AIRPORT_SPATIAL_COORDINATE_CONVERTER_H

5
src/spatial/QuadTree.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "QuadTree.h"
// 显式实例化常用类型
template class QuadTree<Vehicle>;
template class QuadTree<Aircraft>;

196
src/spatial/QuadTree.h Normal file
View File

@ -0,0 +1,196 @@
#ifndef AIRPORT_SPATIAL_QUAD_TREE_H
#define AIRPORT_SPATIAL_QUAD_TREE_H
#include "types/BasicTypes.h"
#include <vector>
#include <memory>
#include <type_traits>
// 边界定义
struct Bounds {
double x; // 左上角 x 坐标
double y; // 左上角 y 坐标
double width; // 宽度
double height; // 高度
Bounds() = default;
// 使用左上角点和宽高构造
Bounds(double x_, double y_, double width_, double height_)
: x(x_), y(y_), width(width_), height(height_) {}
// 检查点是否在边界内
bool contains(const Vector2D& point) const {
return point.x >= x && point.x <= (x + width) &&
point.y >= y && point.y <= (y + height);
}
// 检查是否与另一个边界相交
bool intersects(const Bounds& other) const {
return !(other.x > (x + width) || (other.x + other.width) < x ||
other.y > (y + height) || (other.y + other.height) < y);
}
// 获取中心点
Vector2D getCenter() const {
return {x + width/2, y + height/2};
}
};
template<typename T>
class QuadTree {
public:
explicit QuadTree(const Bounds& bounds, int capacity = 4)
: bounds_(bounds), capacity_(capacity) {}
const Bounds& getBounds() const { return bounds_; }
void insert(const T& item) { insertInternal(item); }
void insert(T&& item) { insertInternal(std::move(item)); }
std::vector<T> queryRange(const Bounds& range) const {
std::vector<T> found;
if (!bounds_.intersects(range)) {
return found;
}
for (const auto& item : items_) {
if (range.contains(getPosition(item))) {
found.push_back(item);
}
}
if (divided_) {
auto nw = northwest_->queryRange(range);
auto ne = northeast_->queryRange(range);
auto sw = southwest_->queryRange(range);
auto se = southeast_->queryRange(range);
found.insert(found.end(), nw.begin(), nw.end());
found.insert(found.end(), ne.begin(), ne.end());
found.insert(found.end(), sw.begin(), sw.end());
found.insert(found.end(), se.begin(), se.end());
}
return found;
}
std::vector<T> queryNearby(const Vector2D& point, double radius) const {
// 创建一个以point为中心边长为radius*2的正方形边界
Bounds range(
point.x - radius, // x
point.y - radius, // y
radius * 2, // width
radius * 2 // height
);
return queryRange(range);
}
void clear() {
items_.clear();
divided_ = false;
northwest_.reset();
northeast_.reset();
southwest_.reset();
southeast_.reset();
}
private:
Bounds bounds_;
int capacity_;
std::vector<T> items_;
bool divided_ = false;
std::unique_ptr<QuadTree> northwest_;
std::unique_ptr<QuadTree> northeast_;
std::unique_ptr<QuadTree> southwest_;
std::unique_ptr<QuadTree> southeast_;
template <typename U>
void insertInternal(U&& item) {
const Vector2D point = getPosition(item);
// 如果点不在当前树的边界内,忽略该对象
if (!bounds_.contains(point)) {
return;
}
// 叶子节点:未分裂且未达到容量,直接存放
if (!divided_ && items_.size() < static_cast<size_t>(capacity_)) {
items_.emplace_back(std::forward<U>(item));
return;
}
// 达到容量后分裂,并将已有 items_ 重新分发到子节点
if (!divided_) {
subdivide();
auto oldItems = std::move(items_);
items_.clear();
items_.shrink_to_fit();
for (auto& old : oldItems) {
insertIntoChildren(std::move(old));
}
}
insertIntoChildren(std::forward<U>(item));
}
template <typename U>
void insertIntoChildren(U&& item) {
const Vector2D point = getPosition(item);
// 由于 contains 使用 <= 边界,点可能落在多象限边界上;这里固定按顺序选择一个子象限
if (northwest_ && northwest_->bounds_.contains(point)) {
northwest_->insertInternal(std::forward<U>(item));
return;
}
if (northeast_ && northeast_->bounds_.contains(point)) {
northeast_->insertInternal(std::forward<U>(item));
return;
}
if (southwest_ && southwest_->bounds_.contains(point)) {
southwest_->insertInternal(std::forward<U>(item));
return;
}
if (southeast_ && southeast_->bounds_.contains(point)) {
southeast_->insertInternal(std::forward<U>(item));
return;
}
// 理论上不会走到这里(已检查 bounds_ contains兜底放回当前节点
items_.emplace_back(std::forward<U>(item));
}
void subdivide() {
double w = bounds_.width / 2;
double h = bounds_.height / 2;
// 创建四个子区域
Bounds nw(bounds_.x, bounds_.y, w, h);
northwest_ = std::make_unique<QuadTree>(nw, capacity_);
Bounds ne(bounds_.x + w, bounds_.y, w, h);
northeast_ = std::make_unique<QuadTree>(ne, capacity_);
Bounds sw(bounds_.x, bounds_.y + h, w, h);
southwest_ = std::make_unique<QuadTree>(sw, capacity_);
Bounds se(bounds_.x + w, bounds_.y + h, w, h);
southeast_ = std::make_unique<QuadTree>(se, capacity_);
divided_ = true;
}
static Vector2D getPosition(const T& item) {
if constexpr (std::is_same_v<T, Vehicle> || std::is_same_v<T, Aircraft>) {
return item.position; // 直接返回 position 成员
} else {
return item.getPosition(); // 对于其他类型假设有getPosition方法
}
}
};
#endif // AIRPORT_SPATIAL_QUAD_TREE_H

151
src/types/BasicTypes.cpp Normal file
View File

@ -0,0 +1,151 @@
#include "types/BasicTypes.h"
#include "utils/Logger.h"
#include "spatial/CoordinateConverter.h"
#include <cmath>
double Vector2D::magnitude() const {
return std::sqrt(x * x + y * y);
}
double Vector2D::direction() const {
double angle = std::atan2(x, y) * 180.0 / M_PI;
if (angle < 0) {
angle += 360.0;
}
return angle;
}
double MovingObject::calculateDistance(const GeoPosition& pos1, const GeoPosition& pos2) {
// 使用 Haversine 公式计算实际距离
const double EARTH_RADIUS = 6371000.0; // 地球半径(米)
// 转换为弧度
double lat1 = pos1.latitude * M_PI / 180.0;
double lon1 = pos1.longitude * M_PI / 180.0;
double lat2 = pos2.latitude * M_PI / 180.0;
double lon2 = pos2.longitude * M_PI / 180.0;
// 计算差值
double dlat = lat2 - lat1;
double dlon = lon2 - lon1;
// Haversine 公式
double a = std::sin(dlat/2) * std::sin(dlat/2) +
std::cos(lat1) * std::cos(lat2) *
std::sin(dlon/2) * std::sin(dlon/2);
double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1-a));
return EARTH_RADIUS * c;
}
double MovingObject::calculateHeading(const GeoPosition& from, const GeoPosition& to) {
// 计算经纬度差值
double dlon = to.longitude - from.longitude;
double dlat = to.latitude - from.latitude;
// 将经度差转换为实际距离(考虑纬度影响)
double lat_rad = from.latitude * M_PI / 180.0;
double dx = dlon * std::cos(lat_rad); // 东西方向的距离(经度)
double dy = dlat; // 南北方向的距离(纬度)
// 计算航向角正北为0度顺时针增加
double angle = std::atan2(dx, dy) * 180.0 / M_PI;
if (angle < 0) {
angle += 360.0;
}
return angle;
}
bool Aircraft::isValidPosition(const GeoPosition& newPos) const {
if (positionHistory.empty()) {
return true;
}
double distance = calculateDistance(positionHistory.back().geo, newPos);
return distance <= MAX_POSITION_JUMP;
}
bool Aircraft::isValidSpeed(double speed) const {
return speed <= MAX_SPEED;
}
bool Vehicle::isValidPosition(const GeoPosition& newPos) const {
if (positionHistory.empty()) {
return true;
}
double distance = calculateDistance(positionHistory.back().geo, newPos);
return distance <= MAX_POSITION_JUMP;
}
bool Vehicle::isValidSpeed(double speed) const {
return speed <= MAX_SPEED;
}
void MovingObject::updateMotion(const GeoPosition& newPos, uint64_t newTime) {
// 检查是否是新数据
bool isNewData = positionHistory.empty() || newTime > positionHistory.back().timestamp;
if (isNewData) {
// 更新位置历史
positionHistory.emplace_back(newPos, newTime);
if (positionHistory.size() > MAX_HISTORY) {
positionHistory.pop_front();
}
// 计算速度和航向
if (positionHistory.size() >= 2) {
// 使用最近的两个点来计算速度和航向
const auto& curr = positionHistory.back();
const auto& prev = positionHistory[positionHistory.size() - 2];
// 计算距离和时间差
double distance = calculateDistance(prev.geo, curr.geo); // 单位:米
double timeDiff = static_cast<double>(curr.timestamp - prev.timestamp) / 1000.0; // 转换为秒
// 只有当位置变化足够大且时间差足够长时才更新速度和航向
static const double MIN_DISTANCE = 0.1; // 最小位置变化阈值(米)
static const double MIN_TIME = 0.05; // 最小时间差阈值(秒)
if (distance > MIN_DISTANCE && timeDiff > MIN_TIME) {
// 计算新的速度
double newSpeed = distance / timeDiff; // 米/秒
if (isValidSpeed(newSpeed)) {
// 使用指数移动平均来平滑速度,增大平滑因子以加快更新
const double alpha = 0.8; // 增大平滑因子
if (speed == 0) {
speed = newSpeed; // 第一次计算
} else {
speed = speed * (1 - alpha) + newSpeed * alpha; // 平滑更新
}
}
// 计算并直接更新航向,不需要平滑处理
heading = calculateHeading(prev.geo, curr.geo);
}
}
}
// 更新当前位置
geo = newPos;
timestamp = newTime;
position = CoordinateConverter::instance().toLocalXY(geo.latitude, geo.longitude);
}
void MovingObject::copyHistoryFrom(const MovingObject& other) {
positionHistory = other.positionHistory;
}
bool MovingObject::hasPositionChanged() const {
if (positionHistory.size() < 2) {
return true;
}
const auto& latest = positionHistory.back();
const auto& previous = positionHistory[positionHistory.size() - 2];
// 计算位置变化
double distance = calculateDistance(latest.geo, previous.geo);
return distance > 0.1; // 位置变化超过0.1米才认为发生了变化
}

113
src/types/BasicTypes.h Normal file
View File

@ -0,0 +1,113 @@
#pragma once
#include <string>
#include <deque>
#include <cstdint>
// 移动物体类型
enum class MovingObjectType {
AIRCRAFT, // 航空器
SPECIAL, // 特勤车
UNMANNED // 无人车(可控车辆)
};
// 基础数据类型
struct Vector2D {
double x;
double y;
Vector2D() : x(0), y(0) {}
Vector2D(double x_, double y_) : x(x_), y(y_) {}
double magnitude() const;
double direction() const;
};
struct GeoPosition {
double latitude;
double longitude;
};
struct PositionRecord {
GeoPosition geo;
uint64_t timestamp;
PositionRecord(const GeoPosition& g, uint64_t t) : geo(g), timestamp(t) {}
};
// 移动物体基类
class MovingObject {
public:
MovingObject() : heading(0.0), speed(0.0), timestamp(0), type(MovingObjectType::SPECIAL) {}
virtual ~MovingObject() = default;
std::string id;
Vector2D position;
GeoPosition geo;
double heading;
double speed;
int64_t timestamp;
MovingObjectType type; // 添加类型字段
std::deque<PositionRecord> positionHistory;
static double calculateDistance(const GeoPosition& pos1, const GeoPosition& pos2);
static double calculateHeading(const GeoPosition& from, const GeoPosition& to);
virtual bool isValidPosition(const GeoPosition& newPos) const = 0;
virtual bool isValidSpeed(double speed) const = 0;
virtual double getSpeedSmoothingFactor() const { return 0.3; }
void updateMotion(const GeoPosition& newPos, uint64_t newTime);
void copyHistoryFrom(const MovingObject& other);
bool hasPositionChanged() const;
// 添加类型识别虚函数
virtual bool isAircraft() const { return type == MovingObjectType::AIRCRAFT; }
virtual bool isSpecialVehicle() const { return type == MovingObjectType::SPECIAL; }
virtual bool isUnmannedVehicle() const { return type == MovingObjectType::UNMANNED; }
static constexpr size_t MAX_HISTORY = 10;
};
// 航空器类
class Aircraft : public MovingObject {
public:
Aircraft() { type = MovingObjectType::AIRCRAFT; }
std::string flightNo;
std::string trackNumber;
double altitude;
bool isValidPosition(const GeoPosition& newPos) const override;
bool isValidSpeed(double speed) const override;
static constexpr double MAX_SPEED = 100.0; // 最大速度(米/秒)
static constexpr double MAX_POSITION_JUMP = 50.0; // 最大位置跳变(米)
bool isAircraft() const override { return true; }
bool isSpecialVehicle() const override { return false; }
};
// 车辆类
class Vehicle : public MovingObject {
public:
Vehicle() {
type = MovingObjectType::UNMANNED; // 默认为无人车类型
isControllable = true; // 默认为可控
}
std::string vehicleNo;
bool isControllable; // true 表示无人车false 表示特勤车
bool isValidPosition(const GeoPosition& newPos) const override;
bool isValidSpeed(double speed) const override;
static constexpr double MAX_SPEED = 20.0; // 最大速度(米/秒)
static constexpr double MAX_POSITION_JUMP = 10.0; // 最大位置跳变(米)
bool isAircraft() const override { return false; }
bool isSpecialVehicle() const override {
return type == MovingObjectType::SPECIAL; // 只判断类型
}
};

View File

@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <cstdint>
enum class SignalStatus {
RED,
GREEN,
YELLOW,
UNKNOWN
};
struct TrafficLightSignal {
std::string trafficLightId; // 红绿灯设备 ID
SignalStatus ns_status = SignalStatus::UNKNOWN; // 南北向状态
SignalStatus ew_status = SignalStatus::UNKNOWN; // 东西向状态
uint64_t timestamp; // 时间戳
std::string intersectionId; // 路口编号
double latitude; // 路口纬度
double longitude; // 路口经度
};

View File

@ -0,0 +1,60 @@
#ifndef AIRPORT_TYPES_VEHICLE_COMMAND_H
#define AIRPORT_TYPES_VEHICLE_COMMAND_H
#include <string>
#include "VehicleData.h"
// 指令类型
enum class CommandType {
ALERT, // 告警指令
SIGNAL, // 信号灯指令
WARNING, // 预警指令
RESUME, // 恢复指令
PARKING // 安全停靠
};
// 信号灯状态
enum class SignalState {
RED, // 红灯
GREEN, // 绿灯
YELLOW // 黄灯
};
// 指令原因
enum class CommandReason {
TRAFFIC_LIGHT, // 红绿灯控制
AIRCRAFT_CROSSING, // 航空器交叉
SPECIAL_VEHICLE, // 特勤车辆
AIRCRAFT_PUSH, // 航空器推出
RESUME_TRAFFIC, // 恢复通行
PARKING_SIDE // 安全停靠
};
struct VehicleCommand {
std::string vehicleId; // 车辆ID
CommandType type; // 指令类型
CommandReason reason; // 指令原因
uint64_t timestamp; // 时间戳
SignalState signalState; // 信号灯状态(仅当 type 为 SIGNAL 时有效)
std::string intersectionId; // 路口ID仅当 type 为 SIGNAL 时有效)
double latitude; // 目标位置纬度(路口/航空器/特勤车)
double longitude; // 目标位置经度(路口/航空器/特勤车)
double relativeSpeed; // 相对速度(仅当 type 为 ALERT/WARNING 时有效)
double relativeMotionX; // 相对运动 X 分量(仅当 type 为 ALERT/WARNING 时有效)
double relativeMotionY; // 相对运动 Y 分量(仅当 type 为 ALERT/WARNING 时有效)
double minDistance; // 最小距离(仅当 type 为 ALERT/WARNING 时有效)
VehicleCommand() :
type(CommandType::RESUME),
reason(CommandReason::RESUME_TRAFFIC),
timestamp(0),
signalState(SignalState::GREEN),
latitude(0),
longitude(0),
relativeSpeed(0),
relativeMotionX(0),
relativeMotionY(0),
minDistance(0) {}
};
#endif // AIRPORT_TYPES_VEHICLE_COMMAND_H

View File

@ -0,0 +1,6 @@
#include "VehicleData.h"
#include "spatial/CoordinateConverter.h"
void VehicleData::updateLocalPosition(const CoordinateConverter& converter) {
position = converter.toLocalXY(geo.latitude, geo.longitude);
}

30
src/types/VehicleData.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef AIRPORT_TYPES_VEHICLE_DATA_H
#define AIRPORT_TYPES_VEHICLE_DATA_H
#include <cstdint>
#include <string>
#include "BasicTypes.h"
// 前向声明
class CoordinateConverter;
enum class VehicleType {
AIRCRAFT,
VEHICLE
};
struct VehicleData {
std::string id; // 唯一标识
VehicleType type; // 类型
GeoPosition geo; // 地理坐标(经纬度)
Vector2D position; // 平面坐标(米)
Vector2D velocity; // 速度(米/秒)
double heading; // 航向角(度)
uint64_t timestamp; // 时间戳
double altitude{0.0}; // 高度(米,仅飞行器有效)
// 只保留声明
void updateLocalPosition(const CoordinateConverter& converter);
};
#endif // AIRPORT_TYPES_VEHICLE_DATA_H

7
src/utils/Logger.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "Logger.h"
// 显式实例化模板,以支持常用类型
template void Logger::log(const char* level, const char* msg);
template void Logger::log(const char* level, std::string msg);
template void Logger::log(const char* level, int msg);
template void Logger::log(const char* level, double msg);

99
src/utils/Logger.h Normal file
View File

@ -0,0 +1,99 @@
#ifndef AIRPORT_UTILS_LOGGER_H
#define AIRPORT_UTILS_LOGGER_H
#include <iostream>
#include <fstream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <mutex>
enum class LogLevel {
DEBUG,
INFO,
WARNING,
ERROR
};
class Logger {
public:
static void initialize(const std::string& filename, LogLevel level = LogLevel::INFO) {
std::lock_guard<std::mutex> lock(getMutex());
currentLevel() = level;
logFile().open(filename, std::ios::app);
}
static void setLogLevel(LogLevel level) {
std::lock_guard<std::mutex> lock(getMutex());
currentLevel() = level;
}
template<typename... Args>
static void debug(Args... args) {
if (currentLevel() <= LogLevel::DEBUG) {
log("DEBUG", args...);
}
}
template<typename... Args>
static void info(Args... args) {
if (currentLevel() <= LogLevel::INFO) {
log("INFO", args...);
}
}
template<typename... Args>
static void warning(Args... args) {
if (currentLevel() <= LogLevel::WARNING) {
log("WARNING", args...);
}
}
template<typename... Args>
static void error(Args... args) {
if (currentLevel() <= LogLevel::ERROR) {
log("ERROR", args...);
}
}
private:
static LogLevel& currentLevel() {
static LogLevel level = LogLevel::INFO;
return level;
}
static std::ofstream& logFile() {
static std::ofstream file;
return file;
}
static std::mutex& getMutex() {
static std::mutex mutex;
return mutex;
}
template<typename... Args>
static void log(const char* level, Args... args) {
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) % 1000;
std::stringstream ss;
ss << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S")
<< '.' << std::setfill('0') << std::setw(3) << now_ms.count()
<< " [" << level << "] ";
ss << std::fixed << std::setprecision(14);
(ss << ... << args) << std::endl;
std::lock_guard<std::mutex> lock(getMutex());
std::cerr << ss.str();
if (logFile().is_open()) {
logFile() << ss.str();
logFile().flush();
}
}
};
#endif // AIRPORT_UTILS_LOGGER_H

View File

@ -0,0 +1,111 @@
#include "ControllableVehicles.h"
#include "utils/Logger.h"
#include <fstream>
#include <stdexcept>
// 定义静态成员变量
ControllableVehicles* ControllableVehicles::instance_ = nullptr;
ControllableVehicles& ControllableVehicles::getInstance() {
if (instance_ == nullptr) {
instance_ = new ControllableVehicles("config/unmanned_vehicles.json");
}
return *instance_;
}
ControllableVehicles::ControllableVehicles(const std::string& configFile)
: http_client_(std::make_unique<HTTPClient>()) {
if (!configFile.empty()) {
loadConfig(configFile);
}
}
ControllableVehicles::~ControllableVehicles() {
if (instance_ != nullptr) {
delete instance_;
instance_ = nullptr;
}
}
const std::vector<ControllableVehicleConfig>& ControllableVehicles::getVehicles() const {
return vehicles_;
}
const ControllableVehicleConfig* ControllableVehicles::findVehicle(const std::string& vehicleNo) const {
auto iter = std::find_if(vehicles_.begin(), vehicles_.end(),
[&](const ControllableVehicleConfig& config) {
return config.vehicleNo == vehicleNo;
});
return iter != vehicles_.end() ? &(*iter) : nullptr;
}
bool ControllableVehicles::isControllable(const std::string& vehicleNo) const {
// 查找车辆配置
auto* config = findVehicle(vehicleNo);
if (!config) {
return false;
}
// 只有无人车类型是可控的
return config->type == "UNMANNED";
}
bool ControllableVehicles::loadConfig(const std::string& configFile) {
try {
std::ifstream file(configFile);
if (!file.is_open()) {
Logger::error("Failed to open controllable vehicles config file: ", configFile);
return false;
}
nlohmann::json jsonConfig;
file >> jsonConfig;
for (const auto& item : jsonConfig["vehicles"]) {
ControllableVehicleConfig config;
config.vehicleNo = item["vehicleNo"].get<std::string>();
config.type = item["type"].get<std::string>();
config.ip = item["ip"].get<std::string>();
config.port = item["port"].get<int>();
vehicles_.push_back(config);
Logger::info("Added vehicle: ", config.vehicleNo, ", type: ", config.type);
}
Logger::info("Loaded ", vehicles_.size(), " controllable vehicles");
return true;
} catch (const std::exception& e) {
Logger::error("Failed to parse controllable vehicles config: ", e.what());
return false;
}
}
bool ControllableVehicles::sendCommand(const std::string& vehicleNo, const VehicleCommand& command) {
auto vehicle = findVehicle(vehicleNo);
if (!vehicle) {
Logger::error("Vehicle ", vehicleNo, " not found in controllable vehicles");
return false;
}
try {
bool success = http_client_->sendCommand(vehicle->ip, vehicle->port, command);
if (success) {
Logger::info("Successfully sent command to vehicle ", vehicleNo, ": ",
command.type == CommandType::SIGNAL ? "SIGNAL" :
command.type == CommandType::ALERT ? "ALERT" :
command.type == CommandType::WARNING ? "WARNING" :
command.type == CommandType::PARKING ? "PARKING" : "RESUME",
"/",
command.reason == CommandReason::TRAFFIC_LIGHT ? "TRAFFIC_LIGHT" :
command.reason == CommandReason::AIRCRAFT_CROSSING ? "AIRCRAFT_CROSSING" :
command.reason == CommandReason::SPECIAL_VEHICLE ? "SPECIAL_VEHICLE" :
command.reason == CommandReason::AIRCRAFT_PUSH ? "AIRCRAFT_PUSH" :
command.reason == CommandReason::PARKING_SIDE ? "PARKING_SIDE" : "RESUME_TRAFFIC");
} else {
Logger::error("Failed to send command to vehicle ", vehicleNo);
}
return success;
} catch (const std::exception& e) {
Logger::error("Exception while sending command to vehicle ", vehicleNo, ": ", e.what());
return false;
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include "types/VehicleCommand.h"
#include "network/HTTPClient.h"
struct ControllableVehicleConfig {
std::string vehicleNo; // 车牌号
std::string type; // 车辆类型UNMANNED 或 SPECIAL
std::string ip; // IP地址
int port; // 端口号
};
class ControllableVehicles {
private:
static ControllableVehicles* instance_;
ControllableVehicles(const std::string& configFile);
ControllableVehicles(const ControllableVehicles&) = delete;
ControllableVehicles& operator=(const ControllableVehicles&) = delete;
~ControllableVehicles();
std::vector<ControllableVehicleConfig> vehicles_;
std::unique_ptr<HTTPClient> http_client_;
bool loadConfig(const std::string& configFile);
public:
static ControllableVehicles& getInstance();
const std::vector<ControllableVehicleConfig>& getVehicles() const;
const ControllableVehicleConfig* findVehicle(const std::string& vehicleNo) const;
bool isControllable(const std::string& vehicleNo) const;
bool sendCommand(const std::string& vehicleNo, const VehicleCommand& command);
};

246
tests/AirportBoundsTest.cpp Normal file
View File

@ -0,0 +1,246 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "config/AirportBounds.h"
#include "utils/Logger.h"
#include <cmath>
#include <fstream>
#include <filesystem>
// 测试用的 AirportBounds 类,暴露 protected 成员以便测试
class TestAirportBounds : public AirportBounds {
public:
using AirportBounds::AirportBounds;
using AirportBounds::toAirportCoordinate;
using AirportBounds::airportBounds_;
using AirportBounds::areaBounds_;
using AirportBounds::referencePoint_;
using AirportBounds::rotationAngle_;
// 设置测试数据
void setTestData(const Vector2D& refPoint, double angle, const Bounds& bounds) {
referencePoint_ = refPoint;
rotationAngle_ = angle * M_PI / 180.0; // 转换为弧度
airportBounds_ = bounds;
}
};
class AirportBoundsTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置日志级别
Logger::setLogLevel(LogLevel::DEBUG);
// 创建测试实例
bounds_ = std::make_unique<TestAirportBounds>();
// 设置基本测试数据
// 参考点设为原点 (0, 0)
// 旋转角度设为 90 度,便于验证
bounds_->setTestData(
Vector2D{ 0, 0 }, // 参考点
90.0, // 旋转角度(度)
Bounds(-2000, -2000, 4000, 4000) // 4km x 4km 的区域
);
}
// 辅助函数:检查两个点是否足够接近(考虑浮点误差)
bool pointsAreClose(const Vector2D& p1, const Vector2D& p2, double tolerance = 0.1) {
return std::abs(p1.x - p2.x) < tolerance && std::abs(p1.y - p2.y) < tolerance;
}
std::unique_ptr<TestAirportBounds> bounds_;
};
// 测试坐标转换 - 参考点
TEST_F(AirportBoundsTest, ReferencePointTransformation) {
// 参考点应该转换为原点 (0,0)
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 0, 0 });
EXPECT_TRUE(pointsAreClose(result, Vector2D{ 0, 0 }))
<< "参考点转换结果: (" << result.x << ", " << result.y << ")";
}
// 测试坐标转换 - 基本位移90度逆时针旋转
TEST_F(AirportBoundsTest, BasicTranslation) {
// 向右移动 100 米,逆时针旋转 90 度后,应该在 y 轴正方向
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 100, 0 });
EXPECT_NEAR(result.x, 0, 0.001);
EXPECT_NEAR(result.y, 100, 0.001)
<< "向右100米的点逆时针旋转90度后应该在 (0, 100),实际在: ("
<< result.x << ", " << result.y << ")";
// 向上移动 100 米,逆时针旋转 90 度后,应该在 x 轴负方向
result = bounds_->toAirportCoordinate(Vector2D{ 0, 100 });
EXPECT_NEAR(result.x, -100, 0.001);
EXPECT_NEAR(result.y, 0, 0.001)
<< "向上100米的点逆时针旋转90度后应该在 (-100, 0),实际在: ("
<< result.x << ", " << result.y << ")";
}
// 测试坐标转换 - 复合变换90度逆时针旋转
TEST_F(AirportBoundsTest, CompositeTransformation) {
// 向右上方移动 (100, 100),逆时针旋转 90 度
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 100, 100 });
EXPECT_NEAR(result.x, -100, 0.001);
EXPECT_NEAR(result.y, 100, 0.001)
<< "点(100,100)逆时针旋转90度后应该在 (-100, 100),实际在: ("
<< result.x << ", " << result.y << ")";
}
// 测试边界检查 - 边界内的点
TEST_F(AirportBoundsTest, PointInBounds) {
// 测试参考点(一定在边界内)
EXPECT_TRUE(bounds_->isPointInBounds(Vector2D{ 0, 0 }))
<< "参考点应该在边界内";
// 测试边界内的点
EXPECT_TRUE(bounds_->isPointInBounds(Vector2D{ 1000, 1000 }))
<< "距离参考点1km的点应该在边界内";
}
// 测试边界检查 - 边界外的点
TEST_F(AirportBoundsTest, PointOutOfBounds) {
// 测试边界外的点
EXPECT_FALSE(bounds_->isPointInBounds(Vector2D{ 3000, 3000 }))
<< "距离参考点3km的点应该在边界外";
// 测试另一个边界外的点
EXPECT_FALSE(bounds_->isPointInBounds(Vector2D{ -2500, -2500 }))
<< "距离参考点2.5km的点应该在边界外";
}
// 测试不同旋转角度45度逆时针旋转
TEST_F(AirportBoundsTest, DifferentRotations) {
// 重新设置为45度逆时针旋转
bounds_->setTestData(
Vector2D{ 0, 0 }, // 参考点
45.0, // 旋转角度(度)
Bounds(-2000, -2000, 4000, 4000)
);
// 向右移动 100 米,逆时针旋转 45 度
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 100, 0 });
double sqrt2_2 = std::sqrt(2.0) / 2.0; // cos(45°) = sin(45°) = √2/2
EXPECT_NEAR(result.x, 100 * sqrt2_2, 0.001);
EXPECT_NEAR(result.y, 100 * sqrt2_2, 0.001)
<< "向右100米的点逆时针旋转45度后应该在 (70.71, 70.71),实际在: ("
<< result.x << ", " << result.y << ")";
// 向上移动 100 米,逆时针旋转 45 度
result = bounds_->toAirportCoordinate(Vector2D{ 0, 100 });
EXPECT_NEAR(result.x, -100 * sqrt2_2, 0.001);
EXPECT_NEAR(result.y, 100 * sqrt2_2, 0.001)
<< "向上100米的点逆时针旋转45度后应该在 (-70.71, 70.71),实际在: ("
<< result.x << ", " << result.y << ")";
}
// 测试旋转变换30度逆时针旋转
TEST_F(AirportBoundsTest, RotationTransformation) {
// 重新设置为30度逆时针旋转
bounds_->setTestData(
Vector2D{ 0, 0 }, // 参考点
30.0, // 旋转角度(度)
Bounds(-2000, -2000, 4000, 4000)
);
// 在世界坐标系中向右移动1000米逆时针旋转 30 度
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 1000, 0 });
double cos30 = std::cos(M_PI / 6); // cos(30°) ≈ 0.866025
double sin30 = std::sin(M_PI / 6); // sin(30°) ≈ 0.5
// 在机场坐标系中的期望结果:
// x = 1000 * cos(30°) ≈ 866.025
// y = 1000 * sin(30°) ≈ 500
EXPECT_NEAR(result.x, 1000 * cos30, 0.1);
EXPECT_NEAR(result.y, 1000 * sin30, 0.1)
<< "向右1000米的点逆时针旋转30度后应该在 (866.025, 500),实际在: ("
<< result.x << ", " << result.y << ")";
// 在世界坐标系中向上移动1000米逆时针旋转 30 度
result = bounds_->toAirportCoordinate(Vector2D{ 0, 1000 });
// 在机场坐标系中的期望结果:
// x = -1000 * sin(30°) ≈ -500
// y = 1000 * cos(30°) ≈ 866.025
EXPECT_NEAR(result.x, -1000 * sin30, 0.1);
EXPECT_NEAR(result.y, 1000 * cos30, 0.1)
<< "向上1000米的点逆时针旋转30度后应该在 (-500, 866.025),实际在: ("
<< result.x << ", " << result.y << ")";
}
// 测试区域检查
TEST_F(AirportBoundsTest, AreaCheck) {
// 重新设置为0度旋转便于测试
bounds_->setTestData(
Vector2D{ 0, 0 }, // 参考点
0.0, // 旋转角度(度)
Bounds(-2000, -2000, 4000, 4000)
);
// 设置测试区域
bounds_->areaBounds_[AreaType::RUNWAY] = Bounds(-1000, -100, 2000, 200);
bounds_->areaBounds_[AreaType::TAXIWAY] = Bounds(-800, -300, 1600, 600);
// 测试跑道上的点
Vector2D runwayPoint{ 500, 0 }; // 跑道中心偏右500米
EXPECT_TRUE(bounds_->isPointInArea(runwayPoint, AreaType::RUNWAY))
<< "跑道上的点应该在跑道区域内";
// 测试滑行道上的点
Vector2D taxiwayPoint{ 0, 200 }; // 滑行道中心向上200米
EXPECT_TRUE(bounds_->isPointInArea(taxiwayPoint, AreaType::TAXIWAY))
<< "滑行道上的点应该在滑行道区域内";
// 测试区域外的点
Vector2D outPoint{ 3000, 3000 }; // 远离机场的点
EXPECT_FALSE(bounds_->isPointInArea(outPoint, AreaType::RUNWAY))
<< "远离机场的点不应该在任何区域内";
EXPECT_FALSE(bounds_->isPointInArea(outPoint, AreaType::TAXIWAY))
<< "远离机场的点不应该在任何区域内";
}
// 测试配置文件加载
TEST_F(AirportBoundsTest, ConfigLoading) {
// 使用项目中的配置文件
AirportBounds bounds("config/airport_bounds.json");
// 验证配置是否正确加载
const auto& airportBounds = bounds.getAirportBounds();
EXPECT_TRUE(airportBounds.width > 0 && airportBounds.height > 0)
<< "机场边界的宽度和高度应该为正值";
// 验证区域配置
const auto& runwayBounds = bounds.getAreaBounds(AreaType::RUNWAY);
EXPECT_TRUE(runwayBounds.width > 0 && runwayBounds.height > 0)
<< "跑道区域的宽度和高度应该为正值";
EXPECT_TRUE(runwayBounds.width < airportBounds.width && runwayBounds.height < airportBounds.height)
<< "跑道区域应该小于机场边界";
}
// 测试配置文件错误处理
TEST_F(AirportBoundsTest, ConfigErrorHandling) {
// 测试文件不存在
EXPECT_THROW(AirportBounds("nonexistent.json"), std::runtime_error);
// 测试配置文件格式错误
EXPECT_THROW(AirportBounds("tests/data/invalid_airport_bounds.json"), std::runtime_error);
}
// 测试边界条件
TEST_F(AirportBoundsTest, EdgeCases) {
// 重新设置为0度旋转便于测试
bounds_->setTestData(
Vector2D{ 0, 0 }, // 参考点
0.0, // 旋转角度(度)
Bounds(-2000, -2000, 4000, 4000)
);
// 测试正好在边界上的点
Vector2D edge{ 2000, 2000 }; // 右上角边界点
EXPECT_TRUE(bounds_->isPointInBounds(edge))
<< "边界上的点应该被认为在边界内";
// 测试边界外的点
Vector2D outside{ 2001, 2001 }; // 刚好超出边界
EXPECT_FALSE(bounds_->isPointInBounds(outside))
<< "边界外的点应该被认为在边界外";
}

View File

@ -0,0 +1,539 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <memory>
#include "detector/CollisionDetector.h"
#include "vehicle/ControllableVehicles.h"
#include "utils/Logger.h"
#include "config/SystemConfig.h"
#include "config/AirportBounds.h"
// Mock ControllableVehicles 类
class MockControllableVehicles {
public:
MockControllableVehicles() = default;
MOCK_METHOD(bool, isControllable, (const std::string& vehicleNo), (const));
MOCK_METHOD(const ControllableVehicleConfig*, findVehicle, (const std::string& vehicleNo), (const));
MOCK_METHOD(void, sendCommand, (const std::string& vehicleNo, const VehicleCommand& cmd));
// 转换为 ControllableVehicles& 的操作符
operator const ControllableVehicles&() const {
return ControllableVehicles::getInstance();
}
};
// Mock AirportBounds 类
class MockAirportBounds : public AirportBounds {
public:
MockAirportBounds() : AirportBounds("") {
// 设置测试区域边界
airportBounds_ = Bounds(0, 0, 5000, 4000);
areaBounds_[AreaType::TEST_ZONE] = airportBounds_;
// 设置测试区域配置
AreaConfig config;
config.collision_radius = {50.0, 50.0, 25.0}; // aircraft, special, unmanned
config.height_threshold = 10.0;
config.warning_zone_radius = {100.0, 100.0, 50.0};
config.alert_zone_radius = {50.0, 50.0, 25.0};
areaConfigs_[AreaType::TEST_ZONE] = config;
// 设置系统配置
auto& system_config = SystemConfig::instance();
system_config.collision_detection.prediction.time_window = 30.0; // 设置预测时间窗口为30秒
}
MOCK_METHOD(AreaType, getAreaType, (const Vector2D& position), (const));
const AreaConfig& getAreaConfig(AreaType type) const override {
auto it = areaConfigs_.find(type);
if (it == areaConfigs_.end()) {
throw std::runtime_error("Invalid area type");
}
return it->second;
}
};
class BasicCollisionTest : public ::testing::Test {
protected:
void SetUp() override {
Logger::setLogLevel(LogLevel::DEBUG); // 设置日志级别为 DEBUG
airportBounds_ = std::make_unique<MockAirportBounds>();
mockControllableVehicles_ = std::make_unique<MockControllableVehicles>();
// 设置 Mock 对象的行为
EXPECT_CALL(*airportBounds_, getAreaType(::testing::_))
.WillRepeatedly(::testing::Return(AreaType::TEST_ZONE));
// 设置 Mock ControllableVehicles 的行为
EXPECT_CALL(*mockControllableVehicles_, isControllable(::testing::_))
.WillRepeatedly(::testing::Return(true));
detector_ = std::make_unique<CollisionDetector>(*airportBounds_, *mockControllableVehicles_);
}
std::unique_ptr<MockAirportBounds> airportBounds_;
std::unique_ptr<MockControllableVehicles> mockControllableVehicles_;
std::unique_ptr<CollisionDetector> detector_;
};
// 1. 静态碰撞测试
TEST_F(BasicCollisionTest, StaticCollision) {
// 创建两个静止且重叠的物体
Vehicle v1;
v1.vehicleNo = "V1";
v1.position = {100.0, 100.0};
v1.speed = 0.0;
v1.heading = 0.0;
v1.type = MovingObjectType::UNMANNED;
Vehicle v2;
v2.vehicleNo = "V2";
v2.position = {120.0, 100.0}; // 距离20米小于碰撞半径25米
v2.speed = 0.0;
v2.heading = 0.0;
v2.type = MovingObjectType::UNMANNED;
auto result = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(result.willCollide) << "距离小于碰撞半径的静止物体应该检测为碰撞";
EXPECT_DOUBLE_EQ(result.timeToCollision, 0.0) << "静止物体的碰撞时间应该为0";
EXPECT_EQ(result.type, CollisionType::STATIC) << "应该识别为静态碰撞";
// 增加更多验证
EXPECT_DOUBLE_EQ(result.minDistance, 20.0) << "最小距离应该是当前距离20米";
EXPECT_DOUBLE_EQ(result.timeToMinDistance, 0.0) << "静止物体最小距离时间应该为0";
EXPECT_NEAR(result.collisionPoint.x, 110.0, 0.1) << "碰撞点应该在两车中点";
EXPECT_NEAR(result.collisionPoint.y, 100.0, 0.1) << "碰撞点应该在同一水平线上";
// 验证物体状态
EXPECT_NEAR(result.object1State.position.x, 100.0, 0.1);
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
EXPECT_DOUBLE_EQ(result.object1State.speed, 0.0);
EXPECT_DOUBLE_EQ(result.object1State.heading, 0.0);
EXPECT_NEAR(result.object2State.position.x, 120.0, 0.1);
EXPECT_NEAR(result.object2State.position.y, 100.0, 0.1);
EXPECT_DOUBLE_EQ(result.object2State.speed, 0.0);
EXPECT_DOUBLE_EQ(result.object2State.heading, 0.0);
}
// 2. 相向碰撞测试
TEST_F(BasicCollisionTest, HeadOnCollision) {
// 创建两个相向运动的物体
Vehicle v1;
v1.vehicleNo = "V1";
v1.position = {100.0, 100.0};
v1.speed = 10.0;
v1.heading = 90.0; // 向东
v1.type = MovingObjectType::UNMANNED;
Vehicle v2;
v2.vehicleNo = "V2";
v2.position = {200.0, 100.0}; // 在v1东边100米
v2.speed = 10.0;
v2.heading = 270.0; // 向西
v2.type = MovingObjectType::UNMANNED;
auto result = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(result.willCollide) << "相向运动的物体应该检测为碰撞";
EXPECT_NEAR(result.timeToCollision, 2.5, 0.1) << "碰撞时间应该接近2.5秒((100-50)米/20米每秒)";
EXPECT_EQ(result.type, CollisionType::HEAD_ON) << "应该识别为相向碰撞";
EXPECT_NEAR(result.collisionPoint.x, 150.0, 0.1) << "碰撞点应该在两车中点处(100+200)/2=150";
EXPECT_NEAR(result.collisionPoint.y, 100.0, 0.1) << "碰撞点应该在同一水平线上";
// 增加更多验证
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离该是碰撞半径之和50米";
EXPECT_NEAR(result.timeToMinDistance, 2.5, 0.1) << "最小距离时间应该等于碰撞时间";
// 验证物体状态
EXPECT_NEAR(result.object1State.position.x, 125.0, 0.1);
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
EXPECT_DOUBLE_EQ(result.object1State.speed, 10.0);
EXPECT_DOUBLE_EQ(result.object1State.heading, 90.0);
EXPECT_NEAR(result.object2State.position.x, 175.0, 0.1);
EXPECT_NEAR(result.object2State.position.y, 100.0, 0.1);
EXPECT_DOUBLE_EQ(result.object2State.speed, 10.0);
EXPECT_DOUBLE_EQ(result.object2State.heading, 270.0);
}
// 3. 平行运动测试
TEST_F(BasicCollisionTest, ParallelMotion) {
// 创建两个平行运动的物体
Vehicle v1;
v1.vehicleNo = "V1";
v1.position = {100.0, 100.0};
v1.speed = 10.0;
v1.heading = 90.0; // 向东
v1.type = MovingObjectType::UNMANNED;
Vehicle v2;
v2.vehicleNo = "V2";
v2.position = {100.0, 150.0}; // 在v1北边50米大于碰撞半径
v2.speed = 10.0;
v2.heading = 90.0; // 向东
v2.type = MovingObjectType::UNMANNED;
auto result = detector_->checkCollision(v1, v2, 30.0);
EXPECT_FALSE(result.willCollide) << "平行运动且距离大于碰撞半径的物体不应该检测为碰撞";
EXPECT_EQ(result.type, CollisionType::PARALLEL) << "应该识别为平行运动";
// 增加更多验证
EXPECT_DOUBLE_EQ(result.minDistance, 50.0) << "最小距离应该是初始距离50米";
EXPECT_DOUBLE_EQ(result.timeToMinDistance, 0.0) << "平行运动的最小距离时间应该为0";
EXPECT_DOUBLE_EQ(result.timeToCollision, std::numeric_limits<double>::infinity()) << "平行运动不会碰撞";
// 不验证碰撞点,因为不会发生碰撞
}
// 4. 交叉路径测试
TEST_F(BasicCollisionTest, CrossingPaths) {
// 创建两个倾斜交叉运动的物体
Vehicle v1;
v1.vehicleNo = "V1";
v1.position = {100.0, 100.0};
v1.speed = 15.0;
v1.heading = 75.0; // 向东北偏东方向运动
v1.type = MovingObjectType::UNMANNED;
Vehicle v2;
v2.vehicleNo = "V2";
v2.position = {150.0, 150.0};
v2.speed = 10.0;
v2.heading = 105.0; // 向东南偏东方向运动,与 v1 夹角为 30 度
v2.type = MovingObjectType::UNMANNED;
// 添加航向角度差的调试日志
double angle_diff = std::abs(v1.heading - v2.heading);
Logger::debug("航向角度差检查:");
Logger::debug("v1 航向: " + std::to_string(v1.heading));
Logger::debug("v2 航向: " + std::to_string(v2.heading));
Logger::debug("航向角度差: " + std::to_string(angle_diff));
auto result = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(result.willCollide) << "交叉路径的物体应该检测为碰撞";
EXPECT_EQ(result.type, CollisionType::CROSSING) << "应该识别为交叉碰撞";
// 计算碰撞时间和位置:
// v1: 速度分量 (3.882, 14.489) m/s // 15 m/s * (cos(15°), sin(15°))
// v2: 速度分量 (-2.588, 9.659) m/s // 10 m/s * (cos(75°), sin(75°))
// 相对速度: (-6.47, -4.83) m/s
// 相对速度大小: 8.06 m/s
// 碰撞点在两车位置的中点
double collision_time = 2.07; // 根据实际计算得到
Vector2D collision_point = {126.34, 150.006}; // 两车位置的中点
EXPECT_NEAR(result.timeToCollision, collision_time, 0.1) << "考虑碰撞半径25米碰撞时间应该接近2.07秒";
EXPECT_NEAR(result.collisionPoint.x, collision_point.x, 0.1) << "碰撞点x坐标应该在126.34";
EXPECT_NEAR(result.collisionPoint.y, collision_point.y, 0.1) << "碰撞点y坐标应该在150.006";
// 增加更多验证
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离应该是碰撞半径之和50米";
EXPECT_NEAR(result.timeToMinDistance, collision_time, 0.1) << "最小距离时间应该等于碰撞时间";
// 验证物体状态
EXPECT_NEAR(result.object1State.position.x, 108.04, 0.1);
EXPECT_NEAR(result.object1State.position.y, 130.007, 0.1);
EXPECT_DOUBLE_EQ(result.object1State.speed, 15.0);
EXPECT_DOUBLE_EQ(result.object1State.heading, 75.0);
EXPECT_NEAR(result.object2State.position.x, 144.64, 0.1);
EXPECT_NEAR(result.object2State.position.y, 170.005, 0.1);
EXPECT_DOUBLE_EQ(result.object2State.speed, 10.0);
EXPECT_DOUBLE_EQ(result.object2State.heading, 105.0);
}
// 5. 垂直交叉路径测试
TEST_F(BasicCollisionTest, PerpendicularCrossingPaths) {
// 创建两个垂直交叉运动的物体
Vehicle v1;
v1.vehicleNo = "V1";
v1.position = {100.0, 100.0};
v1.speed = 10.0;
v1.heading = 90.0; // 向东运动
v1.type = MovingObjectType::UNMANNED;
Vehicle v2;
v2.vehicleNo = "V2";
v2.position = {150.0, 150.0};
v2.speed = 10.0;
v2.heading = 180.0; // 向南运动,与 v1 夹角为 90 度
v2.type = MovingObjectType::UNMANNED;
auto result = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(result.willCollide) << "垂直交叉路径的物体应该检测为碰撞";
EXPECT_EQ(result.type, CollisionType::CROSSING) << "应该识别为交叉碰撞";
// 计算碰撞时间和位置:
// v1: 速度分量 (10.0, 0.0) m/s
// v2: 速度分量 (0.0, -10.0) m/s
// 相对速度: (-10.0, -10.0) m/s
// 相对速度大小: 14.14 m/s
// 当前距离小于安全距离时,立即判定为碰撞
double collision_time = 0.0; // 当前距离小于安全距离,立即碰撞
Vector2D collision_point = {125.0, 125.0}; // 两车当前位置的中点
EXPECT_NEAR(result.timeToCollision, collision_time, 0.1) << "当前距离小于安全距离碰撞时间应该为0";
EXPECT_NEAR(result.collisionPoint.x, collision_point.x, 0.1) << "碰撞点x坐标应该在125.0";
EXPECT_NEAR(result.collisionPoint.y, collision_point.y, 0.1) << "碰撞点y坐标应该在125.0";
// 增加更多验证
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离应该是碰撞半径之和50米";
EXPECT_NEAR(result.timeToMinDistance, collision_time, 0.1) << "最小距离时间应该等于碰撞时间";
// 验证物体状态
EXPECT_NEAR(result.object1State.position.x, 100.0, 0.1);
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
EXPECT_DOUBLE_EQ(result.object1State.speed, 10.0);
EXPECT_DOUBLE_EQ(result.object1State.heading, 90.0);
EXPECT_NEAR(result.object2State.position.x, 150.0, 0.1);
EXPECT_NEAR(result.object2State.position.y, 150.0, 0.1);
EXPECT_DOUBLE_EQ(result.object2State.speed, 10.0);
EXPECT_DOUBLE_EQ(result.object2State.heading, 180.0);
}
TEST_F(BasicCollisionTest, DivergentMotion) {
// 设置两个物体背向运动的场景
// 物体1在(150,100)向右运动航向90度
// 物体2在(200,100)向右运动航向90度
// 两物体都在远离对方,不应该发生碰撞
Vehicle obj1;
obj1.vehicleNo = "V1";
obj1.position = {150, 100};
obj1.speed = 10;
obj1.heading = 90; // 右运动
obj1.type = MovingObjectType::UNMANNED;
Vehicle obj2;
obj2.vehicleNo = "V2";
obj2.position = {300, 100}; // 增加初始距离到 150m
obj2.speed = 10;
obj2.heading = 90; // 也向右运动
obj2.type = MovingObjectType::SPECIAL;
auto result = detector_->checkCollision(obj1, obj2, 10.0);
// 验证结果
EXPECT_FALSE(result.willCollide) << "同向运动且距离大于安全距离的物体不应该发生碰撞";
EXPECT_EQ(result.type, CollisionType::PARALLEL) << "应该识别为平行运动";
// 初始距离应该是 150m
EXPECT_NEAR(result.minDistance, 150.0, 0.1);
// 由于两车都以相同速度向右运动,最小距离应该保持不变
EXPECT_NEAR(result.timeToMinDistance, 0.0, 0.1);
}
// 6. 追尾场景测试
TEST_F(BasicCollisionTest, TailgatingMotion) {
// 创建两个同向运动的物体,后车速度大于前车
Vehicle v1; // 前车
v1.vehicleNo = "V1";
v1.position = {60.0, 100.0}; // 前车在前方60米处
v1.speed = 10.0; // 前车速度10m/s
v1.heading = 90.0; // 向东运动
v1.type = MovingObjectType::UNMANNED;
Vehicle v2; // 后车
v2.vehicleNo = "V2";
v2.position = {0.0, 100.0}; // 后车在原点
v2.speed = 15.0; // 后车速度15m/s
v2.heading = 90.0; // 向东运动
v2.type = MovingObjectType::UNMANNED;
auto result = detector_->checkCollision(v1, v2, 30.0);
// 验证碰撞类型
EXPECT_EQ(result.type, CollisionType::PARALLEL) << "应该识别为平行运动";
// 验证会发生碰撞
EXPECT_TRUE(result.willCollide) << "后车速度大于前车,应该预测到碰撞";
// 验证碰撞时间初始距离60米相对速度5m/s安全距离50米需要缩短10米所以碰撞时间应该是2秒
EXPECT_NEAR(result.timeToCollision, 2.0, 0.1) << "碰撞时间应该接近2秒";
// 验证最小距离(应该是安全距离)
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离应该是安全距离50米";
// 验证最小距离时间(应该等于碰撞时间)
EXPECT_NEAR(result.timeToMinDistance, 2.0, 0.1) << "最小距离时间应该等于碰撞时间";
// 验证碰撞点(在两车碰撞时的中点)
// 前车:初始位置 60 + 10 * 2 = 80
// 后车:初始位置 0 + 15 * 2 = 30
// 碰撞点应该在 (80 + 30) / 2 = 55
EXPECT_NEAR(result.collisionPoint.x, 55.0, 0.1) << "碰撞点x坐标应该在55米处";
EXPECT_NEAR(result.collisionPoint.y, 100.0, 0.1) << "碰撞点y坐标应该保持在100米";
// 验证碰撞时刻的物体状态
// 前车位置60 + 10 * 2 = 80
EXPECT_NEAR(result.object1State.position.x, 80.0, 0.1);
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
EXPECT_DOUBLE_EQ(result.object1State.speed, 10.0);
EXPECT_DOUBLE_EQ(result.object1State.heading, 90.0);
// 后车位置0 + 15 * 2 = 30
EXPECT_NEAR(result.object2State.position.x, 30.0, 0.1);
EXPECT_NEAR(result.object2State.position.y, 100.0, 0.1);
EXPECT_DOUBLE_EQ(result.object2State.speed, 15.0);
EXPECT_DOUBLE_EQ(result.object2State.heading, 90.0);
}
// 6. QN002 与 AC001 交叉路径测试(实际场景)
TEST_F(BasicCollisionTest, QN002AircraftCrossing) {
// 创建 AC001 航空器,从 T7 出发
Aircraft aircraft;
aircraft.flightNo = "AC001";
aircraft.position = {0.0, 0.0}; // T7 点的相对坐标
aircraft.speed = 50.0 / 3.6; // 转换为米/秒
aircraft.heading = 45.0; // 根据 T7 到 T11 的方向计算
aircraft.type = MovingObjectType::AIRCRAFT;
// 创建 QN002 无人车,从 T4 到 T8 路径上的一点发
Vehicle qn002;
qn002.vehicleNo = "QN002";
qn002.position = {-2.0, 120.0}; // 在 T4-T8 路径上,距离 T7 约 120 米(小于检测范围 150 米)
qn002.speed = 36.0 / 3.6; // 转换为米/秒
qn002.heading = 135.0; // 根据 T4 到 T8 的方向计算
qn002.type = MovingObjectType::UNMANNED;
qn002.isControllable = true;
// 检测碰撞
auto result = detector_->checkCollision(aircraft, qn002, 30.0);
// 验证是否检测到碰撞
EXPECT_TRUE(result.willCollide) << "QN002 与 AC001 的交叉路径应该检测为碰撞";
EXPECT_EQ(result.type, CollisionType::CROSSING) << "应该识别为交叉碰撞";
// 记录详细的测试信息
Logger::debug("QN002-AC001 碰撞测试结果:");
Logger::debug("碰撞类型: ", static_cast<int>(result.type));
Logger::debug("最小距离: ", result.minDistance, "m");
Logger::debug("碰撞时间: ", result.timeToCollision, "s");
Logger::debug("碰撞点: (", result.collisionPoint.x, ",", result.collisionPoint.y, ")");
// 验证碰撞参数
EXPECT_GT(result.timeToCollision, 0.0) << "碰撞时间应该大于0";
EXPECT_LT(result.timeToCollision, 30.0) << "碰撞时间应该在预测窗口内";
EXPECT_LE(result.minDistance, 75.0) << "小距离应该小于预警距离";
// 验证物体状态
EXPECT_DOUBLE_EQ(result.object1State.speed, 50.0 / 3.6);
EXPECT_DOUBLE_EQ(result.object1State.heading, 45.0);
EXPECT_DOUBLE_EQ(result.object2State.speed, 36.0 / 3.6);
EXPECT_DOUBLE_EQ(result.object2State.heading, 135.0);
}
TEST_F(BasicCollisionTest, CrossingBeforeAndAfter) {
// 设置 Mock 对象的预期行为
EXPECT_CALL(*mockControllableVehicles_, isControllable(::testing::StrEq("QN002")))
.WillRepeatedly(::testing::Return(true));
// 创建一个航空器和一个无人车
Aircraft aircraft;
aircraft.id = "AC001";
aircraft.type = MovingObjectType::AIRCRAFT;
aircraft.speed = 10.0; // 10米/秒
aircraft.heading = 90.0; // 向东
Vehicle vehicle;
vehicle.id = "QN002";
vehicle.vehicleNo = "QN002";
vehicle.type = MovingObjectType::UNMANNED;
vehicle.isControllable = true;
vehicle.speed = 10.0; // 10米/秒
vehicle.heading = 0.0; // 向北
// 设置初始位置:航空器在交叉点西侧,无人车在南侧
Vector2D crossPoint{300.0, 300.0}; // 交叉点坐标
// 1. 测试交叉前的情况
// 航空器在交叉点西侧,无人车在南侧
aircraft.position = {crossPoint.x - 80.0, crossPoint.y}; // 航空器在交叉点西侧80
vehicle.position = {crossPoint.x, crossPoint.y - 80.0}; // 无人车在交叉点南侧80米
// 更新交通数据
std::vector<Aircraft> aircrafts = {aircraft};
std::vector<Vehicle> vehicles = {vehicle};
detector_->updateTraffic(aircrafts, vehicles);
// 检测碰撞使用30秒的预测窗口
auto risks = detector_->detectCollisions();
// 记录详细的测试信息
Logger::debug("交叉前碰撞检测:");
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
Logger::debug("交叉点位置: (", crossPoint.x, ",", crossPoint.y, ")");
ASSERT_FALSE(risks.empty()) << "交叉前应该检测到碰撞风险";
EXPECT_EQ(risks[0].level, RiskLevel::WARNING) << "交叉前应该是预警级别";
// 2. 测试交叉过程中
// 航空器在交叉点西侧20米无人车在南侧20米
aircraft.position = {crossPoint.x - 20.0, crossPoint.y};
vehicle.position = {crossPoint.x, crossPoint.y - 20.0};
// 更新交通数据
aircrafts = {aircraft};
vehicles = {vehicle};
detector_->updateTraffic(aircrafts, vehicles);
// 检测碰撞
risks = detector_->detectCollisions();
// 记录详细的测试信息
Logger::debug("交叉过程碰撞检测:");
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
ASSERT_FALSE(risks.empty()) << "交叉过程中应该检测到碰撞风险";
EXPECT_EQ(risks[0].level, RiskLevel::CRITICAL) << "交叉过程中应该是告警级别";
// 3. 测试交叉后的情况
// 航空器已经通过交叉点并远离安全距离,无人车还在交叉点附近
aircraft.position = {crossPoint.x + 160.0, crossPoint.y}; // 航空器在交叉点东侧160米大于安全距离150米
vehicle.position = {crossPoint.x, crossPoint.y - 10.0}; // 无人车还在交叉点附近
// 更新交通数据
aircrafts = {aircraft};
vehicles = {vehicle};
detector_->updateTraffic(aircrafts, vehicles);
// 检测碰撞
risks = detector_->detectCollisions();
// 记录详细的测试信息
Logger::debug("交叉后碰撞检测:");
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
EXPECT_TRUE(risks.empty()) << "航空器通过远离安全距离后,应该解除冲突";
// 4. 测试无人车继续运动
// 无人车向北移动到交叉点
vehicle.position = {crossPoint.x, crossPoint.y};
// 更新交通数据
vehicles = {vehicle};
detector_->updateTraffic(aircrafts, vehicles);
// 检测碰撞
risks = detector_->detectCollisions();
// 记录详细的测试信息
Logger::debug("无人车继续运动碰撞检测:");
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
EXPECT_TRUE(risks.empty()) << "航空器已远离,无人车继续运动不应产生新的冲突";
}

128
tests/BasicTypesTest.cpp Normal file
View File

@ -0,0 +1,128 @@
#include <gtest/gtest.h>
#include "types/BasicTypes.h"
#include <ctime>
class BasicTypesTest : public ::testing::Test {
protected:
void SetUp() override {
// 测试设置
}
};
// 测试 Vector2D
TEST_F(BasicTypesTest, Vector2DMagnitude) {
Vector2D v(3.0, 4.0);
EXPECT_DOUBLE_EQ(v.magnitude(), 5.0);
}
TEST_F(BasicTypesTest, Vector2DDirection) {
Vector2D v(1.0, 1.0); // 45度角
EXPECT_NEAR(v.direction(), 45.0, 0.001);
}
// 测试 MovingObject 的速度计算
class TestMovingObject : public MovingObject {
protected:
bool isValidPosition(const GeoPosition& newPos) const override {
return true;
}
bool isValidSpeed(double speed) const override {
return speed <= 100.0;
}
double getSpeedSmoothingFactor() const override {
return 0.3;
}
};
TEST_F(BasicTypesTest, MovingObjectSpeedCalculation) {
TestMovingObject obj;
// 使用毫秒级时间戳
uint64_t now = 1000000;
// 第一个位置
GeoPosition pos1{36.0, 120.0};
obj.updateMotion(pos1, now);
EXPECT_DOUBLE_EQ(obj.speed, 0.0); // 第一个点速度应该为0
// 第二个位置向东移动约90米/秒)
GeoPosition pos2{36.0, 120.001};
double distance = obj.calculateDistance(pos1, pos2);
printf("Distance: %.2f meters\n", distance);
obj.updateMotion(pos2, now + 1000); // 1000毫秒后
printf("Speed: %.2f m/s\n", obj.speed);
EXPECT_NEAR(obj.speed, 90.0, 2.0); // 使用 Haversine 公式计算的实际距离
// 检查航向角
EXPECT_NEAR(obj.heading, 90.0, 0.1); // 应该朝向正东
}
// 测试 Aircraft
TEST_F(BasicTypesTest, AircraftValidation) {
Aircraft aircraft;
aircraft.flightNo = "CES2501";
// 使用毫秒级时间戳
uint64_t now = 1000000;
// 测试位置更新
GeoPosition pos1{36.0, 120.0};
aircraft.updateMotion(pos1, now);
GeoPosition pos2{36.0, 120.001}; // 约90米
aircraft.updateMotion(pos2, now + 2000); // 2000毫秒后
// 速度应该约为45米/秒
EXPECT_NEAR(aircraft.speed, 45.0, 2.0);
}
// 测试 Vehicle
TEST_F(BasicTypesTest, VehicleValidation) {
Vehicle vehicle;
vehicle.vehicleNo = "VEH001";
// 使用毫秒级时间戳
uint64_t now = 1000000;
// 测试位置更新
GeoPosition pos1{36.0, 120.0};
vehicle.updateMotion(pos1, now);
// 使用约9米的位置变化小于10米的跳变限制
GeoPosition pos2{36.00008, 120.0}; // 约9米
double distance = vehicle.calculateDistance(pos1, pos2);
printf("Vehicle distance: %.2f meters\n", distance);
vehicle.updateMotion(pos2, now + 1000); // 1000毫秒后
printf("Vehicle speed: %.2f m/s\n", vehicle.speed);
// 速度应该约为9米/秒小于20米/秒的限制)
EXPECT_NEAR(vehicle.speed, 9.0, 1.0);
}
// 添加边界条件测试
TEST_F(BasicTypesTest, VectorZeroMagnitude) {
Vector2D v(0.0, 0.0);
EXPECT_DOUBLE_EQ(v.magnitude(), 0.0);
}
// 测试距离计算
TEST_F(BasicTypesTest, DistanceCalculation) {
TestMovingObject obj;
// 测试经度方向的距离约90米
GeoPosition pos1{36.0, 120.0};
GeoPosition pos2{36.0, 120.001};
double distance1 = obj.calculateDistance(pos1, pos2);
EXPECT_NEAR(distance1, 90.0, 1.0);
// 测试纬度方向的距离约22米
GeoPosition pos3{36.0, 120.0};
GeoPosition pos4{36.0002, 120.0};
double distance2 = obj.calculateDistance(pos3, pos4);
EXPECT_NEAR(distance2, 22.0, 1.0);
}

26
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,26 @@
#
set(TEST_SOURCES
AirportBoundsTest.cpp
BasicCollisionTest.cpp
CollisionDetectorTest.cpp
TrafficLightDetectorTest.cpp
SafetyZoneTest.cpp
BasicTypesTest.cpp
HTTPDataSourceTest.cpp
DataCollectorTest.cpp
)
#
add_executable(unit_tests ${TEST_SOURCES})
#
target_link_libraries(unit_tests
PRIVATE
${PROJECT_NAME}_lib
GTest::gtest_main
GTest::gmock_main
)
#
include(GoogleTest)
gtest_discover_tests(unit_tests)

View File

@ -0,0 +1,322 @@
#include "detector/CollisionDetector.h"
#include "vehicle/ControllableVehicles.h"
#include "config/AirportBounds.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "utils/Logger.h"
#include <chrono>
#include <filesystem>
// Mock ControllableVehicles 类
class MockControllableVehicles {
public:
MockControllableVehicles() = default;
MOCK_METHOD(bool, isControllable, (const std::string& vehicleNo), (const));
MOCK_METHOD(const ControllableVehicleConfig*, findVehicle, (const std::string& vehicleNo), (const));
MOCK_METHOD(void, sendCommand, (const std::string& vehicleNo, const VehicleCommand& cmd));
// 转换为 ControllableVehicles& 的操作符
operator const ControllableVehicles&() const {
return ControllableVehicles::getInstance();
}
};
// Mock AirportBounds 类
class MockAirportBounds : public AirportBounds {
public:
MockAirportBounds() : AirportBounds("") {
// 设置测试区域边界
airportBounds_ = Bounds(0, 0, 5000, 4000);
areaBounds_[AreaType::TEST_ZONE] = airportBounds_;
// 设置测试区域配置
AreaConfig config;
config.collision_radius = {50.0, 50.0, 25.0}; // aircraft, special, unmanned
config.height_threshold = 10.0;
config.warning_zone_radius = {100.0, 100.0, 50.0};
config.alert_zone_radius = {50.0, 50.0, 25.0};
areaConfigs_[AreaType::TEST_ZONE] = config;
}
MOCK_CONST_METHOD1(getAreaType, AreaType(const Vector2D& position));
const AreaConfig& getAreaConfig(AreaType type) const override {
auto it = areaConfigs_.find(type);
if (it == areaConfigs_.end()) {
throw std::runtime_error("Invalid area type");
}
return it->second;
}
const Bounds& getAirportBounds() const override {
return airportBounds_;
}
};
class CollisionDetectorTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置日志级别为 DEBUG
Logger::initialize("", LogLevel::DEBUG);
// 打印当前工作目录
std::cout << "Current working directory: " << std::filesystem::current_path() << std::endl;
// 创建 Mock 对象
airportBounds_ = std::make_unique<MockAirportBounds>();
mockControllableVehicles_ = std::make_unique<MockControllableVehicles>();
// 设置 Mock 对象的行为
EXPECT_CALL(*airportBounds_, getAreaType(::testing::_))
.WillRepeatedly(::testing::Return(AreaType::TEST_ZONE));
// 创建冲突检测器
detector_ = std::make_unique<CollisionDetector>(*airportBounds_, *mockControllableVehicles_);
}
void TearDown() override {
Logger::initialize("", LogLevel::INFO);
}
std::unique_ptr<MockAirportBounds> airportBounds_;
std::unique_ptr<MockControllableVehicles> mockControllableVehicles_;
std::unique_ptr<CollisionDetector> detector_;
};
// 测试可控车辆(无人车)与航空器的碰撞检测
TEST_F(CollisionDetectorTest, UnmannedVehicleAircraftCollision) {
// 设置测试数据
Aircraft aircraft;
aircraft.flightNo = "TEST001";
aircraft.position = {100.0, 100.0};
aircraft.speed = 15.0;
aircraft.heading = 90.0; // 向东行驶
aircraft.type = MovingObjectType::AIRCRAFT;
Vehicle vehicle;
vehicle.vehicleNo = "VEH001";
vehicle.position = {120.0, 105.0}; // 在航空器前方偏北5米处
vehicle.speed = 8.0;
vehicle.heading = 270.0; // 向西行驶(与航空器相向而行)
vehicle.type = MovingObjectType::UNMANNED;
vehicle.isControllable = true;
// 测试1相向而行且距离较近
auto collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "相向而行且距离较近的航空器和无人车应该检测到碰撞";
// 测试2增加距离到边界值
vehicle.position = {150.0, 105.0}; // 增加到50米距离
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "在警告距离内应该检测到碰撞";
// 测试3超出碰撞检测范围
vehicle.position = {900.0, 100.0}; // 增加到800米距离
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "距离较远时不应该检测到碰撞";
}
// 测试无人车与特勤车的碰撞检测
TEST_F(CollisionDetectorTest, UnmannedVehicleSpecialVehicleCollision) {
Vehicle unmanned;
unmanned.vehicleNo = "VEH001";
unmanned.position = {100.0, 100.0};
unmanned.speed = 10.0;
unmanned.heading = 90.0; // 向东行驶
unmanned.type = MovingObjectType::UNMANNED;
unmanned.isControllable = true;
Vehicle special;
special.vehicleNo = "VEH002";
special.position = {120.0, 100.0}; // 距离20米
special.speed = 5.0;
special.heading = 270.0; // 向西行驶
special.type = MovingObjectType::SPECIAL;
special.isControllable = false;
// 测试1相向而行且距离较近
auto collisionResult = detector_->checkCollision(unmanned, special, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "相向而行且距离较近的无人车和特勤车应该检测到碰撞";
// 测试2在警告距离边界
special.position = {150.0, 100.0}; // 增加到50米距离
collisionResult = detector_->checkCollision(unmanned, special, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "在警告距离应该检测到碰撞";
// 测试3超出碰撞检测范围
special.position = {300.0, 100.0}; // 增加到200米距离
collisionResult = detector_->checkCollision(unmanned, special, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "超出警告距离时不应该检测到碰撞";
}
// 测试斜向叉路径碰撞
TEST_F(CollisionDetectorTest, DiagonalCrossingPathsCollision) {
Vehicle v1;
v1.vehicleNo = "VEH001";
v1.position = {100.0, 100.0};
v1.speed = 10.0;
v1.heading = 45.0; // 北行驶
v1.type = MovingObjectType::UNMANNED;
v1.isControllable = true;
Vehicle v2;
v2.vehicleNo = "VEH002";
v2.position = {150.0, 150.0};
v2.speed = 10.0;
v2.heading = 225.0; // 向西南行驶与v1相向
v2.type = MovingObjectType::UNMANNED;
v2.isControllable = true;
// 测试1路径交叉且距离较近
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "斜向相向而行且路径交叉的车辆应该检测到碰撞";
// 测试2不同速度比
v1.speed = 5.0; // 降低速度
v2.speed = 15.0; // 提高速度
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "不同速度比的斜向相向车辆应该检测到碰撞";
// 测试3超出碰撞检测范围
v2.position = {300.0, 300.0}; // 增加到较远距离
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "距离较远时不应该检测到碰撞";
}
// 测试垂直交叉路径碰撞
TEST_F(CollisionDetectorTest, PerpendicularCrossingPathsCollision) {
Vehicle v1;
v1.vehicleNo = "VEH001";
v1.position = {100.0, 100.0};
v1.speed = 10.0;
v1.heading = 90.0; // 向东行驶
v1.type = MovingObjectType::UNMANNED;
v1.isControllable = true;
Vehicle v2;
v2.vehicleNo = "VEH002";
v2.position = {120.0, 120.0}; // 在v1东北方20米处
v2.speed = 10.0;
v2.heading = 180.0; // 向南行驶
v2.type = MovingObjectType::UNMANNED;
v2.isControllable = true;
// 测试1垂直相交且距离较近
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "垂直交叉路径的车辆应该检测到碰撞";
// 测试2不同速度比
v1.speed = 5.0; // 降低速度
v2.speed = 15.0; // 提高速度
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "不同速度的垂直交叉路径车辆应该检测到碰撞";
// 测试3超出碰撞检测范围
v2.position = {200.0, 200.0}; // 增加到较远距离
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "距离较远时应该检测到碰撞";
}
// 测试静止辆的碰撞检测
TEST_F(CollisionDetectorTest, StationaryVehiclesCollision) {
Vehicle v1;
v1.vehicleNo = "VEH001";
v1.position = {100.0, 100.0};
v1.speed = 0.0; // 静止
v1.heading = 90.0;
v1.type = MovingObjectType::UNMANNED;
v1.isControllable = true;
Vehicle v2;
v2.vehicleNo = "VEH002";
v2.position = {105.0, 100.0}; // 距离5米
v2.speed = 0.0; // 静止
v2.heading = 90.0;
v2.type = MovingObjectType::UNMANNED;
v2.isControllable = true;
// 测试1两个静止车辆距离小于安全距离
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "距离小于安全距离的静止车辆应该检测到碰撞";
// 测试2一动一静移动车辆接近静止车辆
v2.position = {120.0, 100.0}; // 增加距离到20米
v2.speed = 5.0; // 开始移动
v2.heading = 270.0; // 向西行驶朝向v1
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "移动车辆接近静止车辆时应该检测到碰撞";
// 测试3一动一静移动车辆远离静止车辆
v2.position = {160.0, 100.0}; // 增加到60米大于安全距离(50米)
v2.speed = 5.0; // 开始移动
v2.heading = 90.0; // 向东行驶远离v1
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "移动车辆远离静止车辆且距离于安全距离时不应该检测到碰撞";
}
// 测试同向运动的碰撞检测
TEST_F(CollisionDetectorTest, TailgatingCollision) {
Vehicle v1;
v1.vehicleNo = "VEH001";
v1.position = {100.0, 100.0};
v1.speed = 10.0;
v1.heading = 90.0; // 向东行驶
v1.type = MovingObjectType::UNMANNED;
v1.isControllable = true;
Vehicle v2;
v2.vehicleNo = "VEH002";
v2.position = {40.0, 100.0}; // 在v1西侧60米处100-60=40
v2.speed = 10.0;
v2.heading = 90.0; // 也向东行驶
v2.type = MovingObjectType::UNMANNED;
v2.isControllable = true;
// 测试1同向同速
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "同向同速的车辆不应该检测到碰撞";
// 测试2同向不同速v2速度更快会追上v1
v2.speed = 15.0;
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "同向但速度较快的车辆追上前车时应该检测到碰撞";
// 测试3同向不同速但距离较远
v2.position = {0.0, 100.0}; // 在原点
v2.speed = 11.0; // 减小速度差,从 15m/s 改为 11m/s
collisionResult = detector_->checkCollision(v1, v2, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "相对速度小且距离较远时不应该检测到碰撞";
}
// 测试航空器与静止车辆的碰撞检测
TEST_F(CollisionDetectorTest, AircraftStationaryVehicleCollision) {
Aircraft aircraft;
aircraft.flightNo = "TEST001";
aircraft.position = {100.0, 100.0};
aircraft.speed = 15.0;
aircraft.heading = 90.0; // 向东行驶
aircraft.type = MovingObjectType::AIRCRAFT;
Vehicle vehicle;
vehicle.vehicleNo = "VEH001";
vehicle.position = {150.0, 100.0}; // 在航空器前方50米处
vehicle.speed = 0.0; // 静止
vehicle.heading = 90.0;
vehicle.type = MovingObjectType::UNMANNED;
vehicle.isControllable = true;
// 测试1航空器接近静止车辆
auto collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
EXPECT_TRUE(collisionResult.willCollide) << "航空器接近静止车辆时应该检测到碰撞";
// 测试2静止车辆在航空器航向偏离处
vehicle.position = {200.0, 200.0}; // 在航空器前方偏北距离约100米大于安全距离75米
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "航空器与不在航向上的静止车辆不应该检测到碰撞";
// 测试3静止车辆在航空器后方
vehicle.position = {0.0, 100.0}; // 在航空器后方100米大于安全距离75米
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
EXPECT_FALSE(collisionResult.willCollide) << "航空器与后方的静止车辆不应该检测到碰撞";
}

906
tests/DataCollectorTest.cpp Normal file
View File

@ -0,0 +1,906 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "collector/DataCollector.h"
#include "config/AirportBounds.h"
#include "spatial/CoordinateConverter.h"
#include "utils/Logger.h"
#include "network/MessageTypes.h"
#include "core/System.h"
// 创建一个 Mock DataSource 类
class MockDataSource : public DataSource {
public:
MOCK_METHOD0(connect, bool());
MOCK_METHOD0(disconnect, void());
MOCK_CONST_METHOD0(isAvailable, bool());
MOCK_METHOD1(fetchPositionAircraftData, bool(std::vector<Aircraft>&));
MOCK_METHOD1(fetchPositionVehicleData, bool(std::vector<Vehicle>&));
MOCK_METHOD1(fetchTrafficLightSignals, bool(std::vector<TrafficLightSignal>&));
MOCK_METHOD2(sendUnmannedVehicleCommand, bool(const std::string&, const VehicleCommand&));
MOCK_METHOD2(fetchUnmannedVehicleStatus, bool(const std::string&, std::string&));
};
// 创建测试专用的 AirportBounds 类
class TestAirportBounds : public AirportBounds {
public:
using AirportBounds::AirportBounds; // 继承构造函数
void setBounds(const Bounds& bounds) {
this->airportBounds_ = bounds;
}
void setReferencePoint(const Vector2D& point) {
this->referencePoint_ = point;
}
void setRotationAngle(double angle) {
this->rotationAngle_ = angle * M_PI / 180.0; // 转换为弧度
}
protected:
using AirportBounds::airportBounds_;
using AirportBounds::referencePoint_;
using AirportBounds::rotationAngle_;
};
class DataCollectorTest : public ::testing::Test {
protected:
void SetUp() override {
// 设置日志级别
Logger::setLogLevel(LogLevel::DEBUG);
// 初始化系统配置
SystemConfig::instance().load("config/system_config.json");
// 创建测试实例
bounds_ = std::make_unique<TestAirportBounds>();
// 设置机场边界参数
bounds_->setReferencePoint(Vector2D{0, 0}); // 世界坐标系中的参考点
bounds_->setRotationAngle(68.53); // 与 airport_bounds.json 保持一致
bounds_->setBounds(Bounds(-100, -200, 800, 400)); // 与 airport_bounds.json 保持一致
// 初始化坐标转换器
const auto& refPoint = SystemConfig::instance().airport.reference_point;
CoordinateConverter::instance().setReferencePoint(refPoint.latitude, refPoint.longitude);
collector = std::make_unique<DataCollector>(*bounds_);
mockSource = std::make_shared<::testing::NiceMock<MockDataSource>>();
collector->setDataSource(mockSource);
// 初始化配置
dataSourceConfig_.position.refresh_interval_ms = 100;
dataSourceConfig_.position.timeout_ms = 5000; // 添加连接超时配置
dataSourceConfig_.position.read_timeout_ms = 2000; // 添加读取超时配置
dataSourceConfig_.vehicle.refresh_interval_ms = 100;
dataSourceConfig_.vehicle.timeout_ms = 5000;
dataSourceConfig_.vehicle.read_timeout_ms = 2000;
dataSourceConfig_.traffic_light.refresh_interval_ms = 100;
dataSourceConfig_.traffic_light.timeout_ms = 5000;
dataSourceConfig_.traffic_light.read_timeout_ms = 2000;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 500; // 设置警告间隔
EXPECT_TRUE(collector->initialize(dataSourceConfig_, warnConfig));
}
void TearDown() override {
collector.reset();
}
// 创建测试用的航空器
Aircraft createTestAircraft(const std::string& id, double lat, double lon) {
Aircraft aircraft;
aircraft.id = id;
aircraft.flightNo = id;
aircraft.geo.latitude = lat;
aircraft.geo.longitude = lon;
aircraft.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
return aircraft;
}
// 创建测试用的车辆
Vehicle createTestVehicle(const std::string& id, double lat, double lon) {
Vehicle vehicle;
vehicle.id = id;
vehicle.vehicleNo = id;
vehicle.geo.latitude = lat;
vehicle.geo.longitude = lon;
vehicle.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
return vehicle;
}
// 创建测试用的红绿灯信号
TrafficLightSignal createTestTrafficLight(const std::string& id, int state) {
TrafficLightSignal signal;
signal.trafficLightId = id;
signal.ns_status = static_cast<SignalStatus>(state);
signal.ew_status = static_cast<SignalStatus>(state);
signal.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
return signal;
}
// 创建边界内的航空器
Aircraft createOutOfBoundsAircraft(const std::string& id) {
// 计算偏移量,考虑机场旋转角度
double angle = 68.53 * M_PI / 180.0; // 转换为弧度
double offset = 0.027; // 约 3km
double lat_offset = offset * std::cos(angle);
double lon_offset = offset * std::sin(angle);
const auto& refPoint = SystemConfig::instance().airport.reference_point;
return createTestAircraft(id, refPoint.latitude + lat_offset, refPoint.longitude + lon_offset);
}
// 创建边界内的车辆
Vehicle createOutOfBoundsVehicle(const std::string& id) {
// 计算偏移量,考虑机场旋转角度
double angle = 68.53 * M_PI / 180.0; // 转换为弧度
double offset = 0.0225; // 约 2.5km
double lat_offset = -offset * std::cos(angle); // 负值表示向相反方向偏移
double lon_offset = -offset * std::sin(angle);
const auto& refPoint = SystemConfig::instance().airport.reference_point;
return createTestVehicle(id, refPoint.latitude + lat_offset, refPoint.longitude + lon_offset);
}
std::unique_ptr<DataCollector> collector;
std::shared_ptr<MockDataSource> mockSource;
std::unique_ptr<TestAirportBounds> bounds_;
std::unique_ptr<CoordinateConverter> converter_;
DataSourceConfig dataSourceConfig_;
};
// 修改初始化测试用例
TEST_F(DataCollectorTest, Initialization) {
DataSourceConfig dataSourceConfig;
// 设置位置数据配置
dataSourceConfig.position.host = "localhost";
dataSourceConfig.position.port = 8080;
dataSourceConfig.position.aircraft_path = "/api/getCurrentFlightPositions";
dataSourceConfig.position.refresh_interval_ms = 1000;
dataSourceConfig.position.timeout_ms = 5000;
dataSourceConfig.position.read_timeout_ms = 2000;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 1000;
warnConfig.log_interval_ms = 2000;
EXPECT_TRUE(collector->initialize(dataSourceConfig, warnConfig));
}
// 测试数据采集
TEST_F(DataCollectorTest, DataCollectionTest) {
// 设置数据源配置
DataSourceConfig config;
// 设置位置数据配置
config.position.host = "localhost";
config.position.port = 8080;
config.position.refresh_interval_ms = 100;
config.position.timeout_ms = 1000;
config.position.read_timeout_ms = 500;
// 设置无人车数据配置
config.vehicle.host = "localhost";
config.vehicle.port = 8081;
config.vehicle.refresh_interval_ms = 100;
config.vehicle.timeout_ms = 1000;
config.vehicle.read_timeout_ms = 500;
// 设置红绿灯数据配置
config.traffic_light.host = "localhost";
config.traffic_light.port = 8082;
config.traffic_light.refresh_interval_ms = 100;
config.traffic_light.timeout_ms = 1000;
config.traffic_light.read_timeout_ms = 500;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 200;
collector->initialize(config, warnConfig);
std::vector<Aircraft> testAircraft = {
createTestAircraft("TEST1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
createTestAircraft("TEST2", 36.354483470 + 0.0002 * std::cos(68.53 * M_PI / 180.0),
120.08502054 + 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向偏移约 20m
};
std::vector<Vehicle> testVehicles = {
createTestVehicle("VEH1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
createTestVehicle("VEH2", 36.354483470 - 0.0002 * std::cos(68.53 * M_PI / 180.0),
120.08502054 - 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向反向偏移约 20m
};
std::vector<TrafficLightSignal> testSignals = {
createTestTrafficLight("TL001", 0),
createTestTrafficLight("TL002", 1)
};
// 设置 Mock 数据返回
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testAircraft),
::testing::Return(true)
));
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testVehicles),
::testing::Return(true)
));
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testSignals),
::testing::Return(true)
));
std::string testStatus = "NORMAL";
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<1>(testStatus),
::testing::Return(true)
));
// 启动数据采集
collector->start();
// 等待数据更新(至少 1000ms确保数据被采集
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// 获取最新数据
auto [aircraft, vehicles, traffic_lights] = collector->getLatestData();
auto [aircraft_ts, vehicle_ts, traffic_light_ts] = collector->getLastUpdateTimestamps();
// 验证数据
EXPECT_EQ(aircraft.size(), 2);
if (!aircraft.empty()) {
EXPECT_EQ(aircraft[0].flightNo, "TEST1");
EXPECT_EQ(aircraft[1].flightNo, "TEST2");
}
EXPECT_EQ(vehicles.size(), 2);
if (!vehicles.empty()) {
EXPECT_EQ(vehicles[0].vehicleNo, "VEH1");
EXPECT_EQ(vehicles[1].vehicleNo, "VEH2");
}
EXPECT_EQ(traffic_lights.size(), 2);
if (!traffic_lights.empty()) {
EXPECT_EQ(traffic_lights[0].trafficLightId, "TL001");
EXPECT_EQ(traffic_lights[1].trafficLightId, "TL002");
}
// 验证时间戳
EXPECT_GT(aircraft_ts, 0);
EXPECT_GT(vehicle_ts, 0);
EXPECT_GT(traffic_light_ts, 0);
// 停止数据采集
collector->stop();
}
// 测试数据采集循环
TEST_F(DataCollectorTest, DataCollectionLoop) {
std::vector<Aircraft> testAircraft = {
createTestAircraft("TEST1", 36.354483470, 120.08502054) // 参考点
};
std::vector<Vehicle> testVehicles = {
createTestVehicle("VEH1", 36.354483470, 120.08502054) // 参考点
};
// 设置 Mock 数据返回
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testAircraft),
::testing::Return(true)
));
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testVehicles),
::testing::Return(true)
));
// 启动采集
collector->start();
// 等待数据采集
std::this_thread::sleep_for(std::chrono::seconds(2));
// 停止采集
collector->stop();
// 验证数据
auto aircraft = collector->getAircraftData();
EXPECT_EQ(aircraft.size(), 1);
auto vehicles = collector->getVehicleData();
EXPECT_EQ(vehicles.size(), 1);
}
// 测试错误处理
TEST_F(DataCollectorTest, ErrorHandling) {
// 设置 Mock 返回错误
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillRepeatedly(::testing::Return(false));
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillRepeatedly(::testing::Return(false));
// 启动数据采集
collector->start();
// 等待数据更新(至少三个采集周期)
std::this_thread::sleep_for(std::chrono::milliseconds(
dataSourceConfig_.position.refresh_interval_ms * 3));
// 获取最新数据
auto [aircraft, vehicles, traffic_lights] = collector->getLatestData();
// 验证数据为空
EXPECT_TRUE(aircraft.empty());
EXPECT_TRUE(vehicles.empty());
// 停止数据采集
collector->stop();
}
// 测试红绿灯信号获取
TEST_F(DataCollectorTest, TrafficLightSignalsTest) {
std::vector<TrafficLightSignal> testSignals = {
createTestTrafficLight("TL001", 0), // RED = 0
createTestTrafficLight("TL002", 1), // GREEN = 1
createTestTrafficLight("TL003", 2) // YELLOW = 2
};
// 设置 Mock 数据返回
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testSignals),
::testing::Return(true)
));
// 启动数据采集
collector->start();
// 等待数据更新(至少三个采集周期)
std::this_thread::sleep_for(std::chrono::milliseconds(
dataSourceConfig_.position.refresh_interval_ms * 3));
// 验证数据
auto signals = collector->getTrafficLightSignals();
EXPECT_EQ(signals.size(), 3);
if (signals.size() >= 3) {
EXPECT_EQ(signals[0].trafficLightId, "TL001");
EXPECT_EQ(signals[0].ns_status, SignalStatus::RED); // RED = 0
EXPECT_EQ(signals[1].trafficLightId, "TL002");
EXPECT_EQ(signals[1].ns_status, SignalStatus::GREEN); // GREEN = 1
}
// 停止数据采集
collector->stop();
}
// 测试红绿灯信号错误处理
TEST_F(DataCollectorTest, TrafficLightSignalsErrorTest) {
// 设置 Mock 返回错误
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
.WillRepeatedly(::testing::Return(false));
// 启动数据采集
collector->start();
// 等待数据更新(至少三个采集周期)
std::this_thread::sleep_for(std::chrono::milliseconds(
dataSourceConfig_.position.refresh_interval_ms * 3));
// 验证数据为空
auto signals = collector->getTrafficLightSignals();
EXPECT_TRUE(signals.empty());
// 停止数据采集
collector->stop();
}
// 测试连接健康检查和超时告警
TEST_F(DataCollectorTest, ConnectionHealthAndTimeout) {
DataSourceConfig dataSourceConfig;
// 设置位置数据配置
dataSourceConfig.position.host = "localhost";
dataSourceConfig.position.port = 8080;
dataSourceConfig.position.timeout_ms = 1000; // 1秒超时
dataSourceConfig.position.read_timeout_ms = 500; // 0.5秒读取超时
dataSourceConfig.position.refresh_interval_ms = 100; // 100ms刷新间隔
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 500; // 500ms告警间隔
collector->initialize(dataSourceConfig, warnConfig);
// 模拟连接健康检查失败
EXPECT_CALL(*mockSource, isAvailable())
.WillRepeatedly(::testing::Return(false));
// 模拟数据获取失败
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillRepeatedly(::testing::Return(false));
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillRepeatedly(::testing::Return(false));
// 启动采集
collector->start();
// 等待超过超时时间
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
// 停止采集
collector->stop();
// 验证数据为空
auto aircraft = collector->getAircraftData();
EXPECT_TRUE(aircraft.empty());
auto vehicles = collector->getVehicleData();
EXPECT_TRUE(vehicles.empty());
}
// 测试连接恢复
TEST_F(DataCollectorTest, ConnectionRecovery) {
DataSourceConfig config;
// 设置位置数据配置
config.position.host = "localhost";
config.position.port = 8080;
config.position.refresh_interval_ms = 100;
config.position.timeout_ms = 1000;
config.position.read_timeout_ms = 500;
// 设置无人车数据配置
config.vehicle.host = "localhost";
config.vehicle.port = 8081;
config.vehicle.refresh_interval_ms = 100;
config.vehicle.timeout_ms = 1000;
config.vehicle.read_timeout_ms = 500;
// 设置红绿灯数据配置
config.traffic_light.host = "localhost";
config.traffic_light.port = 8082;
config.traffic_light.refresh_interval_ms = 100;
config.traffic_light.timeout_ms = 1000;
config.traffic_light.read_timeout_ms = 500;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 200;
collector->initialize(config, warnConfig);
std::vector<Aircraft> testAircraft = {
createTestAircraft("TEST1", 36.354483470, 120.08502054) // 参考点
};
// 设置 Mock 行为: 前两次失败,然后成功
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(false))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testAircraft),
::testing::Return(true)
));
// 添加车辆数据的 mock 行为
std::vector<Vehicle> testVehicles;
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(false))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testVehicles),
::testing::Return(true)
));
// 设置无人车数据 Mock 行为
std::string testStatus = "NORMAL";
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(false))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<1>(testStatus),
::testing::Return(true)
));
// 设置红绿灯数据 Mock 行为
std::vector<TrafficLightSignal> testSignals = {
createTestTrafficLight("TL001", 0)
};
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(false))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testSignals),
::testing::Return(true)
));
// 启动数据采集
collector->start();
// 等待足够长的时间以确保连接恢复
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// 获取最新数据
auto [aircraft, vehicles, traffic_lights] = collector->getLatestData();
// 验证数据
EXPECT_EQ(aircraft.size(), 1);
if (!aircraft.empty()) {
EXPECT_EQ(aircraft[0].flightNo, "TEST1");
}
// 验证红绿灯数据
EXPECT_EQ(traffic_lights.size(), 1);
if (!traffic_lights.empty()) {
EXPECT_EQ(traffic_lights[0].trafficLightId, "TL001");
}
// 停止数据采集
collector->stop();
}
// 测试长连接下的数据获取
TEST_F(DataCollectorTest, LongConnectionDataFetch) {
DataSourceConfig dataSourceConfig;
// 设置位置数据配置
dataSourceConfig.position.host = "localhost";
dataSourceConfig.position.port = 8080;
dataSourceConfig.position.refresh_interval_ms = 100;
dataSourceConfig.position.timeout_ms = 1000;
dataSourceConfig.position.read_timeout_ms = 500;
collector->initialize(dataSourceConfig, WarnConfig{});
std::vector<Aircraft> testAircraft = {
createTestAircraft("TEST1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
createTestAircraft("TEST2", 36.354483470 + 0.0002 * std::cos(68.53 * M_PI / 180.0),
120.08502054 + 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向偏移约 20m
};
std::vector<Vehicle> testVehicles = {
createTestVehicle("VEH1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
createTestVehicle("VEH2", 36.354483470 - 0.0002 * std::cos(68.53 * M_PI / 180.0),
120.08502054 - 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向反向偏移约 20m
};
std::vector<TrafficLightSignal> testSignals = {
createTestTrafficLight("TL001", 0)
};
// 模拟连接保持可用
EXPECT_CALL(*mockSource, isAvailable())
.WillRepeatedly(::testing::Return(true));
// 模拟多次成功获取数据
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.Times(::testing::AtLeast(3))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testAircraft),
::testing::Return(true)
));
// 添加其他数据源的 mock 行为
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testVehicles),
::testing::Return(true)
));
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<0>(testSignals),
::testing::Return(true)
));
// 添加无人车状态获取的 mock 行为
std::string testStatus = "NORMAL";
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
.WillRepeatedly(::testing::DoAll(
::testing::SetArgReferee<1>(testStatus),
::testing::Return(true)
));
// 启动采集
collector->start();
// 等待多个刷新周期
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 增加等待时间确保执行次数
// 验证数据
auto aircraft = collector->getAircraftData();
EXPECT_FALSE(aircraft.empty());
if (!aircraft.empty()) {
EXPECT_EQ(aircraft[0].flightNo, "TEST1");
}
auto vehicles = collector->getVehicleData();
EXPECT_FALSE(vehicles.empty());
if (!vehicles.empty()) {
EXPECT_EQ(vehicles[0].vehicleNo, "VEH1");
}
auto signals = collector->getTrafficLightSignals();
EXPECT_FALSE(signals.empty());
if (!signals.empty()) {
EXPECT_EQ(signals[0].trafficLightId, "TL001");
}
// 停止采集
collector->stop();
}
TEST_F(DataCollectorTest, InitializeWithTimeoutConfig) {
DataSourceConfig config;
// 设置位置数据配置
config.position.host = "localhost";
config.position.port = 8080;
config.position.timeout_ms = 5000; // 连接超时 5 秒
config.position.read_timeout_ms = 2000; // 读取超时 2 秒
config.position.refresh_interval_ms = 100;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 500;
EXPECT_NO_THROW(collector->initialize(config, warnConfig));
}
TEST_F(DataCollectorTest, TimeoutWarningTest) {
DataSourceConfig config;
// 设置位置数据配置
config.position.host = "localhost";
config.position.port = 8080;
config.position.timeout_ms = 1000; // 连接超时 1 秒
config.position.read_timeout_ms = 500; // 读取超时 0.5 秒
config.position.refresh_interval_ms = 100;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 200; // 警告间隔 0.2 秒
collector->initialize(config, warnConfig);
// 模拟连接成功但读取超时
EXPECT_CALL(*mockSource, isAvailable())
.WillRepeatedly(::testing::Return(true));
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillRepeatedly(::testing::Return(false)); // 持续读取超时
// 启动采集
collector->start();
// 等待足够长的时间以触发多次警告
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// 停止采集
collector->stop();
}
// 测试超时警告机制
TEST_F(DataCollectorTest, TimeoutWarningMechanism) {
DataSourceConfig config;
// 设置位置数据配置
config.position.host = "localhost";
config.position.port = 8080;
config.position.timeout_ms = 200; // 连接超时 200ms
config.position.read_timeout_ms = 100; // 读取超时 100ms
config.position.refresh_interval_ms = 10;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 50; // 警告间隔 50ms
// 创建一个计数器来记录收到的警告数量
int warningCount = 0;
// 创建一个 mock System 来捕获警告
class MockSystem : public System {
public:
MOCK_METHOD1(broadcastTimeoutWarning, void(const network::TimeoutWarningMessage&));
};
auto mockSystem = std::make_shared<MockSystem>();
// 设置期望接收到超时警告
EXPECT_CALL(*mockSystem, broadcastTimeoutWarning(::testing::_))
.WillRepeatedly(::testing::Invoke([&warningCount](const network::TimeoutWarningMessage& warning) {
warningCount++;
// 验证警告消息的内容
EXPECT_THAT(warning.source, ::testing::AnyOf(
::testing::StrEq("position"),
::testing::StrEq("unmanned"),
::testing::StrEq("traffic_light")
));
EXPECT_GT(warning.elapsed_ms, 0);
}));
// 设置 mock system 到 collector
collector->setSystem(mockSystem);
collector->initialize(config, warnConfig);
// 启动采集器
collector->start();
// 模拟所有数据源获取失败
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillRepeatedly(::testing::Return(false));
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillRepeatedly(::testing::Return(false));
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
.WillRepeatedly(::testing::Return(false));
// 等待足够长的时间以确保警告被发送
std::this_thread::sleep_for(std::chrono::milliseconds(300));
// 停止采集器
collector->stop();
// 验证是否收到了警告
EXPECT_GT(warningCount, 0);
}
// 测试读取超时机制
TEST_F(DataCollectorTest, ReadTimeoutMechanism) {
DataSourceConfig config;
// 设置位置数据配置
config.position.host = "localhost";
config.position.port = 8080;
config.position.timeout_ms = 1000; // 连接超时 1000ms
config.position.read_timeout_ms = 500; // 读取超时 500ms
config.position.refresh_interval_ms = 100;
// 设置无人车数据配置
config.vehicle.host = "localhost";
config.vehicle.port = 8081;
config.vehicle.timeout_ms = 1000;
config.vehicle.read_timeout_ms = 500;
config.vehicle.refresh_interval_ms = 100;
// 设置红绿灯数据配置
config.traffic_light.host = "localhost";
config.traffic_light.port = 8082;
config.traffic_light.timeout_ms = 1000;
config.traffic_light.read_timeout_ms = 500;
config.traffic_light.refresh_interval_ms = 100;
WarnConfig warnConfig;
warnConfig.warning_interval_ms = 200; // 警告间隔 200ms
// 创建一个计数器来记录收到的警告数量
int warningCount = 0;
// 创建一个 mock System 来捕获警告
class MockSystem : public System {
public:
MOCK_METHOD1(broadcastTimeoutWarning, void(const network::TimeoutWarningMessage&));
};
auto mockSystem = std::make_shared<MockSystem>();
// 设置期望接收到超时警告
EXPECT_CALL(*mockSystem, broadcastTimeoutWarning(::testing::_))
.WillRepeatedly(::testing::Invoke([&warningCount](const network::TimeoutWarningMessage& warning) {
warningCount++;
// 验证警告消息的内容
EXPECT_THAT(warning.source, ::testing::AnyOf(
::testing::StrEq("position"),
::testing::StrEq("unmanned"),
::testing::StrEq("traffic_light")
));
EXPECT_GT(warning.elapsed_ms, 0);
EXPECT_TRUE(warning.is_read_timeout);
}));
// 设置 mock system 到 collector
collector->setSystem(mockSystem);
collector->initialize(config, warnConfig);
// 启动采集器
collector->start();
// 模拟所有数据源获取失败
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillRepeatedly(::testing::Return(false));
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillRepeatedly(::testing::Return(false));
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
.WillRepeatedly(::testing::Return(false));
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
.WillRepeatedly(::testing::Return(false));
// 等待足够长的时间以确保警告被发送
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
// 停止采集器
collector->stop();
// 验证是否收到了警告
EXPECT_GT(warningCount, 0);
}
// 添加新的测试用例:测试边界外数据过滤
TEST_F(DataCollectorTest, OutOfBoundsDataFiltering) {
DataSourceConfig dataSourceConfig;
WarnConfig warnConfig;
EXPECT_TRUE(collector->initialize(dataSourceConfig, warnConfig));
// 创建测试数据:混合边界内外的数据
const auto& refPoint = SystemConfig::instance().airport.reference_point;
// 创建边界内的航空器
std::vector<Aircraft> testAircraft = {
createTestAircraft("IN_BOUNDS_1", refPoint.latitude, refPoint.longitude), // 参考点
createTestAircraft("IN_BOUNDS_2",
refPoint.latitude + 0.001 * std::cos(68.53 * M_PI / 180.0), // 沿机场方向偏移约 100m
refPoint.longitude + 0.001 * std::sin(68.53 * M_PI / 180.0)),
createOutOfBoundsAircraft("OUT_BOUNDS_1"), // 边界外偏移约 3km
createOutOfBoundsAircraft("OUT_BOUNDS_2") // 边界外偏移约 3km
};
// 创建边界内的车辆
std::vector<Vehicle> testVehicles = {
createTestVehicle("VEH_IN_1", refPoint.latitude, refPoint.longitude), // 参考点
createTestVehicle("VEH_IN_2",
refPoint.latitude + 0.001 * std::cos(68.53 * M_PI / 180.0), // 沿机场方向偏移约 100m
refPoint.longitude + 0.001 * std::sin(68.53 * M_PI / 180.0)),
createOutOfBoundsVehicle("VEH_OUT_1"), // 边界外偏移约 2.5km
createOutOfBoundsVehicle("VEH_OUT_2") // 边界外偏移约 2.5km
};
// 设置 Mock 数据返回
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
.WillOnce(::testing::DoAll(
::testing::SetArgReferee<0>(testAircraft),
::testing::Return(true)
));
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
.WillOnce(::testing::DoAll(
::testing::SetArgReferee<0>(testVehicles),
::testing::Return(true)
));
// 执行刷新
collector->fetchPositionData();
// 验证过滤结果
const auto& filteredAircraft = collector->getAircraftData();
const auto& filteredVehicles = collector->getVehicleData();
// 检查过滤后的数量
EXPECT_EQ(filteredAircraft.size(), 2) << "应该只保留2架在边界内的航空器";
EXPECT_EQ(filteredVehicles.size(), 2) << "应该只保留2辆在边界内的车辆";
// 检查保留的是正确的航空器
if (filteredAircraft.size() >= 2) {
EXPECT_EQ(filteredAircraft[0].flightNo, "IN_BOUNDS_1");
EXPECT_EQ(filteredAircraft[1].flightNo, "IN_BOUNDS_2");
}
// 检查保留的是正确的车辆
if (filteredVehicles.size() >= 2) {
EXPECT_EQ(filteredVehicles[0].vehicleNo, "VEH_IN_1");
EXPECT_EQ(filteredVehicles[1].vehicleNo, "VEH_IN_2");
}
}

View File

@ -0,0 +1,286 @@
#include "network/HTTPDataSource.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "utils/Logger.h"
#include <set>
#include <memory>
#include <thread>
class HTTPDataSourceTest : public ::testing::Test {
protected:
void SetUp() override {}
void TearDown() override {}
};
TEST_F(HTTPDataSourceTest, BasicFunctionality) {
DataSourceConfig config;
config.position.host = "localhost";
config.position.port = 8081;
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
config.position.auth.auth_required = true;
config.position.auth.auth_path = "/login";
config.position.auth.username = "dianxin";
config.position.auth.password = "dianxin@123";
config.position.timeout_ms = 5000;
config.position.read_timeout_ms = 2000;
config.position.refresh_interval_ms = 100;
HTTPDataSource source(config);
ASSERT_TRUE(source.connect()) << "连接失败";
std::vector<Aircraft> aircraft;
bool success = source.fetchPositionAircraftData(aircraft);
ASSERT_TRUE(success) << "获取航空器数据失败";
ASSERT_FALSE(aircraft.empty()) << "航空器数据为空";
std::vector<Vehicle> vehicles;
ASSERT_TRUE(source.fetchPositionVehicleData(vehicles)) << "获取车辆数据失败";
ASSERT_FALSE(vehicles.empty()) << "车辆数据为空";
}
TEST_F(HTTPDataSourceTest, AuthenticationFailure) {
DataSourceConfig config;
config.position.host = "localhost";
config.position.port = 8081;
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
config.position.refresh_interval_ms = 1000;
config.position.timeout_ms = 1000;
config.position.read_timeout_ms = 500;
config.position.auth.auth_required = true;
config.position.auth.username = "wrong_user";
config.position.auth.password = "wrong_pass";
config.position.auth.auth_path = "/login";
HTTPDataSource source(config);
std::vector<Aircraft> aircraft;
EXPECT_FALSE(source.fetchPositionAircraftData(aircraft));
}
TEST_F(HTTPDataSourceTest, Reconnection) {
DataSourceConfig config;
config.position.host = "localhost";
config.position.port = 8081;
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
config.position.refresh_interval_ms = 1000;
config.position.timeout_ms = 1000;
config.position.read_timeout_ms = 500;
config.position.auth.auth_required = true;
config.position.auth.username = "dianxin";
config.position.auth.password = "dianxin@123";
config.position.auth.auth_path = "/login";
HTTPDataSource source(config);
EXPECT_TRUE(source.connect());
std::vector<Aircraft> aircraft;
EXPECT_TRUE(source.fetchPositionAircraftData(aircraft));
source.disconnect();
EXPECT_TRUE(source.connect());
aircraft.clear();
EXPECT_TRUE(source.fetchPositionAircraftData(aircraft));
}
TEST_F(HTTPDataSourceTest, AuthenticationOnly) {
DataSourceConfig config;
config.position.host = "localhost";
config.position.port = 8081;
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
config.position.refresh_interval_ms = 1000;
config.position.timeout_ms = 1000;
config.position.read_timeout_ms = 500;
config.position.auth.auth_required = true;
config.position.auth.username = "dianxin";
config.position.auth.password = "dianxin@123";
config.position.auth.auth_path = "/login";
HTTPDataSource source(config);
EXPECT_TRUE(source.connect());
std::vector<Aircraft> aircraft;
EXPECT_TRUE(source.fetchPositionAircraftData(aircraft));
}
TEST_F(HTTPDataSourceTest, TrafficLightSignals) {
DataSourceConfig config;
config.position.host = "localhost";
config.position.port = 8081;
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
config.position.auth.auth_required = true;
config.position.auth.auth_path = "/login";
config.position.auth.username = "dianxin";
config.position.auth.password = "dianxin@123";
config.position.timeout_ms = 5000;
config.position.read_timeout_ms = 2000;
config.position.refresh_interval_ms = 100;
config.traffic_light.host = "localhost";
config.traffic_light.port = 8081;
config.traffic_light.signal_path = "/api/VehicleCommandInfo";
config.vehicle.host = "localhost";
config.vehicle.port = 8081;
config.vehicle.command_path = "/api/VehicleCommandInfo";
HTTPDataSource source(config);
ASSERT_TRUE(source.connect());
// 先获取车辆状态来初始化车辆
std::vector<Vehicle> vehicles;
ASSERT_TRUE(source.fetchPositionVehicleData(vehicles)) << "获取车辆状态失败";
ASSERT_FALSE(vehicles.empty()) << "车辆数据为空";
// 发送绿灯指令(优先级 2
VehicleCommand green_command;
green_command.vehicleId = "QN001";
green_command.type = CommandType::SIGNAL;
green_command.reason = CommandReason::TRAFFIC_LIGHT;
green_command.signalState = SignalState::GREEN;
green_command.intersectionId = "INTERSECTION_001";
green_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source.sendUnmannedVehicleCommand(green_command.vehicleId, green_command)) << "发送绿灯指令失败";
// 等待绿灯指令生效
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 发送红灯指令(优先级 4
VehicleCommand red_command;
red_command.vehicleId = "QN001";
red_command.type = CommandType::SIGNAL;
red_command.reason = CommandReason::TRAFFIC_LIGHT;
red_command.signalState = SignalState::RED;
red_command.intersectionId = "INTERSECTION_001";
red_command.latitude = 36.36;
red_command.longitude = 120.08;
red_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source.sendUnmannedVehicleCommand(red_command.vehicleId, red_command)) << "发送红灯指令失败";
// 等待红灯指令生效
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 最后发送绿灯指令
green_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source.sendUnmannedVehicleCommand(green_command.vehicleId, green_command)) << "发送最终绿灯指令失败";
}
TEST_F(HTTPDataSourceTest, UnmannedVehicleCommand) {
DataSourceConfig config;
config.position.host = "localhost";
config.position.port = 8081;
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
config.position.auth.auth_required = true;
config.position.auth.auth_path = "/login";
config.position.auth.username = "dianxin";
config.position.auth.password = "dianxin@123";
config.position.timeout_ms = 5000;
config.position.read_timeout_ms = 2000;
config.position.refresh_interval_ms = 100;
config.vehicle.host = "localhost";
config.vehicle.port = 8081;
config.vehicle.command_path = "/api/VehicleCommandInfo";
config.vehicle.auth.auth_required = true;
config.vehicle.auth.auth_path = "/login";
config.vehicle.auth.username = "dianxin";
config.vehicle.auth.password = "dianxin@123";
config.vehicle.timeout_ms = 5000;
config.vehicle.read_timeout_ms = 2000;
HTTPDataSource source(config);
ASSERT_TRUE(source.connect()) << "连接失败";
// 先获取车辆状态来初始化车辆
std::vector<Vehicle> vehicles;
ASSERT_TRUE(source.fetchPositionVehicleData(vehicles)) << "获取车辆状态失败";
ASSERT_FALSE(vehicles.empty()) << "车辆数据为空";
// 先发送 RESUME 指令清除所有状态
VehicleCommand resume_command;
resume_command.vehicleId = "QN001";
resume_command.type = CommandType::RESUME;
resume_command.reason = CommandReason::RESUME_TRAFFIC;
resume_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source.sendUnmannedVehicleCommand(resume_command.vehicleId, resume_command)) << "发送恢复指令失败";
// 等待 RESUME 指令生效
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 发送 WARNING 指令(优先级 3
VehicleCommand warning_command;
warning_command.vehicleId = "QN001";
warning_command.type = CommandType::WARNING;
warning_command.reason = CommandReason::AIRCRAFT_CROSSING;
warning_command.relativeSpeed = 50;
warning_command.relativeMotionX = 50;
warning_command.relativeMotionY = 50;
warning_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source.sendUnmannedVehicleCommand(warning_command.vehicleId, warning_command)) << "发送预警指令失败";
// 等待 WARNING 指令生效
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 发送 ALERT 指令(优先级 5
VehicleCommand alert_command;
alert_command.vehicleId = "QN001";
alert_command.type = CommandType::ALERT;
alert_command.reason = CommandReason::AIRCRAFT_CROSSING;
alert_command.relativeSpeed = 100;
alert_command.relativeMotionX = 100;
alert_command.relativeMotionY = 100;
alert_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source.sendUnmannedVehicleCommand(alert_command.vehicleId, alert_command)) << "发送告警指令失败";
// 等待 ALERT 指令生效
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 最后发送 RESUME 指令清除状态
resume_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source.sendUnmannedVehicleCommand(resume_command.vehicleId, resume_command)) << "发送最终恢复指令失败";
}
TEST_F(HTTPDataSourceTest, TrafficLightAndUnmannedAuthFailure) {
DataSourceConfig config;
config.traffic_light.host = "localhost";
config.traffic_light.port = 8081;
config.traffic_light.signal_path = "/api/VehicleCommandInfo";
config.traffic_light.auth.auth_required = true;
config.traffic_light.auth.username = "wrong_user";
config.traffic_light.auth.password = "wrong_pass";
config.vehicle.host = "localhost";
config.vehicle.port = 8081;
config.vehicle.command_path = "/api/VehicleCommandInfo";
config.vehicle.auth.auth_required = true;
config.vehicle.auth.username = "wrong_user";
config.vehicle.auth.password = "wrong_pass";
HTTPDataSource source(config);
std::vector<TrafficLightSignal> signals;
EXPECT_FALSE(source.fetchTrafficLightSignals(signals));
VehicleCommand command;
command.vehicleId = "TEST_VEHICLE_001";
command.type = CommandType::SIGNAL;
command.reason = CommandReason::RESUME_TRAFFIC;
command.signalState = SignalState::GREEN;
command.timestamp = 0;
EXPECT_FALSE(source.sendUnmannedVehicleCommand(command.vehicleId, command));
}

190
tests/SafetyZoneTest.cpp Normal file
View File

@ -0,0 +1,190 @@
#include <gtest/gtest.h>
#include "detector/SafetyZone.h"
#include "utils/Logger.h"
#include "config/SystemConfig.h"
class SafetyZoneTest : public ::testing::Test {
protected:
void SetUp() override {
// 确保 SystemConfig 已初始化
SystemConfig::instance();
// 创建一个安全区,中心点在(0,0)飞机安全区半径50米特勤车安全区半径30米
safetyZone = std::make_unique<SafetyZone>(Vector2D{0, 0}, 50.0, 30.0);
}
std::unique_ptr<SafetyZone> safetyZone;
// 创建测试用的移动对象
Aircraft createAircraft(const std::string& id, double x, double y) {
Aircraft obj;
obj.id = id;
obj.position = Vector2D{x, y};
obj.geo.latitude = 0; // 这里的经纬度不重要
obj.geo.longitude = 0;
obj.type = MovingObjectType::AIRCRAFT; // 设置类型为航空器
return obj;
}
Vehicle createSpecialVehicle(const std::string& id, double x, double y) {
Vehicle obj;
obj.id = id;
obj.position = Vector2D{x, y};
obj.geo.latitude = 0;
obj.geo.longitude = 0;
obj.type = MovingObjectType::SPECIAL; // 设置类型为特勤车
obj.isControllable = false; // 设置为非可控车辆(即特勤车)
return obj;
}
Vehicle createUnmannedVehicle(const std::string& id, double x, double y) {
Vehicle obj;
obj.id = id;
obj.position = Vector2D{x, y};
obj.geo.latitude = 0;
obj.geo.longitude = 0;
obj.type = MovingObjectType::UNMANNED; // 设置类型为无人车
obj.isControllable = true; // 设置为可控车辆(即无人车)
return obj;
}
// 获取飞机有效距离(安全区半径 + 飞机半长度)
double getAircraftEffectiveDistance() const {
const auto& config = SystemConfig::instance().collision_detection.prediction;
return safetyZone->getAircraftRadius() + config.aircraft_size / 2.0;
}
// 获取特勤车有效距离(安全区半径 + 车辆半长度)
double getVehicleEffectiveDistance() const {
const auto& config = SystemConfig::instance().collision_detection.prediction;
return safetyZone->getSpecialVehicleRadius() + config.vehicle_size / 2.0;
}
};
// 测试初始状态
TEST_F(SafetyZoneTest, InitialState) {
EXPECT_EQ(safetyZone->getState(), SafetyZoneState::INACTIVE);
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::NONE);
EXPECT_EQ(safetyZone->getCurrentRadius(), 0.0);
}
// 测试飞机进入安全区
TEST_F(SafetyZoneTest, AircraftEntering) {
double effectiveDistance = getAircraftEffectiveDistance();
// 创建一个飞机,位置在安全区边缘内侧
auto aircraft = createAircraft("AC001", effectiveDistance - 1.0, 0.0);
// 检查飞机是否在安全区内
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft));
// 尝试激活安全区
EXPECT_TRUE(safetyZone->tryActivate(aircraft));
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
EXPECT_EQ(safetyZone->getCurrentRadius(), 50.0);
// 测试飞机完全离开安全区
aircraft = createAircraft("AC001", effectiveDistance + 1.0, 0.0);
EXPECT_FALSE(safetyZone->isObjectInZone(aircraft));
}
// 测试特勤车进入安全区
TEST_F(SafetyZoneTest, SpecialVehicleEntering) {
double effectiveDistance = getVehicleEffectiveDistance();
// 创建一个特勤车,位置在安全区边缘内侧
auto vehicle = createSpecialVehicle("TQ001", effectiveDistance - 1.0, 0.0);
// 检查特勤车是否在安全区内
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle));
// 尝试激活安全区
EXPECT_TRUE(safetyZone->tryActivate(vehicle));
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::VEHICLE);
EXPECT_EQ(safetyZone->getCurrentRadius(), 30.0);
// 测试特勤车完全离开安全区
vehicle = createSpecialVehicle("TQ001", effectiveDistance + 1.0, 0.0);
EXPECT_FALSE(safetyZone->isObjectInZone(vehicle));
}
// 测试无人车进入安全区
TEST_F(SafetyZoneTest, UnmannedVehicleEntering) {
// 创建一个无人车,位置在安全区内
auto vehicle = createUnmannedVehicle("UV001", 10.0, 0.0);
// 检查无人车是否在安全区内应该返回false因为无人车不能设置安全区类型
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle));
EXPECT_FALSE(safetyZone->tryActivate(vehicle));
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::NONE);
EXPECT_EQ(safetyZone->getCurrentRadius(), 0.0);
}
// 测试安全区类型锁定
TEST_F(SafetyZoneTest, SafetyZoneTypeLocking) {
double aircraftEffectiveDistance = getAircraftEffectiveDistance();
double vehicleEffectiveDistance = getVehicleEffectiveDistance();
// 先让飞机进入安全区
auto aircraft = createAircraft("AC001", aircraftEffectiveDistance - 5.0, 0.0);
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft));
EXPECT_TRUE(safetyZone->tryActivate(aircraft));
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
// 尝试让特勤车进入已经被飞机设置类型的安全区
auto vehicle = createSpecialVehicle("TQ001", vehicleEffectiveDistance - 5.0, 0.0);
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle));
EXPECT_FALSE(safetyZone->tryActivate(vehicle));
// 确认安全区类型和尺寸没有改变
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
EXPECT_EQ(safetyZone->getCurrentRadius(), 50.0);
}
// 测试边界条件
TEST_F(SafetyZoneTest, BoundaryConditions) {
double aircraftEffectiveDistance = getAircraftEffectiveDistance();
double vehicleEffectiveDistance = getVehicleEffectiveDistance();
// 测试飞机在边界上
auto aircraft1 = createAircraft("AC001", aircraftEffectiveDistance, 0.0);
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft1));
EXPECT_TRUE(safetyZone->tryActivate(aircraft1));
// 测试飞机在边界外
auto aircraft2 = createAircraft("AC002", aircraftEffectiveDistance + 0.1, 0.0);
EXPECT_FALSE(safetyZone->isObjectInZone(aircraft2));
// 重新创建安全区用于测试特勤车
safetyZone = std::make_unique<SafetyZone>(Vector2D{0, 0}, 50.0, 30.0);
// 测试特勤车在边界上
auto vehicle1 = createSpecialVehicle("TQ001", vehicleEffectiveDistance, 0.0);
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle1));
EXPECT_TRUE(safetyZone->tryActivate(vehicle1));
// 测试特勤车在边界外
auto vehicle2 = createSpecialVehicle("TQ002", vehicleEffectiveDistance + 0.1, 0.0);
EXPECT_FALSE(safetyZone->isObjectInZone(vehicle2));
}
// 测试状态重置
TEST_F(SafetyZoneTest, StateReset) {
double effectiveDistance = getAircraftEffectiveDistance();
// 先让飞机进入安全区
auto aircraft = createAircraft("AC001", effectiveDistance - 5.0, 0.0);
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft));
EXPECT_TRUE(safetyZone->tryActivate(aircraft));
// 设置状态为激活
safetyZone->setState(SafetyZoneState::ACTIVE);
EXPECT_EQ(safetyZone->getState(), SafetyZoneState::ACTIVE);
// 重置状态
safetyZone->setState(SafetyZoneState::INACTIVE);
EXPECT_EQ(safetyZone->getState(), SafetyZoneState::INACTIVE);
// 确认类型和尺寸保持不变
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
EXPECT_EQ(safetyZone->getCurrentRadius(), 50.0);
}

View File

@ -0,0 +1,141 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <fstream>
#include <nlohmann/json.hpp>
#include "detector/TrafficLightDetector.h"
#include "config/IntersectionConfig.h"
#include "vehicle/ControllableVehicles.h"
#include "utils/Logger.h"
#include "types/VehicleCommand.h"
#include "types/TrafficLightTypes.h"
#include "core/System.h"
// Mock System 类
class MockSystem : public System {
public:
MOCK_METHOD(void, broadcastTrafficLightCommand, (const std::string&, const VehicleCommand&));
static MockSystem& getInstance() {
static MockSystem instance;
return instance;
}
private:
MockSystem() = default;
MockSystem(const MockSystem&) = delete;
MockSystem& operator=(const MockSystem&) = delete;
};
class TrafficLightDetectorTest : public ::testing::Test {
protected:
void SetUp() override {
// 创建测试用的交叉口配置
Intersection intersection;
intersection.id = "INT001";
intersection.name = "Test Intersection";
intersection.position.latitude = 36.36;
intersection.position.longitude = 120.08;
intersection.position.altitude = 9.5;
intersection.width = 50.0;
intersection.trafficLightId = "TL001";
// 创建临时配置文件
std::ofstream config_file("test_intersections.json");
nlohmann::json j;
j["intersections"] = nlohmann::json::array();
j["intersections"].push_back({
{"id", intersection.id},
{"name", intersection.name},
{"position", {
{"latitude", intersection.position.latitude},
{"longitude", intersection.position.longitude},
{"altitude", intersection.position.altitude}
}},
{"width", intersection.width},
{"trafficLightId", intersection.trafficLightId},
{"safetyZone", {
{"aircraftRadius", 50.0},
{"vehicleRadius", 50.0}
}}
});
config_file << j.dump(4);
config_file.close();
intersectionConfig = IntersectionConfig::load("test_intersections.json");
detector = std::make_unique<TrafficLightDetector>(
intersectionConfig,
ControllableVehicles::getInstance(),
MockSystem::getInstance()
);
}
void TearDown() override {
detector.reset();
std::remove("test_intersections.json");
}
// 创建测试用的红绿灯信号
TrafficLightSignal createTestSignal(const std::string& id, SignalStatus status) {
TrafficLightSignal signal;
signal.trafficLightId = id;
signal.ns_status = status;
signal.ew_status = status;
signal.timestamp = std::chrono::system_clock::now().time_since_epoch().count();
return signal;
}
// 创建测试用的车辆数据
Vehicle createTestVehicle(const std::string& id, double lat, double lon) {
Vehicle v;
v.id = id;
v.vehicleNo = id;
v.geo.latitude = lat;
v.geo.longitude = lon;
v.timestamp = std::chrono::system_clock::now().time_since_epoch().count();
return v;
}
std::unique_ptr<TrafficLightDetector> detector;
IntersectionConfig intersectionConfig;
};
// 测试红绿灯信号处理
TEST_F(TrafficLightDetectorTest, SignalProcessing) {
// 创建一个在交叉口范围内的车辆
Vehicle vehicle = createTestVehicle("V001", 36.36, 120.08);
std::vector<Vehicle> vehicles = {vehicle};
// 测试红灯
TrafficLightSignal redSignal = createTestSignal("TL001", SignalStatus::RED);
detector->processSignal(redSignal, vehicles);
// 测试绿灯
TrafficLightSignal greenSignal = createTestSignal("TL001", SignalStatus::GREEN);
detector->processSignal(greenSignal, vehicles);
}
// 测试无效的红绿灯信号
TEST_F(TrafficLightDetectorTest, InvalidSignal) {
Vehicle vehicle = createTestVehicle("V001", 36.36, 120.08);
std::vector<Vehicle> vehicles = {vehicle};
// 测试无效的红绿灯ID
TrafficLightSignal invalidSignal = createTestSignal("TL999", SignalStatus::RED);
detector->processSignal(invalidSignal, vehicles);
}
// 测试边界情况
TEST_F(TrafficLightDetectorTest, EdgeCases) {
// 测试空车辆列表
std::vector<Vehicle> emptyVehicles;
TrafficLightSignal signal = createTestSignal("TL001", SignalStatus::RED);
detector->processSignal(signal, emptyVehicles);
// 测试交叉口范围外的车辆
Vehicle farVehicle = createTestVehicle("V002", 36.40, 120.12);
std::vector<Vehicle> vehicles = {farVehicle};
detector->processSignal(signal, vehicles);
}

View File

@ -0,0 +1,5 @@
{
"airport": {
"rotation_angle": 68.53
}
}

303
tools/map.html Normal file
View File

@ -0,0 +1,303 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>机场路口地图</title>
<style>
body {
margin: 0;
padding: 20px;
background: #f0f0f0;
font-family: Arial, sans-serif;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
canvas {
border: 1px solid #ddd;
margin: 20px 0;
}
.legend {
margin-top: 20px;
padding: 10px;
background: #f8f8f8;
border-radius: 4px;
}
.legend-item {
margin: 5px 0;
display: flex;
align-items: center;
}
.legend-color {
width: 20px;
height: 20px;
margin-right: 10px;
border: 1px solid #999;
}
</style>
</head>
<body>
<div class="container">
<h1>机场路口地图</h1>
<canvas id="mapCanvas" width="1000" height="800"></canvas>
<div class="legend">
<h3>图例</h3>
<div class="legend-item">
<div class="legend-color" style="background: #666;"></div>
<span>道路</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #f00;"></div>
<span>路口</span>
</div>
</div>
</div>
<script>
// 路口坐标数据
const intersections = [
{point: "T1", longitude: 120.0868853, latitude: 36.35496367},
{point: "T2", longitude: 120.08502054, latitude: 36.35448347},
{point: "T3", longitude: 120.08341044, latitude: 36.35406879},
{point: "T4", longitude: 120.08558121, latitude: 36.35305878},
{point: "T5", longitude: 120.08400957, latitude: 36.35265197},
{point: "T6", longitude: 120.08649105, latitude: 36.35074527},
{point: "T7", longitude: 120.08562915, latitude: 36.35052372},
{point: "T8", longitude: 120.08676664, latitude: 36.35004529},
{point: "T9", longitude: 120.08520616, latitude: 36.34964473},
{point: "T10", longitude: 120.08710569, latitude: 36.34917893},
{point: "T11", longitude: 120.0873865, latitude: 36.3509885},
{point: "T12", longitude: 120.08603613, latitude: 36.35190217}
];
// 参考点
const referencePoint = {
latitude: 36.34807893,
longitude: 120.08201044
};
// 获取 Canvas 上下文
const canvas = document.getElementById('mapCanvas');
const ctx = canvas.getContext('2d');
// 计算坐标范围
let minLat = Math.min(...intersections.map(p => p.latitude));
let maxLat = Math.max(...intersections.map(p => p.latitude));
let minLon = Math.min(...intersections.map(p => p.longitude));
let maxLon = Math.max(...intersections.map(p => p.longitude));
// 添加边距
const margin = 0.0001;
minLat -= margin;
maxLat += margin;
minLon -= margin;
maxLon += margin;
// 计算中心纬度
const centerLat = (minLat + maxLat) / 2;
// 地球半径(米)
const EARTH_RADIUS = 6378137.0;
// 计算1度经度和纬度对应的实际距离
const metersPerLatDegree = (Math.PI / 180) * EARTH_RADIUS;
const metersPerLonDegree = metersPerLatDegree * Math.cos(centerLat * Math.PI / 180);
// 计算实际距离范围(米)
const totalWidthMeters = (maxLon - minLon) * metersPerLonDegree;
const totalHeightMeters = (maxLat - minLat) * metersPerLatDegree;
// 计算画布缩放比例,保持宽高比
const scale = Math.min(canvas.width / totalWidthMeters, canvas.height / totalHeightMeters);
// 坐标转换函数
function convertToCanvas(lon, lat) {
// 将经纬度转换为实际距离(米)
const x = (lon - minLon) * metersPerLonDegree;
const y = (lat - minLat) * metersPerLatDegree;
// 使用统一的缩放比例转换为画布坐标
const canvasX = x * scale;
const canvasY = canvas.height - (y * scale);
// 居中显示
const offsetX = (canvas.width - totalWidthMeters * scale) / 2;
const offsetY = (canvas.height - totalHeightMeters * scale) / 2;
return {
x: canvasX + offsetX,
y: canvasY - offsetY
};
}
// 绘制道路
function drawRoad(start, end) {
const startPos = convertToCanvas(start.longitude, start.latitude);
const endPos = convertToCanvas(end.longitude, end.latitude);
ctx.beginPath();
ctx.moveTo(startPos.x, startPos.y);
ctx.lineTo(endPos.x, endPos.y);
ctx.strokeStyle = '#666';
ctx.lineWidth = 3;
ctx.stroke();
}
// 绘制路口
function drawIntersection(intersection) {
const pos = convertToCanvas(intersection.longitude, intersection.latitude);
// 绘制圆点
ctx.beginPath();
ctx.arc(pos.x, pos.y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#f00';
ctx.fill();
// 绘制标签
ctx.font = '12px Arial';
ctx.fillStyle = '#000';
ctx.textAlign = 'center';
ctx.fillText(intersection.point, pos.x, pos.y - 10);
}
// 计算 T2-T6 的旋转角度
const t2 = intersections.find(p => p.point === "T2");
const t6 = intersections.find(p => p.point === "T6");
const angle = Math.atan2(
convertToCanvas(t6.longitude, t6.latitude).y - convertToCanvas(t2.longitude, t2.latitude).y,
convertToCanvas(t6.longitude, t6.latitude).x - convertToCanvas(t2.longitude, t2.latitude).x
);
// 设置画布中心点
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// 清空画布
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制网格
const gridSize = 25; // 网格大小为25像素
ctx.beginPath();
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 1;
// 绘制垂直线
for(let x = 0; x <= canvas.width; x += gridSize) {
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
}
// 绘制水平线
for(let y = 0; y <= canvas.height; y += gridSize) {
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
// 保存当前状态
ctx.save();
// 将原点移到画布中心
ctx.translate(centerX, centerY);
// 旋转画布
ctx.rotate(-angle);
// 将原点移回去
ctx.translate(-centerX, -centerY);
// 计算并显示道路的偏离角度
function calculateRoadAngle(start, end) {
const startPos = convertToCanvas(start.longitude, start.latitude);
const endPos = convertToCanvas(end.longitude, end.latitude);
// 将坐标点转换到旋转后的坐标系
const rotatedStartX = (startPos.x - centerX) * Math.cos(-angle) - (startPos.y - centerY) * Math.sin(-angle);
const rotatedStartY = (startPos.x - centerX) * Math.sin(-angle) + (startPos.y - centerY) * Math.cos(-angle);
const rotatedEndX = (endPos.x - centerX) * Math.cos(-angle) - (endPos.y - centerY) * Math.sin(-angle);
const rotatedEndY = (endPos.x - centerX) * Math.sin(-angle) + (endPos.y - centerY) * Math.cos(-angle);
// 计算在旋转坐标系中的方向向量(从起点指向终点)
const dx = rotatedEndX - rotatedStartX;
const dy = rotatedEndY - rotatedStartY;
// 计算与垂直方向的夹角(弧度)
const angleRad = Math.atan2(-dx, dy);
// 转换为角度并取绝对值
const deviationAngle = Math.abs(angleRad * 180 / Math.PI);
// 显示角度(文字方向跟随旋转后的坐标系)
ctx.save();
// 将文字位置移到终点附近
ctx.translate(endPos.x, endPos.y + 15);
ctx.rotate(angle);
ctx.font = '12px Arial';
ctx.fillStyle = '#000';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(deviationAngle.toFixed(1) + '°', 0, 0);
ctx.restore();
}
// 绘制道路网络
// 计算指定道路的角度
drawRoad(intersections[0], intersections[2]); // T1-T3
calculateRoadAngle(intersections[0], intersections[2]);
drawRoad(intersections[3], intersections[4]); // T4-T5
calculateRoadAngle(intersections[3], intersections[4]);
drawRoad(intersections[10], intersections[6]); // T11-T7 (修改顺序)
calculateRoadAngle(intersections[10], intersections[6]);
drawRoad(intersections[7], intersections[8]); // T8-T9
calculateRoadAngle(intersections[7], intersections[8]);
// 绘制其他道路
drawRoad(intersections[1], intersections[9]); // T2-T10
drawRoad(intersections[5], intersections[7]); // T6-T8
drawRoad(intersections[11], intersections[3]); // T12-T4
// 绘制所有路口
intersections.forEach(drawIntersection);
// 恢复画布状态
ctx.restore();
// 绘制比例尺
ctx.beginPath();
ctx.moveTo(gridSize * 2, canvas.height - 20);
ctx.lineTo(gridSize * 5, canvas.height - 20);
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制刻度
ctx.beginPath();
ctx.moveTo(gridSize * 2, canvas.height - 15);
ctx.lineTo(gridSize * 2, canvas.height - 25);
ctx.moveTo(gridSize * 3, canvas.height - 15);
ctx.lineTo(gridSize * 3, canvas.height - 25);
ctx.moveTo(gridSize * 4, canvas.height - 15);
ctx.lineTo(gridSize * 4, canvas.height - 25);
ctx.moveTo(gridSize * 5, canvas.height - 15);
ctx.lineTo(gridSize * 5, canvas.height - 25);
ctx.lineWidth = 1;
ctx.stroke();
// 计算两个网格单位代表的实际距离(米)
const gridMeters = gridSize / scale;
ctx.fillStyle = '#000';
ctx.textAlign = 'center';
// 在刻度处显示距离
ctx.fillText('0', gridSize * 2, canvas.height - 5);
ctx.fillText(Math.round(gridMeters) + '', gridSize * 3, canvas.height - 5);
ctx.fillText(Math.round(gridMeters * 1) + '', gridSize * 4, canvas.height - 5);
ctx.fillText(Math.round(gridMeters * 2) + '米', gridSize * 5, canvas.height - 5);
</script>
</body>
</html>

740
tools/map_websocket.html Normal file
View File

@ -0,0 +1,740 @@
<!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>
<script src="https://unpkg.com/leaflet-rotatedmarker/leaflet.rotatedMarker.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;
}
.traffic-light-yellow {
background-color: yellow;
}
.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;
}
.countdown-yellow {
color: #ffff33;
}
.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;
}
.safety-border {
width: 100%;
height: 100%;
border: 2px solid;
background: none;
}
.emergency-border {
border-color: rgba(255, 0, 0, 0.8);
}
.core-border {
border-color: rgba(255, 165, 0, 0.6);
}
.warning-border {
border-color: rgba(255, 255, 0, 0.4);
}
</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 intersectionMarkers = 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;
// 将换行符转换为 HTML 换行
div.innerHTML = `${new Date().toLocaleTimeString()} - ${message.replace(/\n/g, '<br>')}`;
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';
// 创建或更新安全边框
const borders = [
{ size: 250, class: 'warning-border' }, // 外围预警区
{ size: 150, class: 'core-border' }, // 核心安全区
{ size: 100, class: 'emergency-border' } // 紧急制动区
];
borders.forEach(border => {
const borderId = id + '_' + border.class;
let borderMarker = markers.get(borderId);
if (!borderMarker) {
// 创建边框标记
borderMarker = L.marker(position, {
icon: L.divIcon({
className: 'safety-border ' + border.class,
iconSize: [border.size, border.size],
iconAnchor: [border.size / 2, border.size / 2]
}),
rotationAngle: data.heading || 0,
rotationOrigin: 'center center'
}).addTo(map);
markers.set(borderId, borderMarker);
} else {
// 更新边框位置和方向
borderMarker.setLatLng(position);
if (data.heading !== undefined) {
borderMarker.setRotationAngle(data.heading);
}
}
});
} 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),
rotationAngle: data.heading || 0,
rotationOrigin: 'center center'
}).addTo(map);
marker.bindTooltip(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;
// New function to update intersection traffic lights
function updateIntersectionTrafficLights(data) {
const intersectionId = data.intersection_id;
const position = [data.position.latitude, data.position.longitude];
const nsStatus = data.ns_status; // 0: red, 1: green, 2: yellow
const ewStatus = data.ew_status; // 0: red, 1: green, 2: yellow
const nsColor = nsStatus === 0 ? 'red' : nsStatus === 1 ? 'green' : 'yellow';
const ewColor = ewStatus === 0 ? 'red' : ewStatus === 1 ? 'green' : 'yellow';
// Calculate offset positions for NS and EW lights
const nsPositionOffset = [position[0] + 0.00003, position[1]]; // Slightly north
const ewPositionOffset = [position[0], position[1] + 0.00003]; // Slightly east
let currentMarkers = intersectionMarkers.get(intersectionId);
if (!currentMarkers) {
// Create new marker group
currentMarkers = {};
currentMarkers.nsMarker = L.marker(nsPositionOffset, {
icon: createIcon(`traffic-light traffic-light-${nsColor}`),
pane: 'markerPane' // Ensure lights are above roads
}).addTo(map).bindTooltip(`Intersection ${intersectionId} (NS)`);
currentMarkers.ewMarker = L.marker(ewPositionOffset, {
icon: createIcon(`traffic-light traffic-light-${ewColor}`),
pane: 'markerPane'
}).addTo(map).bindTooltip(`Intersection ${intersectionId} (EW)`);
intersectionMarkers.set(intersectionId, currentMarkers);
} else {
// Update existing markers
currentMarkers.nsMarker.setLatLng(nsPositionOffset);
currentMarkers.nsMarker.setIcon(createIcon(`traffic-light traffic-light-${nsColor}`));
currentMarkers.ewMarker.setLatLng(ewPositionOffset);
currentMarkers.ewMarker.setIcon(createIcon(`traffic-light traffic-light-${ewColor}`));
}
}
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);
// 忽略心跳响应
if (data.type === 'heartbeat') {
return;
}
let type = 'info'; // 默认类型
let message = '';
switch (data.type) {
case 'position_update':
type = 'position';
updatePosition(data);
message = `位置更新: ${data.object_id} (${data.object_type})\n` +
`位置: (${data.position.longitude.toFixed(6)}, ${data.position.latitude.toFixed(6)})\n` +
`航向: ${data.heading !== undefined ? data.heading.toFixed(2) + '°' : 'N/A'}\n` +
`速度: ${data.speed !== undefined ? data.speed.toFixed(2) + ' m/s' : 'N/A'}`;
break;
case 'intersection_traffic_light_status':
type = 'info';
updateIntersectionTrafficLights(data);
message = `路口交通灯状态更新:\n` +
`路口 ID: ${data.intersection_id}\n` +
`南北状态: ${data.ns_status === 0 ? '红' : data.ns_status === 1 ? '绿' : '黄'}\n` +
`东西状态: ${data.ew_status === 0 ? '红' : data.ew_status === 1 ? '绿' : '黄'}`;
break;
case 'collision_warning':
type = 'warning';
message = '收到碰撞预警:\n' + JSON.stringify(data, null, 2);
break;
case 'vehicle_command':
type = 'command';
console.log('收到指令消息:', data); // 调试日志
updateVehicleCommand(data.vehicleId, data.commandType);
// 为控制指令添加中文描述
const commandTypes = {
'SIGNAL': '信号灯',
'ALERT': '告警',
'WARNING': '预警',
'RESUME': '恢复',
'PARKING': '安全停靠'
};
const reasons = {
'TRAFFIC_LIGHT': '红绿灯控制',
'AIRCRAFT_CROSSING': '航空器交叉',
'SPECIAL_VEHICLE': '特勤车辆',
'AIRCRAFT_PUSH': '航空器推出',
'RESUME_TRAFFIC': '恢复通行',
'PARKING_SIDE': '安全停靠'
};
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;
default:
message = '收到未知类型消息:\n' + JSON.stringify(data, null, 2);
}
log(message, type);
} catch (e) {
log('消息解析错误: ' + e.message, 'error');
}
};
} 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, key) => {
map.removeLayer(marker);
});
markers.clear();
// 清除旧的红绿灯标记
// trafficLights.forEach(light => map.removeLayer(light));
// trafficLights.clear();
// 清除路口红绿灯标记
intersectionMarkers.forEach((markers, id) => {
if (markers.nsMarker) map.removeLayer(markers.nsMarker);
if (markers.ewMarker) map.removeLayer(markers.ewMarker);
});
intersectionMarkers.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>

1162
tools/mock_server.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,109 @@
import time
import json
import urllib.request
import urllib.error
import logging
import os
import sys
# --- 配置 ---
CPP_SERVER_URL = "http://localhost:8082/trafficlight" # C++ TrafficLightHttpServer 的地址和端口
SEND_INTERVAL = 1.0 # 发送间隔(秒)
# 红绿灯循环周期 (North/South State, East/West State, Duration in seconds)
TRAFFIC_LIGHT_CYCLE = [
("GREEN", "RED", 10),
("YELLOW", "RED", 2),
("RED", "GREEN", 10),
("RED", "YELLOW", 2),
]
# --- 日志设置 ---
LOG_DIR = 'logs'
if not os.path.exists(LOG_DIR):
try:
os.makedirs(LOG_DIR)
except OSError as e:
print(f"Error creating log directory {LOG_DIR}: {e}", file=sys.stderr)
LOG_DIR = '.' # Fallback to current directory
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(LOG_DIR, 'mock_traffic_light_client.log')),
logging.StreamHandler() # 同时输出到控制台
]
)
# --- 全局状态 ---
current_cycle_index = 0
last_cycle_switch_time = time.time()
# --- 辅助函数 ---
def generate_di_payload(ns_state, ew_state):
"""根据南北和东西状态生成 DI 信号 Payload。"""
# 初始化所有 DI 信号为 0
payload = {f"DI-{i:02d}": 0 for i in range(1, 19)}
# 设置南北向状态 (DI-11: 北红, DI-12: 北黄, DI-13: 北绿)
if ns_state == "RED": payload["DI-11"] = 1
elif ns_state == "YELLOW": payload["DI-12"] = 1
elif ns_state == "GREEN": payload["DI-13"] = 1
# 设置东西向状态 (DI-14: 东红, DI-15: 东黄, DI-16: 东绿)
if ew_state == "RED": payload["DI-14"] = 1
elif ew_state == "YELLOW": payload["DI-15"] = 1
elif ew_state == "GREEN": payload["DI-16"] = 1
return payload
def send_traffic_light_update(url, payload):
"""使用 urllib 发送 POST 请求。"""
try:
data = json.dumps(payload).encode('utf-8')
req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'}, method='POST')
# 设置一个较短的超时时间,避免长时间阻塞
with urllib.request.urlopen(req, timeout=0.8) as response:
logging.debug(f"Sent: {payload}, Status: {response.status}")
# 可以选择性地读取响应内容
# response.read()
except (urllib.error.URLError, TimeoutError, ConnectionRefusedError) as e:
logging.error(f"Failed to send to {url}: {e}")
except Exception as e:
logging.error(f"Unexpected error during send: {e}")
# --- 主循环 ---
def traffic_light_sender_loop():
"""主循环,管理状态切换并发送数据。"""
global current_cycle_index, last_cycle_switch_time
logging.info(f"Starting traffic light client, sending to {CPP_SERVER_URL}")
while True:
try:
now = time.time()
# 检查是否需要切换循环阶段
current_ns_state, current_ew_state, duration = TRAFFIC_LIGHT_CYCLE[current_cycle_index]
if now - last_cycle_switch_time >= duration:
current_cycle_index = (current_cycle_index + 1) % len(TRAFFIC_LIGHT_CYCLE)
last_cycle_switch_time = now
next_ns_state, next_ew_state, _ = TRAFFIC_LIGHT_CYCLE[current_cycle_index]
logging.info(f"Switching phase: {current_ns_state}/{current_ew_state} -> {next_ns_state}/{next_ew_state}")
# 更新当前状态以供本次发送
current_ns_state, current_ew_state, _ = TRAFFIC_LIGHT_CYCLE[current_cycle_index]
# 生成 Payload 并发送
payload = generate_di_payload(current_ns_state, current_ew_state)
send_traffic_light_update(CPP_SERVER_URL, payload)
# 等待指定间隔
time.sleep(SEND_INTERVAL)
except KeyboardInterrupt:
logging.info("Traffic light client stopped by user.")
break
except Exception as e:
logging.error(f"Error in main loop: {e}", exc_info=True)
# 等待一段时间再重试,避免快速失败循环
time.sleep(5)
# --- 启动入口 ---
if __name__ == '__main__':
traffic_light_sender_loop()

135
tools/test_websocket.html Normal file
View File

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<title>WebSocket 测试</title>
<style>
#messages {
width: 100%;
height: 800px;
overflow-y: auto;
border: 1px solid #ccc;
margin-bottom: 10px;
padding: 10px;
font-family: monospace;
}
.error { color: red; }
.success { color: green; }
.info { color: blue; }
.position { color: #666; }
.warning { color: #f90; }
.command { color: #800080; } /* 紫色显示控制指令 */
</style>
</head>
<body>
<h2>WebSocket 测试客户端</h2>
<div id="messages"></div>
<div>
<button onclick="connect()">连接</button>
<button onclick="disconnect()">断开</button>
<button onclick="clearMessages()">清空日志</button>
</div>
<script>
let ws = null;
const messagesDiv = document.getElementById('messages');
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 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);
const formattedData = JSON.stringify(data, null, 2);
// 根据消息类型使用不同的样式
let type = 'info';
let message = '收到消息:\n' + formattedData;
switch (data.type) {
case 'position_update':
type = 'position';
break;
case 'collision_warning':
type = 'warning';
break;
case 'vehicle_command':
type = 'command';
// 为控制指令添加中文描述
const commandTypes = {
'SIGNAL': '信号灯',
'ALERT': '告警',
'WARNING': '预警',
'RESUME': '恢复',
'PARKING': '安全停靠'
};
const reasons = {
'TRAFFIC_LIGHT': '红绿灯控制',
'AIRCRAFT_CROSSING': '航空器交叉',
'SPECIAL_VEHICLE': '特勤车辆',
'AIRCRAFT_PUSH': '航空器推出',
'RESUME_TRAFFIC': '恢复通行',
'PARKING_SIDE': '安全停靠'
};
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;
}
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;
}
</script>
</body>
</html>