commit 1fc14d7438ce7934813afc1b4b51e28032a7bb06 Author: sladro Date: Tue Jan 27 15:24:05 2026 +0800 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1ab6e90 --- /dev/null +++ b/.clang-format @@ -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 \ No newline at end of file diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..2463b1a --- /dev/null +++ b/.clangd @@ -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 \ No newline at end of file diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..f90de47 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,3 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +.cursorignore +build/ diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..bb6293d --- /dev/null +++ b/.cursorrules @@ -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 进行性能测试 +- 在头文件中定义函数,在源文件中实现函数 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd1febf --- /dev/null +++ b/.gitignore @@ -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* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..09c3fdd --- /dev/null +++ b/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e819168 --- /dev/null +++ b/README.md @@ -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 服务: + +### 模拟场景 + +- 航空器: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) - 恢复指令 diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..204db36 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,7 @@ +{ + "commands": [ + "cd build", + "cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..", + "ln -s build/compile_commands.json ." + ] +} diff --git a/config/airport_bounds.json b/config/airport_bounds.json new file mode 100644 index 0000000..3d1d153 --- /dev/null +++ b/config/airport_bounds.json @@ -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 + } + } + } + } +} \ No newline at end of file diff --git a/config/intersections.json b/config/intersections.json new file mode 100644 index 0000000..c5c0ad8 --- /dev/null +++ b/config/intersections.json @@ -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 + } + } + ] +} \ No newline at end of file diff --git a/config/system_config.json b/config/system_config.json new file mode 100644 index 0000000..f9ae417 --- /dev/null +++ b/config/system_config.json @@ -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路口" +} \ No newline at end of file diff --git a/config/unmanned_vehicles.json b/config/unmanned_vehicles.json new file mode 100644 index 0000000..4ec60a2 --- /dev/null +++ b/config/unmanned_vehicles.json @@ -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 + } + ] +} \ No newline at end of file diff --git a/docs/airport_bounds_description.md b/docs/airport_bounds_description.md new file mode 100644 index 0000000..74e0610 --- /dev/null +++ b/docs/airport_bounds_description.md @@ -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. 高度阈值应考虑实际运营需求和安全裕度 diff --git a/docs/configuration_guide.md b/docs/configuration_guide.md new file mode 100644 index 0000000..f1b892d --- /dev/null +++ b/docs/configuration_guide.md @@ -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 +- 更新内容:初始版本 + + diff --git a/docs/configuration_guide.pdf b/docs/configuration_guide.pdf new file mode 100644 index 0000000..bac4e09 Binary files /dev/null and b/docs/configuration_guide.pdf differ diff --git a/docs/conflict_detection_algorithms.md b/docs/conflict_detection_algorithms.md new file mode 100644 index 0000000..9b3d14e --- /dev/null +++ b/docs/conflict_detection_algorithms.md @@ -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 occupants; // 占用该格子的物体ID + }; + + bool checkPathConflict(const Vehicle& v1, const Vehicle& v2, + const std::vector& 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. 扩展性要求 + - 支持新增检测算法 + - 支持参数配置调整 + - 支持不同类型交通工具 diff --git a/docs/deployment_guide.md b/docs/deployment_guide.md new file mode 100644 index 0000000..d59e3b6 --- /dev/null +++ b/docs/deployment_guide.md @@ -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 启动失�� + +- 检查 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. 联系技术支持团队 diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..26b8ef8 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,1070 @@ +# 机场碰撞预警系统设计文档 + +## 1. 系统概述 + +### 1.1 系统目标 + +实现机场内航空器与车辆的实时位置监控和碰撞预警。 + +### 1.2 主要功能 + +- 实时获取航空器位置数据 +- 实时获取车辆位置数据 +- 碰撞风险检测 +- 预警信息输出 + +## 2. 系统架构 + +### 2.1 整体架构 + +- 数据采集层:负责从外部系统获取位置数据 +- 数据处理层:进行坐标转换和数据规范化 +- 碰撞检测层:执行碰撞风险分析 +- 预警输出层:生成和发送预警信息 + +### 2.2 核心模块 + +1. System:系统总控模块 +2. DataCollector:数据采集模块 +3. CollisionDetector:碰撞检测模块 +4. CoordinateConverter:坐标转换模块 + +## 3. 数据模型 + +### 3.1 基础数据类型 + +```cpp +// 移动物体类型 +enum class MovingObjectType { + AIRCRAFT, // 航空器 + SPECIAL, // 特勤车 + UNMANNED // 无人车(可控车辆) +}; + +// 二维向量 +struct Vector2D { + double x; // 东西方向(米) + double y; // 南北方向(米) + + double magnitude() const; // 计算向量大小 + double direction() const; // 计算向量方向 +}; + +// 地理坐标 +struct GeoPosition { + double latitude; // 纬度 + double longitude; // 经度 +}; + +// 位置记录 +struct PositionRecord { + GeoPosition geo; // 地理位置 + uint64_t timestamp; // 时间戳 +}; + +// 移动物体基类 +class MovingObject { +public: + std::string id; // 唯一标识 + Vector2D position; // 平面坐标 + GeoPosition geo; // 地理坐标 + double heading; // 航向角 + double speed; // 速度 + int64_t timestamp; // 时间戳 + MovingObjectType type; // 物体类型 + std::deque positionHistory; // 位置历史记录 + + virtual bool isValidPosition(const GeoPosition& newPos) const = 0; + virtual bool isValidSpeed(double speed) const = 0; + void updateMotion(const GeoPosition& newPos, uint64_t newTime); +}; +``` + +### 3.2 业务数据类型 + +```cpp +// 航空器 +class Aircraft : public MovingObject { +public: + std::string flightNo; // 航班号 + std::string trackNumber; // 航迹号 + double altitude; // 高度(米) + + static constexpr double MAX_SPEED = 100.0; // 最大速度(米/秒) + static constexpr double MAX_POSITION_JUMP = 50.0; // 最大位置跳变(米) + + bool isValidPosition(const GeoPosition& newPos) const override; + bool isValidSpeed(double speed) const override; +}; + +// 车辆 +class Vehicle : public MovingObject { +public: + std::string vehicleNo; // 车牌号 + bool isControllable; // 是否可控 + + static constexpr double MAX_SPEED = 20.0; // 最大速度(米/秒) + static constexpr double MAX_POSITION_JUMP = 10.0; // 最大位置跳变(米) + + bool isValidPosition(const GeoPosition& newPos) const override; + bool isValidSpeed(double speed) const override; +}; +``` + +### 3.3 配置数据类型 + +```cpp +// 可控车辆配置 +struct ControllableVehicleConfig { + std::string vehicleNo; // 车牌号 + std::string ip; // 车辆IP地址 + int port; // 车辆端口号 +}; +``` + +## 4. 接口设计 + +### 4.1 数据源接口 + +```cpp +class DataSource { + virtual std::vector getAircraftData() = 0; + virtual std::vector getVehicleData() = 0; +}; +``` + +### 4.2 碰撞检测接口 + +```cpp +class CollisionDetector { + bool detectAircraft(const Aircraft& a1, const Aircraft& a2); + bool detectAircraftVehicle(const Aircraft& aircraft, const Vehicle& vehicle); +}; +``` + +## 5. 碰撞检测算法 + +### 5.1 碰撞类型 + +系统支持以下碰撞类型: + +1. HEAD_ON:相向碰撞(航向差大于 150 度) +2. CROSSING:交叉碰撞(航向差在 15-165 度之间) +3. PARALLEL:平行碰撞(航向差小于 30 度) +4. STATIC:静止碰撞(双方速度接近 0) + +### 5.2 风险等级 + +```cpp +enum class RiskLevel { + NONE = 0, // 无风险 + WARNING = 1, // 预警:进入预警区域 + CRITICAL = 2 // 告警:进入危险区域 +}; + +enum class WarningZoneType { + NONE = 0, // 无风险区域 + WARNING = 1, // 预警区域 + DANGER = 2 // 危险区域 +}; +``` + +### 5.3 碰撞检测流程 + +1. 距离检测: + + ```cpp + // 计算当前距离 + 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); + + // 如果当前距离大于警告区域,直接返回不会碰撞 + if (current_distance > (warning_radius1 + warning_radius2)) { + return false; + } + ``` + +2. 碰撞类型判断: + + ```cpp + double angle_diff = getAngleDifference(heading1, heading2); + if (angle_diff > 150.0) { + // 相向碰撞检测 + type = CollisionType::HEAD_ON; + } else if (angle_diff > 15.0 && angle_diff < 165.0) { + // 交叉碰撞检测 + type = CollisionType::CROSSING; + } else { + // 平行碰撞检测 + type = CollisionType::PARALLEL; + } + ``` + +3. 相对运动分析: + + ```cpp + // 计算速度分量 + double vx1 = speed1 * std::cos((90.0 - heading1) * M_PI / 180.0); + double vy1 = speed1 * std::sin((90.0 - heading1) * M_PI / 180.0); + double vx2 = speed2 * std::cos((90.0 - heading2) * M_PI / 180.0); + double vy2 = speed2 * std::sin((90.0 - heading2) * M_PI / 180.0); + + // 计算相对运动 + double rel_vx = vx2 - vx1; + double rel_vy = vy2 - vy1; + double rel_speed = std::sqrt(rel_vx*rel_vx + rel_vy*rel_vy); + ``` + +4. 碰撞预测: + + ```cpp + // 在时间窗口内采样检查 + for (int i = 1; i <= STEPS; ++i) { + double t = i * dt; + + // 计算t时刻的位置 + Vector2D future1 = predictPosition(pos1, speed1, heading1, t); + Vector2D future2 = predictPosition(pos2, speed2, heading2, t); + + // 计算t时刻的距离 + double distance = calculateDistance(future1, future2); + + // 检查是否会碰撞 + if (distance <= safe_distance) { + return true; + } + } + ``` + +### 5.4 碰撞记录管理 + +```cpp +struct CollisionRecord { + Vector2D collisionPoint; // 冲突点位置 + int64_t detectedTime; // 检测到冲突的时间 + RiskLevel maxLevel; // 历史最高风险等级 + bool resolved; // 是否已解除 +}; +``` + +碰撞解除条件: + +1. 对于已通过交叉点的情况: + - 物体距离交叉点的距离大于安全距离 +2. 对于未通过交叉点的情况: + - 两个物体都在安全距离之外 + - 预测不会发生碰撞 + +### 5.5 区域特定阈值 + +不同区域采用不同的安全距离阈值: + +| 区域 | 航空器地面阈值 | 车辆阈值 | +|----------|----------------|-----------| +| 跑道 | 100米 | 50米 | +| 滑行道 | 50米 | 30米 | +| 停机位 | 40米 | 20米 | +| 服务区 | 30米 | 15米 | + +### 5.6 空间优化 + +使用四叉树进行空间索引,优化查询性能: + +1.四叉树构建: + +```cpp +QuadTree vehicleTree_(bounds, 8); // 容量为8的四叉树 +``` + +2.邻近查询: + +```cpp +auto nearbyVehicles = vehicleTree_.queryNearby( + position, // 中心点 + threshold // 查询半径 +); +``` + +### 5.7 性能考虑 + +1. 空间复杂度: + - 四叉树:O(n),其中 n 为车辆数量 + - 航空器列表:O(m),其中 m 为航空器数量 + +2. 时间复杂度: + - 四叉树查询:O(log n) + - 总体碰撞检测:O(m * log n) + +### 5.8 安全保障 + +1. 距离冗余: + - 使用阈值的一半作为直接报警条件 + - 为不同区域设置合适的安全距离 + +2. 运动预测: + - 考虑物体的相对运动 + - 提前发现潜在碰撞风险 + +3. 降级处理: + - 当无法计算相对运动时,仅使用距离判断 + - 保证基本的安全检测功能 + +### 5.9 碰撞检测主流程 + +```cpp +// 加载可控车辆配置 +std::vector controllableVehicles = loadControllableVehicleConfig(); + +for (const auto& aircraft : aircrafts) { + for (const auto& vehicle : vehicles) { + if (detectCollision(aircraft, vehicle)) { + // 检查是否为可控车辆 + auto iter = std::find_if(controllableVehicles.begin(), controllableVehicles.end(), + [&](const ControllableVehicleConfig& config) { + return config.vehicleNo == vehicle.vehicleNo; + }); + + if (iter != controllableVehicles.end()) { + // 生成控制指令 + auto command = generateCommand(aircraft, vehicle); + // 发送控制指令 + sendCommand(command, iter->ip, iter->port); + } else { + // 发送普通预警 + sendWarning(aircraft, vehicle); + } + } + } +} +``` + +### 5.10 可控车辆配置加载 + +从配置文件加载可控车辆信息: + +```cpp +std::vector loadControllableVehicleConfig() { + std::vector configs; + + // 读取配置文件 + std::ifstream file("controllable_vehicles.json"); + nlohmann::json jsonConfig; + file >> jsonConfig; + + // 解析配置项 + for (const auto& item : jsonConfig["vehicles"]) { + ControllableVehicleConfig config; + config.vehicleNo = item["vehicleNo"].get(); + config.ip = item["ip"].get(); + config.port = item["port"].get(); + configs.push_back(config); + } + + return configs; +} +``` + +配置文件 `controllable_vehicles.json` 的格式如下: + +```json +{ + "vehicles": [ + { + "vehicleNo": "VEH001", + "ip": "192.168.1.101", + "port": 8080 + }, + { + "vehicleNo": "VEH002", + "ip": "192.168.1.102", + "port": 8080 + }, + { + "vehicleNo": "VEH003", + "ip": "192.168.1.103", + "port": 8080 + } + ] +} +``` + +## 6. 坐标转换 + +### 6.1 转换方法 + +- 使用等角投影方法 +- 基于机场参考点(青岛胶东国际机场:北纬36°21'43.2",东经120°05'16.8") +- 考虑地球曲率影响 +- WGS84椭球体参数 + +## 7. 数据更新策略 + +### 7.1 数据采集 + +- 定时轮询(默认1秒) +- 独立的数据采集线程 +- 分别缓存航空器和车辆数据 + +### 7.2 碰撞检测 + +- 实时检测(默认100ms) +- 独立的检测线程 +- 分类处理不同类型的碰撞风险 + +## 8. 开发规范 + +### 8.1 代码规范 + +- Google C++ Style +- 智能指针管理 +- 异常安全 +- RAII原则 + +### 8.2 性能规范 + +- 避免虚函数滥用 +- 减少动态内存分配 +- 合理使用模板 +- 注意数据对齐 + +### 8.3 网络编程规范 + +#### 8.3.1 数据读取策略 + +- 使用分块读取代替一次性读取 +- 合理设置分块大小(如1KB) +- 正确处理部分读取的情况 +- 验证数据完整性 + +#### 8.3.2 错误处理 + +- 实现请求重试机制 +- 设置合理的超时时间 +- 正确处理连接断开 +- 完善的错误日志记录 + +#### 8.3.3 连接管理 + +- 及时关闭无用连接 +- 正确处理连接状态 +- 实现连接池(如需要) +- 处理并发连接 + +#### 8.3.4 性能考虑 + +- 使用异步操作处理并发 +- 实现数据缓存机制 +- 免频繁建立连接 +- 合理控制缓冲区大小 + +#### 8.3.5 最佳实践 + +```cpp +// 分块读取示例 +const size_t chunk_size = 1024; // 1KB per chunk +while (total_read < content_length) { + size_t to_read = std::min(chunk_size, content_length - total_read); + // 读取数据块 + // 处理错误 + // 更新计数 +} + +// 重试机制示例 +const int max_retries = 3; +for (int retry = 0; retry < max_retries; ++retry) { + try { + // 尝试操作 + break; // 成功则退出 + } catch (...) { + if (retry == max_retries - 1) throw; // 最后一次重试失败 + // 等待后重试 + } +} +``` + +### 8.4 HTTP 数据读取规范 + +#### 8.4.1 问题描述 + +在使用 Boost.Asio 读取 HTTP 响应时,可能会遇到 "End of file" 错误。这个错误通常发生在: + +- 读取响应体时 +- 服务器已关闭连接 +- 数据未完全读取 + +#### 8.4.2 错误原因分析 + +1. HTTP 响应头读取时可能已包含部分响应体数据 +2. 忽略缓冲区中的这部分数据会导致: + - 数据丢失 + - 读取位置错误 + - 连接提前关闭 + +#### 8.4.3 解决方案 + +1. 正确处理响应数据: + +```cpp +// 1. 读取响应头 +size_t header_length = asio::read_until(*socket_, response_buf, "\r\n\r\n"); + +// 2. 处理缓冲区中的响应体数据 +size_t body_part = response_buf.size() - header_length; +if (body_part > 0) { + // 提取已缓冲的响应体数据 + body.append( + asio::buffers_begin(response_buf.data()) + header_length, + asio::buffers_end(response_buf.data()) + ); + total_read += body_part; +} + +// 3. 继续读取剩余数据 +while (total_read < content_length) { + // 读取剩余的响应体 +} +``` + +#### 8.4.4 最佳实践 + +1. 数据读取原则: + - 使用简单直接的读取逻辑 + - 准确跟踪已读取的数据量 + - 正确处理缓冲区中的所有数据 + - 避免复杂的重试机制 + +2. 错误处理: + - 及时检查错误码 + - 提供详细的错误日志 + - 在错误发生时及时关闭连接 + - 保持连接状态的一致性 + +3. 数据完整性: + - 验证 Content-Length + - 确保读取完整的响应体 + - 避免数据丢失或重复 + - 正确处理分块数据 + +4. 日志记录: + - 记录关键操作步骤 + - 包含详细的错误信息 + - 记录数据读取进度 + - 便于问题诊断和调试 + +## 9. 测试策略 + +### 9.1 单元测试 + +- 模块功能测试 +- 边界条件测试 +- 异常处理测试 +- 内存泄漏测试 +- 坐标转换精度测试 + +### 9.2 性能测试 + +- 延迟测试 +- 并发测试 +- 压力测试 +- 内存使用测试 +- 坐标转换性能测试 + +## 9. 测试场景 + +### 9.1 碰撞风险场景模拟 + +#### 9.1.1 场景描述 + +模拟一个典型的地面碰撞风险场景: + +- 航空器在跑道上滑行 +- 地面车辆垂直接近跑道 +- 两者轨迹存在交叉点 +- 如不采取措施将发生碰撞 + +#### 9.1.2 场景参数 + +1. 航空器参数: + - 初始位置:跑道西端 (AIRPORT_LON - 0.002, AIRPORT_LAT) + - 运动方向:向东滑行 + - 运动速度:0.0002经度/秒 + - 高度:5米(地面滑行高度) + +2. 车辆参数: + - 初始位置:跑道南侧 (AIRPORT_LON, AIRPORT_LAT - 0.002) + - 运动方向:向北行驶 + - 运动速度:0.0002纬度/秒 + - 与航空器轨迹垂直相交 + +3. 时间参数: + - 更新频率:每秒一次 + - 场景周期:10秒 + - 碰撞风险时间:约5秒(场景中点) + +#### 9.1.3 数据特点 + +1. 位置数据: + - 只提供实时位置信息 + - 不包含速度信息 + - 不包含方向信息 + - 位置精度:小数点后6位 + +2. 数据格式: + +```json +// 航空器数据 +{ + "flightNo": "CES2501", + "longitude": 120.088003, + "latitude": 36.361999, + "time": 1700123456, +} + +// 车辆数据 +{ + "vehicleNo": "VEH001", + "longitude": 120.088003, + "latitude": 36.361999, + "time": 1700123456 +} +``` + +#### 9.1.4 预期结果 + +1. 碰撞检测: + - 在交叉点附近应触发预警 + - 预警时间:碰撞前约5秒 + - 持续时间:直到危险解除 + +2. 风险等级: + - 距离大于100米:低风险 + - 距离50-100米:中等风险 + - 距离小于50米:高风险 + +3. 系统响应: + - 实时计算相对距离 + - 及时发出预警信息 + - 记录完整的碰撞风险过程 + +## 10. 风险控制 + +### 10.1 技术风险 + +- 实时性保证 +- 内存管理 +- 多线程同步 +- 系统稳定性 +- 坐标转换精度 + +### 10.2 解决方案 + +- 性能优化 +- 资源监控 +- 故障恢复 +- 降级处理 +- 坐标转换验证 + +## 11. 后续优化 + +1. 引入GPU加速计算 +2. 优化空间索引算法 +3. 改进故障检测机制 +4. 增加自适应负载均衡 +5. 完善监控告警系统 +6. 优化坐标转换算法 +7. 支持多种投影方式 + +使用纯C++实现可以获得: + +1. 最佳的实时性能 +2. 直接的硬件控制 +3. 精确的内存管理 +4. 最小的系统开销 +5. 可预测的行为表现 + +## 10. 单元测试 + +### 10.1 测试框架 + +- 使用 Google Test 框架 +- 支持 EXPECT 和 ASSERT 断言 +- 支持 Mock 对象 +- 支持测试夹具(Test Fixtures) + +### 10.2 测试用例组织 + +#### 10.2.1 基础类型测试 (BasicTypesTest) + +1. Vector2D 测试 + - 测试向量大小计算 + - 测试方向角计算 + +2. MovingObject 测试 + - 测试速度计算 + - 测试航向角计算 + - 验证位置历史记录 + +3. 数据有效性测试 + - 测试航空器数据 + - 测试车辆数据 + - 验证速度和位置限制 + +#### 10.2.2 碰撞检测测试 (CollisionDetectorTest) + +1. 安全距离测试 + - 测试水平安全距离 + - 测试垂直安全距离 + +2. 边界条件测试 + - 测试临界距离 + - 测试高度边界 + +3. 特殊情况测试 + - 测试零距离情况 + - 测试对角线距离 + +#### 10.2.3 数据采集测试 (DataCollectorTest) + +1. 速度计算测试 + + ```cpp + // 测试数据生成 + double lon = 120.08 + (LON_CHANGE_PER_SEC * i); // 每秒经度变化 + uint64_t timestamp = baseTime + (i * 1); // 每秒一个点 + + // 验证速度计算 + EXPECT_NEAR(aircraft.speed, 55.0, 5.0); // 允许5m/s误差 + ``` + +2. Mock 数据源 + + ```cpp + class MockDataSource : public DataSource { + MOCK_METHOD(bool, connect, (), (override)); + MOCK_METHOD(bool, fetchAircraftData, (std::vector&), (override)); + MOCK_METHOD(bool, fetchVehicleData, (std::vector&), (override)); + }; + ``` + +### 10.3 测试数据生成 + +#### 10.3.1 位置数据 + +1. 航空器数据 + - 基于青岛胶东机场坐标 + - 模拟地面滑行场景 + - 生成连续位置点 + +2. 车辆数据 + - 模拟地面车辆运动 + - 生成可能的碰撞场景 + +#### 10.3.2 时间序列 + +1. 时间戳生成 + - 使用 Unix 时间戳 + - 每秒更新一次 + - 保证时间连性 + +2. 数据更新频率 + - 模拟实际系统的更新频率 + - 确保数据连续性 + - 避免时间戳重复 + +### 10.4 测试覆盖率 + +1. 基础功能测试 + - 坐标转换 + - 速度计算 + - 航向计算 + +2. 错误处理测试 + - 无效数据处理 + - 连接错误处理 + - 数据解析错误 + +3. 边界条件测试 + - 最大/最小速度 + - 位置跳变限制 + - 时间戳异常 + +### 10.5 测试最佳实践 + +1. 测试数据准备 + - 使用实际的地理坐标 + - 模拟真实的运动场景 + - 考虑数据精度 + +2. Mock 对象使用 + - 模拟外部依赖 + - 控制测试环境 + - 验证交互逻辑 + +3. 日志记录 + - 记录测试过程 + - 输出中间结果 + - 便于问题诊断 + +## 11. 可控车辆通信 + +### 11.1 通信方式 + +使用 HTTP 协议与可控车辆通信: + +- 碰撞检测系统作为 HTTP 客户端,向可控车辆发送 POST 请求 +- 请求地址格式为: `http://:/command` +- 请求 Body 中包含控制指令的 JSON 表示 +- 可控车辆作为 HTTP 服务器,接收并处理控制指令 + +### 11.2 指令格式 + +使用 JSON 格式表示控制指令: + +```json +POST /command HTTP/1.1 +Host: : +Content-Type: application/json + +{ + "command": "stop", + "timestamp": 1700123456 +} +``` + +或者: + +```json +POST /command HTTP/1.1 +Host: : +Content-Type: application/json + +{ + "command": "change_route", + "route": [ + {"longitude": 120.08, "latitude": 36.36}, + {"longitude": 120.09, "latitude": 36.37}, + {"longitude": 120.10, "latitude": 36.38} + ], + "timestamp": 1700123456 +} +``` + +### 11.3 可控车辆响应 + +可控车辆收到指令后,需要及时响应: + +1. 立即执行停车、减速等指令 +2. 如果是改变路线,则规划新的路径并切换 +3. 将执行结果以 JSON 格式返回: + +```json +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "status": "ok", + "message": "Command executed successfully", + "timestamp": 1700123456 +} +``` + +或者: + +```json +HTTP/1.1 500 Internal Server Error +Content-Type: application/json + +{ + "status": "error", + "message": "Failed to execute command", + "error": "No route found", + "timestamp": 1700123456 +} +``` + +### 11.4 安全考虑 + +由于 HTTP 是明文传输协议,存在安全隐患。需要采取以下措施: + +1. 使用 HTTPS 代替 HTTP,实现加密通信 +2. 对可控车辆的访问进行身份验证,防止非法控制 +3. 对控制指令进行数字签名,防止指令被篡改 +4. 对敏感数据(如路径点坐标)进行加密,防止泄露 + +### 11.5 可用性考虑 + +为保证可控车辆的控制链路高可用,需要: + +1. 监控可控车辆的 HTTP 服务可用性,发现异常及时告警 +2. 对控制指令的发送进行重试和超时处理,避免单次请求失败导致控制中断 +3. 考虑引入备用的控制方式(如 MQTT),作为 HTTP 不可用时的降级方案 + +### 11.6 指令发送 + +根据可控车辆的IP地址和端口号,发送控制指令: + +```cpp +void sendCommand(const ControlCommand& command, const std::string& ip, int port) { + // 创建HTTP客户端 + HttpClient client(ip, port); + + // 构造请求 + HttpRequest request; + request.setMethod(HttpRequest::POST); + request.setPath("/command"); + request.setHeader("Content-Type", "application/json"); + request.setBody(command.toJson()); + + // 发送请求 + HttpResponse response = client.send(request); + + // 处理响应 + if (response.getStatus() == HttpResponse::OK) { + // 指令发送成功 + logger.info("Command sent successfully"); + } else { + // 指令发送失败 + logger.error("Failed to send command, status code: {}", response.getStatus()); + } +} +``` + +使用 HTTP 客户端库,向可控车辆发送 POST 请求。请求路径为 `/command`,请求体为控制指令的 JSON 表示。根据响应状态码判断指令是否发送成功。 + +## 遗留问题 + +### 1. Python 包依赖问题 + +- 当前部署脚本在 Python 3.6 环境下存在依赖问题 +- 需要添加 importlib-metadata 和 zipp 包 +- 需要调整包版本以兼容 Python 3.6 +- 考虑使用系统自带的 Python 包替代 pip 安装 + +### 2. 部署脚本改进 + +- prepare_deploy.sh 中下载 Python 包时需要包含所有依赖 +- deploy.sh 中需要按正确顺序安装 Python 包 +- 考虑使用 requirements.txt 管理 Python 依赖 +- 需要优化部署脚本的错误处理和日志输出 + +### 3. 生产环境适配 + +- 需要适配离线环境的部署需求 +- 需要完善 SELinux 相关的配置 +- 需要优化日志文件的管理 +- 需要完善服务启动失败的故障排查机制 + +### 4. 后续优化建议 + +- 简化部署步骤,减少人工干预 +- 添加部署回滚机制 +- 添加部署验证步骤 +- 完善部署文档 +- 简化部署步骤,减少人工干预 +- 添加部署回滚机制 +- 添加部署验证步骤 +- 完善部署文档 + +## 机场边界设计 + +### 问题描述 + +实际机场通常是倾斜的(如东南-西北走向),需要一个合适的方案来判断车辆和飞机是否在机场各个区域内。 + +### 设计方案 + +#### 坐标系统设计 + +1. 世界坐标系 + - 使用标准的笛卡尔坐标系 + - X 轴指向东,Y 轴指向北 + +2. 机场局部坐标系 + - 原点:可配置的机场参考点 + - 方向:通过旋转角度将世界坐标系对齐到机场主轴 + - 旋转规则: + - 以正北方向为基准 + - 正值表示逆时针旋转 + - 负值表示顺时针旋转 + - 例:东南-西北走向的机场(以青岛国际机场为例),从东南指向西北为 -21.47 度 + +#### 核心组件 + +1. `AirportBounds` 类 + - 负责管理机场边界和区域定义 + - 提供坐标转换功能 + - 判断位置所属区域 + +2. 配置文件 (`airport_bounds.json`) + + ```json + { + "airport": { + "rotation_angle": -45.0, // 机场旋转角度(度) + "reference_point": { // 机场参考点 + "x": 0.0, + "y": 0.0 + }, + "bounds": { // 机场总边界 + "x": -2000, + "y": -1000, + "width": 4000, + "height": 2000 + } + }, + "areas": { // 各功能区定义 + // ... 具体区域配置 + } + } + ``` + +#### 坐标转换流程 + +1. 位置判断流程 + - 接收世界坐标系中的点坐标 + - 将点坐标转换到机场局部坐标系 + - 在局部坐标系中进行边界检查 + +2. 坐标转换算法 + + ```cpp + Vector2D toAirportCoordinate(const Vector2D& point) { + // 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_); + + return Vector2D{ + dx * cos_angle + dy * sin_angle, + -dx * sin_angle + dy * cos_angle + }; + } + ``` + +### 优势 + +1. 实现简单,易于理解和维护 +2. 计算开销小 +3. 不需要修改现有的碰撞检测逻辑 +4. 可以方便地调整机场方向 +5. 支持不同区域的差异化配置 + +### 使用说明 + +1. 确定机场实际旋转角度 +2. 选择合适的参考点(通常是机场中心或跑道中心) +3. 在机场局部坐标系中定义各个区域的边界 +4. 配置各区域的安全参数(如碰撞半径、警告区域等) + +### 注意事项 + +1. 角度配置采用度数(而非弧度) +2. 顺时针旋转使用负角度 +3. 确保参考点选择合理,便于区域划分 +4. 区域边界在局部坐标系中定义 + +如果需要计算角度(比如判断车辆朝向),则使用向量点积方法 diff --git a/docs/docker_deployment_design.md b/docs/docker_deployment_design.md new file mode 100644 index 0000000..28ee624 --- /dev/null +++ b/docs/docker_deployment_design.md @@ -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. 启动���务 + +```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. 处理方法 + +- 检查容器日志 +- 验证配置文件 +- 检查��源使用 +- 查看系统日志 + +## 维护计划 + +### 1. 日常维护 + +- 日志清理 +- 磁盘空间检查 +- 性能监控 +- 配置备份 + +### 2. 定期更新 + +- 安全补丁更新 +- 基础镜像更新 +- 应用版本更新 diff --git a/docs/mock_server.md b/docs/mock_server.md new file mode 100644 index 0000000..77709a7 --- /dev/null +++ b/docs/mock_server.md @@ -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 服务: + +## 8. 注意事项 + +1. 位置更新 + - 位置更新频率:1秒一次 + - 坐标系统:WGS84 + - 速度单位:km/h(内部计算使用 m/s) + +2. 车辆控制 + - 车辆收到停止指令后会立即停止位置更新 + - 只有收到 RESUME 指令且没有更高优先级的指令时才会恢复运动 + - 每个指令都必须包含时间戳 + +3. 错误处理 + - 无效的车辆ID:返回 404 错误 + - 无效的指令格式:返回 400 错误 + - 服务器内部错误:返回 500 错误 diff --git a/docs/official_api.md b/docs/official_api.md new file mode 100644 index 0000000..645c87f --- /dev/null +++ b/docs/official_api.md @@ -0,0 +1,225 @@ +# 数据接口对接方案 + +## 第1章 位置数据接口对接方案 + +### 1.1 登录认证 + +1. 登录接口: +2. 请求方式:post +3. 参数:username、password +4. 示例: +5、返回值 data 为返回的鉴权token,后续接口需要再header中携带,data所有的数据是一个token,不要截断 +示例:{ +    "status": 200, +    "msg": "登入成功", +    "data": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY" +} + +### 1.2 航空器位置数据接入 + +数据来源:接入并转发从空管接收到的融合数据 + +1. 接口地址: + +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. 接口地址: + +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 接口地址: + +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. 接口地址: + +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. 接口地址: + +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 | 是 | 被接管或干预相关信息,是否被远控RemoteControl,True:车辆在遥控器远控模式,False:车辆处于自动驾驶模式 | +| Command | int | 是 | 接收的远程指令信息,0:恢复,1:急停,2:缓停 | +| airportInfo | list | 否 | 机场特殊要求的其他信息 | +| vehicleMode | int | 是 | 无人设备控制模式(底盘控制模式),1:手动(司机驾驶),2:自动,3:遥控器,4:远程,5:故障等待 | +| gearState | int | 是 | 车辆当前档位,1:N,2:D,3:P,4:R, 5: 未知 | +| chassisReady | boolean | 是 | 底盘是否准备就绪,True:车辆发控制指令就可以走,false: 其他 | +| collisionStatus | boolean | 否 | 防撞梁是否触发,true:触发,false:未触发 | +| clearance | int | 是 | 0:关闭,1:开启(示廓灯) | +| turnSignalStstus | int | 是 | 转向灯状态,0:off , 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":[] + } +] diff --git a/docs/performance_test_report.md b/docs/performance_test_report.md new file mode 100644 index 0000000..2dc3092 --- /dev/null +++ b/docs/performance_test_report.md @@ -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; + 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 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::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 items_; // 物体列表 + bool divided_ = false; // 是否已分割 + + std::unique_ptr northwest_; // 四个子节点 + std::unique_ptr northeast_; + std::unique_ptr southwest_; + std::unique_ptr 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 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. 缓存策略: + - 缓存航空器数据 + - 使用四叉树索引车辆数据 + - 及时清理无效数据 diff --git a/docs/requirements.md b/docs/requirements.md new file mode 100644 index 0000000..2534900 --- /dev/null +++ b/docs/requirements.md @@ -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. 其他 diff --git a/docs/warning_strategy.md b/docs/warning_strategy.md new file mode 100644 index 0000000..717b3b8 --- /dev/null +++ b/docs/warning_strategy.md @@ -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 时有效) +}; +``` diff --git a/scripts/build_execute_package.sh b/scripts/build_execute_package.sh new file mode 100644 index 0000000..b7f665b --- /dev/null +++ b/scripts/build_execute_package.sh @@ -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" \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..2ef145d --- /dev/null +++ b/scripts/deploy.sh @@ -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 \ No newline at end of file diff --git a/scripts/prepare_deploy.sh b/scripts/prepare_deploy.sh new file mode 100644 index 0000000..23c1628 --- /dev/null +++ b/scripts/prepare_deploy.sh @@ -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" \ No newline at end of file diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100644 index 0000000..b2647bf --- /dev/null +++ b/scripts/uninstall.sh @@ -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" \ No newline at end of file diff --git a/src/collector/DataCollector.cpp b/src/collector/DataCollector.cpp new file mode 100644 index 0000000..451dd01 --- /dev/null +++ b/src/collector/DataCollector.cpp @@ -0,0 +1,471 @@ +#include "collector/DataCollector.h" +#include "network/HTTPDataSource.h" +#include "utils/Logger.h" +#include +#include +#include +#include + +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(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( + 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( + 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( + 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( + now - last_position_fetch_) + .count(); + if (position_elapsed > dataSourceConfig_.position.timeout_ms) { + sendTimeoutWarning("position", position_elapsed); + } + + // 检查无人车数据超时 + auto unmanned_elapsed = + std::chrono::duration_cast( + now - last_unmanned_fetch_) + .count(); + if (unmanned_elapsed > dataSourceConfig_.vehicle.timeout_ms) { + sendTimeoutWarning("unmanned", unmanned_elapsed); + } + + // 检查红绿灯数据超时 + auto traffic_light_elapsed = + std::chrono::duration_cast( + 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( + 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( + 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( + 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( + 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, + std::vector& 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 newAircraft; + std::vector newVehicles; + + if (!dataSource_) { + Logger::error("No data source available"); + return false; + } + + bool success = dataSource_->fetchPositionAircraftData(newAircraft) && + dataSource_->fetchPositionVehicleData(newVehicles); + + if (success) { + std::lock_guard 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 newSignals; + + if (!dataSource_) { + Logger::error("No data source available"); + return false; + } + + bool success = dataSource_->fetchTrafficLightSignals(newSignals); + + if (success) { + std::lock_guard lock(cacheMutex_); + trafficLightCache_ = std::move(newSignals); + // 更新时间戳 + if (!trafficLightCache_.empty()) { + lastTrafficLightTimestamp_ = trafficLightCache_[0].timestamp; + } + resetTimeout("traffic_light"); + } + + return success; +} + +std::vector DataCollector::getTrafficLightSignals() { + std::lock_guard lock(cacheMutex_); + return trafficLightCache_; +} + +bool DataCollector::fetchTrafficLightSignals( + std::vector& signals) { + if (!dataSource_) { + return false; + } + return dataSource_->fetchTrafficLightSignals(signals); +} + +std::vector DataCollector::getAircraftData() { + std::lock_guard lock(cacheMutex_); + return aircraftCache_; +} + +std::vector DataCollector::getVehicleData() { + std::lock_guard 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); +} \ No newline at end of file diff --git a/src/collector/DataCollector.h b/src/collector/DataCollector.h new file mode 100644 index 0000000..70e505b --- /dev/null +++ b/src/collector/DataCollector.h @@ -0,0 +1,117 @@ +#ifndef AIRPORT_COLLECTOR_DATA_COLLECTOR_H +#define AIRPORT_COLLECTOR_DATA_COLLECTOR_H + +#include +#include +#include +#include +#include +#include +#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, std::vector> + getLatestData() const { + std::lock_guard lock(cacheMutex_); + return std::make_tuple(aircraftCache_, vehicleCache_, trafficLightCache_); + } + + // 获取数据的最后更新时间 + std::tuple getLastUpdateTimestamps() const { + std::lock_guard lock(cacheMutex_); + return {lastAircraftTimestamp_, lastVehicleTimestamp_, lastTrafficLightTimestamp_}; + } + + // 获取单个数据的接口 + std::vector getAircraftData(); + std::vector getVehicleData(); + std::vector getTrafficLightSignals(); + bool fetchTrafficLightSignals(std::vector& signals); + bool fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status); + bool fetchPositionData(); // 获取位置数据 + + void setSystem(std::shared_ptr system) { system_ = system; } + void setDataSource(std::shared_ptr source) { dataSource_ = source; } + + // 获取数据的引用访问接口 + const std::vector& getAircraftRef() const { + std::lock_guard lock(cacheMutex_); + return aircraftCache_; + } + + const std::vector& getVehicleRef() const { + std::lock_guard lock(cacheMutex_); + return vehicleCache_; + } + +private: + std::shared_ptr dataSource_; + std::shared_ptr system_; + DataSourceConfig dataSourceConfig_; + WarnConfig warnConfig_; + const AirportBounds& airportBounds_; + + // 三个独立的采集线程 + std::thread positionThread_; // 位置数据采集线程 + std::thread unmannedThread_; // 无人车状态采集线程 + std::thread trafficLightThread_; // 红绿灯数据采集线程 + std::atomic running_{false}; + std::atomic loopCount_{0}; + + // 缓存当前数据 + mutable std::mutex cacheMutex_; + std::vector aircraftCache_; + std::vector vehicleCache_; + std::vector trafficLightCache_; + + // 保存上一次位置信息(用于计算速度) + std::map lastAircraftPositions_; + std::map lastVehiclePositions_; + + // 数据时间戳 + std::atomic lastAircraftTimestamp_{0}; + std::atomic lastVehicleTimestamp_{0}; + std::atomic 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, std::vector& vehicles); +}; + +#endif // AIRPORT_COLLECTOR_DATA_COLLECTOR_H \ No newline at end of file diff --git a/src/collector/DataSource.h b/src/collector/DataSource.h new file mode 100644 index 0000000..a38b720 --- /dev/null +++ b/src/collector/DataSource.h @@ -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 +#include +#include + +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) = 0; + virtual bool fetchPositionVehicleData(std::vector& 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& signals) = 0; +}; + +#endif // AIRPORT_COLLECTOR_DATA_SOURCE_H \ No newline at end of file diff --git a/src/collector/DataSourceConfig.cpp b/src/collector/DataSourceConfig.cpp new file mode 100644 index 0000000..aa6fd74 --- /dev/null +++ b/src/collector/DataSourceConfig.cpp @@ -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; +} \ No newline at end of file diff --git a/src/collector/DataSourceConfig.h b/src/collector/DataSourceConfig.h new file mode 100644 index 0000000..650fb34 --- /dev/null +++ b/src/collector/DataSourceConfig.h @@ -0,0 +1,171 @@ +#ifndef AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H +#define AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H + +#include +#include +#include + +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 diff --git a/src/config/AirportBounds.cpp b/src/config/AirportBounds.cpp new file mode 100644 index 0000000..2e45a1e --- /dev/null +++ b/src/config/AirportBounds.cpp @@ -0,0 +1,179 @@ +#include "AirportBounds.h" +#include "utils/Logger.h" +#include +#include +#include +#include + +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() * M_PI / 180.0; + } + + if (airport.contains("reference_point")) { + referencePoint_.x = airport["reference_point"]["x"].get(); + referencePoint_.y = airport["reference_point"]["y"].get(); + } + + // 加载机场边界 + auto& airportBounds = j["airport"]["bounds"]; + airportBounds_ = { airportBounds["x"].get(), + airportBounds["y"].get(), + airportBounds["width"].get(), + airportBounds["height"].get() }; + + // 检查 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(), bounds["y"].get(), + bounds["width"].get(), bounds["height"].get() }; + + // 加载区域配置 + 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(), + collision_radius["special"].get(), + collision_radius["unmanned"].get() }; + + RadiusConfig warning_config = { + warning_zone_radius["aircraft"].get(), + warning_zone_radius["special"].get(), + warning_zone_radius["unmanned"].get() }; + + RadiusConfig alert_config = { + alert_zone_radius["aircraft"].get(), + alert_zone_radius["special"].get(), + alert_zone_radius["unmanned"].get() }; + + areaConfigs_[type] = { collision_config, + config["height_threshold"].get(), + 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); +} \ No newline at end of file diff --git a/src/config/AirportBounds.h b/src/config/AirportBounds.h new file mode 100644 index 0000000..55d212a --- /dev/null +++ b/src/config/AirportBounds.h @@ -0,0 +1,58 @@ +#ifndef AIRPORT_BOUNDS_H +#define AIRPORT_BOUNDS_H +#include +#include +#include +#include "types/BasicTypes.h" +#include "spatial/QuadTree.h" +#include "AreaConfig.h" +#include + +// 机场区域定义 +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 areaBounds_; // 各区域边界 + std::unordered_map areaConfigs_; // 各区域配置 + + // 从配置文件加载数据 + virtual void loadConfig(const std::string& configFile); + + double rotationAngle_ = 0.0; // 机场与正北方向的夹角(弧度) + Vector2D referencePoint_; // 机场参考点(旋转中心) +}; + +#endif // AIRPORT_BOUNDS_H \ No newline at end of file diff --git a/src/config/AreaConfig.h b/src/config/AreaConfig.h new file mode 100644 index 0000000..373fae3 --- /dev/null +++ b/src/config/AreaConfig.h @@ -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 \ No newline at end of file diff --git a/src/config/IntersectionConfig.cpp b/src/config/IntersectionConfig.cpp new file mode 100644 index 0000000..f869465 --- /dev/null +++ b/src/config/IntersectionConfig.cpp @@ -0,0 +1,78 @@ +#include "config/IntersectionConfig.h" +#include "utils/Logger.h" +#include +#include +#include +#include + +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(); + info.name = item["name"].get(); + info.trafficLightId = item["trafficLightId"].get(); + + // 加载位置信息,检查是否为 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(); + info.position.latitude = item["position"]["latitude"].get(); + info.position.altitude = item["position"]["altitude"].get(); + + // 加载路口宽度和安全区配置,检查是否为 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(); + info.safetyZone.aircraftRadius = item["safetyZone"]["aircraftRadius"].get(); + info.safetyZone.vehicleRadius = item["safetyZone"]["vehicleRadius"].get(); + + 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; +} \ No newline at end of file diff --git a/src/config/IntersectionConfig.h b/src/config/IntersectionConfig.h new file mode 100644 index 0000000..e7295aa --- /dev/null +++ b/src/config/IntersectionConfig.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#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& getIntersections() const { return intersections_; } + +private: + std::vector intersections_; +}; \ No newline at end of file diff --git a/src/config/SystemConfig.cpp b/src/config/SystemConfig.cpp new file mode 100644 index 0000000..6f2a9aa --- /dev/null +++ b/src/config/SystemConfig.cpp @@ -0,0 +1,21 @@ +#include "SystemConfig.h" +#include "utils/Logger.h" +#include + +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())); + } +} \ No newline at end of file diff --git a/src/config/SystemConfig.h b/src/config/SystemConfig.h new file mode 100644 index 0000000..d673c48 --- /dev/null +++ b/src/config/SystemConfig.h @@ -0,0 +1,293 @@ +#pragma once + +#include +#include +#include +#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 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(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路口")); +} + diff --git a/src/config/WarnConfig.h b/src/config/WarnConfig.h new file mode 100644 index 0000000..9c87c37 --- /dev/null +++ b/src/config/WarnConfig.h @@ -0,0 +1,11 @@ +#ifndef AIRPORT_NETWORK_WARN_CONFIG_H +#define AIRPORT_NETWORK_WARN_CONFIG_H + +#include + +struct WarnConfig { + int warning_interval_ms; + int log_interval_ms; +}; + +#endif // AIRPORT_NETWORK_WARN_CONFIG_H \ No newline at end of file diff --git a/src/core/System.cpp b/src/core/System.cpp new file mode 100644 index 0000000..89848a1 --- /dev/null +++ b/src/core/System.cpp @@ -0,0 +1,856 @@ +#include +#include +#include +#include +#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 + +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(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( + 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("config/airport_bounds.json"); + + // 初始化冲突检测器 + collisionDetector_ = std::make_unique(*airportBounds_, controllableVehicles_); + + // 初始化红绿灯检测器 + trafficLightDetector_ = std::make_unique(intersection_config_, controllableVehicles_, *this); + + // 初始化安全区 + initializeSafetyZones(); + + // 加载告警配置 + WarnConfig warnConfig{ + system_config.warning.warning_interval_ms, + system_config.warning.log_interval_ms }; + + // 创建数据采集器并初始化 + dataCollector_ = std::make_unique(*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( + 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 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 vehicleMaxRiskLevels; // 统一记录所有车辆的最高风险等级 + std::vector detectedRisks; // 统一收集所有检测到的风险 + + // 检查安全区更新 + auto safety_zone_elapsed = std::chrono::duration_cast( + 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(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& vehicles, + const std::vector& objects, + std::unordered_map& vehicleMaxRiskLevels, + std::vector& 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(risk.level), + ", distance=", risk.distance); + + break; + } + } + } + } + } +} + +void System::processCollisions( + const std::vector& detectedRisks, + std::unordered_map& vehicleMaxRiskLevels) { + + // 记录当前有风险的可控车辆 + std::unordered_set currentVehiclesWithRisk; + + // 修改日志输出,显示更多信息 + Logger::debug("开始处理碰撞风险,风险数量: ", detectedRisks.size()); + for (const auto& risk : detectedRisks) { + Logger::debug("待处理风险: id1=", risk.id1, + ", id2=", risk.id2, + ", level=", static_cast(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::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(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::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::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& 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& 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::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() == 1) { + ns_status = SignalStatus::GREEN; + } else if (di_data.contains("DI-12") && di_data.at("DI-12").get() == 1) { + ns_status = SignalStatus::YELLOW; + } else if (di_data.contains("DI-11") && di_data.at("DI-11").get() == 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() == 1) { + ew_status = SignalStatus::GREEN; + } else if (di_data.contains("DI-15") && di_data.at("DI-15").get() == 1) { + ew_status = SignalStatus::YELLOW; + } else if (di_data.contains("DI-14") && di_data.at("DI-14").get() == 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::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(signal.ns_status); + messageJson["ew_status"] = static_cast(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()); + } +} \ No newline at end of file diff --git a/src/core/System.h b/src/core/System.h new file mode 100644 index 0000000..ff4ee52 --- /dev/null +++ b/src/core/System.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#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 + +// 前向声明 +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& detectedRisks, + std::unordered_map& vehicleMaxRiskLevels); + + // 初始化安全区 + void initializeSafetyZones(); + + // 更新安全区状态 + void updateSafetyZoneStates(const std::vector& objects); + + // 检查目标是否进入安全区 + void checkSafetyZoneIntrusion(const MovingObject& obj); + + // 检查无人车与安全区的冲突 + void checkUnmannedVehicleSafetyZones(const std::vector& vehicles, + const std::vector& objects, + std::unordered_map& vehicleMaxRiskLevels, + std::vector& detectedRisks); + + // 处理安全区风险 + bool handleSafetyZoneRisk(const Vehicle& vehicle, + const SafetyZone* zone, + const std::vector& objects, + double distance, + const std::string& intersectionId, + CommandType cmdType, + const std::string& riskLevel); + + // 记录上一次有安全区风险的无人车 + std::unordered_map lastVehiclesWithSafetyZoneRisk_; // + + std::atomic running_{false}; + std::thread processThread_; + + std::unique_ptr airportBounds_; + ControllableVehicles& controllableVehicles_{ControllableVehicles::getInstance()}; + std::unique_ptr collisionDetector_; + std::unique_ptr trafficLightDetector_; + std::unique_ptr dataCollector_; + + // WebSocket 服务器 + std::unique_ptr ws_server_; + std::thread ws_thread_; + + // 新增: 红绿灯 HTTP 服务器 + std::unique_ptr traffic_light_http_server_; + std::thread traffic_light_http_server_thread_; + + // 路口配置 + IntersectionConfig intersection_config_; + + // 安全区管理 + std::unordered_map> safetyZones_; + + static System* instance_; + + // 记录上一次有风险的车辆列表 + std::unordered_set 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 latest_aircraft_; + std::vector latest_vehicles_; + std::vector latest_traffic_lights_; +}; \ No newline at end of file diff --git a/src/detector/CollisionDetector.cpp b/src/detector/CollisionDetector.cpp new file mode 100644 index 0000000..f938cb5 --- /dev/null +++ b/src/detector/CollisionDetector.cpp @@ -0,0 +1,729 @@ +#include "CollisionDetector.h" + +#include "config/SystemConfig.h" +#include "utils/Logger.h" +#include +#include + +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, + const std::vector& 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 CollisionDetector::detectCollisions() { + std::vector risks; + + // 获取配置参数 + const auto& predictionConfig = + SystemConfig::instance().collision_detection.prediction; + + // 获取所有车辆 + auto allVehicles = vehicleTree_.queryRange(vehicleTree_.getBounds()); + + // 可控车辆(无人车) + std::vector unmannedVehicles; + for (const auto& vehicle : allVehicles) { + if (vehicle.isControllable) { + unmannedVehicles.push_back(vehicle); + } + } + + // 记录当前检测到的冲突对 + std::unordered_set, 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(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(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(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(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(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 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(type1), + ", 物体2类型=", static_cast(type2), + ", 当前距离=", collisionResult.distance, + "m, 预测最小距离=", collisionResult.minDistance, + "m, 预警阈值=", thresholds.warning, + "m, 警报阈值=", thresholds.critical, + "m, 预测碰撞=", collisionResult.willCollide ? "是" : "否", + ", 风险等级=", static_cast(level), + ", 区域类型=", static_cast(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; +} \ No newline at end of file diff --git a/src/detector/CollisionDetector.h b/src/detector/CollisionDetector.h new file mode 100644 index 0000000..27c3e7b --- /dev/null +++ b/src/detector/CollisionDetector.h @@ -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 +#include + +// 碰撞风险等级 +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& p) const { + return std::hash()(p.first + p.second); + } +}; + +class CollisionDetector { +public: + CollisionDetector(const AirportBounds& bounds, const ControllableVehicles& controllableVehicles); + + // 更新交通数据 + void updateTraffic(const std::vector& aircraft, + const std::vector& vehicles); + + // 检测所有可能的碰撞 + std::vector detectCollisions(); + + // 统一的碰撞检测函数 + CollisionResult checkCollision( + const MovingObject& obj1, + const MovingObject& obj2, + double timeWindow = 30.0) const; + +private: + const AirportBounds& airportBounds_; + QuadTree vehicleTree_; + std::vector aircraftData_; + const ControllableVehicles* controllableVehicles_; + + // 冲突记录映射表:<(id1,id2), CollisionRecord> + std::unordered_map, 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 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 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 \ No newline at end of file diff --git a/src/detector/CollisionTypes.h b/src/detector/CollisionTypes.h new file mode 100644 index 0000000..30ce7dd --- /dev/null +++ b/src/detector/CollisionTypes.h @@ -0,0 +1,47 @@ +#pragma once + +#include "types/BasicTypes.h" +#include +#include + +// 碰撞类型 +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::infinity()), + collisionPoint({0, 0}), + minDistance(std::numeric_limits::infinity()), + distance(std::numeric_limits::infinity()), + timeToMinDistance(std::numeric_limits::infinity()), + type(CollisionType::STATIC) {} +}; \ No newline at end of file diff --git a/src/detector/RectangleCollision.h b/src/detector/RectangleCollision.h new file mode 100644 index 0000000..3bf47b7 --- /dev/null +++ b/src/detector/RectangleCollision.h @@ -0,0 +1,117 @@ +#ifndef AIRPORT_DETECTOR_RECTANGLE_COLLISION_H +#define AIRPORT_DETECTOR_RECTANGLE_COLLISION_H + +#include "types/BasicTypes.h" +#include +#include +#include + +// 碰撞检测结果 +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 \ No newline at end of file diff --git a/src/detector/SafetyZone.cpp b/src/detector/SafetyZone.cpp new file mode 100644 index 0000000..620172c --- /dev/null +++ b/src/detector/SafetyZone.cpp @@ -0,0 +1,126 @@ +#include "SafetyZone.h" +#include "utils/Logger.h" +#include "config/SystemConfig.h" +#include + +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; +} \ No newline at end of file diff --git a/src/detector/SafetyZone.h b/src/detector/SafetyZone.h new file mode 100644 index 0000000..5a96803 --- /dev/null +++ b/src/detector/SafetyZone.h @@ -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; +}; \ No newline at end of file diff --git a/src/detector/TrafficLightDetector.cpp b/src/detector/TrafficLightDetector.cpp new file mode 100644 index 0000000..84e4f5a --- /dev/null +++ b/src/detector/TrafficLightDetector.cpp @@ -0,0 +1,72 @@ +#include "detector/TrafficLightDetector.h" +#include "utils/Logger.h" +#include "types/BasicTypes.h" +#include "core/System.h" +#include + +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& 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::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 ? "绿灯" : "黄灯"); +} \ No newline at end of file diff --git a/src/detector/TrafficLightDetector.h b/src/detector/TrafficLightDetector.h new file mode 100644 index 0000000..d90124a --- /dev/null +++ b/src/detector/TrafficLightDetector.h @@ -0,0 +1,27 @@ +#pragma once + +#include "config/IntersectionConfig.h" +#include "vehicle/ControllableVehicles.h" +#include "types/TrafficLightTypes.h" +#include + +class System; + +class TrafficLightDetector { +public: + TrafficLightDetector(const IntersectionConfig& intersectionConfig, + ControllableVehicles& controllableVehicles, + System& system); + + void processSignal(const TrafficLightSignal& signal, + const std::vector& vehicles); + +private: + const IntersectionConfig& intersection_config_; + ControllableVehicles& controllable_vehicles_; + System& system_; + TrafficLightSignal current_signal_; + + // 发送信号灯指令 + void sendSignalCommand(const std::string& vehicleNo, SignalState state); +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c463c39 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,67 @@ +#include "core/System.h" +#include "utils/Logger.h" +#include "config/SystemConfig.h" +#include +#include +#include +#include + +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; + } +} \ No newline at end of file diff --git a/src/mock/MockDataService.cpp b/src/mock/MockDataService.cpp new file mode 100644 index 0000000..fe31d76 --- /dev/null +++ b/src/mock/MockDataService.cpp @@ -0,0 +1,168 @@ +#include "mock/MockDataService.h" +#include +#include +#include + +MockDataService::MockDataService(const AirportBounds& bounds) + : bounds_(bounds) +{ +} + +std::vector MockDataService::generateAircraftData() { + std::vector 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 MockDataService::generateVehicleData() { + std::vector 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 x_dist(areaBounds.x, + areaBounds.x + areaBounds.width); + std::uniform_real_distribution 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{30.0, 80.0}(rng_); + case AreaType::TAXIWAY: + return std::uniform_real_distribution{5.0, 15.0}(rng_); + case AreaType::GATE: + return std::uniform_real_distribution{0.0, 5.0}(rng_); + case AreaType::SERVICE: + return std::uniform_real_distribution{0.0, 10.0}(rng_); + default: + return 0.0; + } +} + +double MockDataService::generateHeading(AreaType areaType) { + // 根据区域类型返回合适的航向范围 + switch (areaType) { + case AreaType::RUNWAY: + // 跑道通常只有两个方向 + return std::uniform_int_distribution{0, 1}(rng_) * 180.0; + default: + // 其他区域可以是任意方向 + return std::uniform_real_distribution{0.0, 360.0}(rng_); + } +} + +double MockDataService::generateAltitude(AreaType areaType) { + // 根据区域类型返回合适的高度范围 + switch (areaType) { + case AreaType::RUNWAY: + return std::uniform_real_distribution{0.0, 15.0}(rng_); + case AreaType::TAXIWAY: + return std::uniform_real_distribution{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 MockDataService::generateTrafficLightData() { + std::vector 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(state); +} \ No newline at end of file diff --git a/src/mock/MockDataService.h b/src/mock/MockDataService.h new file mode 100644 index 0000000..2852115 --- /dev/null +++ b/src/mock/MockDataService.h @@ -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 +#include + +class MockDataService { +public: + explicit MockDataService(const AirportBounds& bounds); + + std::vector generateAircraftData(); + std::vector generateVehicleData(); + std::vector 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 \ No newline at end of file diff --git a/src/network/HTTPClient.cpp b/src/network/HTTPClient.cpp new file mode 100644 index 0000000..a1408f0 --- /dev/null +++ b/src/network/HTTPClient.cpp @@ -0,0 +1,142 @@ +#include "HTTPClient.h" +#include "utils/Logger.h" +#include + +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(userp); + response->append(static_cast(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(); + std::string msg = response["msg"].get(); + + 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; + } +} \ No newline at end of file diff --git a/src/network/HTTPClient.h b/src/network/HTTPClient.h new file mode 100644 index 0000000..998fa74 --- /dev/null +++ b/src/network/HTTPClient.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#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_; +}; \ No newline at end of file diff --git a/src/network/HTTPDataSource.cpp b/src/network/HTTPDataSource.cpp new file mode 100644 index 0000000..0f16b47 --- /dev/null +++ b/src/network/HTTPDataSource.cpp @@ -0,0 +1,605 @@ +#include "HTTPDataSource.h" +#include "HTTPClient.h" +#include "utils/Logger.h" +#include +#include + +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(userp); + response->append(static_cast(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( + 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( + 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) { + std::lock_guard 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& vehicles) { + std::lock_guard 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& signals) { + std::lock_guard 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 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() != 200) { + Logger::error("Command failed: ", j["msg"].get()); + 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 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() != 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 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() != 200) { + Logger::error("认证失败: ", j["msg"].get()); + return false; + } + + std::string token = j["data"].get(); + 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) { + 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() != 200) { + Logger::error("Failed to get aircraft data: ", j["msg"].get()); + return false; + } + + const auto& data = j["data"]; + aircraft.clear(); + for (const auto& item : data) { + Aircraft ac; + ac.flightNo = item["flightNo"].get(); + ac.id = ac.flightNo; + ac.geo.longitude = item["longitude"].get(); + ac.geo.latitude = item["latitude"].get(); + ac.timestamp = item["time"].get(); + + if (item.contains("altitude")) { + ac.altitude = item["altitude"].get(); + } + if (item.contains("trackNumber")) { + ac.trackNumber = item["trackNumber"].get(); + } + 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& 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() != 200) { + Logger::error("Failed to get vehicle data: ", j["msg"].get()); + return false; + } + + const auto& data = j["data"]; + vehicles.clear(); + for (const auto& item : data) { + Vehicle vehicle; + vehicle.vehicleNo = item["vehicleNo"].get(); + vehicle.id = vehicle.vehicleNo; + vehicle.geo.longitude = item["longitude"].get(); + vehicle.geo.latitude = item["latitude"].get(); + vehicle.timestamp = item["time"].get(); + + if (item.contains("direction")) { + vehicle.heading = item["direction"].get(); + } + if (item.contains("speed")) { + vehicle.speed = item["speed"].get(); + } + 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& 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() != 200) { + Logger::error("Failed to get traffic light data: ", j["msg"].get()); + return false; + } + + const auto& data = j["data"]; + signals.clear(); + for (const auto& item : data) { + TrafficLightSignal signal; + signal.trafficLightId = item["id"].get(); + signal.ns_status = [&]() { + int state = item["ns_status"].get(); + 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(); + signal.intersectionId = item["intersection"].get(); + signal.latitude = item["position"]["latitude"].get(); + signal.longitude = item["position"]["longitude"].get(); + signals.push_back(signal); + } + return true; + } catch (const std::exception& e) { + Logger::error("Failed to parse traffic light response: ", e.what()); + return false; + } +} diff --git a/src/network/HTTPDataSource.h b/src/network/HTTPDataSource.h new file mode 100644 index 0000000..25b42d3 --- /dev/null +++ b/src/network/HTTPDataSource.h @@ -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 +#include "collector/DataSourceConfig.h" +#include +#include +#include +#include "core/System.h" +#include +#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) override; + bool fetchPositionVehicleData(std::vector& 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& 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 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); + bool parsePositionVehicleResponse(const std::string& response, std::vector& vehicles); + + // 红绿灯响应解析 + bool parseTrafficLightResponse(const std::string& response, std::vector& 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 + diff --git a/src/network/MessageTypes.h b/src/network/MessageTypes.h new file mode 100644 index 0000000..37ae820 --- /dev/null +++ b/src/network/MessageTypes.h @@ -0,0 +1,71 @@ +#pragma once +#include +#include + +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 \ No newline at end of file diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h new file mode 100644 index 0000000..0cdb367 --- /dev/null +++ b/src/network/NetworkInterface.h @@ -0,0 +1,17 @@ +#ifndef AIRPORT_NETWORK_INTERFACE_H +#define AIRPORT_NETWORK_INTERFACE_H + +#include +#include +#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 \ No newline at end of file diff --git a/src/network/TrafficLightHttpServer.cpp b/src/network/TrafficLightHttpServer.cpp new file mode 100644 index 0000000..a6805a7 --- /dev/null +++ b/src/network/TrafficLightHttpServer.cpp @@ -0,0 +1,377 @@ +#include "TrafficLightHttpServer.h" +#include "utils/Logger.h" +#include "core/System.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 bad_request(const http::request& req, beast::string_view why) { + http::response 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 not_found(const http::request& req) { + http::response 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 server_error(const http::request& req, beast::string_view what) { + http::response 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 method_not_allowed(const http::request& req) { + http::response 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 ok_response(const http::request& req, const std::string& message = "OK") { + http::response 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 + { + tcp::socket socket_; + beast::flat_buffer buffer_; + std::string const& doc_root_; // Keep for potential future use + http::request req_; + // Strand to ensure sequential execution of handlers for this session. + net::strand 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&& res) { + auto sp = std::make_shared>(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 + { + 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( + 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( + 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 + diff --git a/src/network/TrafficLightHttpServer.h b/src/network/TrafficLightHttpServer.h new file mode 100644 index 0000000..dd802c7 --- /dev/null +++ b/src/network/TrafficLightHttpServer.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // For uint16_t +#include + +// Forward declare Logger if header is heavy, or include if needed often +// #include "utils/Logger.h" + +// 前向声明 System 类 +class System; + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +using tcp = boost::asio::ip::tcp; // from +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 listener_; + std::vector threads_; + std::atomic 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(); + }; + +} \ No newline at end of file diff --git a/src/network/WebSocketServer.cpp b/src/network/WebSocketServer.cpp new file mode 100644 index 0000000..a4a26aa --- /dev/null +++ b/src/network/WebSocketServer.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include "network/WebSocketServer.h" +#include "utils/Logger.h" +#include + +namespace network { + + WebSocketServer::WebSocketServer(uint16_t port) + : acceptor_(ioc_, { boost::asio::ip::tcp::v4(), port }) {} + + WebSocketServer::~WebSocketServer() { + // 关闭所有连接 + std::lock_guard 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 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>(std::move(socket)); + + // 异步完成 WebSocket 握手 + ws->async_accept( + [this, ws](boost::system::error_code ec) { + if (!ec) { + // 握手成功,保存会话并开始读取消息 + { + std::lock_guard 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> ws) { + // 为每个会话创建一个专用的缓冲区 + auto buffer = std::make_shared(); + + // 异步读取消息 + 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 lock(sessions_mutex_); + auto it = std::find_if(sessions_.begin(), sessions_.end(), + [ws](const std::weak_ptr>& 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 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 \ No newline at end of file diff --git a/src/network/WebSocketServer.h b/src/network/WebSocketServer.h new file mode 100644 index 0000000..426be69 --- /dev/null +++ b/src/network/WebSocketServer.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +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> ws); + + boost::asio::io_context ioc_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector>> sessions_; + std::mutex sessions_mutex_; + std::atomic running_{true}; +}; + +} // namespace network \ No newline at end of file diff --git a/src/spatial/CoordinateConverter.cpp b/src/spatial/CoordinateConverter.cpp new file mode 100644 index 0000000..7081d49 --- /dev/null +++ b/src/spatial/CoordinateConverter.cpp @@ -0,0 +1,52 @@ +#include "spatial/CoordinateConverter.h" +#include "config/SystemConfig.h" +#include "utils/Logger.h" +#include + +// 地球半径(米) +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; +} \ No newline at end of file diff --git a/src/spatial/CoordinateConverter.h b/src/spatial/CoordinateConverter.h new file mode 100644 index 0000000..cc78141 --- /dev/null +++ b/src/spatial/CoordinateConverter.h @@ -0,0 +1,39 @@ +#ifndef AIRPORT_SPATIAL_COORDINATE_CONVERTER_H +#define AIRPORT_SPATIAL_COORDINATE_CONVERTER_H + +#include "types/BasicTypes.h" +#include +#include + +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 \ No newline at end of file diff --git a/src/spatial/QuadTree.cpp b/src/spatial/QuadTree.cpp new file mode 100644 index 0000000..61e7645 --- /dev/null +++ b/src/spatial/QuadTree.cpp @@ -0,0 +1,5 @@ +#include "QuadTree.h" + +// 显式实例化常用类型 +template class QuadTree; +template class QuadTree; \ No newline at end of file diff --git a/src/spatial/QuadTree.h b/src/spatial/QuadTree.h new file mode 100644 index 0000000..472d016 --- /dev/null +++ b/src/spatial/QuadTree.h @@ -0,0 +1,196 @@ +#ifndef AIRPORT_SPATIAL_QUAD_TREE_H +#define AIRPORT_SPATIAL_QUAD_TREE_H + +#include "types/BasicTypes.h" +#include +#include +#include + +// 边界定义 +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 +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 queryRange(const Bounds& range) const { + std::vector 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 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 items_; + bool divided_ = false; + + std::unique_ptr northwest_; + std::unique_ptr northeast_; + std::unique_ptr southwest_; + std::unique_ptr southeast_; + + template + void insertInternal(U&& item) { + const Vector2D point = getPosition(item); + + // 如果点不在当前树的边界内,忽略该对象 + if (!bounds_.contains(point)) { + return; + } + + // 叶子节点:未分裂且未达到容量,直接存放 + if (!divided_ && items_.size() < static_cast(capacity_)) { + items_.emplace_back(std::forward(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(item)); + } + + template + void insertIntoChildren(U&& item) { + const Vector2D point = getPosition(item); + + // 由于 contains 使用 <= 边界,点可能落在多象限边界上;这里固定按顺序选择一个子象限 + if (northwest_ && northwest_->bounds_.contains(point)) { + northwest_->insertInternal(std::forward(item)); + return; + } + if (northeast_ && northeast_->bounds_.contains(point)) { + northeast_->insertInternal(std::forward(item)); + return; + } + if (southwest_ && southwest_->bounds_.contains(point)) { + southwest_->insertInternal(std::forward(item)); + return; + } + if (southeast_ && southeast_->bounds_.contains(point)) { + southeast_->insertInternal(std::forward(item)); + return; + } + + // 理论上不会走到这里(已检查 bounds_ contains),兜底放回当前节点 + items_.emplace_back(std::forward(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(nw, capacity_); + + Bounds ne(bounds_.x + w, bounds_.y, w, h); + northeast_ = std::make_unique(ne, capacity_); + + Bounds sw(bounds_.x, bounds_.y + h, w, h); + southwest_ = std::make_unique(sw, capacity_); + + Bounds se(bounds_.x + w, bounds_.y + h, w, h); + southeast_ = std::make_unique(se, capacity_); + + divided_ = true; + } + + static Vector2D getPosition(const T& item) { + if constexpr (std::is_same_v || std::is_same_v) { + return item.position; // 直接返回 position 成员 + } else { + return item.getPosition(); // 对于其他类型,假设有getPosition方法 + } + } +}; + +#endif // AIRPORT_SPATIAL_QUAD_TREE_H \ No newline at end of file diff --git a/src/types/BasicTypes.cpp b/src/types/BasicTypes.cpp new file mode 100644 index 0000000..79c7aec --- /dev/null +++ b/src/types/BasicTypes.cpp @@ -0,0 +1,151 @@ +#include "types/BasicTypes.h" +#include "utils/Logger.h" +#include "spatial/CoordinateConverter.h" +#include + +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(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米才认为发生了变化 +} \ No newline at end of file diff --git a/src/types/BasicTypes.h b/src/types/BasicTypes.h new file mode 100644 index 0000000..fc2105f --- /dev/null +++ b/src/types/BasicTypes.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +// 移动物体类型 +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 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; // 只判断类型 + } +}; \ No newline at end of file diff --git a/src/types/TrafficLightTypes.h b/src/types/TrafficLightTypes.h new file mode 100644 index 0000000..249c26c --- /dev/null +++ b/src/types/TrafficLightTypes.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +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; // 路口经度 +}; \ No newline at end of file diff --git a/src/types/VehicleCommand.h b/src/types/VehicleCommand.h new file mode 100644 index 0000000..e73e0c5 --- /dev/null +++ b/src/types/VehicleCommand.h @@ -0,0 +1,60 @@ +#ifndef AIRPORT_TYPES_VEHICLE_COMMAND_H +#define AIRPORT_TYPES_VEHICLE_COMMAND_H + +#include +#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 \ No newline at end of file diff --git a/src/types/VehicleData.cpp b/src/types/VehicleData.cpp new file mode 100644 index 0000000..7ab1e45 --- /dev/null +++ b/src/types/VehicleData.cpp @@ -0,0 +1,6 @@ +#include "VehicleData.h" +#include "spatial/CoordinateConverter.h" + +void VehicleData::updateLocalPosition(const CoordinateConverter& converter) { + position = converter.toLocalXY(geo.latitude, geo.longitude); +} \ No newline at end of file diff --git a/src/types/VehicleData.h b/src/types/VehicleData.h new file mode 100644 index 0000000..3c4a863 --- /dev/null +++ b/src/types/VehicleData.h @@ -0,0 +1,30 @@ +#ifndef AIRPORT_TYPES_VEHICLE_DATA_H +#define AIRPORT_TYPES_VEHICLE_DATA_H + +#include +#include +#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 \ No newline at end of file diff --git a/src/utils/Logger.cpp b/src/utils/Logger.cpp new file mode 100644 index 0000000..3a3fb36 --- /dev/null +++ b/src/utils/Logger.cpp @@ -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); \ No newline at end of file diff --git a/src/utils/Logger.h b/src/utils/Logger.h new file mode 100644 index 0000000..ac045cb --- /dev/null +++ b/src/utils/Logger.h @@ -0,0 +1,99 @@ +#ifndef AIRPORT_UTILS_LOGGER_H +#define AIRPORT_UTILS_LOGGER_H + +#include +#include +#include +#include +#include +#include + +enum class LogLevel { + DEBUG, + INFO, + WARNING, + ERROR +}; + +class Logger { +public: + static void initialize(const std::string& filename, LogLevel level = LogLevel::INFO) { + std::lock_guard lock(getMutex()); + currentLevel() = level; + logFile().open(filename, std::ios::app); + } + + static void setLogLevel(LogLevel level) { + std::lock_guard lock(getMutex()); + currentLevel() = level; + } + + template + static void debug(Args... args) { + if (currentLevel() <= LogLevel::DEBUG) { + log("DEBUG", args...); + } + } + + template + static void info(Args... args) { + if (currentLevel() <= LogLevel::INFO) { + log("INFO", args...); + } + } + + template + static void warning(Args... args) { + if (currentLevel() <= LogLevel::WARNING) { + log("WARNING", args...); + } + } + + template + 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 + 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( + 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 lock(getMutex()); + std::cerr << ss.str(); + if (logFile().is_open()) { + logFile() << ss.str(); + logFile().flush(); + } + } +}; + +#endif // AIRPORT_UTILS_LOGGER_H \ No newline at end of file diff --git a/src/vehicle/ControllableVehicles.cpp b/src/vehicle/ControllableVehicles.cpp new file mode 100644 index 0000000..2e1ec17 --- /dev/null +++ b/src/vehicle/ControllableVehicles.cpp @@ -0,0 +1,111 @@ +#include "ControllableVehicles.h" +#include "utils/Logger.h" +#include +#include + +// 定义静态成员变量 +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()) { + if (!configFile.empty()) { + loadConfig(configFile); + } +} + +ControllableVehicles::~ControllableVehicles() { + if (instance_ != nullptr) { + delete instance_; + instance_ = nullptr; + } +} + +const std::vector& 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(); + config.type = item["type"].get(); + config.ip = item["ip"].get(); + config.port = item["port"].get(); + 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; + } +} \ No newline at end of file diff --git a/src/vehicle/ControllableVehicles.h b/src/vehicle/ControllableVehicles.h new file mode 100644 index 0000000..d789bbd --- /dev/null +++ b/src/vehicle/ControllableVehicles.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#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 vehicles_; + std::unique_ptr http_client_; + bool loadConfig(const std::string& configFile); + +public: + static ControllableVehicles& getInstance(); + const std::vector& 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); +}; \ No newline at end of file diff --git a/tests/AirportBoundsTest.cpp b/tests/AirportBoundsTest.cpp new file mode 100644 index 0000000..a5d5b32 --- /dev/null +++ b/tests/AirportBoundsTest.cpp @@ -0,0 +1,246 @@ +#include +#include +#include "config/AirportBounds.h" +#include "utils/Logger.h" +#include +#include +#include + +// 测试用的 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(); + + // 设置基本测试数据 + // 参考点设为原点 (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 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)) + << "边界外的点应该被认为在边界外"; +} \ No newline at end of file diff --git a/tests/BasicCollisionTest.cpp b/tests/BasicCollisionTest.cpp new file mode 100644 index 0000000..1241041 --- /dev/null +++ b/tests/BasicCollisionTest.cpp @@ -0,0 +1,539 @@ +#include +#include +#include + +#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(); + mockControllableVehicles_ = std::make_unique(); + + // 设置 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(*airportBounds_, *mockControllableVehicles_); + } + + std::unique_ptr airportBounds_; + std::unique_ptr mockControllableVehicles_; + std::unique_ptr 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::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(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 aircrafts = {aircraft}; + std::vector 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()) << "航空器已远离,无人车继续运动不应产生新的冲突"; +} \ No newline at end of file diff --git a/tests/BasicTypesTest.cpp b/tests/BasicTypesTest.cpp new file mode 100644 index 0000000..8ab20de --- /dev/null +++ b/tests/BasicTypesTest.cpp @@ -0,0 +1,128 @@ +#include +#include "types/BasicTypes.h" +#include + +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); +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a32fb0d --- /dev/null +++ b/tests/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/tests/CollisionDetectorTest.cpp b/tests/CollisionDetectorTest.cpp new file mode 100644 index 0000000..6fcf5f9 --- /dev/null +++ b/tests/CollisionDetectorTest.cpp @@ -0,0 +1,322 @@ +#include "detector/CollisionDetector.h" +#include "vehicle/ControllableVehicles.h" +#include "config/AirportBounds.h" +#include +#include +#include "utils/Logger.h" +#include +#include + +// 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(); + mockControllableVehicles_ = std::make_unique(); + + // 设置 Mock 对象的行为 + EXPECT_CALL(*airportBounds_, getAreaType(::testing::_)) + .WillRepeatedly(::testing::Return(AreaType::TEST_ZONE)); + + // 创建冲突检测器 + detector_ = std::make_unique(*airportBounds_, *mockControllableVehicles_); + } + + void TearDown() override { + Logger::initialize("", LogLevel::INFO); + } + + std::unique_ptr airportBounds_; + std::unique_ptr mockControllableVehicles_; + std::unique_ptr 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) << "航空器与后方的静止车辆不应该检测到碰撞"; +} \ No newline at end of file diff --git a/tests/DataCollectorTest.cpp b/tests/DataCollectorTest.cpp new file mode 100644 index 0000000..2337aec --- /dev/null +++ b/tests/DataCollectorTest.cpp @@ -0,0 +1,906 @@ +#include +#include +#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&)); + MOCK_METHOD1(fetchPositionVehicleData, bool(std::vector&)); + MOCK_METHOD1(fetchTrafficLightSignals, bool(std::vector&)); + 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(); + + // 设置机场边界参数 + 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(*bounds_); + mockSource = std::make_shared<::testing::NiceMock>(); + 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::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::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(state); + signal.ew_status = static_cast(state); + signal.timestamp = std::chrono::duration_cast( + 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 collector; + std::shared_ptr mockSource; + std::unique_ptr bounds_; + std::unique_ptr 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 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 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 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 testAircraft = { + createTestAircraft("TEST1", 36.354483470, 120.08502054) // 参考点 + }; + + std::vector 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 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 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 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 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 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 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 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(); + + // 设置期望接收到超时警告 + 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(); + + // 设置期望接收到超时警告 + 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 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 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"); + } +} \ No newline at end of file diff --git a/tests/HTTPDataSourceTest.cpp b/tests/HTTPDataSourceTest.cpp new file mode 100644 index 0000000..e4e914b --- /dev/null +++ b/tests/HTTPDataSourceTest.cpp @@ -0,0 +1,286 @@ +#include "network/HTTPDataSource.h" +#include +#include +#include "utils/Logger.h" +#include +#include +#include + +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; + bool success = source.fetchPositionAircraftData(aircraft); + ASSERT_TRUE(success) << "获取航空器数据失败"; + ASSERT_FALSE(aircraft.empty()) << "航空器数据为空"; + + std::vector 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; + 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; + 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; + 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 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::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::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::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 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::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::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::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::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 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)); +} \ No newline at end of file diff --git a/tests/SafetyZoneTest.cpp b/tests/SafetyZoneTest.cpp new file mode 100644 index 0000000..9cfbcfb --- /dev/null +++ b/tests/SafetyZoneTest.cpp @@ -0,0 +1,190 @@ +#include +#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(Vector2D{0, 0}, 50.0, 30.0); + } + + std::unique_ptr 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(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); +} \ No newline at end of file diff --git a/tests/TrafficLightDetectorTest.cpp b/tests/TrafficLightDetectorTest.cpp new file mode 100644 index 0000000..7a6561b --- /dev/null +++ b/tests/TrafficLightDetectorTest.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#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( + 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 detector; + IntersectionConfig intersectionConfig; +}; + +// 测试红绿灯信号处理 +TEST_F(TrafficLightDetectorTest, SignalProcessing) { + // 创建一个在交叉口范围内的车辆 + Vehicle vehicle = createTestVehicle("V001", 36.36, 120.08); + std::vector 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 vehicles = {vehicle}; + + // 测试无效的红绿灯ID + TrafficLightSignal invalidSignal = createTestSignal("TL999", SignalStatus::RED); + + detector->processSignal(invalidSignal, vehicles); +} + +// 测试边界情况 +TEST_F(TrafficLightDetectorTest, EdgeCases) { + // 测试空车辆列表 + std::vector emptyVehicles; + TrafficLightSignal signal = createTestSignal("TL001", SignalStatus::RED); + + detector->processSignal(signal, emptyVehicles); + + // 测试交叉口范围外的车辆 + Vehicle farVehicle = createTestVehicle("V002", 36.40, 120.12); + std::vector vehicles = {farVehicle}; + + detector->processSignal(signal, vehicles); +} \ No newline at end of file diff --git a/tests/data/invalid_airport_bounds.json b/tests/data/invalid_airport_bounds.json new file mode 100644 index 0000000..bb7f5b4 --- /dev/null +++ b/tests/data/invalid_airport_bounds.json @@ -0,0 +1,5 @@ +{ + "airport": { + "rotation_angle": 68.53 + } +} \ No newline at end of file diff --git a/tools/map.html b/tools/map.html new file mode 100644 index 0000000..7c2dbd7 --- /dev/null +++ b/tools/map.html @@ -0,0 +1,303 @@ + + + + + 机场路口地图 + + + +
+

机场路口地图

+ +
+

图例

+
+
+ 道路 +
+
+
+ 路口 +
+
+
+ + + + \ No newline at end of file diff --git a/tools/map_websocket.html b/tools/map_websocket.html new file mode 100644 index 0000000..3a2dbba --- /dev/null +++ b/tools/map_websocket.html @@ -0,0 +1,740 @@ + + + + + 机场车辆监控 + + + + + + + +

机场车辆监控系统

+
+
+
+
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/tools/mock_server.py b/tools/mock_server.py new file mode 100644 index 0000000..853d8a3 --- /dev/null +++ b/tools/mock_server.py @@ -0,0 +1,1162 @@ +from flask import Flask, jsonify, request +import time +import math +import random +import logging +import os +import uuid + +# 创建 logs 目录(如果不存在) +if not os.path.exists('logs'): + os.makedirs('logs') + +# 配置日志 +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('logs/mock_server.log'), + logging.StreamHandler() + ] +) + +app = Flask(__name__) + +# 地球半径(米) +EARTH_RADIUS = 6378137.0 + +COMMAND_PRIORITIES = { + "ALERT": 5, + "WARNING": 3, + "PARKING": 2, + "RESUME": 1 +} + +def meters_to_degrees(meters, latitude): + """ + 将米转换为度,考虑纬度的影响 + """ + # 纬度方向:1度 = 111,319.9米 + meters_per_deg_lat = 111319.9 + # 经度方向:1度 = 111,319.9 * cos(latitude)米 + meters_per_deg_lon = meters_per_deg_lat * math.cos(math.radians(latitude)) + return meters / meters_per_deg_lat, meters / meters_per_deg_lon + +# 距离配置(米) +DIST_300M = 300 +DIST_200M = 200 +DIST_150M = 150 +DIST_100M = 100 +DIST_50M = 50 + +# 时间配置(秒) +WAIT_TIME_AFTER_RETURN = 10.0 # 返回起点后的等待时间 +UPDATE_INTERVAL = 1.0 # 位置更新间隔, 默认 1 秒 +TRAFFIC_LIGHT_SWITCH_INTERVAL = 10.0 # 红绿灯切换间隔 + +# 两个路口的位置 +T2_INTERSECTION = {"longitude": 120.08502054, "latitude": 36.35448347} +T6_INTERSECTION = {"longitude": 120.08649105, "latitude": 36.35074527} + +# 坐标点 +# 无人车1起点 +POINT_T1 = {"longitude": 120.0868853, "latitude": 36.35496367} +# 冲突点 1 +POINT_T2 = {"longitude": 120.08502054, "latitude": 36.35448347} +# 特勤车终点 +POINT_T3 = {"longitude": 120.08341044, "latitude": 36.35406879} +# 无人车1终点, 无人车 2 起点,特勤车起点 +POINT_T4 = {"longitude": 120.08558121, "latitude": 36.35305878} +POINT_T5 = {"longitude": 120.08400957, "latitude": 36.35265197} +# 冲突点 2 +POINT_T6 = {"longitude": 120.08649105, "latitude": 36.35074527} +# 航空器 1终点 +POINT_T7 = {"longitude": 120.08562915, "latitude": 36.35052372} +# 无人车 2终点 +POINT_T8 = {"longitude": 120.08676664, "latitude": 36.35004529} +POINT_T9 = {"longitude": 120.08520616, "latitude": 36.34964473} +POINT_T10 = {"longitude": 120.08710569, "latitude": 36.34917893} +# 航空器 1终点 +POINT_T11 = {"longitude": 120.0873865, "latitude": 36.3509885} + +# QN002 新起点(距离 T6 路口 150 米) +POINT_T12 = {"longitude": 120.08603613, "latitude": 36.35190217} # T4 和 T6 之间,距 T6 150米 + +# AC001 新起点(距离 T6 路口 200 米) +POINT_T13 = {"longitude": 120.08509148, "latitude": 36.35041247} # T6向南 200 米 + +# 飞机和车辆尺寸(半径 米) +AIRCRAFT_SIZE_M = 30.0 +VEHICLE_SIZE_M = 10.0 + +# 计算初始方向向量 +initial_target_lat = POINT_T11["latitude"] - POINT_T7["latitude"] +initial_target_lon = POINT_T11["longitude"] - POINT_T7["longitude"] + +# 归一化方向向量 +initial_dist = math.sqrt(initial_target_lat * initial_target_lat + initial_target_lon * initial_target_lon) +if initial_dist > 0: + initial_target_lat /= initial_dist + initial_target_lon /= initial_dist + +# 修改航空器数据的初始位置和方向 +aircraft_data = [ + { + "flightNo": "AC001", # 航空器 + "longitude": POINT_T7["longitude"], + "latitude": POINT_T7["latitude"], + "time": int(time.time() * 1000), + "altitude": 0.0, + "trackNumber": 1001, # 改为数字格式 + "speed": 36.0, # 内部使用的速度字段,不会在 API 返回中显示 + "direction": { # 内部使用的方向字段,不会在 API 返回中显示 + "lat": initial_target_lat, + "lon": initial_target_lon + } + } +] + +# 添加默认速度常量 +DEFAULT_VEHICLE_SPEED = 18.0 # km/h,默认 36km/h +EMERGENCY_BRAKE_DECELERATION = 0.8 # 紧急制动减速度 (每次更新减速 80%) +NORMAL_BRAKE_DECELERATION = 0.2 # 正常制动减速度 (每次更新减速 20%) + +# 计算 QN002 的初始方向向量 +qn002_target_lat = POINT_T8["latitude"] - POINT_T4["latitude"] +qn002_target_lon = POINT_T8["longitude"] - POINT_T4["longitude"] + +# 归一化方向向量 +qn002_dist = math.sqrt(qn002_target_lat * qn002_target_lat + qn002_target_lon * qn002_target_lon) +if qn002_dist > 0: + qn002_target_lat /= qn002_dist + qn002_target_lon /= qn002_dist + +# 修改车辆数据中 QN002 的初始位置和方向 +vehicle_data = [ + { + "vehicleNo": "QN001", # 无人车1 + "longitude": POINT_T1["longitude"], + "latitude": POINT_T1["latitude"], + "time": int(time.time() * 1000), + "direction": 180.0, # 向南方向为180度 + "speed": DEFAULT_VEHICLE_SPEED + }, + { + "vehicleNo": "QN002", # 无人车2 + "longitude": POINT_T12["longitude"], + "latitude": POINT_T12["latitude"], + "time": int(time.time() * 1000), + "direction": 90.0, # 向东方向为90度 + "speed": DEFAULT_VEHICLE_SPEED + }, + { + "vehicleNo": "TQ001", # 特勤车 + "longitude": POINT_T4["longitude"], + "latitude": POINT_T4["latitude"], + "time": int(time.time() * 1000), + "direction": 270.0, # 向西方向为270度 + "speed": DEFAULT_VEHICLE_SPEED + } +] + +# 添加车辆状态类 +class VehicleState: + def __init__(self, vehicle_no): + self.vehicle_no = vehicle_no + self.is_running = True # 运行状态 + self.current_command = None # 当前执行的指令 + self.command_priority = 0 # 当前指令优先级 + self.target_speed = DEFAULT_VEHICLE_SPEED # 目标速度 + self.brake_mode = None # 制动模式:'emergency' 或 'normal' + self.command_reason = None # 指令原因 + self.last_command_time = time.time() # 最后一次指令时间 + self.traffic_light_state = None # 当前红绿灯状态 + self.target_lat = None # 目标纬度 + self.target_lon = None # 目标经度 + self.is_traffic_light_stop = False # 是否因红灯停车 + + def can_be_overridden_by(self, command_type): + """判断当前指令是否可以被新指令覆盖""" + # 红绿灯信号不参与指令优先级判断 + if command_type in ["RED", "GREEN", "YELLOW"]: + return True + + new_priority = COMMAND_PRIORITIES.get(command_type, 0) + current_priority = COMMAND_PRIORITIES.get(self.current_command, 0) + + # 相同类型的指令可以覆盖(因为需要持续发送) + if command_type == self.current_command: + return True + + # ALERT 指令可以被 RESUME 解除 + if self.current_command == "ALERT": + return command_type == "RESUME" + + # WARNING 指令可以被 RESUME 或相同及更高优先级指令覆盖 + if self.current_command == "WARNING": + return command_type == "RESUME" or new_priority >= current_priority + + # 其他情况下,相同或更高优先级可以覆盖 + return new_priority >= current_priority + + def update_command(self, command_type, target_lat=None, target_lon=None): + """更新指令状态""" + # 更新目标位置 + if target_lat is not None and target_lon is not None: + self.target_lat = target_lat + self.target_lon = target_lon + + # 如果是红绿灯状态,只更新状态,不影响其他指令 + if command_type in ["RED", "GREEN", "YELLOW"]: + old_state = self.traffic_light_state + self.traffic_light_state = command_type + + # 更新红灯停车状态 + if command_type == "RED": + self.is_traffic_light_stop = True + elif command_type in ["GREEN", "YELLOW"]: + self.is_traffic_light_stop = False + + print(f"车辆 {self.vehicle_no} 收到红绿灯信号: {command_type} (原状态: {old_state})") + else: + # 其他指令正常更新 + self.current_command = command_type + self.command_priority = COMMAND_PRIORITIES.get(command_type, 0) + + # RESUME 指令不清除红绿灯状态,红绿灯状态由信号灯独立控制 + if command_type == "RESUME": + # 清除其他状态 + self.brake_mode = None + self.command_reason = None + + # 更新运行状态 + self.is_running = self.can_move() + print(f"更新车辆 {self.vehicle_no} 状态: command={self.current_command}, traffic_light={self.traffic_light_state}, priority={self.command_priority}, is_running={self.is_running}") + + def can_move(self): + """检查车辆是否可以移动""" + # 如果有告警指令,不能移动 + if self.current_command == "ALERT": + return False + + # 如果有预警指令,不能移动 + if self.current_command == "WARNING": + return False + + # 如果有安全停靠指令,不能移动 + if self.current_command == "PARKING": + return False + + # 如果是红灯停车状态,不能移动 + if self.is_traffic_light_stop: + return False + + # 其他情况可以移动 + return True + + def log_state(self): + """记录当前车辆状态""" + print(f""" +车辆状态: +- 车辆编号: {self.vehicle_no} +- 运行状态: {'运行中' if self.is_running else '已停止'} +- 当前指令: {self.current_command} +- 红绿灯状态: {self.traffic_light_state} +- 红灯停车: {'是' if self.is_traffic_light_stop else '否'} +- 指令优先级: {self.command_priority} +- 目标速度: {self.target_speed} +- 制动模式: {self.brake_mode} +- 指令原因: {self.command_reason} +- 目标位置: ({self.target_lat}, {self.target_lon}) +""") + +# 添加车辆状态管理 +vehicle_states = {} +for vehicle in vehicle_data: + vehicle_states[vehicle["vehicleNo"]] = VehicleState(vehicle["vehicleNo"]) + +def calculate_distance_to_target(vehicle, target_lat, target_lon): + """计算车辆到目标位的距离(米)""" + dlat = target_lat - vehicle["latitude"] + dlon = target_lon - vehicle["longitude"] + + # 使用 Haversine 公式计算距离 + R = 6371000 # 地球半径(米) + a = math.sin(dlat/2) * math.sin(dlat/2) + \ + math.cos(math.radians(vehicle["latitude"])) * math.cos(math.radians(target_lat)) * \ + math.sin(dlon/2) * math.sin(dlon/2) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) + return R * c + +@app.route('/api/VehicleCommandInfo', methods=['POST']) +def handle_vehicle_command(): + try: + data = request.json + vehicle_id = data.get("vehicleID") + command_type = data.get("commandType", "").upper() + reason = data.get("commandReason", "").upper() + signal_state = data.get("signalState", "").upper() + target_lat = data.get("latitude", None) + target_lon = data.get("longitude", None) + + print(f"收到车辆控制指令: vehicle_id={vehicle_id}, type={command_type}, reason={reason}, signal_state={signal_state}") + print(f"完整请求数据: {data}") + + # 检查 SIGNAL 类型命令必须包含 signalState + if command_type == "SIGNAL" and not signal_state: + print(f"SIGNAL 类型命令缺少 signalState") + return jsonify({ + "code": 400, + "msg": "SIGNAL command must include signalState", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }), 400 + + # 检查车辆是否存在 + vehicle_state = vehicle_states.get(vehicle_id) + if not vehicle_state: + print(f"未找到车辆: {vehicle_id}") + return jsonify({ + "code": 404, + "msg": f"Vehicle {vehicle_id} not found", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }), 404 + + # 打印当前车辆状态 + print(f"当前车辆状态: vehicle={vehicle_id}") + vehicle_state.log_state() + + # 特勤车只响应红绿灯信号 + if vehicle_id.startswith("TQ"): + if command_type != "SIGNAL": + print(f"特勤车辆忽略非红绿灯指令: {vehicle_id}") + return jsonify({ + "code": 200, + "msg": "Special vehicle only responds to traffic light signals", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }) + # 更新红绿灯状态和指令状态 + vehicle_state.update_command(signal_state, target_lat, target_lon) + print(f"特勤车 {vehicle_id} 更新状态: command={command_type}, traffic_light={vehicle_state.traffic_light_state}") + return jsonify({ + "code": 200, + "msg": "Traffic light state updated", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }) + + # 检查指令优先级并添加详细日志 + check_command = signal_state if command_type == "SIGNAL" else command_type + can_override = vehicle_state.can_be_overridden_by(check_command) + print(f"指令优先级检查: vehicle={vehicle_id}, current_command={vehicle_state.current_command}, " + f"new_command={check_command}, can_override={can_override}") + + if not can_override: + print(f"指令优先级过低: vehicle={vehicle_id}, current_priority={vehicle_state.command_priority}, " + f"command={check_command}") + return jsonify({ + "code": 400, + "msg": "Command priority too low", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }) + + # 处理不同类型的指令 + if command_type == "SIGNAL": + # 更新红绿灯状态和指令状态 + vehicle_state.update_command(signal_state, target_lat, target_lon) + print(f"车辆 {vehicle_id} 更新状态: command={command_type}, traffic_light={vehicle_state.traffic_light_state}") + return jsonify({ + "code": 200, + "msg": "Traffic light state updated", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }) + elif command_type in ["ALERT", "WARNING"]: + # 查找当前车辆 + current_vehicle = None + for v in vehicle_data: + if v["vehicleNo"] == vehicle_id: + current_vehicle = v + break + + # 执行告警指令直接停车 + print(f"执行{'紧急' if command_type == 'ALERT' else '正常'}制动: vehicle={vehicle_id}") + vehicle_state.current_command = command_type + vehicle_state.command_priority = COMMAND_PRIORITIES.get(command_type, 0) + vehicle_state.is_running = False + vehicle_state.target_speed = 0 + vehicle_state.brake_mode = "emergency" if command_type == "ALERT" else "normal" + vehicle_state.target_lat = target_lat + vehicle_state.target_lon = target_lon + # 立即更新车辆速度 + if current_vehicle: + current_vehicle["speed"] = 0 + + return jsonify({ + "code": 200, + "msg": "Command executed successfully", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }) + + elif command_type == "RESUME": + print(f"执行恢复指令: vehicle_id={vehicle_id}") + # 检查当前车辆 + current_vehicle = None + for v in vehicle_data: + if v["vehicleNo"] == vehicle_id: + current_vehicle = v + break + + # 清除限制性指令 + if vehicle_state.current_command in ["ALERT", "WARNING"]: + print(f"清除限制性指令: vehicle={vehicle_id}") + vehicle_state.current_command = None + vehicle_state.command_priority = 0 + vehicle_state.is_running = True + vehicle_state.target_speed = DEFAULT_VEHICLE_SPEED + vehicle_state.brake_mode = None + vehicle_state.target_lat = None + vehicle_state.target_lon = None + + # 更新车辆运行状态 + vehicle_state.is_running = vehicle_state.can_move() + if vehicle_state.is_running and current_vehicle: + print(f"车辆 {vehicle_id} 恢复运行") + current_vehicle["speed"] = DEFAULT_VEHICLE_SPEED + + # 记录状态变化但不更新指令 + vehicle_state.command_reason = reason + vehicle_state.last_command_time = time.time() + + print(f"Vehicle {vehicle_id} state updated: running={vehicle_state.is_running}, " + f"command={vehicle_state.current_command}, traffic_light={vehicle_state.traffic_light_state}, " + f"reason={reason}, priority={vehicle_state.command_priority}, " + f"target_speed={vehicle_state.target_speed}, brake_mode={vehicle_state.brake_mode}") + + return jsonify({ + "code": 200, + "msg": "Command executed successfully", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }) + + return jsonify({ + "code": 200, + "msg": "Command executed successfully", + "transId": data.get("transId"), + "timestamp": int(time.time() * 1000) + }) + + except Exception as e: + print(f"Error handling vehicle command: {str(e)}") + return jsonify({ + "code": 500, + "msg": str(e), + "transId": data.get("transId", ""), + "timestamp": int(time.time() * 1000) + }), 500 + +def calculate_distance_to_intersection(vehicle, intersection): + """计算车辆到路口的距离(米)""" + vehicle_lat = vehicle["latitude"] + vehicle_lon = vehicle["longitude"] + intersection_lat = intersection["latitude"] + intersection_lon = intersection["longitude"] + + # 使用经纬度计算距离 + lat_diff = (vehicle_lat - intersection_lat) * 111319.9 + lon_diff = (vehicle_lon - intersection_lon) * (111319.9 * math.cos(math.radians(vehicle_lat))) + return math.sqrt(lat_diff * lat_diff + lon_diff * lon_diff) + +def calculate_path_direction(start_point, end_point): + """计算两点之间的方向角度(相对于正北方向,顺时针)""" + dlat = end_point["latitude"] - start_point["latitude"] + dlon = end_point["longitude"] - start_point["longitude"] + angle_rad = math.atan2(dlon, dlat) # 使用 atan2 计算角度 + angle_deg = math.degrees(angle_rad) + # 转换为顺时针方向(正北为0度) + return (90 - angle_deg) % 360 + +def get_front_traffic_light(vehicle, distance_to_west, distance_to_east): + """获取车辆前方的红绿灯状态""" + # 根据车辆的位置和方向判断前方路口 + if vehicle["vehicleNo"] == "QN001": + # 确保 phase 属性存在 + if "phase" not in vehicle: + vehicle["phase"] = 0 + + # QN001 的路线:T1 -> T2 -> T4 + if vehicle["phase"] == 0: # T1 -> T2 阶段 + # 计算 T1 到 T2 的实际方向 + actual_direction = calculate_path_direction(POINT_T1, POINT_T2) + # 允许 15 度的误差范围 + direction_diff = abs(vehicle["direction"] - actual_direction) + direction_diff = min(direction_diff, 360 - direction_diff) + + # 在西路口以北时,向 T2 移动需要判断西路口红绿灯 + if direction_diff <= 15 and vehicle["latitude"] > T2_INTERSECTION["latitude"]: + return traffic_light_data[0], distance_to_west # 西路口红绿灯 + + elif vehicle["vehicleNo"] == "QN002": + # 计算 T12 到 T8 的实际方向 + actual_direction = calculate_path_direction(POINT_T12, POINT_T8) + # 允许 15 度的误差范围 + direction_diff = abs(vehicle["direction"] - actual_direction) + direction_diff = min(direction_diff, 360 - direction_diff) + + # 在东路口以西时,向 T8 移动需要判断东路红绿灯 + if direction_diff <= 15 and vehicle["longitude"] < T6_INTERSECTION["longitude"]: + return traffic_light_data[1], distance_to_east # 东路口红绿灯 + + elif vehicle["vehicleNo"] == "TQ001": + # 确保 phase 属性存在 + if "phase" not in vehicle: + vehicle["phase"] = 0 + + # TQ001 的路线:T4 -> T2 -> T3 + if vehicle["phase"] == 0: # T4 -> T2 阶段 + # 计算 T4 到 T2 的实际方向 + actual_direction = calculate_path_direction(POINT_T4, POINT_T2) + # 允许 15 度的误差范围 + direction_diff = abs(vehicle["direction"] - actual_direction) + direction_diff = min(direction_diff, 360 - direction_diff) + + # 在西路口以东时,向 T2 移动需要判断西路口红绿灯 + if direction_diff <= 15 and vehicle["longitude"] > T2_INTERSECTION["longitude"]: + return traffic_light_data[0], distance_to_west # 西路口红绿灯 + + # 其他情况,表示车辆已经过路口或不需要判断红绿灯 + return None, float('inf') + +def update_position_with_vector(obj, target_point, start_point, speed, elapsed_time, return_to_start=False): + """ + 基于向量的位置更新逻辑 + + Args: + obj: 需要更新位置的对象(航空器或车辆) + target_point: 目标点坐标 {"latitude": float, "longitude": float} + start_point: 起点坐标 {"latitude": float, "longitude": float} + speed: 移动速度(km/h) + elapsed_time: 经过的时间(秒) + return_to_start: 是否在到达目标点时返回起点 + """ + # 检查是否在等待状态 + if "wait_until" not in obj: + obj["wait_until"] = 0 + + current_time = time.time() + if current_time < obj["wait_until"]: + return False # 还在等待中,不更新位置 + + # 计算这一步要移动的距离(米)并转换为经纬度 + speed_mps = speed * 1000 / 3600 + distance = speed_mps * elapsed_time + + # 将移动距离转换为经纬度变化量 + move_dlat, move_dlon = meters_to_degrees(distance, obj["latitude"]) + + # 将15米的判断距离转换为经纬度 + check_dlat, check_dlon = meters_to_degrees(15, obj["latitude"]) + + # 计算当前位置到终点的向量 + vector_lat = target_point["latitude"] - obj["latitude"] + vector_lon = target_point["longitude"] - obj["longitude"] + + # 计算向量长度(经纬度空间) + vector_length = math.sqrt(vector_lat * vector_lat + vector_lon * vector_lon) + + # 检查是否到达终点 + if vector_length <= math.sqrt(check_dlat * check_dlat + check_dlon * check_dlon): + if return_to_start: + # 返回起点并设置等待时间 + obj["latitude"] = start_point["latitude"] + obj["longitude"] = start_point["longitude"] + obj["wait_until"] = current_time + WAIT_TIME_AFTER_RETURN # 使用常量 + print(f"对象 {obj.get('flightNo', obj.get('vehicleNo'))} 返回起点,等待{WAIT_TIME_AFTER_RETURN}秒后继续") + else: + # 否则就停在目标点 + obj["latitude"] = target_point["latitude"] + obj["longitude"] = target_point["longitude"] + return True # 表示已到达终点 + else: + # 归一化方向向量 + unit_lat = vector_lat / vector_length + unit_lon = vector_lon / vector_length + + # 计算这一步的位置变化 + dlat = move_dlat * unit_lat + dlon = move_dlon * unit_lon + + # 更新位置 + obj["latitude"] += dlat + obj["longitude"] += dlon + return False # 表示正在移动中 + +def update_aircraft_position(aircraft, elapsed_time): + """更新航空器位置""" + reached_target = update_position_with_vector( + aircraft, + POINT_T11, # 目标点 + POINT_T13, # 起点 + aircraft["speed"], + elapsed_time, + return_to_start=True # 航空器需要返回起点 + ) + + if reached_target: + print(f"航空器 {aircraft['flightNo']} 到达终点,返回起点 T7") + +def update_vehicle_position(vehicle, elapsed_time): + """更新车辆位置""" + # 获取车辆状态 + vehicle_state = vehicle_states.get(vehicle["vehicleNo"]) + if not vehicle_state: + return + + # 确保所有车辆都有 phase 属性 + if "phase" not in vehicle: + vehicle["phase"] = 0 + print(f"初始化车辆 {vehicle['vehicleNo']} 的 phase 属性为 0") + + # 获取前方红绿灯状态和距离 + traffic_light, distance = get_front_traffic_light(vehicle, + calculate_distance_to_intersection(vehicle, T2_INTERSECTION), + calculate_distance_to_intersection(vehicle, T6_INTERSECTION)) + + # 特勤车只响应红绿灯 + if vehicle["vehicleNo"].startswith("TQ"): + # 红灯且距离停止线小于等于 50 米时停车 + if traffic_light and traffic_light["state"] == 0 and distance <= DIST_50M: + vehicle["speed"] = 0 + print(f"特勤车 {vehicle['vehicleNo']} 遇红灯在停止线前停车,距路口 {distance:.1f}米") + return + # 其他情况正常行驶 + vehicle["speed"] = DEFAULT_VEHICLE_SPEED + print(f"特勤车 {vehicle['vehicleNo']} 正常行驶,速度={vehicle['speed']}km/h") + else: + # 普通车辆的移动逻辑 + # 红灯且距离停止线小于等于 50 米时必须停车 + if traffic_light and traffic_light["state"] == 0 and distance <= DIST_50M: + vehicle["speed"] = 0 + vehicle_state.current_command = "RED" # 设置当前指令为红灯 + vehicle_state.is_running = False # 设置为停止状态 + print(f"车辆 {vehicle['vehicleNo']} 遇红灯在停止线前停车,距路口 {distance:.1f}米") + return + elif vehicle_state.current_command in ["ALERT", "WARNING"]: + # 其他停车指令 + vehicle["speed"] = 0 + print(f"车辆 {vehicle['vehicleNo']} 不能移动: command={vehicle_state.current_command}") + return + + # 可以移动,设置正常速度 + vehicle["speed"] = DEFAULT_VEHICLE_SPEED + print(f"车辆 {vehicle['vehicleNo']} 可以移动,速度={vehicle['speed']}km/h") + + # 更新位置 + if vehicle["vehicleNo"].startswith("QN"): + # 无人车的路径更新逻辑 + if vehicle["vehicleNo"] == "QN001": + if vehicle["phase"] == 0: # T1 -> T2 + reached_target = update_position_with_vector( + vehicle, + POINT_T2, # 目标点(西路口) + POINT_T1, # 起点 + vehicle["speed"], + elapsed_time, + return_to_start=False # 不返回起点,继续到下一个点 + ) + + if reached_target: + vehicle["phase"] = 1 # 切换到下一阶段 + print(f"无人车 {vehicle['vehicleNo']} 到达 T2,开始前往 T4") + + elif vehicle["phase"] == 1: # T2 -> T4 + reached_target = update_position_with_vector( + vehicle, + POINT_T4, # 目标点 + POINT_T2, # 起点 + vehicle["speed"], + elapsed_time, + return_to_start=False # 不返回起点,继续到下一个点 + ) + + if reached_target: + # 到达 T4 后直接跳回起点 T1 + vehicle["latitude"] = POINT_T1["latitude"] + vehicle["longitude"] = POINT_T1["longitude"] + vehicle["phase"] = 0 # 重置到初始阶段 + print(f"无人车 {vehicle['vehicleNo']} 到达 T4,直接返回起点 T1") + + else: # QN002 + reached_target = update_position_with_vector( + vehicle, + POINT_T8, # 目标点 + POINT_T12, # 使用新的起点 + vehicle["speed"], + elapsed_time, + return_to_start=True # QN002 需要返回起点 + ) + + if reached_target: + print(f"无人车 {vehicle['vehicleNo']} 到达终点,返回起点 T12") + + elif vehicle["vehicleNo"].startswith("TQ"): + # 特勤车的路径更新逻辑 + if vehicle["phase"] == 0: # T4 -> T2 + reached_target = update_position_with_vector( + vehicle, + POINT_T2, # 目标点(西路口) + POINT_T4, # 起点 + vehicle["speed"], + elapsed_time, + return_to_start=False # 不返回起点,继续到下一个点 + ) + + if reached_target: + vehicle["phase"] = 1 # 切换到下一阶段 + print(f"特勤车 {vehicle['vehicleNo']} 到达 T2,开始前往 T3") + + elif vehicle["phase"] == 1: # T2 -> T3 + reached_target = update_position_with_vector( + vehicle, + POINT_T3, # 目标点(特勤车终点) + POINT_T2, # 起点 + vehicle["speed"], + elapsed_time, + return_to_start=False # 不返回起点,继续到下一个点 + ) + + if reached_target: + # 到达 T3 后直接跳回起点 T4 + vehicle["latitude"] = POINT_T4["latitude"] + vehicle["longitude"] = POINT_T4["longitude"] + vehicle["phase"] = 0 # 重置到初始阶段 + print(f"特勤车 {vehicle['vehicleNo']} 到达 T3,直接返回起点 T4") + + # 新时间戳 + vehicle["time"] = int(time.time() * 1000) + +# 定义路口信息 +INTERSECTIONS = { + "TL001": { + "id": "INT001", + "name": "西路口", + "latitude": T2_INTERSECTION["latitude"], + "longitude": T2_INTERSECTION["longitude"] + }, + "TL002": { + "id": "INT002", + "name": "东路口", + "latitude": T6_INTERSECTION["latitude"], + "longitude": T6_INTERSECTION["longitude"] + } +} + +def generate_traffic_light_data(): + """生成红绿灯数据""" + traffic_light_data = [] + + # 生成两个固定路口的红绿灯数据 + for tl_id, intersection in INTERSECTIONS.items(): + signal = { + "id": tl_id, + "state": 1, # 0: RED, 1: GREEN,测试时保持绿灯 + "timestamp": int(time.time() * 1000), + "intersection": intersection["id"], + "position": { + "latitude": intersection["latitude"], + "longitude": intersection["longitude"] + } + } + traffic_light_data.append(signal) + + return traffic_light_data + +# 红绿灯数据 +traffic_light_data = generate_traffic_light_data() + +# 分别记录航空器和车辆的上次更新时间 +last_aircraft_update_time = time.time() +last_vehicle_update_time = time.time() +last_light_switch_time = time.time() + +def check_auth(): + auth_header = request.headers.get('Authorization') + if not auth_header or auth_header != AUTH_TOKEN: + return False + return True + +@app.route('/openApi/getCurrentFlightPositions', methods=['GET', 'OPTIONS']) +def get_flight_positions(): + """获取当前航空器位置信息""" + if request.method == 'OPTIONS': + return '', 204 + + if not check_auth(): + return jsonify({ + "status": 401, + "msg": "认证失败", + "data": None + }), 401 + global last_aircraft_update_time + current_time = time.time() + elapsed_time = current_time - last_aircraft_update_time + + # 只在达到更新间隔时更新位置 + if elapsed_time >= UPDATE_INTERVAL: + for aircraft in aircraft_data: + update_aircraft_position(aircraft, UPDATE_INTERVAL) + aircraft["time"] = int(current_time * 1000) + last_aircraft_update_time = current_time + + # 创建符合 API 格式的响应数据 + response_data = [] + for aircraft in aircraft_data: + api_aircraft = { + "flightNo": aircraft["flightNo"], + "longitude": aircraft["longitude"], + "latitude": aircraft["latitude"], + "time": aircraft["time"], + "altitude": aircraft["altitude"], + "trackNumber": aircraft["trackNumber"] + } + response_data.append(api_aircraft) + + return jsonify({ + "status": 200, + "msg": "当前航空器实时位置数据", + "data": response_data + }) + +def switch_traffic_light_state(): + """统一处理红绿灯状态切换""" + global last_light_switch_time + current_time = time.time() + + # 西路口红绿灯每15秒切换一次 + elapsed_since_switch = current_time - last_light_switch_time + if elapsed_since_switch >= TRAFFIC_LIGHT_SWITCH_INTERVAL: # 使用常量 + # 获取当前状态 + current_state = traffic_light_data[0]["state"] + + # 根据当前状态决定下一个状态 + if current_state == 0: # 当前是红灯 + # 切换到黄灯(表示红绿灯故障) + traffic_light_data[0]["state"] = 2 # 2 表示黄灯 + print(f"西路口红绿灯状态切换为: 黄灯(红绿灯故障)") + elif current_state == 2: # 当前是黄灯 + # 从黄灯切换到绿灯 + traffic_light_data[0]["state"] = 1 # 1 表示绿灯 + print(f"西路口红绿灯状态切换为: 绿灯") + else: # 当前是绿灯 + # 从绿灯切换到红灯 + traffic_light_data[0]["state"] = 0 # 0 表示红灯 + print(f"西路口红绿灯状态切换为: 红灯") + + last_light_switch_time = current_time + + # 更新东路口红绿灯(根据航空器位置) + if aircraft_data: + aircraft = aircraft_data[0] + lat_diff = abs(aircraft["latitude"] - T6_INTERSECTION["latitude"]) * 111319.9 + + old_state = traffic_light_data[1]["state"] + traffic_light_data[1]["state"] = 1 if lat_diff > DIST_50M else 1 + + if old_state != traffic_light_data[1]["state"]: + print(f"东路口红绿灯状态切换为: {'绿灯' if traffic_light_data[1]['state'] == 1 else '红灯'}") + +@app.route('/openApi/getCurrentVehiclePositions', methods=['GET', 'OPTIONS']) +def get_vehicle_positions(): + """获取当前车辆位置信息""" + if request.method == 'OPTIONS': + return '', 204 + if not check_auth(): + return jsonify({ + "status": 401, + "msg": "认证失败", + "data": None + }), 401 + global last_vehicle_update_time + current_time = time.time() + elapsed_time = current_time - last_vehicle_update_time + + try: + # 统一处理红绿灯状态切换 + # switch_traffic_light_state() # 采用模拟的红绿灯脚本,注释掉此处的调用 + + # 只在达到更新间隔时更新位置 + if elapsed_time >= UPDATE_INTERVAL: + for vehicle in vehicle_data: + update_vehicle_position(vehicle, UPDATE_INTERVAL) + vehicle["time"] = int(current_time * 1000) + last_vehicle_update_time = current_time + + return jsonify({ + "status": 200, + "msg": "当前车辆实时位置数据", + "data": vehicle_data + }) + + except Exception as e: + print(f"Error in get_vehicle_positions: {str(e)}") + return jsonify({ + "status": 500, + "msg": str(e), + "data": None + }), 500 + +@app.route('/openApi/getTrafficLightSignals', methods=['GET', 'OPTIONS']) +def get_traffic_light_signals(): + """获取红绿灯信号状态 (旧的轮询接口)""" + if request.method == 'OPTIONS': + return '', 204 + + # --- 注释掉原有逻辑 (2025-05-06 by AI Assistant) --- + # 原因: 为了测试新的 TrafficLightHttpServer 推送机制,暂时禁用此轮询接口返回数据。 + # 模拟和发送红绿灯状态的功能现在由 tools/mock_traffic_light_client.py 负责。 + # 如需恢复轮询测试,请取消下面的注释并注释掉新的 return 语句。 + # # 更新红绿灯状态 + # # switch_traffic_light_state() # 采用模拟的红绿灯脚本,注释掉此处的调用 + # + # # 更新时间戳 + # for signal in traffic_light_data: + # signal[\"timestamp\"] = int(time.time() * 1000) + # + # return jsonify({ + # \"status\": 200, + # \"msg\": \"获取红绿灯信号成功\", + # \"data\": traffic_light_data + # }) + # --- 注释结束 --- + + # 新增: 返回空数据,以禁用轮询数据源 + return jsonify({ + "status": 200, + "msg": "Traffic light polling endpoint disabled to test push mechanism.", + "data": [] + }) + +@app.after_request +def add_cors_headers(response): + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' + response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' + response.headers['Access-Control-Max-Age'] = '3600' + return response + +def check_vehicle_states(): + """定期检查所有车辆状态""" + for vehicle_no, state in vehicle_states.items(): + state.log_state() + +# 认证相关配置 +AUTH_USERNAME = "dianxin" +AUTH_PASSWORD = "dianxin@123" +AUTH_TOKEN = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY" + +@app.route('/login', methods=['POST', 'OPTIONS']) +def login(): + if request.method == 'OPTIONS': + return '', 204 + + # 从 URL query parameters 中获取用户名和密码 + username = request.args.get('username') + password = request.args.get('password') + + print(f"收到登录请求: username={username}, password={password}") + print(f"请求 URL: {request.url}") + print(f"请求方法: {request.method}") + print(f"请求参数: {request.args}") + + if not username or not password: + return jsonify({ + "status": 400, + "msg": "缺少用户名或密码", + "data": None + }), 400 + + if username == AUTH_USERNAME and password == AUTH_PASSWORD: + return jsonify({ + "status": 200, + "msg": "登入成功", + "data": AUTH_TOKEN + }) + else: + return jsonify({ + "status": 401, + "msg": "用户名或密码错误", + "data": None + }), 401 + +@app.route('/openApi/getVehicleStatus', methods=['GET', 'OPTIONS']) +def get_vehicle_status(): + """获取无人车状态信息""" + if request.method == 'OPTIONS': + return '', 204 + + vehicle_id = request.args.get('vehicleId') + if not vehicle_id: + return jsonify({ + "status": 400, + "msg": "缺少 vehicleId 参数", + "data": None + }), 400 + + # 查找对应的车辆 + vehicle_state = vehicle_states.get(vehicle_id) + if not vehicle_state: + return jsonify({ + "status": 404, + "msg": f"未找到车辆 {vehicle_id}", + "data": None + }), 404 + + # 返回车辆状态 + return jsonify({ + "status": 200, + "msg": "获取车辆状态成功", + "data": { + "vehicleId": vehicle_id, + "status": "NORMAL" if vehicle_state.is_running else "STOPPED", + "command": vehicle_state.current_command, + "commandPriority": vehicle_state.command_priority, + "trafficLightState": vehicle_state.traffic_light_state, + "timestamp": int(time.time() * 1000) + } + }) + +# 无人车位置数据生成函数 +def generate_unmanned_vehicle_location_data(): + """生成无人车位置数据,符合官方API格式""" + unmanned_vehicles = [] + + # 从现有vehicle_data中筛选无人车(QN开头的车辆) + for vehicle in vehicle_data: + if vehicle["vehicleNo"].startswith("QN"): + location_info = { + "transId": str(uuid.uuid4()), + "timestamp": int(time.time() * 1000), + "vehicleID": vehicle["vehicleNo"], + "latitude": vehicle["latitude"], + "longitude": vehicle["longitude"], + "speed": vehicle["speed"] / 3.6, # 转换为m/s + "direction": math.radians(vehicle["direction"]) # 转换为弧度 + } + unmanned_vehicles.append(location_info) + + return unmanned_vehicles + +# 无人车状态数据生成函数 +def generate_unmanned_vehicle_state_data(vehicle_id=None, is_single=True): + """生成无人车状态数据,符合官方API格式""" + vehicle_states_data = [] + + if is_single and vehicle_id: + # 单个车辆状态查询 + vehicle_state = vehicle_states.get(vehicle_id) + if vehicle_state: + state_info = { + "transId": str(uuid.uuid4()), + "timestamp": int(time.time() * 1000), + "vehicleID": vehicle_id, + "loginState": True, + "faultInfo": [], + "activeSafety": not vehicle_state.is_running, + "RC": False, + "Command": 1 if vehicle_state.current_command == "ALERT" else 0, + "airportInfo": [], + "vehicleMode": 2, # 自动模式 + "gearState": 2, # D档 + "chassisReady": vehicle_state.is_running, + "collisionStatus": False, + "clearance": 1 if vehicle_state.is_running else 0, + "turnSignalStstus": 0, + "pointCloud": [] + } + vehicle_states_data.append(state_info) + else: + # 所有车辆状态查询 + for vehicle in vehicle_data: + if vehicle["vehicleNo"].startswith("QN"): + vehicle_state = vehicle_states.get(vehicle["vehicleNo"]) + state_info = { + "transId": str(uuid.uuid4()), + "timestamp": int(time.time() * 1000), + "vehicleID": vehicle["vehicleNo"], + "loginState": True, + "faultInfo": [], + "activeSafety": not vehicle_state.is_running if vehicle_state else False, + "RC": False, + "Command": 1 if vehicle_state and vehicle_state.current_command == "ALERT" else 0, + "airportInfo": [], + "vehicleMode": 2, + "gearState": 2, + "chassisReady": vehicle_state.is_running if vehicle_state else True, + "collisionStatus": False, + "clearance": 1 if vehicle_state and vehicle_state.is_running else 0, + "turnSignalStstus": 0, + "pointCloud": [] + } + vehicle_states_data.append(state_info) + + return vehicle_states_data + +# 无人车API接口 +@app.route('/api/VehicleLocationInfo', methods=['GET', 'OPTIONS']) +def get_unmanned_vehicle_location(): + """无人车位置上报接口""" + if request.method == 'OPTIONS': + return '', 204 + + try: + # 更新车辆位置 + current_time = time.time() + global last_vehicle_update_time + elapsed_time = current_time - last_vehicle_update_time + + if elapsed_time >= UPDATE_INTERVAL: + for vehicle in vehicle_data: + update_vehicle_position(vehicle, UPDATE_INTERVAL) + vehicle["time"] = int(current_time * 1000) + last_vehicle_update_time = current_time + + # 生成无人车位置数据 + location_data = generate_unmanned_vehicle_location_data() + + print(f"返回无人车位置数据,数量: {len(location_data)}") + return jsonify(location_data) + + except Exception as e: + print(f"Error in get_unmanned_vehicle_location: {str(e)}") + return jsonify([]), 500 + +@app.route('/api/VehicleStateInfo', methods=['POST', 'OPTIONS']) +def get_unmanned_vehicle_state(): + """无人车状态查询接口""" + if request.method == 'OPTIONS': + return '', 204 + + try: + data = request.json + vehicle_id = data.get("vehicleID") if data else None + is_single = data.get("isSingle", True) if data else False + + print(f"收到无人车状态查询请求: vehicle_id={vehicle_id}, is_single={is_single}") + + # 生成无人车状态数据 + state_data = generate_unmanned_vehicle_state_data(vehicle_id, is_single) + + print(f"返回无人车状态数据,数量: {len(state_data)}") + return jsonify(state_data) + + except Exception as e: + print(f"Error in get_unmanned_vehicle_state: {str(e)}") + return jsonify([]), 500 + +if __name__ == '__main__': + app.run(host='localhost', port=8090, debug=True) \ No newline at end of file diff --git a/tools/mock_traffic_light_client.py b/tools/mock_traffic_light_client.py new file mode 100644 index 0000000..d6dc31a --- /dev/null +++ b/tools/mock_traffic_light_client.py @@ -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() \ No newline at end of file diff --git a/tools/test_websocket.html b/tools/test_websocket.html new file mode 100644 index 0000000..908630a --- /dev/null +++ b/tools/test_websocket.html @@ -0,0 +1,135 @@ + + + + WebSocket 测试 + + + +

WebSocket 测试客户端

+
+
+ + + +
+ + + + \ No newline at end of file