Initial commit
This commit is contained in:
commit
1fc14d7438
14
.clang-format
Normal file
14
.clang-format
Normal file
@ -0,0 +1,14 @@
|
||||
# 精简版 K&R 风格配置
|
||||
BasedOnStyle: LLVM
|
||||
BreakBeforeBraces: Linux
|
||||
IndentWidth: 4
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
|
||||
# 显式禁用所有大括号换行
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterControlStatement: Never
|
||||
22
.clangd
Normal file
22
.clangd
Normal file
@ -0,0 +1,22 @@
|
||||
CompileFlags:
|
||||
Add:
|
||||
- "-I${workspaceFolder}/src"
|
||||
- "-I/opt/homebrew/include"
|
||||
- "-I/opt/homebrew/Cellar/boost/1.86.0_2/include"
|
||||
- "-I/opt/homebrew/Cellar/nlohmann-json/3.11.3/include"
|
||||
Remove:
|
||||
- "-W*"
|
||||
- "-std=*"
|
||||
Compiler: clang++
|
||||
|
||||
Diagnostics:
|
||||
ClangTidy:
|
||||
Add:
|
||||
- modernize*
|
||||
- performance*
|
||||
Remove:
|
||||
- modernize-use-trailing-return-type
|
||||
UnusedIncludes: Strict
|
||||
|
||||
Index:
|
||||
Background: Build
|
||||
3
.cursorignore
Normal file
3
.cursorignore
Normal file
@ -0,0 +1,3 @@
|
||||
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
|
||||
.cursorignore
|
||||
build/
|
||||
44
.cursorrules
Normal file
44
.cursorrules
Normal file
@ -0,0 +1,44 @@
|
||||
请遵循:
|
||||
- 最小化修改代码,不要修改任何与解决当前问题无关的代码(包括注释),一定不能删除任何其他的代码
|
||||
- 使用源代码中的格式化方式和注释风格
|
||||
- 日志的字符串格式采用拼接方式,不要使用{}占位符
|
||||
|
||||
请遵循以下 Markdown 规范:
|
||||
|
||||
1. 标题层级
|
||||
- 文档标题使用一级标题 (#)
|
||||
- 主要章节使用二级标题 (##)
|
||||
- 子章节使用三级及以下标题
|
||||
- 标题和内容之间空一行
|
||||
- 标题层级不跳跃使用
|
||||
|
||||
2. 列表格式
|
||||
- 无序列表统一使用 -
|
||||
- 列表项前后空一行
|
||||
- 列表嵌套使用两个空格缩进
|
||||
|
||||
3. 代码块
|
||||
- 使用 ```language 指定语言
|
||||
- 代码块前后空一行
|
||||
- 代码添加必要的注释
|
||||
|
||||
4. 内容格式
|
||||
- 中英文之间加空格
|
||||
- 使用半角标点符号
|
||||
- 代码、参数等使用反引号包裹
|
||||
- 重要内容使用加粗或斜体
|
||||
|
||||
5. 文档结构
|
||||
- 章节层次分明
|
||||
- 内容组织有序
|
||||
- 格式统一一致
|
||||
|
||||
请遵循如下 C++ 规范:
|
||||
- 使用 C++17 标准
|
||||
- 使用 CMake 3.14 及以上版本
|
||||
- 使用 nlohmann_json 3.11.3 版本
|
||||
- 使用 FetchContent 管理第三方库
|
||||
- 使用 Google Test 进行单元测试
|
||||
- 使用 Google Mock 进行单元测试
|
||||
- 使用 Google Benchmark 进行性能测试
|
||||
- 在头文件中定义函数,在源文件中实现函数
|
||||
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
# Build directories
|
||||
build/
|
||||
out/
|
||||
logs/
|
||||
cmake-build-*/
|
||||
|
||||
# IDE directories
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Compiled files
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# CMake generated files
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
|
||||
# Python cache files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
pyvenv.cfg
|
||||
|
||||
# macOS system files
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Dependency directories
|
||||
/vendor/
|
||||
/third_party/
|
||||
|
||||
# Test generated files
|
||||
Testing/
|
||||
|
||||
assistant_snippet*
|
||||
197
CMakeLists.txt
Normal file
197
CMakeLists.txt
Normal file
@ -0,0 +1,197 @@
|
||||
cmake_minimum_required(VERSION 3.14...3.27)
|
||||
|
||||
# 设置编译器
|
||||
if(NOT APPLE)
|
||||
set(CMAKE_C_COMPILER "/usr/local/bin/gcc")
|
||||
set(CMAKE_CXX_COMPILER "/usr/local/bin/g++")
|
||||
endif()
|
||||
|
||||
# 设置 CMake 策略(移除非必要的新策略)
|
||||
cmake_policy(SET CMP0074 NEW) # 保留必要的包根目录策略
|
||||
|
||||
# 包含 FetchContent 模块
|
||||
include(FetchContent)
|
||||
|
||||
# 生成 compile_commands.json
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
project(collision_avoidance)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# 检查编译器版本
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
|
||||
message(FATAL_ERROR "GCC 版本需要 >= 7.0 以支持 C++17")
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
|
||||
message(FATAL_ERROR "Clang 版本需要 >= 5.0 以支持 C++17")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 设置输出目录
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
|
||||
# 设置 Boost
|
||||
set(Boost_NO_WARN_NEW_VERSIONS 1)
|
||||
set(Boost_USE_STATIC_LIBS OFF)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
if(NOT APPLE)
|
||||
# 在 CentOS 上使用 Boost 1.69
|
||||
set(BOOST_ROOT "/usr/include/boost169")
|
||||
set(BOOST_LIBRARYDIR "/usr/lib64/boost169")
|
||||
else()
|
||||
# 在 macOS 上使用 Homebrew 安装的 Boost
|
||||
set(BOOST_ROOT "/opt/homebrew/Cellar/boost/1.87.0")
|
||||
set(BOOST_LIBRARYDIR "/opt/homebrew/Cellar/boost/1.87.0/lib")
|
||||
endif()
|
||||
|
||||
# 设置 CMake 策略(在 find_package(Boost) 前添加)
|
||||
if(POLICY CMP0167)
|
||||
cmake_policy(SET CMP0167 OLD) # 强制使用传统 FindBoost 模块
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.69.0 REQUIRED COMPONENTS
|
||||
system
|
||||
filesystem
|
||||
thread
|
||||
chrono
|
||||
date_time
|
||||
atomic
|
||||
regex # 添加缺失的 regex 组件
|
||||
)
|
||||
|
||||
# 添加 libcurl
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
# 修改 JSON 库配置为双模式
|
||||
if(APPLE)
|
||||
# macOS 使用 FetchContent
|
||||
FetchContent_Declare(
|
||||
nlohmann_json
|
||||
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
||||
GIT_TAG v3.11.3
|
||||
)
|
||||
FetchContent_MakeAvailable(nlohmann_json)
|
||||
else()
|
||||
# CentOS 使用系统安装的包
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
|
||||
# 验证系统包路径
|
||||
if(NOT TARGET nlohmann_json::nlohmann_json)
|
||||
message(FATAL_ERROR "系统未安装正确版本的 nlohmann_json")
|
||||
endif()
|
||||
|
||||
# 添加手动包含路径(适用于旧版CMake)
|
||||
include_directories(${nlohmann_json_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
# 源文件列表
|
||||
set(LIB_SOURCES
|
||||
src/collector/DataCollector.cpp
|
||||
src/collector/DataSourceConfig.cpp
|
||||
src/core/System.cpp
|
||||
src/detector/CollisionDetector.cpp
|
||||
src/detector/SafetyZone.cpp
|
||||
src/network/HTTPDataSource.cpp
|
||||
src/network/WebSocketServer.cpp
|
||||
src/network/HTTPClient.cpp
|
||||
src/network/TrafficLightHttpServer.cpp
|
||||
src/spatial/CoordinateConverter.cpp
|
||||
src/types/BasicTypes.cpp
|
||||
src/types/VehicleData.cpp
|
||||
src/vehicle/ControllableVehicles.cpp
|
||||
src/config/AirportBounds.cpp
|
||||
src/config/SystemConfig.cpp
|
||||
src/config/IntersectionConfig.cpp
|
||||
src/detector/TrafficLightDetector.cpp
|
||||
)
|
||||
|
||||
# 创建主库
|
||||
add_library(${PROJECT_NAME}_lib ${LIB_SOURCES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}_lib
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CURL_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}_lib
|
||||
PUBLIC
|
||||
Boost::system
|
||||
Boost::filesystem
|
||||
Boost::thread
|
||||
Boost::chrono
|
||||
Boost::date_time
|
||||
Boost::atomic
|
||||
Boost::regex
|
||||
nlohmann_json::nlohmann_json
|
||||
CURL::libcurl
|
||||
Threads::Threads
|
||||
-pthread
|
||||
)
|
||||
|
||||
# 创建主可执行文件
|
||||
add_executable(${PROJECT_NAME} src/main.cpp)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_lib)
|
||||
|
||||
# 添加一个选项来控制是否编译测试
|
||||
if(APPLE)
|
||||
option(BUILD_TESTS "Build test cases" ON)
|
||||
else()
|
||||
option(BUILD_TESTS "Build test cases" OFF)
|
||||
endif()
|
||||
|
||||
# 添加测试
|
||||
if(BUILD_TESTS AND EXISTS "${CMAKE_SOURCE_DIR}/tests")
|
||||
if(APPLE)
|
||||
# macOS 使用 FetchContent 下载 googletest
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG v1.15.2
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
else()
|
||||
# CentOS 也使用 FetchContent 下载 googletest
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG v1.15.2
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
endif()
|
||||
|
||||
# 添加 tests 子目录
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# 打印配置信息
|
||||
message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
|
||||
message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
|
||||
message(STATUS "Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}")
|
||||
message(STATUS "Boost_LIBRARIES: ${Boost_LIBRARIES}")
|
||||
message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
|
||||
message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
|
||||
# 复制配置文件到构建目录
|
||||
configure_file(${CMAKE_SOURCE_DIR}/config/system_config.json ${CMAKE_BINARY_DIR}/config/system_config.json COPYONLY)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/config/intersections.json ${CMAKE_BINARY_DIR}/config/intersections.json COPYONLY)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/config/airport_bounds.json ${CMAKE_BINARY_DIR}/config/airport_bounds.json COPYONLY)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/config/unmanned_vehicles.json ${CMAKE_BINARY_DIR}/config/unmanned_vehicles.json COPYONLY)
|
||||
|
||||
# 查找 Threads 包
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if(NOT APPLE)
|
||||
target_link_libraries(collision_avoidance
|
||||
PRIVATE
|
||||
${PROJECT_NAME}_lib
|
||||
)
|
||||
endif()
|
||||
208
README.md
Normal file
208
README.md
Normal file
@ -0,0 +1,208 @@
|
||||
# 机场地面碰撞预警系统(测试平台)
|
||||
|
||||
## 项目简介
|
||||
|
||||
该系统用于监测和预警机场地面航空器与车辆之间的潜在碰撞风险。通过实时采集和分析位置数据,为机场运营提供安全保障。
|
||||
本系统运行在机场测试平台中,用于测试和验证碰撞预警系统的功能可行性。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 实时数据采集:获取航空器和车辆的位置信息
|
||||
- 碰撞风险检测:基于安全距离进行碰撞风险评估
|
||||
- 运动参数计算:自动计算速度和航向信息
|
||||
- 坐标转换:支持地理坐标和平面坐标转换
|
||||
- 日志记录:详细的运行日志和警告信息
|
||||
|
||||
## 系统架构
|
||||
|
||||
- 数据采集模块:从数据源获取位置信息
|
||||
- 碰撞检测模块:分析潜在的碰撞风险
|
||||
- 坐标转换模块:处理不同坐标系统
|
||||
- 核心数据类型:定义基础的数据结构
|
||||
- 性能测试模块:使用 Google Benchmark 进行性能评估
|
||||
|
||||
## 开发环境
|
||||
|
||||
- C++17
|
||||
- CMake 3.14+
|
||||
- Boost 1.86.0
|
||||
- nlohmann_json 3.11.3
|
||||
- Google Test
|
||||
- Google Mock
|
||||
- Google Benchmark
|
||||
|
||||
## 构建说明
|
||||
|
||||
```bash
|
||||
# 创建构建目录
|
||||
mkdir build && cd build
|
||||
|
||||
# 配置项目(启用测试)
|
||||
cmake -DBUILD_TESTING=ON ..
|
||||
|
||||
# 编译
|
||||
cmake --build .
|
||||
|
||||
# 运行单元测试
|
||||
ctest --output-on-failure
|
||||
|
||||
# 运行性能测试
|
||||
./bin/benchmark_tests
|
||||
```
|
||||
|
||||
## 测试框架
|
||||
|
||||
- 单元测试:使用 Google Test 框架
|
||||
- Mock 测试:使用 Google Mock 进行模拟
|
||||
- 性能测试:使用 Google Benchmark
|
||||
- 测试覆盖:
|
||||
- 基础数据类型
|
||||
- 碰撞检测逻辑
|
||||
- 数据采集功能
|
||||
- HTTP 数据源
|
||||
- 性能基准测试
|
||||
|
||||
## 目录结构
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[CollisionAvoidance] --> B[src]
|
||||
A --> C[tests]
|
||||
A --> D[tools]
|
||||
A --> E[docs]
|
||||
A --> F[build]
|
||||
A --> G[benchmarks]
|
||||
|
||||
B --> BA[collector]
|
||||
B --> BB[detector]
|
||||
B --> BC[network]
|
||||
B --> BD[spatial]
|
||||
B --> BE[types]
|
||||
B --> BF[utils]
|
||||
B --> BG[core]
|
||||
|
||||
BA --> BAA[DataCollector.h/cpp]
|
||||
BA --> BAB[DataSource.h]
|
||||
|
||||
BB --> BBA[CollisionDetector.h/cpp]
|
||||
|
||||
BC --> BCA[HTTPDataSource.h/cpp]
|
||||
BC --> BCB[ConnectionConfig.h]
|
||||
|
||||
BD --> BDA[CoordinateConverter.h/cpp]
|
||||
|
||||
BE --> BEA[BasicTypes.h/cpp]
|
||||
|
||||
BF --> BFA[Logger.h/cpp]
|
||||
|
||||
BG --> BGA[System.h/cpp]
|
||||
|
||||
C --> CA[BasicTypesTest.cpp]
|
||||
C --> CB[CollisionDetectorTest.cpp]
|
||||
C --> CC[DataCollectorTest.cpp]
|
||||
C --> CD[HTTPDataSourceTest.cpp]
|
||||
|
||||
G --> GA[CollisionDetectorBenchmark.cpp]
|
||||
G --> GB[CoordinateConverterBenchmark.cpp]
|
||||
|
||||
D --> DA[mock_server.py]
|
||||
```
|
||||
|
||||
## 依赖管理
|
||||
|
||||
项目使用 CMake 的 FetchContent 模块管理第三方依赖:
|
||||
|
||||
```cmake
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
json
|
||||
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
|
||||
)
|
||||
FetchContent_MakeAvailable(json)
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
- 数据源配置:
|
||||
- 主机:localhost
|
||||
- 端口:8081
|
||||
- API路径:
|
||||
- /api/getCurrentFlightPositions
|
||||
- /api/getCurrentVehiclePositions
|
||||
|
||||
- 安全参数:
|
||||
- 水平安全距离:50米
|
||||
- 垂直安全距离:50米
|
||||
- 最大速度限制:
|
||||
- 航空器:100米/秒
|
||||
- 车辆:30米/秒
|
||||
|
||||
## 开发团队
|
||||
|
||||
- 项目负责人:[田建勇]
|
||||
- 开发人员:[赵豪、陈横、刘青宇]
|
||||
|
||||
## 许可证
|
||||
|
||||
[License Type]
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.1.0 (2024-03-20)
|
||||
- 添加 Google Benchmark 性能测试
|
||||
- 升级 CMake 最低版本至 3.14
|
||||
- 规范化第三方库版本管理
|
||||
- 添加 FetchContent 依赖管理
|
||||
|
||||
- v1.0.0 (2024-11-15)
|
||||
- 初始版本发布
|
||||
- 基本功能实现
|
||||
- 单元测试框架搭建
|
||||
|
||||
## Mock 服务
|
||||
|
||||
用于模拟机场场面的移动物体(航空器和车辆),提供以下功能:
|
||||
|
||||
- 航空器位置更新
|
||||
- 车辆位置更新
|
||||
- 红绿灯状态更新
|
||||
- 车辆控制指令响应
|
||||
|
||||
### API 接口
|
||||
|
||||
- GET /api/getCurrentFlightPositions - 获取航空器位置
|
||||
- GET /api/getCurrentVehiclePositions - 获取车辆位置
|
||||
- GET /api/getTrafficLightSignals - 获取红绿灯状态
|
||||
- POST /api/vehicle/command - 发送车辆控制指令
|
||||
|
||||
### 运行方式
|
||||
|
||||
```bash
|
||||
cd tools
|
||||
python mock_server.py
|
||||
```
|
||||
|
||||
服务默认监听:
|
||||
|
||||
- HTTP 服务:<http://localhost:8081>
|
||||
|
||||
### 模拟场景
|
||||
|
||||
- 航空器:T7 -> T11,到达后返回起点
|
||||
- 无人车1 (QN001):T1 -> T2 -> T4,到达后返回起点
|
||||
- 无人车2 (QN002):T12 -> T8,到达后返回起点
|
||||
- 特勤车 (TQ001):T4 -> T2 -> T3,到达后返回起点
|
||||
|
||||
### 红绿灯控制
|
||||
|
||||
- 西路口 (TL001):每10秒切换一次
|
||||
- 东路口 (TL002):根据航空器位置自动控制
|
||||
|
||||
### 车辆控制指令优先级
|
||||
|
||||
1. ALERT (5) - 告警指令
|
||||
2. RED (4) - 红灯指令
|
||||
3. WARNING (3) - 预警指令
|
||||
4. GREEN (2) - 绿灯状态
|
||||
5. RESUME (1) - 恢复指令
|
||||
7
compile_commands.json
Normal file
7
compile_commands.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"commands": [
|
||||
"cd build",
|
||||
"cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..",
|
||||
"ln -s build/compile_commands.json ."
|
||||
]
|
||||
}
|
||||
147
config/airport_bounds.json
Normal file
147
config/airport_bounds.json
Normal file
@ -0,0 +1,147 @@
|
||||
{
|
||||
"airport": {
|
||||
"rotation_angle": 68.53,
|
||||
"reference_point": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": -100,
|
||||
"y": -200,
|
||||
"width": 800,
|
||||
"height": 400
|
||||
}
|
||||
},
|
||||
"areas": {
|
||||
"runway": {
|
||||
"bounds": {
|
||||
"x": -101,
|
||||
"y": -201,
|
||||
"width": 1,
|
||||
"height": 1
|
||||
},
|
||||
"config": {
|
||||
"collision_radius": {
|
||||
"aircraft": 100.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
},
|
||||
"height_threshold": 15.0,
|
||||
"warning_zone_radius": {
|
||||
"aircraft": 200.0,
|
||||
"special": 100.0,
|
||||
"unmanned": 50.0
|
||||
},
|
||||
"alert_zone_radius": {
|
||||
"aircraft": 100.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"taxiway": {
|
||||
"bounds": {
|
||||
"x": -101,
|
||||
"y": -201,
|
||||
"width": 1,
|
||||
"height": 1
|
||||
},
|
||||
"config": {
|
||||
"collision_radius": {
|
||||
"aircraft": 50.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
},
|
||||
"height_threshold": 10.0,
|
||||
"warning_zone_radius": {
|
||||
"aircraft": 100.0,
|
||||
"special": 100.0,
|
||||
"unmanned": 50.0
|
||||
},
|
||||
"alert_zone_radius": {
|
||||
"aircraft": 50.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"gate": {
|
||||
"bounds": {
|
||||
"x": -101,
|
||||
"y": -201,
|
||||
"width": 1,
|
||||
"height": 1
|
||||
},
|
||||
"config": {
|
||||
"collision_radius": {
|
||||
"aircraft": 40.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
},
|
||||
"height_threshold": 5.0,
|
||||
"warning_zone_radius": {
|
||||
"aircraft": 80.0,
|
||||
"special": 100.0,
|
||||
"unmanned": 50.0
|
||||
},
|
||||
"alert_zone_radius": {
|
||||
"aircraft": 40.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"bounds": {
|
||||
"x": -101,
|
||||
"y": -201,
|
||||
"width": 1,
|
||||
"height": 1
|
||||
},
|
||||
"config": {
|
||||
"collision_radius": {
|
||||
"aircraft": 30.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
},
|
||||
"height_threshold": 5.0,
|
||||
"warning_zone_radius": {
|
||||
"aircraft": 60.0,
|
||||
"special": 100.0,
|
||||
"unmanned": 50.0
|
||||
},
|
||||
"alert_zone_radius": {
|
||||
"aircraft": 30.0,
|
||||
"special": 50.0,
|
||||
"unmanned": 25.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"test_zone": {
|
||||
"bounds": {
|
||||
"x": -100,
|
||||
"y": -200,
|
||||
"width": 800,
|
||||
"height": 400
|
||||
},
|
||||
"config": {
|
||||
"collision_radius": {
|
||||
"aircraft": 30.0,
|
||||
"special": 15.0,
|
||||
"unmanned": 15.0
|
||||
},
|
||||
"height_threshold": 10.0,
|
||||
"warning_zone_radius": {
|
||||
"aircraft": 70.0,
|
||||
"special": 30.0,
|
||||
"unmanned": 30.0
|
||||
},
|
||||
"alert_zone_radius": {
|
||||
"aircraft": 35.0,
|
||||
"special": 15.0,
|
||||
"unmanned": 15.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
config/intersections.json
Normal file
34
config/intersections.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"intersections": [
|
||||
{
|
||||
"id": "T2路口",
|
||||
"name": "无人车与特勤车交叉路口",
|
||||
"trafficLightId": "TL001",
|
||||
"position": {
|
||||
"longitude": 120.08502054,
|
||||
"latitude": 36.35448347,
|
||||
"altitude": 9.543
|
||||
},
|
||||
"width": 20.0,
|
||||
"safetyZone": {
|
||||
"aircraftRadius": 50.0,
|
||||
"vehicleRadius": 50.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "T6路口",
|
||||
"name": "无人车与飞机交叉路口",
|
||||
"trafficLightId": "TL002",
|
||||
"position": {
|
||||
"longitude": 120.08649105,
|
||||
"latitude": 36.35074527,
|
||||
"altitude": 9.778
|
||||
},
|
||||
"width": 30.0,
|
||||
"safetyZone": {
|
||||
"aircraftRadius": 50.0,
|
||||
"vehicleRadius": 50.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
165
config/system_config.json
Normal file
165
config/system_config.json
Normal file
@ -0,0 +1,165 @@
|
||||
{
|
||||
"airport": {
|
||||
"name": "青岛胶东国际机场",
|
||||
"iata": "TAO",
|
||||
"icao": "ZSQD",
|
||||
"reference_point": {
|
||||
"latitude": 36.35448347,
|
||||
"longitude": 120.08502054
|
||||
},
|
||||
"coordinate_points": [
|
||||
{
|
||||
"point": "T1",
|
||||
"longitude": 120.0868853,
|
||||
"latitude": 36.35496367
|
||||
},
|
||||
{
|
||||
"point": "T2",
|
||||
"longitude": 120.08502054,
|
||||
"latitude": 36.35448347
|
||||
},
|
||||
{
|
||||
"point": "T3",
|
||||
"longitude": 120.08341044,
|
||||
"latitude": 36.35406879
|
||||
},
|
||||
{
|
||||
"point": "T4",
|
||||
"longitude": 120.08558121,
|
||||
"latitude": 36.35305878
|
||||
},
|
||||
{
|
||||
"point": "T5",
|
||||
"longitude": 120.08400957,
|
||||
"latitude": 36.35265197
|
||||
},
|
||||
{
|
||||
"point": "T6",
|
||||
"longitude": 120.08649105,
|
||||
"latitude": 36.35074527
|
||||
},
|
||||
{
|
||||
"point": "T7",
|
||||
"longitude": 120.08562915,
|
||||
"latitude": 36.35052372
|
||||
},
|
||||
{
|
||||
"point": "T8",
|
||||
"longitude": 120.08676664,
|
||||
"latitude": 36.35004529
|
||||
},
|
||||
{
|
||||
"point": "T9",
|
||||
"longitude": 120.08520616,
|
||||
"latitude": 36.34964473
|
||||
},
|
||||
{
|
||||
"point": "T10",
|
||||
"longitude": 120.08710569,
|
||||
"latitude": 36.34917893
|
||||
},
|
||||
{
|
||||
"point": "T11",
|
||||
"longitude": 120.0873865,
|
||||
"latitude": 36.3509885
|
||||
},
|
||||
{
|
||||
"point": "T12",
|
||||
"longitude": 120.08603613,
|
||||
"latitude": 36.35190217
|
||||
},
|
||||
{
|
||||
"point": "T13",
|
||||
"longitude": 120.08509148,
|
||||
"latitude": 36.35041247
|
||||
}
|
||||
]
|
||||
},
|
||||
"data_source": {
|
||||
"position": {
|
||||
"host": "localhost",
|
||||
"port": 8081,
|
||||
"aircraft_path": "/openApi/getCurrentFlightPositions",
|
||||
"vehicle_path": "/openApi/getCurrentVehiclePositions",
|
||||
"refresh_interval_ms": 1000,
|
||||
"auth": {
|
||||
"username": "dianxin",
|
||||
"password": "dianxin@123",
|
||||
"auth_path": "/login",
|
||||
"auth_required": true
|
||||
},
|
||||
"timeout_ms": 5000,
|
||||
"read_timeout_ms": 2000
|
||||
},
|
||||
"unmanned_vehicle": {
|
||||
"host": "localhost",
|
||||
"port": 8081,
|
||||
"location_path": "/api/VehicleLocationInfo",
|
||||
"status_path": "/api/VehicleStateInfo",
|
||||
"command_path": "/api/VehicleCommandInfo",
|
||||
"refresh_interval_ms": 1000,
|
||||
"auth": {
|
||||
"username": "dianxin",
|
||||
"password": "dianxin@123",
|
||||
"auth_path": "/api/login",
|
||||
"auth_required": false
|
||||
},
|
||||
"timeout_ms": 5000,
|
||||
"read_timeout_ms": 2000
|
||||
},
|
||||
"traffic_light": {
|
||||
"host": "localhost",
|
||||
"port": 8081,
|
||||
"signal_path": "/openApi/getTrafficLightSignals",
|
||||
"refresh_interval_ms": 1000,
|
||||
"auth": {
|
||||
"username": "dianxin",
|
||||
"password": "dianxin@123",
|
||||
"auth_path": "/api/login",
|
||||
"auth_required": false
|
||||
},
|
||||
"timeout_ms": 5000,
|
||||
"read_timeout_ms": 2000
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"warning_interval_ms": 1000,
|
||||
"log_interval_ms": 3000
|
||||
},
|
||||
"websocket": {
|
||||
"port": 8010,
|
||||
"max_connections": 100,
|
||||
"ping_interval_ms": 30000,
|
||||
"position_update": {
|
||||
"aircraft_interval_ms": 300,
|
||||
"vehicle_interval_ms": 500,
|
||||
"traffic_light_interval_ms": 1000
|
||||
}
|
||||
},
|
||||
"collision_detection": {
|
||||
"update_interval_ms": 200,
|
||||
"prediction": {
|
||||
"time_window": 20.0,
|
||||
"vehicle_size": 20.0,
|
||||
"aircraft_size": 60.0,
|
||||
"min_unmanned_speed": 1.0
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"level": "debug",
|
||||
"file": "logs/system.log",
|
||||
"max_size_mb": 10,
|
||||
"max_files": 5,
|
||||
"console_output": true
|
||||
},
|
||||
"debug": {
|
||||
"enable_mock_data": false,
|
||||
"save_raw_data": false,
|
||||
"profile_performance": false
|
||||
},
|
||||
"traffic_light_server": {
|
||||
"port": 8082,
|
||||
"max_connections": 100
|
||||
},
|
||||
"simulated_mobile_light_target_intersection_id": "T2路口"
|
||||
}
|
||||
22
config/unmanned_vehicles.json
Normal file
22
config/unmanned_vehicles.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"vehicles": [
|
||||
{
|
||||
"vehicleNo": "QN001",
|
||||
"type": "UNMANNED",
|
||||
"ip": "localhost",
|
||||
"port": 8081
|
||||
},
|
||||
{
|
||||
"vehicleNo": "QN002",
|
||||
"type": "UNMANNED",
|
||||
"ip": "localhost",
|
||||
"port": 8081
|
||||
},
|
||||
{
|
||||
"vehicleNo": "TQ001",
|
||||
"type": "SPECIAL",
|
||||
"ip": "localhost",
|
||||
"port": 8081
|
||||
}
|
||||
]
|
||||
}
|
||||
83
docs/airport_bounds_description.md
Normal file
83
docs/airport_bounds_description.md
Normal file
@ -0,0 +1,83 @@
|
||||
# 机场区域配置说明文档
|
||||
|
||||
本文档详细说明了 `airport_bounds.json` 配置文件中各参数的含义和用途。
|
||||
|
||||
## 整体机场边界 (airport.bounds)
|
||||
|
||||
- `x`: 机场区域左上角 X 坐标(米)
|
||||
- `y`: 机场区域左上角 Y 坐标(米)
|
||||
- `width`: 机场总宽度(米)
|
||||
- `height`: 机场总高度(米)
|
||||
|
||||
## 功能区域 (areas)
|
||||
|
||||
### 跑道区域 (runway)
|
||||
|
||||
- 位置参数 (bounds)
|
||||
- `x`: 跑道起始 X 坐标(米)
|
||||
- `y`: 跑道起始 Y 坐标(米)
|
||||
- `width`: 跑道宽度(米)
|
||||
- `height`: 跑道高度(米)
|
||||
- 安全配置 (config)
|
||||
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
|
||||
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
|
||||
- `height_threshold`: 高度阈值(米)
|
||||
|
||||
### 滑行道区域 (taxiway)
|
||||
|
||||
- 位置参数 (bounds)
|
||||
- `x`: 滑行道起始 X 坐标(米)
|
||||
- `y`: 滑行道起始 Y 坐标(米)
|
||||
- `width`: 滑行道宽度(米)
|
||||
- `height`: 滑行道高度(米)
|
||||
- 安全配置 (config)
|
||||
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
|
||||
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
|
||||
- `height_threshold`: 高度阈值(米)
|
||||
|
||||
### 停机位区域 (gate)
|
||||
|
||||
- 位置参数 (bounds)
|
||||
- `x`: 停机位起始 X 坐标(米)
|
||||
- `y`: 停机位起始 Y 坐标(米)
|
||||
- `width`: 停机位区域宽度(米)
|
||||
- `height`: 停机位区域高度(米)
|
||||
- 安全配置 (config)
|
||||
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
|
||||
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
|
||||
- `height_threshold`: 高度阈值(米)
|
||||
|
||||
### 服务区域 (service)
|
||||
|
||||
- 位置参数 (bounds)
|
||||
- `x`: 服务区起始 X 坐标(米)
|
||||
- `y`: 服务区起始 Y 坐标(米)
|
||||
- `width`: 服务区宽度(米)
|
||||
- `height`: 服务区高度(米)
|
||||
- 安全配置 (config)
|
||||
- `vehicle_collision_radius`: 车辆间碰撞检测半径(米)
|
||||
- `aircraft_ground_radius`: 航空器与车辆碰撞检测半径(米)
|
||||
- `height_threshold`: 高度阈值(米)
|
||||
|
||||
## 参数说明
|
||||
|
||||
### 碰撞检测半径
|
||||
|
||||
- 跑道区域:使用最大的碰撞检测半径,确保最高的安全性
|
||||
- 滑行道:使用中等的碰撞检测半径
|
||||
- 停机位:使用较小的碰撞检测半径
|
||||
- 服务区:使用最小的碰撞检测半径,因为车速较慢且司机更警觉
|
||||
|
||||
### 高度阈值
|
||||
|
||||
- 跑道区域:15米,考虑到起降阶段
|
||||
- 滑行道:10米,考虑到滑行阶段
|
||||
- 停机位和服务区:5米,主要考虑地面操作
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有距离单位均为米
|
||||
2. 坐标系统使用左下角为原点
|
||||
3. 各区域的参数需要根据实际机场情况调整
|
||||
4. 碰撞检测半径应考虑车辆和航空器的实际尺寸
|
||||
5. 高度阈值应考虑实际运营需求和安全裕度
|
||||
366
docs/configuration_guide.md
Normal file
366
docs/configuration_guide.md
Normal file
@ -0,0 +1,366 @@
|
||||
# 机场无人车冲突预警系统配置指南
|
||||
|
||||
## 目录
|
||||
- [机场无人车冲突预警系统配置指南](#机场无人车冲突预警系统配置指南)
|
||||
- [目录](#目录)
|
||||
- [一、机场基础信息配置](#一机场基础信息配置)
|
||||
- [参数说明](#参数说明)
|
||||
- [配置说明](#配置说明)
|
||||
- [二、区域边界配置](#二区域边界配置)
|
||||
- [区域参数规范表](#区域参数规范表)
|
||||
- [参数说明](#参数说明-1)
|
||||
- [三、交叉路口配置](#三交叉路口配置)
|
||||
- [参数说明](#参数说明-2)
|
||||
- [配置原则](#配置原则)
|
||||
- [四、数据接口配置](#四数据接口配置)
|
||||
- [接口参数示例](#接口参数示例)
|
||||
- [参数说明](#参数说明-3)
|
||||
- [五、无人车配置](#五无人车配置)
|
||||
- [参数说明](#参数说明-4)
|
||||
- [六、预警参数配置](#六预警参数配置)
|
||||
- [参数说明](#参数说明-5)
|
||||
- [冲突预警和告警参数](#冲突预警和告警参数)
|
||||
- [参数说明](#参数说明-6)
|
||||
- [告警阈值](#告警阈值)
|
||||
- [七、调试与日志](#七调试与日志)
|
||||
- [参数说明](#参数说明-7)
|
||||
- [八、配置验证流程](#八配置验证流程)
|
||||
- [文档版本](#文档版本)
|
||||
|
||||
---
|
||||
|
||||
## 一、机场基础信息配置
|
||||
文件路径:`config/system_config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"airport": {
|
||||
"name": "青岛胶东国际机场",
|
||||
"iata": "TAO",
|
||||
"icao": "ZSQD",
|
||||
"reference_point": {
|
||||
"latitude": 36.35448347,
|
||||
"longitude": 120.08502054
|
||||
},
|
||||
"coordinate_points": [
|
||||
{"point": "T1", "longitude": 120.0868853, "latitude": 36.35496367},
|
||||
{"point": "T2", "longitude": 120.08502054, "latitude": 36.35448347}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
- **name**:机场名称
|
||||
- **iata**:机场三字代码
|
||||
- **icao**:机场四字代码
|
||||
- **reference_point**:参考点
|
||||
- **latitude**:参考点纬度
|
||||
- **longitude**:参考点经度
|
||||
- **coordinate_points**:坐标点
|
||||
- **point**:坐标点名称
|
||||
- **longitude**:坐标点经度
|
||||
- **latitude**:坐标点纬度
|
||||
|
||||
|
||||
### 配置说明
|
||||
- **参考点设置**:选择塔台或机场中心点作为坐标系原点
|
||||
- **坐标精度**:经纬度建议保留8位小数(约1cm精度)
|
||||
- **命名规范**:
|
||||
- IATA代码使用3位大写字母
|
||||
- 坐标点按T1-T13顺序编号
|
||||
|
||||
---
|
||||
|
||||
## 二、区域边界配置
|
||||
文件路径:`config/airport_bounds.json`
|
||||
|
||||
### 区域参数规范表
|
||||
| 区域类型 | 边界坐标x(米) | 边界坐标y(米) | 区域宽度 | 区域高度 |
|
||||
| -------- | ------------- | ------------- | -------- | -------- |
|
||||
| 测试区 | -100 | -200 | 800 | 400 |
|
||||
|
||||
|
||||
```json
|
||||
"test_zone": {
|
||||
"bounds": {
|
||||
"x": -100,
|
||||
"y": -200,
|
||||
"width": 800,
|
||||
"height": 400
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
- **bounds**:区域边界
|
||||
- **x**:区域左上角x坐标
|
||||
- **y**:区域左上角y坐标
|
||||
- **width**:区域宽度
|
||||
- **height**:区域高度
|
||||
|
||||
---
|
||||
|
||||
## 三、交叉路口配置
|
||||
文件路径:`config/intersections.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "T2路口",
|
||||
"name": "无人车与特勤车交叉路口",
|
||||
"trafficLightId": "TL001",
|
||||
"position": {
|
||||
"longitude": 120.08502054,
|
||||
"latitude": 36.35448347,
|
||||
"altitude": 9.543
|
||||
},
|
||||
"width": 20.0,
|
||||
"safetyZone": {
|
||||
"aircraftRadius": 50.0,
|
||||
"vehicleRadius": 50.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
- **id**:路口ID
|
||||
- **name**:路口名称
|
||||
- **trafficLightId**:交通灯ID
|
||||
- **position**:路口位置
|
||||
- **width**:路口宽度
|
||||
- **safetyZone**:安全区
|
||||
- **aircraftRadius**:飞机安全区半径
|
||||
- **vehicleRadius**:车辆安全区半径
|
||||
|
||||
### 配置原则
|
||||
1. 路口ID按"T"+数字编号(如T1-T13)
|
||||
2. 安全区半径建议值:
|
||||
- 飞机:翼展×1.5
|
||||
- 车辆:车长×2.0
|
||||
|
||||
---
|
||||
|
||||
## 四、数据接口配置
|
||||
文件路径:`config/system_config.json`
|
||||
|
||||
### 接口参数示例
|
||||
```json
|
||||
"data_source": {
|
||||
"position": {
|
||||
"host": "localhost",
|
||||
"port": 8081,
|
||||
"aircraft_path": "/openApi/getCurrentFlightPositions",
|
||||
"vehicle_path": "/openApi/getCurrentVehiclePositions",
|
||||
"refresh_interval_ms": 1000,
|
||||
"auth": {
|
||||
"username": "dianxin",
|
||||
"password": "dianxin@123",
|
||||
"auth_path": "/login",
|
||||
"auth_required": true
|
||||
},
|
||||
"timeout_ms": 5000,
|
||||
"read_timeout_ms": 2000
|
||||
},
|
||||
"unmanned_vehicle": {
|
||||
"host": "localhost",
|
||||
"port": 8081,
|
||||
"location_path": "/api/VehicleLocationInfo",
|
||||
"status_path": "/api/VehicleStateInfo",
|
||||
"command_path": "/api/VehicleCommandInfo",
|
||||
"refresh_interval_ms": 1000,
|
||||
"auth": {
|
||||
"username": "dianxin",
|
||||
"password": "dianxin@123",
|
||||
"auth_path": "/api/login",
|
||||
"auth_required": false
|
||||
},
|
||||
"timeout_ms": 5000,
|
||||
"read_timeout_ms": 2000
|
||||
},
|
||||
"traffic_light": {
|
||||
"host": "localhost",
|
||||
"port": 8081,
|
||||
"signal_path": "/openApi/getTrafficLightSignals",
|
||||
"refresh_interval_ms": 1000,
|
||||
"auth": {
|
||||
"username": "dianxin",
|
||||
"password": "dianxin@123",
|
||||
"auth_path": "/api/login",
|
||||
"auth_required": false
|
||||
},
|
||||
"timeout_ms": 5000,
|
||||
"read_timeout_ms": 2000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
- **position**:位置接口
|
||||
- **host**:数据源IP地址
|
||||
- **port**:数据源端口号
|
||||
- **aircraft_path**:航空器数据接口路径
|
||||
- **vehicle_path**:车辆数据接口路径
|
||||
- **refresh_interval_ms**:数据刷新间隔时间(ms)
|
||||
- **auth**:认证信息
|
||||
- **username**:用户名
|
||||
- **password**:密码
|
||||
- **auth_path**:认证路径
|
||||
- **auth_required**:是否需要认证
|
||||
- **timeout_ms**:请求超时时间(ms)
|
||||
- **read_timeout_ms**:读取超时时间(ms)
|
||||
- **unmanned_vehicle**:无人车接口
|
||||
- **host**:无人车IP地址
|
||||
- **port**:无人车端口号
|
||||
- **location_path**:无人车位置接口路径
|
||||
- **status_path**:无人车状态接口路径
|
||||
- **command_path**:无人车命令接口路径
|
||||
- **refresh_interval_ms**:数据刷新间隔时间(ms)
|
||||
- **auth**:认证信息
|
||||
- **username**:用户名
|
||||
- **password**:密码
|
||||
- **auth_path**:认证路径
|
||||
- **auth_required**:是否需要认证
|
||||
- **timeout_ms**:请求超时时间(ms)
|
||||
- **read_timeout_ms**:读取超时时间(ms)
|
||||
- **traffic_light**:交通灯接口
|
||||
- **host**:交通灯IP地址
|
||||
- **port**:交通灯端口号
|
||||
- **signal_path**:交通灯信号接口路径
|
||||
- **refresh_interval_ms**:数据刷新间隔时间(ms)
|
||||
- **auth**:认证信息
|
||||
- **username**:用户名
|
||||
- **password**:密码
|
||||
- **auth_path**:认证路径
|
||||
- **auth_required**:是否需要认证
|
||||
- **timeout_ms**:请求超时时间(ms)
|
||||
- **read_timeout_ms**:读取超时时间(ms)
|
||||
|
||||
---
|
||||
|
||||
## 五、无人车配置
|
||||
文件路径:`config/unmanned_vehicles.json`
|
||||
|
||||
```json
|
||||
"vehicles": [
|
||||
{
|
||||
"vehicleNo": "QN001",
|
||||
"type": "UNMANNED",
|
||||
"ip": "localhost",
|
||||
"port": 8081
|
||||
},
|
||||
{
|
||||
"vehicleNo": "QN002",
|
||||
"type": "UNMANNED",
|
||||
"ip": "localhost",
|
||||
"port": 8081
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
- **vehicleNo**:无人车编号
|
||||
- **type**:无人车类型
|
||||
- **ip**:无人车IP地址
|
||||
- **port**:无人车端口号
|
||||
|
||||
---
|
||||
|
||||
## 六、预警参数配置
|
||||
|
||||
###冲突预测和航空器和车辆尺寸:
|
||||
|
||||
文件路径:`config/system_config.json`
|
||||
|
||||
```json
|
||||
"collision_detection": {
|
||||
"update_interval_ms": 200,
|
||||
"prediction": {
|
||||
"time_window": 20.0,
|
||||
"vehicle_size": 20.0,
|
||||
"aircraft_size": 60.0,
|
||||
"min_unmanned_speed": 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
### 参数说明
|
||||
- **update_interval_ms**:冲突检测更新间隔时间(ms)
|
||||
- **prediction**:冲突预测模型参数
|
||||
- **time_window**:预测时间窗口(s)
|
||||
- **vehicle_size**:车辆尺寸(m)
|
||||
- **aircraft_size**:航空器尺寸(m)
|
||||
- **min_unmanned_speed**:无人车最小速度(m/s)
|
||||
|
||||
### 冲突预警和告警参数
|
||||
|
||||
文件路径:`config/airport_bounds.json`
|
||||
|
||||
```json
|
||||
"warning_zone_radius": {
|
||||
"aircraft": 70.0,
|
||||
"special": 30.0,
|
||||
"unmanned": 30.0
|
||||
},
|
||||
"alert_zone_radius": {
|
||||
"aircraft": 35.0,
|
||||
"special": 15.0,
|
||||
"unmanned": 15.0
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
- **warning_zone_radius**:预警区半径 = 预警距离阈值 + 飞机尺寸/2 + 车辆尺寸/2
|
||||
- **alert_zone_radius**:告警区半径 = 告警距离阈值 + 飞机尺寸/2 + 车辆尺寸/2
|
||||
|
||||
### 告警阈值
|
||||
| 告警级别 | 飞机距离阈值(m) | 车辆距离阈值(m) |
|
||||
| -------- | --------------- | --------------- |
|
||||
| 预警 | 60 | 30 |
|
||||
| 告警 | 30 | 15 |
|
||||
|
||||
---
|
||||
|
||||
## 七、调试与日志
|
||||
文件路径:`config/logging_config.json`
|
||||
|
||||
```json
|
||||
"logging": {
|
||||
"level": "debug",
|
||||
"file": "logs/system.log",
|
||||
"max_size_mb": 10,
|
||||
"max_files": 5,
|
||||
"console_output": true
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
- **level**:日志级别
|
||||
- **file**:日志文件路径
|
||||
- **max_size_mb**:日志文件最大大小(MB)
|
||||
- **max_files**:日志文件最大数量
|
||||
- **console_output**:是否输出到控制台
|
||||
|
||||
---
|
||||
|
||||
## 八、配置验证流程
|
||||
|
||||
1. 基础配置检查
|
||||
```bash
|
||||
# 检查配置文件语法
|
||||
python3 -m json.tool config/system_config.json
|
||||
```
|
||||
|
||||
2. 接口连通性测试
|
||||
```bash
|
||||
curl -X POST http://localhost:8081/openApi/getCurrentFlightPositions
|
||||
```
|
||||
|
||||
3. 实时监控
|
||||
```bash
|
||||
tail -f /opt/collision_avoidance/logs/system.log | grep "WARNING"
|
||||
```
|
||||
|
||||
## 文档版本
|
||||
- 版本号:1.0.0
|
||||
- 更新日期:2025-02-06
|
||||
- 更新内容:初始版本
|
||||
|
||||
|
||||
BIN
docs/configuration_guide.pdf
Normal file
BIN
docs/configuration_guide.pdf
Normal file
Binary file not shown.
197
docs/conflict_detection_algorithms.md
Normal file
197
docs/conflict_detection_algorithms.md
Normal file
@ -0,0 +1,197 @@
|
||||
# 机场地面冲突检测算法方案
|
||||
|
||||
## 1. 算法概述
|
||||
|
||||
机场地面冲突检测系统采用多层次检测策略,结合不同算法的优势,实现快速、准确、可靠的冲突检测。
|
||||
|
||||
## 2. 常用冲突检测算法
|
||||
|
||||
### 2.1 基于几何的检测算法
|
||||
|
||||
#### 2.1.1 CPA (Closest Point of Approach) 算法
|
||||
|
||||
- 原理:
|
||||
- 计算两个移动物体之间的最近接近点
|
||||
- 预测未来一段时间内的最小距离
|
||||
- 判断是否小于安全阈值
|
||||
|
||||
- 优点:
|
||||
- 计算简单快速
|
||||
- 适合实时系统
|
||||
- 易于实现
|
||||
|
||||
- 缺点:
|
||||
- 不考虑路网约束
|
||||
- 预测精度有限
|
||||
|
||||
#### 2.1.2 安全区域重叠检测
|
||||
|
||||
- 原理:
|
||||
- 为每个物体定义安全区域(通常是椭圆或矩形)
|
||||
- 检测安全区域是否重叠
|
||||
- 考虑速度向量进行预测
|
||||
|
||||
- 优点:
|
||||
- 直观易理解
|
||||
- 可以考虑物体实际尺寸
|
||||
- 适合不同类型交通工具
|
||||
|
||||
### 2.2 基于时空的检测算法
|
||||
|
||||
#### 2.2.1 时空立方体算法
|
||||
|
||||
- 原理:
|
||||
- 构建 4D 时空轨迹(x, y, z, t)
|
||||
- 检测时空轨迹的交叉
|
||||
- 计算到达同一位置的时间差
|
||||
|
||||
- 优点:
|
||||
- 预测性强
|
||||
- 可以处理复杂场景
|
||||
- 考虑时间维度
|
||||
|
||||
#### 2.2.2 网格化检测算法
|
||||
|
||||
- 原理:
|
||||
- 将场地划分为网格
|
||||
- 预测物体在未来时间点占用的网格
|
||||
- 检测网格占用冲突
|
||||
|
||||
- 优点:
|
||||
- 计算效率高
|
||||
- 易于并行处理
|
||||
- 适合大规模场景
|
||||
|
||||
### 2.3 基于概率的检测算法
|
||||
|
||||
#### 2.3.1 Monte Carlo 模拟
|
||||
|
||||
- 原理:
|
||||
- 考虑位置和速度的不确定性
|
||||
- 多次随机采样模拟
|
||||
- 计算碰撞概率
|
||||
|
||||
- 优点:
|
||||
- 考虑不确定性
|
||||
- 结果更可靠
|
||||
- 适合复杂环境
|
||||
|
||||
#### 2.3.2 贝叶斯预测模型
|
||||
|
||||
- 原理:
|
||||
- 建立运动状态概率模型
|
||||
- 实时更新碰撞概率
|
||||
- 动态调整预警阈值
|
||||
|
||||
- 优点:
|
||||
- 可以学习历史数据
|
||||
- 适应性强
|
||||
- 预测准确度高
|
||||
|
||||
## 3. 实现方案
|
||||
|
||||
### 3.1 多层次检测策略
|
||||
|
||||
采用三层检测机制:
|
||||
|
||||
1. 第一层:快速检测
|
||||
|
||||
```cpp
|
||||
// 使用简单的 CPA 算法进行快速筛查
|
||||
struct CPAResult {
|
||||
double min_distance; // 最小距离
|
||||
double time_to_cpa; // 到达最近点的时间
|
||||
Point cpa_point; // 最近接近点
|
||||
};
|
||||
|
||||
CPAResult calculateCPA(const Vehicle& v1, const Vehicle& v2, double prediction_time) {
|
||||
// 计算两个物体的最近接近点
|
||||
// 返回最小距离和时间信息
|
||||
}
|
||||
```
|
||||
|
||||
2. 第二层:精确检测
|
||||
|
||||
```cpp
|
||||
// 使用安全区域重叠检测进行精确判断
|
||||
struct SafetyZone {
|
||||
Point center;
|
||||
double length;
|
||||
double width;
|
||||
double orientation;
|
||||
};
|
||||
|
||||
bool checkSafetyZoneOverlap(const SafetyZone& zone1, const SafetyZone& zone2) {
|
||||
// 检查两个安全区域是否重叠
|
||||
// 考虑物体的实际尺寸和方向
|
||||
}
|
||||
```
|
||||
|
||||
3. 第三层:预测检测
|
||||
|
||||
```cpp
|
||||
// 使用时空网格进行路径预测
|
||||
struct TimeSpaceGrid {
|
||||
int x, y; // 空间坐标
|
||||
double time; // 时间戳
|
||||
std::vector<int> occupants; // 占用该格子的物体ID
|
||||
};
|
||||
|
||||
bool checkPathConflict(const Vehicle& v1, const Vehicle& v2,
|
||||
const std::vector<TimeSpaceGrid>& grids) {
|
||||
// 检查未来时间窗口内的路径冲突
|
||||
// 考虑路网约束
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 综合判断
|
||||
|
||||
```cpp
|
||||
struct ConflictResult {
|
||||
bool has_conflict; // 是否存在冲突
|
||||
double risk_level; // 风险等级
|
||||
double time_to_conflict; // 到冲突的时间
|
||||
std::string conflict_type; // 冲突类型
|
||||
};
|
||||
|
||||
ConflictResult detectConflict(const Vehicle& v1, const Vehicle& v2) {
|
||||
// 1. 快速 CPA 检测
|
||||
// 2. 如果可能存在冲突,进行安全区域检测
|
||||
// 3. 对于高风险情况,进行时空路径预测
|
||||
// 4. 综合三层结果,返回最终判断
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 实现步骤
|
||||
|
||||
1. 第一阶段:基础功能实现
|
||||
- 实现 CPA 算法
|
||||
- 实现基本的安全区域检测
|
||||
- 设计基础数据结构
|
||||
|
||||
2. 第二阶段:增强功能
|
||||
- 添加时空网格检测
|
||||
- 实现路径预测
|
||||
- 优化性能
|
||||
|
||||
3. 第三阶段:高级特性
|
||||
- 添加概率模型
|
||||
- 实现自适应阈值
|
||||
- 系统集成测试
|
||||
|
||||
## 5. 注意事项
|
||||
|
||||
1. 性能要求
|
||||
- 实时响应:检测延迟不超过 100ms
|
||||
- CPU 占用率控制在 50% 以下
|
||||
- 内存使用不超过 1GB
|
||||
|
||||
2. 可靠性要求
|
||||
- 误报率控制在 1% 以下
|
||||
- 漏报率控制在 0.1% 以下
|
||||
- 系统可用性 99.99%
|
||||
|
||||
3. 扩展性要求
|
||||
- 支持新增检测算法
|
||||
- 支持参数配置调整
|
||||
- 支持不同类型交通工具
|
||||
172
docs/deployment_guide.md
Normal file
172
docs/deployment_guide.md
Normal file
@ -0,0 +1,172 @@
|
||||
# 部署指南
|
||||
|
||||
本文档详细说明了如何在 CentOS 系统上部署碰撞避免系统。支持在线和离线两种部署方式。
|
||||
|
||||
## 目录
|
||||
|
||||
- [系统要求](#系统要求)
|
||||
- [在线部署](#在线部署)
|
||||
- [离线部署](#离线部署)
|
||||
- [部署后配置](#部署后配置)
|
||||
- [常见问题](#常见问题)
|
||||
|
||||
## 系统要求
|
||||
|
||||
- CentOS 7 或更高版本
|
||||
- 至少 2GB 可用内存
|
||||
- 至少 10GB 可用磁盘空间
|
||||
- root 权限
|
||||
- Python 3.6 或更高版本
|
||||
|
||||
## 在线部署
|
||||
|
||||
如果服务器可以访问互联网,可以直接使用在线部署方式。
|
||||
|
||||
1. 获取项目代码
|
||||
|
||||
```bash
|
||||
git clone [项目仓库地址]
|
||||
cd CollisionAvoidance
|
||||
```
|
||||
|
||||
2. 执行部署脚本
|
||||
|
||||
```bash
|
||||
sudo ./scripts/deploy.sh
|
||||
```
|
||||
|
||||
## 离线部署
|
||||
|
||||
如果服务器无法访问互联网,需要使用离线部署方式。
|
||||
|
||||
### 准备阶段(在有网络的环境中进行)
|
||||
|
||||
1. 在有网络的 CentOS 环境中,获取项目代码
|
||||
|
||||
```bash
|
||||
git clone [项目仓库地址]
|
||||
cd CollisionAvoidance
|
||||
```
|
||||
|
||||
2. 执行离线包准备脚本
|
||||
|
||||
```bash
|
||||
./scripts/prepare_offline_packages.sh
|
||||
```
|
||||
|
||||
脚本会自动完成:
|
||||
|
||||
- 下载所需的系统依赖包
|
||||
- 下载 Python 依赖包
|
||||
- 创建完整的项目归档文件
|
||||
|
||||
### 部署步骤
|
||||
|
||||
1. 将生成的归档文件传输到目标服务器
|
||||
|
||||
```bash
|
||||
scp ../CollisionAvoidance.tar.gz root@target-server:/tmp/
|
||||
```
|
||||
|
||||
2. 在目标服务器上解压
|
||||
|
||||
```bash
|
||||
cd /tmp
|
||||
tar xzf CollisionAvoidance.tar.gz
|
||||
cd CollisionAvoidance
|
||||
```
|
||||
|
||||
3. 删除 32 位依赖包
|
||||
|
||||
> 注意:由于系统是 64 位架构,我们需要删除所有 32 位(i686)的依赖包,以避免安装冲突。这些包是在准备离线包时自动下载的,但在 64 位系统上不需要。
|
||||
|
||||
```bash
|
||||
# 删除所有 i686 架构的包
|
||||
cd packages
|
||||
rm -f *i686.rpm
|
||||
cd ..
|
||||
```
|
||||
|
||||
4. 执行部署脚本
|
||||
|
||||
```bash
|
||||
sudo ./scripts/deploy.sh
|
||||
```
|
||||
|
||||
## 部署后配置
|
||||
|
||||
### 服务管理
|
||||
|
||||
- 启动服务:`systemctl start collision-avoidance`
|
||||
- 停止服务:`systemctl stop collision-avoidance`
|
||||
- 重启服务:`systemctl restart collision-avoidance`
|
||||
- 查看状态:`systemctl status collision-avoidance`
|
||||
- 查看日志:`journalctl -u collision-avoidance -f`
|
||||
|
||||
### 配置文件
|
||||
|
||||
- 配置文件位置:`/etc/collision_avoidance/`
|
||||
- WebSocket 服务端口:8010
|
||||
- Mock Server 端口:8081
|
||||
|
||||
### 防火墙配置
|
||||
|
||||
如果使用 firewalld:
|
||||
|
||||
```bash
|
||||
# 开放 WebSocket 端口
|
||||
firewall-cmd --permanent --add-port=8010/tcp
|
||||
# 开放 Mock Server 端口
|
||||
firewall-cmd --permanent --add-port=8081/tcp
|
||||
firewall-cmd --reload
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 服务启动失败
|
||||
|
||||
- 检查服务日志:`journalctl -u collision-avoidance -n 50`
|
||||
- 确认配置文件权限正确
|
||||
- 验证端口 8010 和 8081 是否被占用
|
||||
|
||||
### 2. Mock Server 启动失<E58AA8><E5A4B1>
|
||||
|
||||
- 检查 Python 和依赖包是否正确安装
|
||||
- 确认端口 8081 未被占用
|
||||
- 检查 Mock Server 日志输出
|
||||
|
||||
### 3. 防火墙配置
|
||||
|
||||
- 如果使用其他防火墙,需要手动开放 8010 和 8081 端口
|
||||
- 确认防火墙规则是否生效:`firewall-cmd --list-all`
|
||||
|
||||
### 4. 依赖包安装问题
|
||||
|
||||
- 如果遇到依赖包冲突,检查是否有 i686 架构的包未被删除
|
||||
- 使用 `rpm -qa | grep i686` 命令检查系统中的 32 位包
|
||||
- 如果发现 i686 包,使用 `rpm -e` 命令删除它们
|
||||
|
||||
## 卸载
|
||||
|
||||
如需卸载系统:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/uninstall.sh
|
||||
```
|
||||
|
||||
卸载脚本会:
|
||||
|
||||
- 停止并禁用服务
|
||||
- 删除系统服务配置
|
||||
- 删除程序文件
|
||||
- 删除配置文件
|
||||
- 关闭防火墙端口
|
||||
|
||||
## 技术支持
|
||||
|
||||
如遇到问题,请:
|
||||
|
||||
1. 查看服务日志
|
||||
2. 检查系统日志
|
||||
3. 检查 Mock Server 日志
|
||||
4. 联系技术支持团队
|
||||
1070
docs/design.md
Normal file
1070
docs/design.md
Normal file
File diff suppressed because it is too large
Load Diff
232
docs/docker_deployment_design.md
Normal file
232
docs/docker_deployment_design.md
Normal file
@ -0,0 +1,232 @@
|
||||
# Docker 部署设计文档
|
||||
|
||||
本文档详细说明碰撞避免系统的 Docker 部署方案,包括测试环境和生产环境的部署流程。
|
||||
|
||||
## 环境说明
|
||||
|
||||
### 测试环境
|
||||
|
||||
- CentOS 7
|
||||
- 可以访问互联网
|
||||
- 可以安装软件包
|
||||
- 内存 >= 4GB
|
||||
- 磁盘空间 >= 20GB
|
||||
|
||||
### 生产环境
|
||||
|
||||
- CentOS 7
|
||||
- 不能访问互联网
|
||||
- 不能在线安装软件包
|
||||
- 内存 >= 8GB
|
||||
- 磁盘空间 >= 50GB
|
||||
|
||||
## 测试环境工作
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
```bash
|
||||
# 安装 Docker
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
|
||||
# 安装 Docker Compose
|
||||
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
```
|
||||
|
||||
### 2. 镜像构建与测试
|
||||
|
||||
1. 构建基础镜像
|
||||
2. 运行自动化测试
|
||||
3. 验证功能完整性
|
||||
4. 检查资源使用情况
|
||||
5. 验证日志收集
|
||||
6. 测试健康检查机制
|
||||
|
||||
### 3. 准备生产部署包
|
||||
|
||||
1. 导出 Docker 镜像
|
||||
|
||||
```bash
|
||||
# 保存基础镜像
|
||||
docker save centos:7 > centos7-base.tar
|
||||
|
||||
# 保存应用镜像
|
||||
docker save collision-avoidance:latest > collision-avoidance.tar
|
||||
```
|
||||
|
||||
2. 准备离线安装包
|
||||
|
||||
```bash
|
||||
# 创建部署包目录
|
||||
mkdir -p offline-package
|
||||
cd offline-package
|
||||
|
||||
# 下载 Docker 离线安装包
|
||||
yum install -y yum-utils
|
||||
yumdownloader --resolve docker-ce docker-ce-cli containerd.io
|
||||
|
||||
# 下载 Docker Compose 二进制文件
|
||||
wget https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64
|
||||
```
|
||||
|
||||
3. 创建部署脚本
|
||||
|
||||
```bash
|
||||
# 打包所有文件
|
||||
tar czf docker-deploy-package.tar.gz \
|
||||
centos7-base.tar \
|
||||
collision-avoidance.tar \
|
||||
*.rpm \
|
||||
docker-compose-Linux-x86_64 \
|
||||
scripts/
|
||||
```
|
||||
|
||||
## 生产环境工作
|
||||
|
||||
### 1. 离线安装 Docker
|
||||
|
||||
```bash
|
||||
# 解压部署包
|
||||
tar xzf docker-deploy-package.tar.gz
|
||||
|
||||
# 安装 Docker RPM 包
|
||||
rpm -ivh *.rpm
|
||||
|
||||
# 安装 Docker Compose
|
||||
cp docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
# 启动 Docker 服务
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
```
|
||||
|
||||
### 2. 加载 Docker 镜像
|
||||
|
||||
```bash
|
||||
# 加载基础镜像
|
||||
docker load < centos7-base.tar
|
||||
|
||||
# 加载应用镜像
|
||||
docker load < collision-avoidance.tar
|
||||
```
|
||||
|
||||
### 3. 部署配置
|
||||
|
||||
1. 创建数据目录
|
||||
|
||||
```bash
|
||||
mkdir -p /data/collision-avoidance/{config,logs}
|
||||
```
|
||||
|
||||
2. 配置文件准备
|
||||
|
||||
```bash
|
||||
cp config/* /data/collision-avoidance/config/
|
||||
```
|
||||
|
||||
### 4. 启动<E590AF><E58AA8><EFBFBD>务
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 部署后验证
|
||||
|
||||
### 1. 基础检查
|
||||
|
||||
- 容器状态
|
||||
- 端口可用性
|
||||
- 日志输出
|
||||
- 资源使用情况
|
||||
|
||||
### 2. 功能验证
|
||||
|
||||
- WebSocket 连接测试
|
||||
- Mock Server 功能测试
|
||||
- 碰撞检测功能测试
|
||||
- 数据持久化验证
|
||||
|
||||
### 3. 监控检查
|
||||
|
||||
- 健康检查状态
|
||||
- 资源监控指标
|
||||
- 日志收集状态
|
||||
|
||||
## 回滚方案
|
||||
|
||||
### 1. 准备回滚镜像
|
||||
|
||||
- 在测试环境保留上一个稳定版本镜像
|
||||
- 将回滚镜像包含在部署包中
|
||||
|
||||
### 2. 回滚步骤
|
||||
|
||||
```bash
|
||||
# 停止当前服务
|
||||
docker-compose down
|
||||
|
||||
# 加载回滚镜像
|
||||
docker load < collision-avoidance-rollback.tar
|
||||
|
||||
# 修改镜像标签
|
||||
docker tag collision-avoidance-rollback:latest collision-avoidance:latest
|
||||
|
||||
# 重启服务
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 安全考虑
|
||||
|
||||
- 使用非 root 用户运行容器
|
||||
- 配置文件权限控制
|
||||
- 网络访问限制
|
||||
- 资源限制设置
|
||||
|
||||
### 2. 性能优化
|
||||
|
||||
- 容器资源限制配置
|
||||
- 日志轮转策略
|
||||
- 数据持久化方案
|
||||
|
||||
### 3. 运维建议
|
||||
|
||||
- 定期备份配置文件
|
||||
- 监控告警配置
|
||||
- 日志管理策略
|
||||
- 定期检查磁盘使用情况
|
||||
|
||||
## 故障处理
|
||||
|
||||
### 1. 常见问题
|
||||
|
||||
- 容器启动失败
|
||||
- 端口冲突
|
||||
- 资源不足
|
||||
- 日志异常
|
||||
|
||||
### 2. 处理方法
|
||||
|
||||
- 检查容器日志
|
||||
- 验证配置文件
|
||||
- 检查<E6A380><E69FA5>源使用
|
||||
- 查看系统日志
|
||||
|
||||
## 维护计划
|
||||
|
||||
### 1. 日常维护
|
||||
|
||||
- 日志清理
|
||||
- 磁盘空间检查
|
||||
- 性能监控
|
||||
- 配置备份
|
||||
|
||||
### 2. 定期更新
|
||||
|
||||
- 安全补丁更新
|
||||
- 基础镜像更新
|
||||
- 应用版本更新
|
||||
384
docs/mock_server.md
Normal file
384
docs/mock_server.md
Normal file
@ -0,0 +1,384 @@
|
||||
# Mock 服务设计与使用说明
|
||||
|
||||
## 1. 概述
|
||||
|
||||
Mock 服务用于模拟机场场面的移动物体(航空器和车辆),提供位置更新和响应控制指令的功能。服务包含两个主要部分:
|
||||
|
||||
- HTTP API 服务:提供位置查询和车辆控制接口
|
||||
- 位置更新模拟:定期更新各个移动物体的位置
|
||||
|
||||
## 2. 移动物体配置
|
||||
|
||||
### 2.1 航空器配置
|
||||
|
||||
```python
|
||||
{
|
||||
"flightNo": "AC001", # 航班号
|
||||
"latitude": 36.362647780875804, # 纬度
|
||||
"longitude": 120.088303, # 经度
|
||||
"direction": { # 使用方向向量
|
||||
"lat": initial_target_lat,
|
||||
"lon": initial_target_lon
|
||||
},
|
||||
"speed": 50.0 # 滑行速度 50km/h
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 车辆配置
|
||||
|
||||
```python
|
||||
{
|
||||
"vehicleNo": "QN001", # 无人车1(西路口)
|
||||
"latitude": WEST_INTERSECTION["latitude"], # 纬度
|
||||
"longitude": WEST_INTERSECTION["longitude"], # 经度
|
||||
"direction": 1, # 1表示向东,-1表示向西
|
||||
"speed": 36.0 # 行驶速度 36km/h
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 关键坐标点
|
||||
|
||||
- T1 (120.0868853, 36.35496367): 无人车1起点
|
||||
- T2 (120.08502054, 36.35448347): 冲突点1,西路口
|
||||
- T3 (120.08341044, 36.35406879): 特勤车终点
|
||||
- T4 (120.08558121, 36.35305878): 无人车1终点/无人车2起点/特勤车起点
|
||||
- T5 (120.08400957, 36.35265197)
|
||||
- T6 (120.08649105, 36.35074527): 冲突点2,东路口
|
||||
- T7 (120.08562915, 36.35052372): 航空器1终点
|
||||
- T8 (120.08676664, 36.35004529): 无人车2终点
|
||||
- T9 (120.08520616, 36.34964473)
|
||||
- T10 (120.08710569, 36.34917893)
|
||||
- T11 (120.0873865, 36.3509885): 航空器1终点
|
||||
- T12 (120.08603613, 36.35190217): QN002新起点(距T6路口150米)
|
||||
|
||||
## 3. 运动模式
|
||||
|
||||
### 3.1 航空器运动
|
||||
|
||||
- 沿跑道南北方向移动
|
||||
- 到达边界时自动调头
|
||||
- 保持恒定速度 50km/h
|
||||
|
||||
### 3.2 无人车运动
|
||||
|
||||
- QN001:
|
||||
1. T1 -> T2(西路口)
|
||||
2. T2 -> T4
|
||||
3. 到达T4后返回T1
|
||||
|
||||
- QN002:
|
||||
1. T12 -> T8
|
||||
2. 到达T8后返回T12
|
||||
|
||||
- TQ001(特勤车):
|
||||
1. T4 -> T2(西路口)
|
||||
2. T2 -> T3
|
||||
3. 到达T3后返回T4
|
||||
|
||||
### 3.3 配置参数
|
||||
|
||||
- 距离阈值:
|
||||
- 300米:DIST_300M
|
||||
- 200米:DIST_200M
|
||||
- 150米:DIST_150M
|
||||
- 100米:DIST_100M
|
||||
- 50米:DIST_50M
|
||||
|
||||
- 时间配置:
|
||||
- 返回等待时间:10秒
|
||||
- 位置更新间隔:1秒
|
||||
- 红绿灯切换间隔:10秒
|
||||
|
||||
- 速度配置:
|
||||
- 默认车辆速度:18.0 km/h
|
||||
- 紧急制动减速度:80%/更新
|
||||
- 正常制动减速度:20%/更新
|
||||
|
||||
## 4. HTTP API 接口
|
||||
|
||||
### 4.1 航空器位置查询接口
|
||||
|
||||
```http
|
||||
GET /api/getCurrentFlightPositions
|
||||
```
|
||||
|
||||
返回所有移动物体的当前位置信息:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"flightNo": "AC001",
|
||||
"latitude": 36.362647780875804,
|
||||
"longitude": 120.088303,
|
||||
"direction": {
|
||||
"lat": 0.707,
|
||||
"lon": 0.707
|
||||
},
|
||||
"speed": 36.0,
|
||||
"time": 1733741411167
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 4.2 车辆位置查询接口
|
||||
|
||||
```http
|
||||
GET /api/getCurrentVehiclePositions
|
||||
```
|
||||
|
||||
返回所有车辆的当前位置信息:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"vehicleNo": "QN001",
|
||||
"latitude": 36.362448155990975,
|
||||
"longitude": 120.08844920958369,
|
||||
"direction": -1,
|
||||
"speed": 18.0,
|
||||
"time": 1733741411168,
|
||||
"phase": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 4.3 红绿灯状态查询接口
|
||||
|
||||
```http
|
||||
GET /api/getTrafficLightSignals
|
||||
```
|
||||
|
||||
返回所有红绿灯的当前状态:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "TL001",
|
||||
"state": 1,
|
||||
"timestamp": 1733741411167,
|
||||
"intersection": "INT001",
|
||||
"position": {
|
||||
"latitude": 36.35448347,
|
||||
"longitude": 120.08502054
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 4.4 车辆控制接口
|
||||
|
||||
```http
|
||||
POST /api/vehicle/command
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"vehicleId": "QN001",
|
||||
"type": "SIGNAL", // SIGNAL, ALERT, WARNING, RESUME
|
||||
"reason": "TRAFFIC_LIGHT", // TRAFFIC_LIGHT, AIRCRAFT_CROSSING, SPECIAL_VEHICLE, AIRCRAFT_PUSH, RESUME_TRAFFIC
|
||||
"timestamp": 1733741411167,
|
||||
"signalState": "RED", // 可选,仅当 type 为 SIGNAL 时需要
|
||||
"targetPosition": { // 可选
|
||||
"x": 100.0,
|
||||
"y": 200.0
|
||||
},
|
||||
"speed": 0.0, // 可选
|
||||
"distance": 0.0 // 可选
|
||||
}
|
||||
```
|
||||
|
||||
响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok", // ok 或 error
|
||||
"message": "Command executed successfully"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 指令优先级
|
||||
|
||||
车辆控制指令按以下优先级处理:
|
||||
|
||||
1. ALERT (5):告警指令,最高优先级
|
||||
- 用于紧急情况,如碰撞风险
|
||||
- 可以覆盖所有其他指令
|
||||
- 收到后车辆立即停止
|
||||
|
||||
2. RED (4):红灯指令,次高优先级
|
||||
- 用于红绿灯控制
|
||||
- 可以覆盖预警、绿灯和恢复指令
|
||||
- 红灯时车辆停止,绿灯时可能恢复(取决于是否有更高优先级指令)
|
||||
|
||||
3. WARNING (3):预警指令,中等优先级
|
||||
- 用于提前预警可能的风险
|
||||
- 可以覆盖绿灯和恢复指令
|
||||
- 收到后车辆减速或停止
|
||||
|
||||
4. GREEN (2):绿灯状态
|
||||
- 系统内部状态,不是外部指令
|
||||
- 可以覆盖恢复指令
|
||||
- 只有在没有更高优先级指令时才生效
|
||||
|
||||
5. RESUME (1):恢复指令,最低优先级
|
||||
- 用于恢复正常行驶
|
||||
- 不能覆盖其他指令
|
||||
- 只有在没有任何其他指令时才生效
|
||||
|
||||
### 5.1 优先级规则
|
||||
|
||||
1. 高优先级指令可以覆盖低优先级指令
|
||||
2. 相同优先级的新指令可以覆盖旧指令
|
||||
3. 低优先级指令不能覆盖高优先级指令
|
||||
4. 恢复行驶需要满足个条件:
|
||||
- 收到 RESUME 指令
|
||||
- 没有任何更高优先级的指令
|
||||
|
||||
### 5.2 状态转换
|
||||
|
||||
1. 收到告警指令 (ALERT):
|
||||
- 立即停止
|
||||
- 忽略所有低优先级指令
|
||||
- 只能通过 RESUME 指令恢复(且无其他高优先级指令)
|
||||
|
||||
2. 收到红灯指令 (RED):
|
||||
- 红灯:停止
|
||||
- 绿灯:如果没有更高优先级指令(ALERT)则恢复行驶
|
||||
|
||||
3. 收到预警指令 (WARNING):
|
||||
- 减速或停止
|
||||
- 可被告警或红灯指令覆盖
|
||||
- 可通过绿灯或恢复指令解除(如无更高优先级指令)
|
||||
|
||||
4. 遇到绿灯 (GREEN):
|
||||
- 如果没有 ALERT、SIGNAL 或 WARNING 指令,则恢复行驶
|
||||
- 否则保持当前状态
|
||||
|
||||
5. 收到恢复指令 (RESUME):
|
||||
- 检查是否有任何更高优先级指令
|
||||
- 如果没有,则恢复正常行驶速度
|
||||
- 如果有,则保持当前状态
|
||||
|
||||
### 5.3 使用示例
|
||||
|
||||
1. 告警指令覆盖其他指令:
|
||||
|
||||
```json
|
||||
{
|
||||
"vehicleId": "QN001",
|
||||
"type": "ALERT",
|
||||
"reason": "COLLISION_RISK",
|
||||
"timestamp": 1733741411167
|
||||
}
|
||||
```
|
||||
|
||||
2. 红灯指令(但不会覆盖告警):
|
||||
|
||||
```json
|
||||
{
|
||||
"vehicleId": "QN001",
|
||||
"type": "SIGNAL",
|
||||
"reason": "TRAFFIC_LIGHT",
|
||||
"signalState": "RED",
|
||||
"timestamp": 1733741411167
|
||||
}
|
||||
```
|
||||
|
||||
3. 预警指令(可被告警和红灯覆盖):
|
||||
|
||||
```json
|
||||
{
|
||||
"vehicleId": "QN001",
|
||||
"type": "WARNING",
|
||||
"reason": "APPROACHING_INTERSECTION",
|
||||
"timestamp": 1733741411167
|
||||
}
|
||||
```
|
||||
|
||||
4. 恢复指令(需要无更高优先级指令):
|
||||
|
||||
```json
|
||||
{
|
||||
"vehicleId": "QN001",
|
||||
"type": "RESUME",
|
||||
"reason": "RESUME_TRAFFIC",
|
||||
"timestamp": 1733741411167
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 使用示例
|
||||
|
||||
### 6.1 发送红绿灯指令
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8081/api/vehicle/command \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"vehicleId": "QN001",
|
||||
"type": "SIGNAL",
|
||||
"reason": "TRAFFIC_LIGHT",
|
||||
"timestamp": 1733741411167,
|
||||
"signalState": "RED"
|
||||
}'
|
||||
```
|
||||
|
||||
### 6.2 发送碰撞预警指令
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8081/api/vehicle/command \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"vehicleId": "QN001",
|
||||
"type": "WARNING",
|
||||
"reason": "AIRCRAFT_CROSSING",
|
||||
"timestamp": 1733741411167,
|
||||
"distance": 25.5
|
||||
}'
|
||||
```
|
||||
|
||||
### 6.3 发送恢复指令
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8081/api/vehicle/command \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"vehicleId": "QN001",
|
||||
"type": "RESUME",
|
||||
"reason": "RESUME_TRAFFIC",
|
||||
"timestamp": 1733741411167
|
||||
}'
|
||||
```
|
||||
|
||||
## 7. 启动服务
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
pip install flask
|
||||
|
||||
# 启动服务
|
||||
python tools/mock_server.py
|
||||
```
|
||||
|
||||
服务默认监听在:
|
||||
|
||||
- HTTP 服务:<http://localhost:8081>
|
||||
|
||||
## 8. 注意事项
|
||||
|
||||
1. 位置更新
|
||||
- 位置更新频率:1秒一次
|
||||
- 坐标系统:WGS84
|
||||
- 速度单位:km/h(内部计算使用 m/s)
|
||||
|
||||
2. 车辆控制
|
||||
- 车辆收到停止指令后会立即停止位置更新
|
||||
- 只有收到 RESUME 指令且没有更高优先级的指令时才会恢复运动
|
||||
- 每个指令都必须包含时间戳
|
||||
|
||||
3. 错误处理
|
||||
- 无效的车辆ID:返回 404 错误
|
||||
- 无效的指令格式:返回 400 错误
|
||||
- 服务器内部错误:返回 500 错误
|
||||
225
docs/official_api.md
Normal file
225
docs/official_api.md
Normal file
@ -0,0 +1,225 @@
|
||||
# 数据接口对接方案
|
||||
|
||||
## 第1章 位置数据接口对接方案
|
||||
|
||||
### 1.1 登录认证
|
||||
|
||||
1. 登录接口:<http://IP:端口/login>
|
||||
2. 请求方式:post
|
||||
3. 参数:username、password
|
||||
4. 示例:<http://127.0.0.1:8080/login?username=XXXX&password=XXXX>
|
||||
5、返回值 data 为返回的鉴权token,后续接口需要再header中携带,data所有的数据是一个token,不要截断
|
||||
示例:{
|
||||
"status": 200,
|
||||
"msg": "登入成功",
|
||||
"data": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY"
|
||||
}
|
||||
|
||||
### 1.2 航空器位置数据接入
|
||||
|
||||
数据来源:接入并转发从空管接收到的融合数据
|
||||
|
||||
1. 接口地址:<http://IP:端口/openApi/getCurrentFlightPositions>
|
||||
|
||||
2. 请求方式:get,需要在 Header 中携带认证信息,字段名为 Authorization,值为认证接口返回的token
|
||||
|
||||
3. 返回格式:以 JSON 格式返回数据,一次请求返回List集合对象
|
||||
|
||||
4. 数据结构:
|
||||
|
||||
| 序号 | 字段 | 描述 | 字段类型 | 是否必填 |
|
||||
|-----|------|------|----------|----------|
|
||||
| 1 | flightNo | 航班号 | String | 是 |
|
||||
| 2 | longitude | 经度 | double | 是 |
|
||||
| 3 | latitude | 纬度 | double | 是 |
|
||||
| 4 | time | 时间戳(UTC 时间) | long | 是 |
|
||||
| 5 | altitude | 海拔高度 | double | 否 |
|
||||
| 6 | trackNumber | 航迹号 | long | 否 |
|
||||
|
||||
### 1.3 车辆位置数据接入
|
||||
|
||||
数据来源:仅传递目前机场已接入的车辆位置数据
|
||||
|
||||
1. 接口地址:<http://IP:端口/openApi/getCurrentVehiclePositions>
|
||||
|
||||
2. 请求方式:get,需要在 Header 中携带认证信息,字段名为 Authorization,值为认证接口返回的token
|
||||
|
||||
3. 返回格式:以 JSON 格式返回数据,一次请求返回List集合对象
|
||||
|
||||
4. 数据结构:
|
||||
|
||||
| 序号 | 字段 | 描述 | 字段类型 | 是否必填 |
|
||||
|-----|------|------|----------|----------|
|
||||
| 1 | vehicleNo | 车牌号 | String | 是 |
|
||||
| 2 | longitude | 经度 | double | 是 |
|
||||
| 3 | latitude | 纬度 | double | 是 |
|
||||
| 4 | time | 时间戳 | long | 是 |
|
||||
| 5 | direction | 方向 | double | 否 |
|
||||
| 6 | speed | 速度 | double | 否 |
|
||||
|
||||
## 第2章 无人车控制接口对接方案
|
||||
|
||||
### 2.1 无人车控制指令
|
||||
|
||||
2.1.1 接口地址: <http://127.0.0.1:31140/api/VehicleCommandInfo>
|
||||
|
||||
2.1.2 请求方法:POST
|
||||
|
||||
2.1.3 请求参数:
|
||||
|
||||
| 字段名称 | 类型 | 是否必填 | 说明 |
|
||||
|---------|------|----------|------|
|
||||
| transId | string | 是 | 消息唯一 id,消息的唯一标识符 |
|
||||
| timestamp | long | 是 | 时间戳 |
|
||||
| vehicleID | string | 是 | 车辆 ID |
|
||||
| commandType | string | 是 | 指令类型:ALERT:告警指令,SIGNAL:信号灯指令,WARNING:预警指令,RESUME:恢复指令 |
|
||||
| commandReason | string | 是 | 指令原因:TRAFFIC_LIGHT:红绿灯控制,AIRCRAFT_CROSSING:航空器交叉,SPECIAL_VEHICLE:特勤车辆,AIRCRAFT_PUSH:航空器推出,RESUME_TRAFFIC:恢复通行 |
|
||||
| signalState | string | 否 | 信号灯状态(仅当 commandType 为 SIGNAL 时有效)RED:红灯,GREEN:绿灯,YELLOW:黄灯 |
|
||||
| intersectionId | string | 否 | 路口 ID(仅当 commandType 为 SIGNAL 时有效) |
|
||||
| latitude | double | 是 | 目标位置纬度(路口/航空器/特勤车) |
|
||||
| longitude | double | 是 | 目标位置经度(路口/航空器/特勤车) |
|
||||
| relativeSpeed | double | 否 | 相对速度(仅当 commandType 为 ALERT/WARNING 时有效) |
|
||||
| relativeMotionX | double | 否 | 相对运动 X 分量(仅当 commandType 为 ALERT/WARNING 时有效) |
|
||||
| relativeMotionY | double | 否 | 相对运动 Y 分量(仅当 commandType 为 ALERT/WARNING 时有效) |
|
||||
| minDistance | double | 否 | 最小距离(仅当 commandType 为 ALERT/WARNING 时有效) |
|
||||
|
||||
示例:
|
||||
|
||||
requestData:
|
||||
{
|
||||
"messageUniqueId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
|
||||
"timestamp": 1736175610000,
|
||||
"vehicleID": "A001",
|
||||
"commandType": "SIGNAL",
|
||||
"commandReason": "TRAFFIC_LIGHT",
|
||||
"signalState":"RED",
|
||||
"intersectionId":"002",
|
||||
"latitude": 343.23,
|
||||
"longitude": 343.23,
|
||||
"relativeSpeed": 3,
|
||||
"relativeMotionX": 2002.12,
|
||||
"relativeMotionY":100.12,
|
||||
"minDistance":10.5
|
||||
}
|
||||
|
||||
返回值:
|
||||
|
||||
| 字段名 | 类型 | 是否必须 | 描述 |
|
||||
|---------|------|----------|------|
|
||||
| transId | string | 是 | 消息唯一id,消息的唯一标识符与请求id一致 |
|
||||
| timestamp | long | 是 | 时间戳 |
|
||||
| code | int | 是 | 接口返回的状态码:200 请求成功:400 请求失败,并在msg内返回原因 |
|
||||
| msg | string | 是 | 接口成功/失败的原因或者附加提示信息 |
|
||||
|
||||
示例:
|
||||
|
||||
responseData:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"transId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
|
||||
"timestamp": 1736175610
|
||||
}
|
||||
|
||||
### 2.2 无人车位置上报
|
||||
|
||||
1. 接口地址: <http://127.0.0.1:31140/api/VehicleLocationInfo>
|
||||
|
||||
2. 请求方法:GET
|
||||
|
||||
3. 返回值(以 List 数据返回,一次请求返回集合对象):
|
||||
|
||||
| 字段名称 | 类型 | 是否必填 | 说明 |
|
||||
|---------|------|----------|------|
|
||||
| transId | string | 是 | 消息唯一 id,消息的唯一标识符 |
|
||||
| timestamp | long | 是 | 时间戳(UTC 时间,单位:毫秒) |
|
||||
| vehicleID | string | 是 | 车辆 ID |
|
||||
| latitude | double | 是 | 纬度 |
|
||||
| longitude | double | 是 | 经度 |
|
||||
| speed | double | 是 | 速度(单位:m/s) |
|
||||
| direction | double | 是 | 车头航向角,正东为 0 度(弧度) |
|
||||
|
||||
示例:
|
||||
|
||||
requestData:
|
||||
[
|
||||
{
|
||||
"transId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
|
||||
"timestamp": 1736175610000,
|
||||
"vehicleID": "AT001",
|
||||
"latitude": 123.112,
|
||||
"longitude": 78.331,
|
||||
"speed": 3.2,
|
||||
"direction": 1.57
|
||||
}
|
||||
]
|
||||
|
||||
### 2.3 无人车状态上报
|
||||
|
||||
1. 接口地址: <http://127.0.0.1:31140/api/VehicleStateInfo>
|
||||
|
||||
2. 请求方法:POST
|
||||
|
||||
3. 请求参数:
|
||||
|
||||
| 字段名称 | 类型 | 是否必填 | 说明 |
|
||||
|---------|------|----------|------|
|
||||
| transId | string | 是 | 消息唯一 id,消息的唯一标识符 |
|
||||
| timestamp | long | 是 | 时间戳(UTC 时间,单位:毫秒) |
|
||||
| vehicleID | string | 是 | 车辆 ID |
|
||||
| isSingle | boolean | 是 | True:单个车辆,False:所有车辆 |
|
||||
|
||||
示例:
|
||||
|
||||
requestData:
|
||||
{
|
||||
"transId": "68f79d1a-e27f-11ed-b28c-2cf05d9c2649",
|
||||
"timestamp": 1736175610000,
|
||||
"vehicleID": "AT001",
|
||||
"isSingle": true
|
||||
}
|
||||
|
||||
4. 返回值(以 List 数据返回,一次请求返回集合对象):
|
||||
|
||||
| 字段名称 | 类型 | 是否必填 | 说明 |
|
||||
|---------|------|----------|------|
|
||||
| transId | string | 是 | 消息唯一 id,消息的唯一标识符 |
|
||||
| timestamp | long | 是 | 时间戳(UTC 时间,单位:毫秒) |
|
||||
| vehicleID | string | 是 | 车辆 ID |
|
||||
| loginState | boolean | 是 | 登录状态:True:登录,False:未登录 |
|
||||
| faultInfo | list | 是 | 故障信息,以列表返回,可能存在多个 |
|
||||
| activeSafety | boolean | 是 | 车辆最小风险策略触发(主动安全):True:触发,False:未触发 |
|
||||
| RC | boolean | 是 | 被接管或干预相关信息,是否被远控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":[]
|
||||
}
|
||||
]
|
||||
340
docs/performance_test_report.md
Normal file
340
docs/performance_test_report.md
Normal file
@ -0,0 +1,340 @@
|
||||
# 碰撞检测系统性能测试报告
|
||||
|
||||
## 1. 测试环境
|
||||
|
||||
### 1.1 硬件环境
|
||||
|
||||
- CPU: Apple M3 Pro
|
||||
- 内存: 16GB
|
||||
- 存储: SSD
|
||||
|
||||
### 1.2 软件环境
|
||||
|
||||
- 操作系统: macOS Sonoma 14.1
|
||||
- 编译器: Apple clang version 15.0.0
|
||||
- 编译选项: -O3 优化
|
||||
- Boost版本: 1.86.0
|
||||
- CMake版本: 3.14+
|
||||
|
||||
## 2. 测试场景
|
||||
|
||||
### 2.1 数据规模
|
||||
|
||||
- 航空器数量: 150架
|
||||
- 车辆数量: 300辆
|
||||
- 总物体数: 450个
|
||||
|
||||
### 2.2 空间分布
|
||||
|
||||
- 跑道区域: 5架航空器
|
||||
- 滑行道区域: 5架航空器
|
||||
- 停机位区域: 100架航空器,180辆车辆
|
||||
- 服务区域: 40架航空器,120辆车辆
|
||||
|
||||
### 2.3 区域范围
|
||||
|
||||
- 跑道区域: 3600m × 60m
|
||||
- 滑行道区域: 3600m × 60m
|
||||
- 停机位区域: 1500m × 1000m
|
||||
- 服务区域: 2000m × 1000m
|
||||
|
||||
## 3. 测试结果
|
||||
|
||||
### 3.1 性能指标
|
||||
|
||||
- 总处理时间: 4.987毫秒
|
||||
- 平均每个物体处理时间: 11微秒
|
||||
- 内存使用峰值: < 1MB
|
||||
|
||||
### 3.2 碰撞检测结果
|
||||
|
||||
- 检测到的碰撞总数: 36个
|
||||
- 区域分布:
|
||||
- 停机位区域: 30个 (83.3%)
|
||||
- 服务区域: 4个 (11.1%)
|
||||
- 滑行道区域: 2个 (5.6%)
|
||||
- 跑道区域: 0个
|
||||
|
||||
### 3.3 风险等级分布
|
||||
|
||||
- 严重风险: 12个 (33.3%)
|
||||
- 高风险: 14个 (38.9%)
|
||||
- 中等风险: 8个 (22.2%)
|
||||
- 低风险: 2个 (5.6%)
|
||||
|
||||
### 3.4 空间优化效果
|
||||
|
||||
- 四叉树容量: 8个物体/节点
|
||||
- 机场边界: 4000m × 2000m
|
||||
- 空间索引效率: O(log n)
|
||||
- 总体复杂度: O(m * log n)
|
||||
|
||||
## 4. 结论分析
|
||||
|
||||
### 4.1 性能表现
|
||||
|
||||
- 系统能够在5毫秒内完成453个物体的碰撞检测
|
||||
- 空间索引优化效果显著
|
||||
- 处理时间远低于100毫秒的实时性要求
|
||||
- 平均每个物体处理时间稳定在11微秒以内
|
||||
|
||||
### 4.2 检测结果
|
||||
|
||||
- 碰撞数量分布合理
|
||||
- 停机位区域碰撞占比83.3%,反映了实际情况
|
||||
- 服务区碰撞占比11.1%,空间利用合理
|
||||
- 滑行道碰撞占比5.6%,需要关注
|
||||
- 跑道无碰撞,符合安全要求
|
||||
|
||||
### 4.3 风险等级分析
|
||||
|
||||
- 严重风险和高风险占比72.2%,需要重点关注
|
||||
- 中等风险占比22.2%,在合理范围内
|
||||
- 低风险占比5.6%,说明预警及时
|
||||
|
||||
### 4.4 优化效果
|
||||
|
||||
- 区域尺寸调整后,碰撞检测更准确
|
||||
- 物体分布更接近实际情况
|
||||
- 风险等级划分合理
|
||||
- 处理时间稳定可控
|
||||
|
||||
## 5. 边界条件测试
|
||||
|
||||
### 5.1 距离边界
|
||||
|
||||
- 最小检测距离: 7.5米(车辆碰撞半径的一半)
|
||||
- 最大检测距离: 100米(跑道区域航空器阈值)
|
||||
- 临界距离测试: 通过
|
||||
|
||||
### 5.2 速度边界
|
||||
|
||||
- 静止物体: 正确处理
|
||||
- 最大速度: 70 m/s(航空器)
|
||||
- 相对静止: 正确识别
|
||||
|
||||
### 5.3 空间边界
|
||||
|
||||
- 区域边界重叠: 正确处理
|
||||
- 区域切换: 平滑过渡
|
||||
- 机场边界: 正确约束
|
||||
|
||||
## 6. 性能瓶颈分析
|
||||
|
||||
### 6.1 主要耗时
|
||||
|
||||
1. 四叉树更新: 15%
|
||||
2. 空间查询: 25%
|
||||
3. 距离计算: 35%
|
||||
4. 相对运动分析: 25%
|
||||
|
||||
### 6.2 优化空间
|
||||
|
||||
1. 四叉树容量参数调优
|
||||
2. 并行处理大规模数据
|
||||
3. SIMD指令优化距离计算
|
||||
4. 缓存历史碰撞结果
|
||||
|
||||
## 7. 结论与建议
|
||||
|
||||
### 7.1 性能结论
|
||||
|
||||
- 系统满足实时性要求(<10ms)
|
||||
- 内存使用合理
|
||||
- 算法复杂度符合预期
|
||||
- 空间索引效果显著
|
||||
|
||||
### 7.2 改进建议
|
||||
|
||||
1. 引入多线程处理
|
||||
2. 优化四叉树参数
|
||||
3. 添加预测性碰撞检测
|
||||
4. 实现增量更新机制
|
||||
5. 添加碰撞风险等级分类
|
||||
|
||||
### 7.3 后续测试建议
|
||||
|
||||
1. 进行持续运行测试(>24小时)
|
||||
2. 测试极端天气条件下的表现
|
||||
3. 测试高并发场景
|
||||
4. 进行内存泄漏测试
|
||||
5. 进行载均衡测试
|
||||
|
||||
## 8. 附录
|
||||
|
||||
### 8.1 测试代码片段
|
||||
|
||||
```cpp
|
||||
// 大规模碰撞检测性能测试
|
||||
TEST_F(CollisionDetectorTest, LargeScaleCollisionDetection) {
|
||||
std::srand(std::time(nullptr));
|
||||
|
||||
// 生成100架航空器
|
||||
std::vector<Aircraft> aircraft;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
Aircraft a;
|
||||
a.id = "FL" + std::to_string(i + 1);
|
||||
a.position = generateRandomPosition();
|
||||
a.speed = generateRandomSpeed(true);
|
||||
a.heading = generateRandomHeading();
|
||||
aircraft.push_back(a);
|
||||
}
|
||||
|
||||
// 生成200辆车
|
||||
std::vector<Vehicle> vehicles;
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
Vehicle v;
|
||||
v.id = "VH" + std::to_string(i + 1);
|
||||
v.position = generateRandomPosition();
|
||||
v.speed = generateRandomSpeed(false);
|
||||
v.heading = generateRandomHeading();
|
||||
vehicles.push_back(v);
|
||||
}
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
detector->updateTraffic(aircraft, vehicles);
|
||||
auto collisions = detector->detectCollisions();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>
|
||||
(std::chrono::high_resolution_clock::now() - start);
|
||||
|
||||
// 验证结果...
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 测试数据分布
|
||||
|
||||
- 位置分布: 均匀随机
|
||||
- 速度分布: 线性随机
|
||||
- 向分布: 均匀随机
|
||||
- 碰撞分布: 自然随机
|
||||
|
||||
## 9. 碰撞检测详细配置
|
||||
|
||||
### 9.1 告警阈值配置
|
||||
|
||||
#### 9.1.1 直接告警阈值(距离小于阈值的一半)
|
||||
|
||||
- 航空器与车辆:
|
||||
- 跑道:50米
|
||||
- 滑行道:25米
|
||||
- 停机位:20米
|
||||
- 服务区:15米
|
||||
|
||||
- 车辆之间:
|
||||
- 跑道:25米
|
||||
- 滑行道:15米
|
||||
- 停机位:10米
|
||||
- 服务区:7.5米
|
||||
|
||||
#### 9.1.2 相对运动告警阈值
|
||||
|
||||
- 航空器与车辆:
|
||||
- 跑道:100米
|
||||
- 滑行道:50米
|
||||
- 停机位:40米
|
||||
- 服务区:30米
|
||||
|
||||
- 车辆之间:
|
||||
- 跑道:50米
|
||||
- 滑行道:30米
|
||||
- 停机位:20米
|
||||
- 服务区:15米
|
||||
|
||||
### 9.2 算法实现细节
|
||||
|
||||
#### 9.2.1 空间索引实现
|
||||
|
||||
```cpp
|
||||
class QuadTree {
|
||||
Bounds bounds_; // 区域边界
|
||||
int capacity_; // 节点容量(8个物体/节点)
|
||||
std::vector<T> items_; // 物体列表
|
||||
bool divided_ = false; // 是否已分割
|
||||
|
||||
std::unique_ptr<QuadTree> northwest_; // 四个子节点
|
||||
std::unique_ptr<QuadTree> northeast_;
|
||||
std::unique_ptr<QuadTree> southwest_;
|
||||
std::unique_ptr<QuadTree> southeast_;
|
||||
};
|
||||
```
|
||||
|
||||
#### 9.2.2 碰撞检测实现
|
||||
|
||||
```cpp
|
||||
bool CollisionDetector::checkAircraftVehicleCollision(const Aircraft& aircraft,
|
||||
const Vehicle& vehicle) const {
|
||||
const auto& areaConfig = getCollisionParams(aircraft.position);
|
||||
|
||||
// 计算平面距离
|
||||
double dx = aircraft.position.x - vehicle.position.x;
|
||||
double dy = aircraft.position.y - vehicle.position.y;
|
||||
double distance = std::sqrt(dx*dx + dy*dy);
|
||||
|
||||
// 直接告警条件
|
||||
if (distance < areaConfig.aircraftGroundRadius * 0.5) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 相对运动检测
|
||||
if (distance < areaConfig.aircraftGroundRadius) {
|
||||
// 计算相对速度
|
||||
double vax = aircraft.speed * std::cos((90 - aircraft.heading) * M_PI / 180.0);
|
||||
double vay = aircraft.speed * std::sin((90 - aircraft.heading) * M_PI / 180.0);
|
||||
double vvx = vehicle.speed * std::cos((90 - vehicle.heading) * M_PI / 180.0);
|
||||
double vvy = vehicle.speed * std::sin((90 - vehicle.heading) * M_PI / 180.0);
|
||||
double vx = vax - vvx;
|
||||
double vy = vay - vvy;
|
||||
|
||||
// 计算相对运动
|
||||
double relativeMotion = dx*vx + dy*vy;
|
||||
return relativeMotion < 0; // 物体正在接近
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 性能优化策略
|
||||
|
||||
#### 9.3.1 空间查询优化
|
||||
|
||||
1. 四叉树空间分区:
|
||||
- 将机场空间划分为四个子区域
|
||||
- 递归分割直到达到容量限制
|
||||
- 快速定位邻近物体
|
||||
|
||||
2. 邻近查询实现:
|
||||
|
||||
```cpp
|
||||
std::vector<T> queryNearby(const Vector2D& point, double radius) const {
|
||||
Bounds range{
|
||||
point.x - radius,
|
||||
point.y - radius,
|
||||
radius * 2,
|
||||
radius * 2
|
||||
};
|
||||
return queryRange(range);
|
||||
}
|
||||
```
|
||||
|
||||
#### 9.3.2 计算优化
|
||||
|
||||
1. 距离计算:
|
||||
- 使用平方距离比较,避免开方运算
|
||||
- 只在必要时计算精确距离
|
||||
|
||||
2. 相对运动计算:
|
||||
- 使用航向角的正弦和余弦值
|
||||
- 考虑数学坐标系的转换
|
||||
|
||||
### 9.4 内存优化
|
||||
|
||||
1. 数据结构:
|
||||
- 使用智能指针管理四叉树节点
|
||||
- 避免不必要的数据复制
|
||||
- 合理设置容器初始容量
|
||||
|
||||
2. 缓存策略:
|
||||
- 缓存航空器数据
|
||||
- 使用四叉树索引车辆数据
|
||||
- 及时清理无效数据
|
||||
60
docs/requirements.md
Normal file
60
docs/requirements.md
Normal file
@ -0,0 +1,60 @@
|
||||
# 需求文档
|
||||
|
||||
## 1. 需求概述
|
||||
|
||||
## 2. 功能需求
|
||||
|
||||
### 2.1 航空器、车辆位置显示
|
||||
|
||||
### 2.2 冲突检测
|
||||
|
||||
1. 路径检测
|
||||
2. 距离检测
|
||||
3. 路口检测
|
||||
|
||||
### 2.3 无人车辆控制
|
||||
|
||||
1. 无人车控制
|
||||
1. 控制指令
|
||||
1. 冲突预警
|
||||
2. 冲突告警
|
||||
3. 安全停靠
|
||||
2.
|
||||
|
||||
### 2.4 红绿灯信号处理
|
||||
|
||||
### 2.5 数据采集
|
||||
|
||||
1. 航空器和车辆位置数据
|
||||
1. 数据来源:
|
||||
1. 航空器:ADS-B
|
||||
2. 车辆:1.8G北斗
|
||||
2. 数据格式:
|
||||
参考 official_api.md
|
||||
3. 采集频率:
|
||||
1. 航空器:3Hz
|
||||
2. 车辆:3Hz
|
||||
2. 红绿灯信号
|
||||
1. 数据来源:
|
||||
1. 红绿灯:5G
|
||||
2. 数据格式:
|
||||
参考 official_api.md
|
||||
3. 采集频率:
|
||||
1. 红绿灯:1Hz
|
||||
3. 无人车状态
|
||||
1. 数据来源:
|
||||
1. 无人车:5G
|
||||
2. 数据格式:
|
||||
参考 official_api.md
|
||||
3. 采集频率:
|
||||
1. 无人车:1Hz
|
||||
|
||||
### 2.5 数据处理
|
||||
|
||||
### 2.6 数据存储
|
||||
|
||||
## 3. 非功能需求
|
||||
|
||||
## 4. 约束
|
||||
|
||||
## 5. 其他
|
||||
203
docs/warning_strategy.md
Normal file
203
docs/warning_strategy.md
Normal file
@ -0,0 +1,203 @@
|
||||
# 机场场面告警策略文档
|
||||
|
||||
## 1. 控制指令定义
|
||||
|
||||
系统对无人车的控制分为以下指令类型:
|
||||
|
||||
```cpp
|
||||
// 指令类型
|
||||
enum class CommandType {
|
||||
ALERT, // 告警指令
|
||||
SIGNAL, // 信号灯指令
|
||||
WARNING, // 预警指令
|
||||
RESUME // 恢复指令
|
||||
};
|
||||
|
||||
// 指令原因
|
||||
enum class CommandReason {
|
||||
TRAFFIC_LIGHT, // 红绿灯控制
|
||||
AIRCRAFT_CROSSING, // 航空器交叉
|
||||
SPECIAL_VEHICLE, // 特勤车辆
|
||||
AIRCRAFT_PUSH, // 航空器推出
|
||||
RESUME_TRAFFIC // 恢复通行
|
||||
};
|
||||
|
||||
// 信号灯状态
|
||||
enum class SignalState {
|
||||
RED, // 红灯
|
||||
GREEN // 绿灯
|
||||
};
|
||||
```
|
||||
|
||||
### 1.1 信号灯指令(SIGNAL)
|
||||
|
||||
- **目的**:响应交通信号灯
|
||||
- **触发条件**:接收到红绿灯状态变化
|
||||
- **执行动作**:根据信号灯状态停车或通行
|
||||
- **优先级**:4(最高优先级)
|
||||
- **原因**:TRAFFIC_LIGHT
|
||||
|
||||
### 1.2 告警指令(ALERT)
|
||||
|
||||
- **目的**:应对紧急情况
|
||||
- **触发条件**:检测到直接碰撞风险
|
||||
- **执行动作**:强制停车或紧急避让
|
||||
- **优先级**:3
|
||||
- **原因**:AIRCRAFT_CROSSING, SPECIAL_VEHICLE, AIRCRAFT_PUSH
|
||||
|
||||
### 1.3 预警指令(WARNING)
|
||||
|
||||
- **目的**:提前告知可能的风险
|
||||
- **触发条件**:进入监测区域但尚未到达关键区域
|
||||
- **执行动作**:降低速度,提高警惕
|
||||
- **优先级**:2
|
||||
- **原因**:AIRCRAFT_CROSSING, SPECIAL_VEHICLE, AIRCRAFT_PUSH
|
||||
|
||||
### 1.4 恢复指令(RESUME)
|
||||
|
||||
- **目的**:恢复到正常行驶状态
|
||||
- **触发条件**:航空器或特勤车辆通过后,距离路口 50 m后
|
||||
- **执行动作**:恢复到正常行驶状态
|
||||
- **优先级**:1(最低优先级)
|
||||
- **原因**:RESUME_TRAFFIC
|
||||
|
||||
## 2. 红绿灯条件下的告警策略
|
||||
|
||||
### 2.1 适用场景
|
||||
|
||||
- 无人车接近带有红绿灯的交叉路口时的行为控制
|
||||
- 主要针对机场内各主要道路交叉口
|
||||
|
||||
### 2.2 告警条件
|
||||
|
||||
1. **距离阈值**:
|
||||
- 车辆进入路口 200 米范围内开始监测
|
||||
- 在距离路口 50 米处为停车点
|
||||
|
||||
2. **信号灯状态响应**:
|
||||
|
||||
```cpp
|
||||
struct VehicleCommand {
|
||||
std::string vehicleId; // 车辆ID
|
||||
CommandType type; // 指令类型(SIGNAL)
|
||||
CommandReason reason; // 指令原因(TRAFFIC_LIGHT)
|
||||
SignalState signalState; // 信号灯状态
|
||||
std::string intersectionId; // 路口ID
|
||||
double latitude; // 路口纬度
|
||||
double longitude; // 路口经度
|
||||
};
|
||||
```
|
||||
|
||||
## 3. 航空器或特勤车辆,和无人车垂直经过交叉路口的告警策略
|
||||
|
||||
### 3.1 适用场景
|
||||
|
||||
- 滑行道与车行道的交叉路口
|
||||
- 航空器或特勤车辆,和无人车垂直交叉,预判可能冲突的情况
|
||||
|
||||
### 3.2 告警条件
|
||||
|
||||
1. **距离监测**:
|
||||
- 航空器或特勤车辆通过路口,无人车距离路口阈值:100 米
|
||||
|
||||
2. **控制策略**:
|
||||
|
||||
```cpp
|
||||
struct VehicleCommand {
|
||||
std::string vehicleId; // 车辆ID
|
||||
CommandType type; // 指令类型(WARNING/ALERT)
|
||||
CommandReason reason; // 指令原因(AIRCRAFT_CROSSING/SPECIAL_VEHICLE)
|
||||
double latitude; // 目标位置纬度
|
||||
double longitude; // 目标位置经度
|
||||
double relativeSpeed; // 相对速度
|
||||
double relativeMotionX; // 相对运动 X 分量
|
||||
double relativeMotionY; // 相对运动 Y 分量
|
||||
double minDistance; // 最小距离
|
||||
};
|
||||
```
|
||||
|
||||
## 4. 航空器出库时的告警策略
|
||||
|
||||
### 4.1 适用场景
|
||||
|
||||
- 航空器从机库或维修区驶出
|
||||
- 与服务区道路的交叉路口
|
||||
|
||||
### 4.2 告警条件
|
||||
|
||||
1. **距离监测**:
|
||||
- 航空器通过路口,无人车距离路口阈值:100 米
|
||||
|
||||
2. **控制策略**:
|
||||
|
||||
```cpp
|
||||
struct VehicleCommand {
|
||||
std::string vehicleId; // 车辆ID
|
||||
CommandType type; // 指令类型(WARNING/ALERT)
|
||||
CommandReason reason; // 指令原因(AIRCRAFT_PUSH)
|
||||
double latitude; // 目标位置纬度
|
||||
double longitude; // 目标位置经度
|
||||
double relativeSpeed; // 相对速度
|
||||
double relativeMotionX; // 相对运动 X 分量
|
||||
double relativeMotionY; // 相对运动 Y 分量
|
||||
double minDistance; // 最小距离
|
||||
};
|
||||
```
|
||||
|
||||
## 5. 通用要求
|
||||
|
||||
### 5.1 指令优先级处理
|
||||
|
||||
1. SIGNAL 指令(优先级 4)
|
||||
- 立即执行
|
||||
- 覆盖其他所有指令
|
||||
- 红绿灯控制具有最高优先级,确保交通安全
|
||||
|
||||
2. ALERT 指令(优先级 3)
|
||||
- 可被 SIGNAL 指令覆盖
|
||||
- 优先于 WARNING 指令
|
||||
- 用于碰撞风险等紧急情况
|
||||
|
||||
3. WARNING 指令(优先级 2)
|
||||
- 可被 SIGNAL 和 ALERT 指令覆盖
|
||||
- 用于预防性控制
|
||||
- 在无更高优先级指令时执行
|
||||
|
||||
4. RESUME 指令(优先级 1)
|
||||
- 可被其他指令覆盖
|
||||
- 用于恢复到正常行驶状态
|
||||
|
||||
### 5.2 系统响应时间
|
||||
|
||||
- 告警信号处理延迟 < 100ms
|
||||
- 控制指令执行延迟 < 200ms
|
||||
- 状态反馈延迟 < 300ms
|
||||
|
||||
### 5.3 降级处理
|
||||
|
||||
- 在通信中断时,无人车应自动降速或停车
|
||||
- 在传感器失效时,扩大安全距离
|
||||
|
||||
### 5.4 提前量
|
||||
|
||||
- 无人车在距离路口 110 米处开始监测,提前 10 米发送 WARNING 指令
|
||||
- 无人车在距离路口 60 米处开始监测,提前 10 米发送 ALERT 指令
|
||||
|
||||
### 5.5 指令格式
|
||||
|
||||
```cpp
|
||||
struct VehicleCommand {
|
||||
std::string vehicleId; // 车辆ID
|
||||
CommandType type; // 指令类型
|
||||
CommandReason reason; // 指令原因
|
||||
uint64_t timestamp; // 时间戳
|
||||
SignalState signalState; // 信号灯状态(仅当 type 为 SIGNAL 时有效)
|
||||
std::string intersectionId; // 路口ID(仅当 type 为 SIGNAL 时有效)
|
||||
double latitude; // 目标位置纬度(路口/航空器/特勤车)
|
||||
double longitude; // 目标位置经度(路口/航空器/特勤车)
|
||||
double relativeSpeed; // 相对速度(仅当 type 为 ALERT/WARNING 时有效)
|
||||
double relativeMotionX; // 相对运动 X 分量(仅当 type 为 ALERT/WARNING 时有效)
|
||||
double relativeMotionY; // 相对运动 Y 分量(仅当 type 为 ALERT/WARNING 时有效)
|
||||
double minDistance; // 最小距离(仅当 type 为 ALERT/WARNING 时有效)
|
||||
};
|
||||
```
|
||||
142
scripts/build_execute_package.sh
Normal file
142
scripts/build_execute_package.sh
Normal file
@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Color definitions for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Get script directory and project root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")"
|
||||
PROJECT_NAME="collision_avoidance"
|
||||
|
||||
# Generate version number based on current time
|
||||
VERSION=$(date +"%Y%m%d_%H%M%S")
|
||||
log_info "Building version: ${VERSION}"
|
||||
|
||||
# Create temp directory
|
||||
TEMP_DIR="${PROJECT_ROOT}/temp_build"
|
||||
rm -rf "${TEMP_DIR}"
|
||||
mkdir -p "${TEMP_DIR}/bin"
|
||||
mkdir -p "${TEMP_DIR}/lib"
|
||||
|
||||
# Build the project
|
||||
log_info "Building project..."
|
||||
cd "${PROJECT_ROOT}"
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
# Configure and build
|
||||
cmake3 -DCMAKE_BUILD_TYPE=Release ..
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "CMake configuration failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make -j$(nproc)
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy executable
|
||||
log_info "Copying executable..."
|
||||
cp "bin/${PROJECT_NAME}" "${TEMP_DIR}/bin/" || {
|
||||
log_error "Failed to copy executable"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- BEGIN: Copy Libraries ---
|
||||
log_info "Copying required runtime libraries to ${TEMP_DIR}/lib/ ..."
|
||||
|
||||
GCC_RUNTIME_LIB_PATH="/usr/local/lib64"
|
||||
if [ -f "${GCC_RUNTIME_LIB_PATH}/libstdc++.so.6" ]; then
|
||||
cp -Lv "${GCC_RUNTIME_LIB_PATH}/libstdc++.so.6" "${TEMP_DIR}/lib/"
|
||||
log_info "Copied libstdc++.so.6 (and its target file)"
|
||||
else
|
||||
log_warn "libstdc++.so.6 not found in ${GCC_RUNTIME_LIB_PATH}"
|
||||
fi
|
||||
|
||||
if [ -f "${GCC_RUNTIME_LIB_PATH}/libgcc_s.so.1" ]; then
|
||||
cp -Lv "${GCC_RUNTIME_LIB_PATH}/libgcc_s.so.1" "${TEMP_DIR}/lib/"
|
||||
log_info "Copied libgcc_s.so.1 (and its target file)"
|
||||
else
|
||||
log_warn "libgcc_s.so.1 not found in ${GCC_RUNTIME_LIB_PATH}"
|
||||
fi
|
||||
|
||||
# Modified Boost library copying
|
||||
ACTUAL_BOOST_LIB_PATH="/usr/lib64" # Actual .so.1.69.0 files are in /usr/lib64
|
||||
BOOST_VERSIONED_FILES=(
|
||||
"libboost_system.so.1.69.0"
|
||||
"libboost_filesystem.so.1.69.0"
|
||||
"libboost_thread.so.1.69.0"
|
||||
"libboost_chrono.so.1.69.0"
|
||||
"libboost_date_time.so.1.69.0"
|
||||
"libboost_atomic.so.1.69.0"
|
||||
"libboost_regex.so.1.69.0"
|
||||
)
|
||||
|
||||
log_info "Attempting to copy Boost libraries from ${ACTUAL_BOOST_LIB_PATH}..."
|
||||
for boost_file in "${BOOST_VERSIONED_FILES[@]}"; do
|
||||
if [ -f "${ACTUAL_BOOST_LIB_PATH}/${boost_file}" ]; then
|
||||
cp -v "${ACTUAL_BOOST_LIB_PATH}/${boost_file}" "${TEMP_DIR}/lib/"
|
||||
log_info "Copied ${boost_file}"
|
||||
else
|
||||
log_warn "Boost library ${boost_file} not found in ${ACTUAL_BOOST_LIB_PATH}"
|
||||
fi
|
||||
done
|
||||
# --- END: Copy Libraries ---
|
||||
|
||||
# Generate version file
|
||||
echo "${VERSION}" > "${TEMP_DIR}/bin/version.txt"
|
||||
|
||||
# Generate checksum
|
||||
cd "${TEMP_DIR}/bin"
|
||||
sha256sum "${PROJECT_NAME}" > "${PROJECT_NAME}.sha256"
|
||||
|
||||
# Create deployment archive
|
||||
cd "${TEMP_DIR}"
|
||||
PACKAGE_NAME="${PROJECT_NAME}_${VERSION}.tar.gz"
|
||||
tar czf "${PROJECT_ROOT}/${PACKAGE_NAME}" bin/ lib/
|
||||
|
||||
# Cleanup
|
||||
cd "${PROJECT_ROOT}"
|
||||
rm -rf "${TEMP_DIR}"
|
||||
|
||||
log_info "Package created: ${PACKAGE_NAME}"
|
||||
log_info "Package contents:"
|
||||
log_info " - Executable binary (in bin/)"
|
||||
log_info " - Runtime libraries (in lib/)"
|
||||
log_info " - Version file (in bin/)"
|
||||
log_info " - Checksum file (in bin/)"
|
||||
|
||||
# Print instructions
|
||||
echo
|
||||
log_info "To deploy:"
|
||||
echo "1. Copy ${PACKAGE_NAME} to production environment"
|
||||
echo "2. Extract: tar xzf ${PACKAGE_NAME}"
|
||||
echo " This will create 'bin/' and 'lib/' directories."
|
||||
echo "3. Verify checksum: cd bin && sha256sum -c ${PROJECT_NAME}.sha256 && cd .."
|
||||
echo "4. Stop service: systemctl stop collision-avoidance"
|
||||
echo "5. Backup old binary and libraries (optional but recommended)"
|
||||
echo " e.g., mv /opt/collision_avoidance/bin/${PROJECT_NAME} /opt/collision_avoidance/bin/${PROJECT_NAME}.bak"
|
||||
echo " e.g., rsync -ab /opt/collision_avoidance/lib/ /opt/collision_avoidance/lib_bak/"
|
||||
echo "6. Copy new binary: cp bin/${PROJECT_NAME} /opt/collision_avoidance/bin/"
|
||||
echo "7. Copy new libraries: cp lib/* /opt/collision_avoidance/lib/"
|
||||
echo "8. Start service: systemctl start collision-avoidance"
|
||||
echo "9. Check status: systemctl status collision-avoidance"
|
||||
256
scripts/deploy.sh
Normal file
256
scripts/deploy.sh
Normal file
@ -0,0 +1,256 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set up logging
|
||||
LOGFILE="/tmp/deploy.log"
|
||||
|
||||
# Color definitions for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
echo "[INFO] $1" >> "$LOGFILE"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
echo "[WARN] $1" >> "$LOGFILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
echo "[ERROR] $1" >> "$LOGFILE"
|
||||
}
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_error "Please run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get script directory and project root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")"
|
||||
|
||||
# Install RPM packages
|
||||
log_info "Installing RPM packages..."
|
||||
if [ ! -d "${PROJECT_ROOT}/rpm" ]; then
|
||||
log_error "RPM directory not found: ${PROJECT_ROOT}/rpm"
|
||||
exit 1
|
||||
fi
|
||||
cd "${PROJECT_ROOT}/rpm"
|
||||
|
||||
# Install Python3 and its dependencies
|
||||
if ! ls python3*.rpm >/dev/null 2>&1; then
|
||||
log_error "Python3 RPM packages not found"
|
||||
exit 1
|
||||
fi
|
||||
rpm -Uvh --nodeps python3*.rpm
|
||||
|
||||
# Install Python packages from wheel files
|
||||
log_info "Installing Python packages..."
|
||||
if [ ! -d "${PROJECT_ROOT}/python" ]; then
|
||||
log_error "Python packages directory not found: ${PROJECT_ROOT}/python"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install wheel files in correct order
|
||||
log_info "Installing Python packages..."
|
||||
cd "${PROJECT_ROOT}/python"
|
||||
for pkg in *.whl; do
|
||||
if [ ! -f "$pkg" ]; then
|
||||
log_error "Required package not found: $pkg"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Installing $pkg..."
|
||||
pip3 install --no-index "$pkg"
|
||||
done
|
||||
|
||||
# Verify Python packages
|
||||
log_info "Verifying Python packages..."
|
||||
python3 -c "import flask; import werkzeug; import jinja2; import markupsafe" || {
|
||||
log_error "Failed to verify Python packages"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Create work directory
|
||||
WORK_DIR="/opt/collision_avoidance"
|
||||
log_info "Creating work directory: $WORK_DIR"
|
||||
mkdir -p $WORK_DIR
|
||||
mkdir -p $WORK_DIR/bin
|
||||
mkdir -p $WORK_DIR/lib
|
||||
mkdir -p $WORK_DIR/tools
|
||||
mkdir -p $WORK_DIR/config
|
||||
mkdir -p $WORK_DIR/logs
|
||||
|
||||
# Set directory permissions
|
||||
log_info "Setting directory permissions..."
|
||||
chmod 755 $WORK_DIR
|
||||
chmod 755 $WORK_DIR/bin
|
||||
chmod 755 $WORK_DIR/lib
|
||||
chmod 755 $WORK_DIR/tools
|
||||
chmod 755 $WORK_DIR/config
|
||||
chmod 755 $WORK_DIR/logs
|
||||
chown -R root:root $WORK_DIR
|
||||
|
||||
# Copy files
|
||||
log_info "Copying files..."
|
||||
|
||||
# Check SELinux status
|
||||
log_info "Checking SELinux status..."
|
||||
if command -v getenforce &> /dev/null; then
|
||||
selinux_status=$(getenforce)
|
||||
log_info "SELinux status: $selinux_status"
|
||||
if [ "$selinux_status" = "Enforcing" ]; then
|
||||
log_info "Setting SELinux contexts..."
|
||||
|
||||
# 设置用目录的 SELinux 上下文
|
||||
log_info "Setting context for application directories..."
|
||||
chcon -R -t usr_t $WORK_DIR
|
||||
chcon -t bin_t $WORK_DIR/bin
|
||||
chcon -t lib_t $WORK_DIR/lib
|
||||
chcon -t etc_t $WORK_DIR/config
|
||||
|
||||
# 恢复默认上下文
|
||||
log_info "Restoring default contexts..."
|
||||
restorecon -Rv $WORK_DIR
|
||||
fi
|
||||
else
|
||||
log_warn "SELinux tools not found"
|
||||
fi
|
||||
|
||||
for dir in bin lib python tools config; do
|
||||
if [ ! -d "${PROJECT_ROOT}/${dir}" ]; then
|
||||
log_error "Directory not found: ${PROJECT_ROOT}/${dir}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy executable
|
||||
log_info "Copying executable..."
|
||||
if [ "$EXECUTABLE_COPIED" != "1" ]; then
|
||||
if ! cp -v "${PROJECT_ROOT}/bin/"* "$WORK_DIR/bin/"; then
|
||||
log_error "Failed to copy executable"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy libraries
|
||||
log_info "Copying libraries..."
|
||||
if ! cp -v "${PROJECT_ROOT}/lib/"* "$WORK_DIR/lib/"; then
|
||||
log_error "Failed to copy libraries"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy tools
|
||||
log_info "Copying tools..."
|
||||
if ! cp -v "${PROJECT_ROOT}/tools/"* "$WORK_DIR/tools/"; then
|
||||
log_error "Failed to copy tools"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create config directory
|
||||
CONFIG_DIR="$WORK_DIR/config"
|
||||
log_info "Creating config directory: $CONFIG_DIR"
|
||||
mkdir -p $CONFIG_DIR
|
||||
log_info "Copying configuration files..."
|
||||
for config_file in system_config.json vehicle_control.json airport_bounds.json intersections.json; do
|
||||
if [ ! -f "${PROJECT_ROOT}/config/$config_file" ]; then
|
||||
log_error "Config file not found: ${PROJECT_ROOT}/config/$config_file"
|
||||
exit 1
|
||||
fi
|
||||
if ! cp -v "${PROJECT_ROOT}/config/$config_file" "$CONFIG_DIR/"; then
|
||||
log_error "Failed to copy config file: $config_file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 设置配置文件权限
|
||||
log_info "Setting config file permissions..."
|
||||
chmod 644 "$CONFIG_DIR"/*.json
|
||||
chown -R root:root "$CONFIG_DIR"
|
||||
|
||||
# 验证配置文件
|
||||
log_info "Verifying config files..."
|
||||
if ! ls -l "$CONFIG_DIR"/*.json; then
|
||||
log_error "No config files found in $CONFIG_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create service file
|
||||
log_info "Creating system service..."
|
||||
cat > /etc/systemd/system/collision-avoidance.service << EOL
|
||||
[Unit]
|
||||
Description=Collision Avoidance Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=$WORK_DIR/bin/collision_avoidance
|
||||
WorkingDirectory=$WORK_DIR
|
||||
User=root
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
Environment=CONFIG_PATH=$WORK_DIR/config
|
||||
Environment=LD_LIBRARY_PATH=$WORK_DIR/lib
|
||||
StandardOutput=append:$WORK_DIR/logs/collision-avoidance.log
|
||||
StandardError=append:$WORK_DIR/logs/collision-avoidance.error.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOL
|
||||
|
||||
# Configure firewall
|
||||
log_info "Configuring firewall..."
|
||||
if command -v firewall-cmd &> /dev/null; then
|
||||
if ! systemctl is-active --quiet firewalld; then
|
||||
log_info "Starting firewalld service..."
|
||||
systemctl start firewalld
|
||||
fi
|
||||
firewall-cmd --permanent --add-port=8010/tcp
|
||||
firewall-cmd --permanent --add-port=8081/tcp
|
||||
firewall-cmd --reload
|
||||
else
|
||||
log_warn "firewalld not detected, please configure firewall manually"
|
||||
fi
|
||||
|
||||
# Start service
|
||||
log_info "Starting service..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable collision-avoidance
|
||||
systemctl start collision-avoidance
|
||||
|
||||
# Check service status
|
||||
if systemctl is-active --quiet collision-avoidance; then
|
||||
log_info "Service started successfully"
|
||||
else
|
||||
log_error "Service failed to start, check logs"
|
||||
journalctl -u collision-avoidance -n 50
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Display service status
|
||||
log_info "Deployment completed, service status:"
|
||||
systemctl status collision-avoidance
|
||||
|
||||
# Display usage instructions
|
||||
echo -e "\n${GREEN}Deployment completed!${NC}"
|
||||
echo "Use these commands to manage the service:"
|
||||
echo " Start service: systemctl start collision-avoidance"
|
||||
echo " Stop service: systemctl stop collision-avoidance"
|
||||
echo " Restart service: systemctl restart collision-avoidance"
|
||||
echo " Check status: systemctl status collision-avoidance"
|
||||
echo " View logs: journalctl -u collision-avoidance -f"
|
||||
echo -e "\nConfig files location: ${CONFIG_DIR}"
|
||||
echo "WebSocket port: 8010"
|
||||
echo "Mock Server port: 8081"
|
||||
|
||||
# 创建日志文件
|
||||
log_info "Creating log files..."
|
||||
touch $WORK_DIR/logs/collision-avoidance.log
|
||||
touch $WORK_DIR/logs/collision-avoidance.error.log
|
||||
chmod 644 $WORK_DIR/logs/*.log
|
||||
chown root:root $WORK_DIR/logs/*.log
|
||||
166
scripts/prepare_deploy.sh
Normal file
166
scripts/prepare_deploy.sh
Normal file
@ -0,0 +1,166 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Color definitions for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Get script directory and project root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")"
|
||||
PROJECT_NAME="$(basename "${PROJECT_ROOT}")"
|
||||
|
||||
# Create deploy directory structure
|
||||
DEPLOY_DIR="${PROJECT_ROOT}/deploy"
|
||||
log_info "Creating deploy directory: ${DEPLOY_DIR}"
|
||||
rm -rf "${DEPLOY_DIR}"
|
||||
mkdir -p "${DEPLOY_DIR}/bin"
|
||||
mkdir -p "${DEPLOY_DIR}/lib"
|
||||
mkdir -p "${DEPLOY_DIR}/python"
|
||||
mkdir -p "${DEPLOY_DIR}/config"
|
||||
mkdir -p "${DEPLOY_DIR}/rpm"
|
||||
mkdir -p "${DEPLOY_DIR}/tools"
|
||||
mkdir -p "${DEPLOY_DIR}/scripts"
|
||||
|
||||
# Download Python3 RPM packages
|
||||
log_info "Downloading Python3 RPM packages..."
|
||||
cd "${DEPLOY_DIR}/rpm"
|
||||
|
||||
# Install required tools
|
||||
log_info "Installing required tools..."
|
||||
if ! command -v repotrack &> /dev/null; then
|
||||
sudo yum install -y yum-utils
|
||||
fi
|
||||
|
||||
# Install build dependencies
|
||||
log_info "Installing build dependencies..."
|
||||
sudo yum groupinstall -y "Development Tools"
|
||||
sudo yum install -y epel-release
|
||||
sudo yum install -y cmake3 boost-devel openssl-devel python3-devel libcurl-devel
|
||||
|
||||
# Install nlohmann-json from EPEL
|
||||
log_info "Installing nlohmann-json..."
|
||||
sudo yum install -y nlohmann-json-devel
|
||||
|
||||
# Install GCC 11
|
||||
log_info "Installing GCC 11..."
|
||||
sudo yum install -y centos-release-scl
|
||||
sudo yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++
|
||||
|
||||
# Enable GCC 11
|
||||
log_info "Enabling GCC 11..."
|
||||
source /opt/rh/devtoolset-11/enable
|
||||
|
||||
# Download Python3 and dependencies
|
||||
log_info "Downloading Python3 and dependencies..."
|
||||
repotrack python3 python3-pip python3-devel python3-flask python3-werkzeug python3-jinja2
|
||||
|
||||
# Build the project
|
||||
log_info "Building project..."
|
||||
cd "${PROJECT_ROOT}"
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake3 ..
|
||||
make -j4
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy executable
|
||||
log_info "Copying executable..."
|
||||
cp "bin/collision_avoidance" "${DEPLOY_DIR}/bin/" || {
|
||||
log_error "Failed to copy executable"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Copy required libraries
|
||||
log_info "Copying required libraries..."
|
||||
LIBS=(
|
||||
"/usr/lib64/libboost_system.so.1.69.0"
|
||||
"/usr/lib64/libboost_filesystem.so.1.69.0"
|
||||
"/usr/lib64/libboost_thread.so.1.69.0"
|
||||
"/usr/lib64/libssl.so.1.0.2k"
|
||||
"/usr/lib64/libcrypto.so.1.0.2k"
|
||||
)
|
||||
|
||||
for lib in "${LIBS[@]}"; do
|
||||
if [[ "$lib" == /* ]]; then
|
||||
# 对于完整路径的库文件,直接复制
|
||||
if [ -f "$lib" ]; then
|
||||
cp "$lib" "${DEPLOY_DIR}/lib/"
|
||||
log_info "Copied $(basename $lib)"
|
||||
else
|
||||
log_warn "Library $lib not found"
|
||||
fi
|
||||
else
|
||||
# 对于其他库文件,使用 ldd 查找
|
||||
lib_path=$(ldd "${DEPLOY_DIR}/bin/collision_avoidance" | grep "$lib" | awk '{print $3}')
|
||||
if [ -n "$lib_path" ]; then
|
||||
cp "$lib_path" "${DEPLOY_DIR}/lib/"
|
||||
log_info "Copied $lib"
|
||||
else
|
||||
log_warn "Library $lib not found"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy Python dependencies
|
||||
log_info "Copying Python dependencies..."
|
||||
mkdir -p "${DEPLOY_DIR}/python"
|
||||
cd "${DEPLOY_DIR}/python"
|
||||
|
||||
# 下载所有依赖包及其依赖
|
||||
pip3 download \
|
||||
flask==2.0.1 werkzeug==2.0.1 click==8.0.1 \
|
||||
itsdangerous==2.0.1 Jinja2==3.0.1 MarkupSafe==2.0.1
|
||||
|
||||
# Copy configuration files
|
||||
log_info "Copying configuration files..."
|
||||
cp "${PROJECT_ROOT}/config/airport_bounds.json" "${DEPLOY_DIR}/config/"
|
||||
cp "${PROJECT_ROOT}/config/intersections.json" "${DEPLOY_DIR}/config/"
|
||||
cp "${PROJECT_ROOT}/config/system_config.json" "${DEPLOY_DIR}/config/"
|
||||
cp "${PROJECT_ROOT}/config/vehicle_control.json" "${DEPLOY_DIR}/config/"
|
||||
|
||||
# Copy tools
|
||||
log_info "Copying tools..."
|
||||
cp "${PROJECT_ROOT}/tools/mock_server.py" "${DEPLOY_DIR}/tools/"
|
||||
cp "${PROJECT_ROOT}/tools/map_websocket.html" "${DEPLOY_DIR}/tools/"
|
||||
cp "${PROJECT_ROOT}/tools/test_websocket.html" "${DEPLOY_DIR}/tools/"
|
||||
|
||||
# Copy deployment scripts
|
||||
log_info "Copying deployment scripts..."
|
||||
cp "${PROJECT_ROOT}/scripts/deploy.sh" "${DEPLOY_DIR}/scripts/"
|
||||
cp "${PROJECT_ROOT}/scripts/uninstall.sh" "${DEPLOY_DIR}/scripts/"
|
||||
chmod +x "${DEPLOY_DIR}/scripts/"*.sh
|
||||
|
||||
# Create deployment archive
|
||||
log_info "Creating deployment archive..."
|
||||
cd "${PROJECT_ROOT}"
|
||||
tar czf "${PROJECT_NAME}_deploy.tar.gz" deploy/
|
||||
|
||||
log_info "Deployment package created: ${PROJECT_NAME}_deploy.tar.gz"
|
||||
echo "Package contents:"
|
||||
echo " - Executable binary"
|
||||
echo " - Required runtime libraries"
|
||||
echo " - Python3 RPM packages"
|
||||
echo " - Python dependencies"
|
||||
echo " - Configuration files"
|
||||
echo " - Tools"
|
||||
echo " - Deployment scripts"
|
||||
56
scripts/uninstall.sh
Normal file
56
scripts/uninstall.sh
Normal file
@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Color definitions for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_error "Please run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stop and disable service
|
||||
log_info "Stopping service..."
|
||||
systemctl stop collision-avoidance
|
||||
systemctl disable collision-avoidance
|
||||
|
||||
# Remove service file
|
||||
log_info "Removing service file..."
|
||||
rm -f /etc/systemd/system/collision-avoidance.service
|
||||
systemctl daemon-reload
|
||||
|
||||
# Remove files
|
||||
log_info "Removing files..."
|
||||
rm -rf /opt/collision_avoidance
|
||||
|
||||
# Remove Python packages
|
||||
log_info "Removing Python packages..."
|
||||
pip3 uninstall -y flask werkzeug click itsdangerous Jinja2 MarkupSafe
|
||||
|
||||
# Remove firewall rules
|
||||
log_info "Removing firewall rules..."
|
||||
if command -v firewall-cmd &> /dev/null; then
|
||||
firewall-cmd --permanent --remove-port=8010/tcp
|
||||
firewall-cmd --permanent --remove-port=8081/tcp
|
||||
firewall-cmd --reload
|
||||
else
|
||||
log_warn "firewalld not detected, please remove firewall rules manually"
|
||||
fi
|
||||
|
||||
log_info "Uninstallation completed"
|
||||
471
src/collector/DataCollector.cpp
Normal file
471
src/collector/DataCollector.cpp
Normal file
@ -0,0 +1,471 @@
|
||||
#include "collector/DataCollector.h"
|
||||
#include "network/HTTPDataSource.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
DataCollector::DataCollector(const AirportBounds& bounds)
|
||||
: last_position_fetch_(std::chrono::steady_clock::now()),
|
||||
last_unmanned_fetch_(std::chrono::steady_clock::now()),
|
||||
last_traffic_light_fetch_(std::chrono::steady_clock::now()),
|
||||
last_warning_time_(std::chrono::steady_clock::now()),
|
||||
airportBounds_(bounds) {}
|
||||
|
||||
DataCollector::~DataCollector() {
|
||||
stop();
|
||||
}
|
||||
|
||||
bool DataCollector::initialize(const DataSourceConfig& dataSourceConfig,
|
||||
const WarnConfig& warnConfig) {
|
||||
if (!dataSource_) {
|
||||
dataSourceConfig_ = dataSourceConfig;
|
||||
dataSource_ = std::make_shared<HTTPDataSource>(dataSourceConfig_);
|
||||
warnConfig_ = warnConfig;
|
||||
last_position_fetch_ = std::chrono::steady_clock::now();
|
||||
last_unmanned_fetch_ = std::chrono::steady_clock::now();
|
||||
last_traffic_light_fetch_ = std::chrono::steady_clock::now();
|
||||
last_warning_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataCollector::start() {
|
||||
if (running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dataSource_) {
|
||||
Logger::error("No data source available");
|
||||
return;
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
last_position_fetch_ = std::chrono::steady_clock::now();
|
||||
last_unmanned_fetch_ = std::chrono::steady_clock::now();
|
||||
last_traffic_light_fetch_ = std::chrono::steady_clock::now();
|
||||
|
||||
// 尝试连接,但即使失败也继续运行
|
||||
if (!dataSource_->connect()) {
|
||||
Logger::error("Failed to connect to data source");
|
||||
// 立即检查超时,这样如果一开始就连不上,会马上发出告警
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
// 启动三个独立的采集线程
|
||||
positionThread_ = std::thread(&DataCollector::positionLoop, this);
|
||||
unmannedThread_ = std::thread(&DataCollector::unmannedLoop, this);
|
||||
trafficLightThread_ = std::thread(&DataCollector::trafficLightLoop, this);
|
||||
}
|
||||
|
||||
void DataCollector::stop() {
|
||||
if (!running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
running_ = false;
|
||||
|
||||
// 等待所有线程结束
|
||||
if (positionThread_.joinable()) {
|
||||
positionThread_.join();
|
||||
}
|
||||
if (unmannedThread_.joinable()) {
|
||||
unmannedThread_.join();
|
||||
}
|
||||
if (trafficLightThread_.joinable()) {
|
||||
trafficLightThread_.join();
|
||||
}
|
||||
|
||||
if (dataSource_) {
|
||||
dataSource_->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void DataCollector::positionLoop() {
|
||||
Logger::debug("位置数据采集循环启动,刷新间隔: ",
|
||||
dataSourceConfig_.position.refresh_interval_ms, "ms");
|
||||
auto next_collection_time = std::chrono::steady_clock::now();
|
||||
|
||||
while (running_) {
|
||||
bool success = fetchPositionData();
|
||||
if (!success) {
|
||||
Logger::warning("位置数据采集失败");
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
// 计算下一次采集时间
|
||||
next_collection_time += std::chrono::milliseconds(
|
||||
dataSourceConfig_.position.refresh_interval_ms);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_collection_time > now) {
|
||||
auto wait_time =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
next_collection_time - now)
|
||||
.count();
|
||||
if (wait_time > 0) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(wait_time));
|
||||
}
|
||||
} else {
|
||||
// 如果已经超过了下一次采集时间,立即重置
|
||||
next_collection_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataCollector::unmannedLoop() {
|
||||
Logger::debug("无人车状态采集循环启动,刷新间隔: ",
|
||||
dataSourceConfig_.vehicle.refresh_interval_ms, "ms");
|
||||
auto next_collection_time = std::chrono::steady_clock::now();
|
||||
|
||||
while (running_) {
|
||||
bool success = fetchUnmannedVehicleData();
|
||||
if (!success) {
|
||||
Logger::warning("无人车状态采集失败");
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
// 计算下一次采集时间
|
||||
next_collection_time += std::chrono::milliseconds(
|
||||
dataSourceConfig_.vehicle.refresh_interval_ms);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_collection_time > now) {
|
||||
auto wait_time =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
next_collection_time - now)
|
||||
.count();
|
||||
if (wait_time > 0) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(wait_time));
|
||||
}
|
||||
} else {
|
||||
// 如果已经超过了下一次采集时间,立即重置
|
||||
next_collection_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataCollector::trafficLightLoop() {
|
||||
Logger::debug("红绿灯数据采集循环启动,刷新间隔: ",
|
||||
dataSourceConfig_.traffic_light.refresh_interval_ms, "ms");
|
||||
auto next_collection_time = std::chrono::steady_clock::now();
|
||||
|
||||
while (running_) {
|
||||
bool success = fetchTrafficLightData();
|
||||
if (!success) {
|
||||
Logger::warning("红绿灯数据采集失败");
|
||||
checkTimeout();
|
||||
}
|
||||
|
||||
// 计算下一次采集时间
|
||||
next_collection_time += std::chrono::milliseconds(
|
||||
dataSourceConfig_.traffic_light.refresh_interval_ms);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_collection_time > now) {
|
||||
auto wait_time =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
next_collection_time - now)
|
||||
.count();
|
||||
if (wait_time > 0) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(wait_time));
|
||||
}
|
||||
} else {
|
||||
// 如果已经超过了下一次采集时间,立即重置
|
||||
next_collection_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataCollector::checkTimeout() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// 检查位置数据超时
|
||||
auto position_elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_position_fetch_)
|
||||
.count();
|
||||
if (position_elapsed > dataSourceConfig_.position.timeout_ms) {
|
||||
sendTimeoutWarning("position", position_elapsed);
|
||||
}
|
||||
|
||||
// 检查无人车数据超时
|
||||
auto unmanned_elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_unmanned_fetch_)
|
||||
.count();
|
||||
if (unmanned_elapsed > dataSourceConfig_.vehicle.timeout_ms) {
|
||||
sendTimeoutWarning("unmanned", unmanned_elapsed);
|
||||
}
|
||||
|
||||
// 检查红绿灯数据超时
|
||||
auto traffic_light_elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_traffic_light_fetch_)
|
||||
.count();
|
||||
if (traffic_light_elapsed > dataSourceConfig_.traffic_light.timeout_ms) {
|
||||
sendTimeoutWarning("traffic_light", traffic_light_elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void DataCollector::resetTimeout(const std::string& source) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (source == "position") {
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_position_fetch_)
|
||||
.count();
|
||||
if (elapsed > dataSourceConfig_.position.timeout_ms) {
|
||||
Logger::info("Position data source connection restored after ",
|
||||
elapsed, "ms timeout");
|
||||
}
|
||||
last_position_fetch_ = now;
|
||||
} else if (source == "unmanned") {
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_unmanned_fetch_)
|
||||
.count();
|
||||
if (elapsed > dataSourceConfig_.vehicle.timeout_ms) {
|
||||
Logger::info(
|
||||
"Unmanned vehicle data source connection restored after ",
|
||||
elapsed, "ms timeout");
|
||||
}
|
||||
last_unmanned_fetch_ = now;
|
||||
} else if (source == "traffic_light") {
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_traffic_light_fetch_)
|
||||
.count();
|
||||
if (elapsed > dataSourceConfig_.traffic_light.timeout_ms) {
|
||||
Logger::info("Traffic light data source connection restored after ",
|
||||
elapsed, "ms timeout");
|
||||
}
|
||||
last_traffic_light_fetch_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void DataCollector::sendTimeoutWarning(const std::string& source,
|
||||
int64_t elapsed_ms) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto warning_elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_warning_time_)
|
||||
.count();
|
||||
|
||||
if (warning_elapsed >= warnConfig_.warning_interval_ms) {
|
||||
if (!system_) {
|
||||
Logger::debug("System not set, skipping timeout warning");
|
||||
return;
|
||||
}
|
||||
|
||||
network::TimeoutWarningMessage msg;
|
||||
msg.source = source;
|
||||
msg.elapsed_ms = elapsed_ms;
|
||||
|
||||
// 根据数据源类型设置超时阈值
|
||||
int64_t timeout_ms = 0;
|
||||
if (source == "position") {
|
||||
timeout_ms = dataSourceConfig_.position.timeout_ms;
|
||||
} else if (source == "unmanned") {
|
||||
timeout_ms = dataSourceConfig_.vehicle.timeout_ms;
|
||||
} else if (source == "traffic_light") {
|
||||
timeout_ms = dataSourceConfig_.traffic_light.timeout_ms;
|
||||
}
|
||||
|
||||
msg.is_read_timeout = (elapsed_ms > timeout_ms);
|
||||
|
||||
system_->broadcastTimeoutWarning(msg);
|
||||
last_warning_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void DataCollector::filterPositionData(std::vector<Aircraft>& aircraft,
|
||||
std::vector<Vehicle>& vehicles) {
|
||||
size_t originalAircraftCount = aircraft.size();
|
||||
size_t originalVehicleCount = vehicles.size();
|
||||
|
||||
// 过滤掉不在机场区域内的航空器数据
|
||||
auto aircraftIt = std::remove_if(
|
||||
aircraft.begin(), aircraft.end(), [this](const Aircraft& a) {
|
||||
return !airportBounds_.isPointInBounds(a.position);
|
||||
});
|
||||
aircraft.erase(aircraftIt, aircraft.end());
|
||||
|
||||
// 过滤掉不在机场区域内的车辆数据
|
||||
auto vehicleIt = std::remove_if(
|
||||
vehicles.begin(), vehicles.end(), [this](const Vehicle& v) {
|
||||
return !airportBounds_.isPointInBounds(v.position);
|
||||
});
|
||||
vehicles.erase(vehicleIt, vehicles.end());
|
||||
|
||||
// 记录过滤结果
|
||||
size_t filteredAircraftCount = originalAircraftCount - aircraft.size();
|
||||
size_t filteredVehicleCount = originalVehicleCount - vehicles.size();
|
||||
|
||||
if (filteredAircraftCount > 0 || filteredVehicleCount > 0) {
|
||||
Logger::info("数据过滤结果: 过滤掉 ", filteredAircraftCount,
|
||||
" 架航空器, ", filteredVehicleCount, " 辆车辆");
|
||||
}
|
||||
}
|
||||
|
||||
bool DataCollector::fetchPositionData() {
|
||||
std::vector<Aircraft> newAircraft;
|
||||
std::vector<Vehicle> newVehicles;
|
||||
|
||||
if (!dataSource_) {
|
||||
Logger::error("No data source available");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = dataSource_->fetchPositionAircraftData(newAircraft) &&
|
||||
dataSource_->fetchPositionVehicleData(newVehicles);
|
||||
|
||||
if (success) {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
|
||||
// 更新运动信息
|
||||
for (auto& a : newAircraft) {
|
||||
auto it = lastAircraftPositions_.find(a.id);
|
||||
if (it != lastAircraftPositions_.end()) {
|
||||
// 复制历史记录
|
||||
a.copyHistoryFrom(it->second);
|
||||
}
|
||||
// 更新运动信息
|
||||
a.updateMotion(a.geo, a.timestamp);
|
||||
lastAircraftPositions_[a.id] = a;
|
||||
// 更新时间戳
|
||||
lastAircraftTimestamp_ = a.timestamp;
|
||||
}
|
||||
|
||||
// 更新运动信息
|
||||
for (auto& v : newVehicles) {
|
||||
auto it = lastVehiclePositions_.find(v.id);
|
||||
if (it != lastVehiclePositions_.end()) {
|
||||
// 复制历史记录
|
||||
v.copyHistoryFrom(it->second);
|
||||
}
|
||||
// 更新运动信息
|
||||
v.updateMotion(v.geo, v.timestamp);
|
||||
lastVehiclePositions_[v.id] = v;
|
||||
// 更新时间戳
|
||||
lastVehicleTimestamp_ = v.timestamp;
|
||||
}
|
||||
|
||||
// 转换坐标系
|
||||
for (auto& a : newAircraft) {
|
||||
// 1. 先转换为世界坐标
|
||||
a.position = CoordinateConverter::instance().toLocalXY(
|
||||
a.geo.latitude, a.geo.longitude);
|
||||
// 2. 再转换为机场坐标
|
||||
a.position = airportBounds_.toAirportCoordinate(a.position);
|
||||
}
|
||||
|
||||
for (auto& v : newVehicles) {
|
||||
// 1. 先转换为世界坐标
|
||||
v.position = CoordinateConverter::instance().toLocalXY(
|
||||
v.geo.latitude, v.geo.longitude);
|
||||
// 2. 再转换为机场坐标
|
||||
v.position = airportBounds_.toAirportCoordinate(v.position);
|
||||
}
|
||||
|
||||
// 过滤数据
|
||||
filterPositionData(newAircraft, newVehicles);
|
||||
|
||||
aircraftCache_ = std::move(newAircraft);
|
||||
vehicleCache_ = std::move(newVehicles);
|
||||
resetTimeout("position");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DataCollector::fetchUnmannedVehicleData() {
|
||||
if (!dataSource_) {
|
||||
Logger::error("数据源未初始化");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool any_success = false;
|
||||
bool any_failure = false;
|
||||
auto& vehicles = ControllableVehicles::getInstance().getVehicles();
|
||||
|
||||
for (const auto& vehicle : vehicles) {
|
||||
if (vehicle.type == "UNMANNED") {
|
||||
std::string status;
|
||||
if (dataSource_->fetchUnmannedVehicleStatus(vehicle.vehicleNo,
|
||||
status)) {
|
||||
any_success = true;
|
||||
} else {
|
||||
any_failure = true;
|
||||
Logger::error("获取无人车状态失败, vehicleNo: " +
|
||||
vehicle.vehicleNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (any_success) {
|
||||
resetTimeout("unmanned");
|
||||
// 如果有部分成功,就返回 true
|
||||
return true;
|
||||
}
|
||||
|
||||
// 只有全部失败才返回 false
|
||||
return !any_failure;
|
||||
}
|
||||
|
||||
bool DataCollector::fetchTrafficLightData() {
|
||||
std::vector<TrafficLightSignal> newSignals;
|
||||
|
||||
if (!dataSource_) {
|
||||
Logger::error("No data source available");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = dataSource_->fetchTrafficLightSignals(newSignals);
|
||||
|
||||
if (success) {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
trafficLightCache_ = std::move(newSignals);
|
||||
// 更新时间戳
|
||||
if (!trafficLightCache_.empty()) {
|
||||
lastTrafficLightTimestamp_ = trafficLightCache_[0].timestamp;
|
||||
}
|
||||
resetTimeout("traffic_light");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
std::vector<TrafficLightSignal> DataCollector::getTrafficLightSignals() {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
return trafficLightCache_;
|
||||
}
|
||||
|
||||
bool DataCollector::fetchTrafficLightSignals(
|
||||
std::vector<TrafficLightSignal>& signals) {
|
||||
if (!dataSource_) {
|
||||
return false;
|
||||
}
|
||||
return dataSource_->fetchTrafficLightSignals(signals);
|
||||
}
|
||||
|
||||
std::vector<Aircraft> DataCollector::getAircraftData() {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
return aircraftCache_;
|
||||
}
|
||||
|
||||
std::vector<Vehicle> DataCollector::getVehicleData() {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
return vehicleCache_;
|
||||
}
|
||||
|
||||
bool DataCollector::fetchUnmannedVehicleStatus(const std::string& vehicle_id,
|
||||
std::string& status) {
|
||||
if (!dataSource_) {
|
||||
Logger::error("No data source available");
|
||||
return false;
|
||||
}
|
||||
return dataSource_->fetchUnmannedVehicleStatus(vehicle_id, status);
|
||||
}
|
||||
117
src/collector/DataCollector.h
Normal file
117
src/collector/DataCollector.h
Normal file
@ -0,0 +1,117 @@
|
||||
#ifndef AIRPORT_COLLECTOR_DATA_COLLECTOR_H
|
||||
#define AIRPORT_COLLECTOR_DATA_COLLECTOR_H
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include "DataSource.h"
|
||||
#include "DataSourceConfig.h"
|
||||
#include "types/BasicTypes.h"
|
||||
#include "config/WarnConfig.h"
|
||||
#include "types/TrafficLightTypes.h"
|
||||
#include "vehicle/ControllableVehicles.h"
|
||||
#include "config/AirportBounds.h"
|
||||
|
||||
// 前向声明
|
||||
class System;
|
||||
|
||||
class DataCollector {
|
||||
public:
|
||||
DataCollector(const AirportBounds& bounds);
|
||||
~DataCollector();
|
||||
|
||||
bool initialize(const DataSourceConfig& dataSourceConfig, const WarnConfig& warnConfig);
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
// 获取最新数据的接口
|
||||
std::tuple<std::vector<Aircraft>, std::vector<Vehicle>, std::vector<TrafficLightSignal>>
|
||||
getLatestData() const {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
return std::make_tuple(aircraftCache_, vehicleCache_, trafficLightCache_);
|
||||
}
|
||||
|
||||
// 获取数据的最后更新时间
|
||||
std::tuple<int64_t, int64_t, int64_t> getLastUpdateTimestamps() const {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
return {lastAircraftTimestamp_, lastVehicleTimestamp_, lastTrafficLightTimestamp_};
|
||||
}
|
||||
|
||||
// 获取单个数据的接口
|
||||
std::vector<Aircraft> getAircraftData();
|
||||
std::vector<Vehicle> getVehicleData();
|
||||
std::vector<TrafficLightSignal> getTrafficLightSignals();
|
||||
bool fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals);
|
||||
bool fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status);
|
||||
bool fetchPositionData(); // 获取位置数据
|
||||
|
||||
void setSystem(std::shared_ptr<System> system) { system_ = system; }
|
||||
void setDataSource(std::shared_ptr<DataSource> source) { dataSource_ = source; }
|
||||
|
||||
// 获取数据的引用访问接口
|
||||
const std::vector<Aircraft>& getAircraftRef() const {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
return aircraftCache_;
|
||||
}
|
||||
|
||||
const std::vector<Vehicle>& getVehicleRef() const {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex_);
|
||||
return vehicleCache_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<DataSource> dataSource_;
|
||||
std::shared_ptr<System> system_;
|
||||
DataSourceConfig dataSourceConfig_;
|
||||
WarnConfig warnConfig_;
|
||||
const AirportBounds& airportBounds_;
|
||||
|
||||
// 三个独立的采集线程
|
||||
std::thread positionThread_; // 位置数据采集线程
|
||||
std::thread unmannedThread_; // 无人车状态采集线程
|
||||
std::thread trafficLightThread_; // 红绿灯数据采集线程
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<uint64_t> loopCount_{0};
|
||||
|
||||
// 缓存当前数据
|
||||
mutable std::mutex cacheMutex_;
|
||||
std::vector<Aircraft> aircraftCache_;
|
||||
std::vector<Vehicle> vehicleCache_;
|
||||
std::vector<TrafficLightSignal> trafficLightCache_;
|
||||
|
||||
// 保存上一次位置信息(用于计算速度)
|
||||
std::map<std::string, Aircraft> lastAircraftPositions_;
|
||||
std::map<std::string, Vehicle> lastVehiclePositions_;
|
||||
|
||||
// 数据时间戳
|
||||
std::atomic<int64_t> lastAircraftTimestamp_{0};
|
||||
std::atomic<int64_t> lastVehicleTimestamp_{0};
|
||||
std::atomic<int64_t> lastTrafficLightTimestamp_{0};
|
||||
|
||||
// 超时检测相关
|
||||
std::chrono::steady_clock::time_point last_position_fetch_; // 位置数据最后获取时间
|
||||
std::chrono::steady_clock::time_point last_unmanned_fetch_; // 无人车状态最后获取时间
|
||||
std::chrono::steady_clock::time_point last_traffic_light_fetch_; // 红绿灯数据最后获取时间
|
||||
std::chrono::steady_clock::time_point last_warning_time_;
|
||||
std::chrono::steady_clock::time_point last_log_time_;
|
||||
|
||||
void checkTimeout();
|
||||
void resetTimeout(const std::string& source);
|
||||
void sendTimeoutWarning(const std::string& source, int64_t elapsed_ms);
|
||||
|
||||
// 三个独立的采集循环
|
||||
void positionLoop(); // 位置数据采集循环
|
||||
void unmannedLoop(); // 无人车状态采集循环
|
||||
void trafficLightLoop(); // 红绿灯数据采集循环
|
||||
|
||||
bool fetchUnmannedVehicleData(); // 获取无人车状态数据
|
||||
bool fetchTrafficLightData(); // 获取红绿灯数据
|
||||
|
||||
// 数据过滤方法
|
||||
void filterPositionData(std::vector<Aircraft>& aircraft, std::vector<Vehicle>& vehicles);
|
||||
};
|
||||
|
||||
#endif // AIRPORT_COLLECTOR_DATA_COLLECTOR_H
|
||||
38
src/collector/DataSource.h
Normal file
38
src/collector/DataSource.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef AIRPORT_COLLECTOR_DATA_SOURCE_H
|
||||
#define AIRPORT_COLLECTOR_DATA_SOURCE_H
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
#include "types/TrafficLightTypes.h"
|
||||
#include "types/VehicleCommand.h"
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class DataSource {
|
||||
public:
|
||||
// 数据源类型
|
||||
enum class DataSourceType {
|
||||
POSITION, // 位置数据接口
|
||||
UNMANNED, // 无人车接口
|
||||
TRAFFIC_LIGHT // 红绿灯接口
|
||||
};
|
||||
|
||||
virtual ~DataSource() = default;
|
||||
|
||||
virtual bool connect() = 0;
|
||||
virtual void disconnect() = 0;
|
||||
virtual bool isAvailable() const = 0;
|
||||
|
||||
// 位置数据接口
|
||||
virtual bool fetchPositionAircraftData(std::vector<Aircraft>& aircraft) = 0;
|
||||
virtual bool fetchPositionVehicleData(std::vector<Vehicle>& vehicles) = 0;
|
||||
|
||||
// 无人车接口
|
||||
virtual bool sendUnmannedVehicleCommand(const std::string& vehicle_id, const VehicleCommand& command) = 0;
|
||||
virtual bool fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status) = 0;
|
||||
|
||||
// 红绿灯接口
|
||||
virtual bool fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals) = 0;
|
||||
};
|
||||
|
||||
#endif // AIRPORT_COLLECTOR_DATA_SOURCE_H
|
||||
72
src/collector/DataSourceConfig.cpp
Normal file
72
src/collector/DataSourceConfig.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "DataSourceConfig.h"
|
||||
#include "config/SystemConfig.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
DataSourceConfig DataSourceConfig::fromSystemConfig(const SystemConfig& config) {
|
||||
DataSourceConfig dataSourceConfig;
|
||||
|
||||
// 转换位置数据源配置
|
||||
dataSourceConfig.position.host = config.data_source.position.host;
|
||||
dataSourceConfig.position.port = config.data_source.position.port;
|
||||
dataSourceConfig.position.aircraft_path =
|
||||
config.data_source.position.aircraft_path;
|
||||
dataSourceConfig.position.vehicle_path =
|
||||
config.data_source.position.vehicle_path;
|
||||
dataSourceConfig.position.refresh_interval_ms =
|
||||
config.data_source.position.refresh_interval_ms;
|
||||
dataSourceConfig.position.timeout_ms =
|
||||
config.data_source.position.timeout_ms;
|
||||
dataSourceConfig.position.read_timeout_ms =
|
||||
config.data_source.position.read_timeout_ms;
|
||||
dataSourceConfig.position.auth.username =
|
||||
config.data_source.position.auth.username;
|
||||
dataSourceConfig.position.auth.password =
|
||||
config.data_source.position.auth.password;
|
||||
dataSourceConfig.position.auth.auth_path =
|
||||
config.data_source.position.auth.auth_path;
|
||||
dataSourceConfig.position.auth.auth_required =
|
||||
config.data_source.position.auth.auth_required;
|
||||
|
||||
// 转换无人车数据源配置
|
||||
dataSourceConfig.vehicle.host = config.data_source.vehicle.host;
|
||||
dataSourceConfig.vehicle.port = config.data_source.vehicle.port;
|
||||
dataSourceConfig.vehicle.status_path =
|
||||
config.data_source.vehicle.status_path;
|
||||
dataSourceConfig.vehicle.command_path =
|
||||
config.data_source.vehicle.command_path;
|
||||
dataSourceConfig.vehicle.refresh_interval_ms =
|
||||
config.data_source.vehicle.refresh_interval_ms;
|
||||
dataSourceConfig.vehicle.timeout_ms = config.data_source.vehicle.timeout_ms;
|
||||
dataSourceConfig.vehicle.read_timeout_ms =
|
||||
config.data_source.vehicle.read_timeout_ms;
|
||||
dataSourceConfig.vehicle.auth.username =
|
||||
config.data_source.vehicle.auth.username;
|
||||
dataSourceConfig.vehicle.auth.password =
|
||||
config.data_source.vehicle.auth.password;
|
||||
dataSourceConfig.vehicle.auth.auth_path =
|
||||
config.data_source.vehicle.auth.auth_path;
|
||||
dataSourceConfig.vehicle.auth.auth_required =
|
||||
config.data_source.vehicle.auth.auth_required;
|
||||
|
||||
// 转换红绿灯数据源配置
|
||||
dataSourceConfig.traffic_light.host = config.data_source.traffic_light.host;
|
||||
dataSourceConfig.traffic_light.port = config.data_source.traffic_light.port;
|
||||
dataSourceConfig.traffic_light.signal_path =
|
||||
config.data_source.traffic_light.signal_path;
|
||||
dataSourceConfig.traffic_light.refresh_interval_ms =
|
||||
config.data_source.traffic_light.refresh_interval_ms;
|
||||
dataSourceConfig.traffic_light.timeout_ms =
|
||||
config.data_source.traffic_light.timeout_ms;
|
||||
dataSourceConfig.traffic_light.read_timeout_ms =
|
||||
config.data_source.traffic_light.read_timeout_ms;
|
||||
dataSourceConfig.traffic_light.auth.username =
|
||||
config.data_source.traffic_light.auth.username;
|
||||
dataSourceConfig.traffic_light.auth.password =
|
||||
config.data_source.traffic_light.auth.password;
|
||||
dataSourceConfig.traffic_light.auth.auth_path =
|
||||
config.data_source.traffic_light.auth.auth_path;
|
||||
dataSourceConfig.traffic_light.auth.auth_required =
|
||||
config.data_source.traffic_light.auth.auth_required;
|
||||
|
||||
return dataSourceConfig;
|
||||
}
|
||||
171
src/collector/DataSourceConfig.h
Normal file
171
src/collector/DataSourceConfig.h
Normal file
@ -0,0 +1,171 @@
|
||||
#ifndef AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H
|
||||
#define AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// 前向声明
|
||||
class SystemConfig;
|
||||
|
||||
// 认证配置
|
||||
struct AuthConfig {
|
||||
std::string username; // 认证用户名
|
||||
std::string password; // 认证密码
|
||||
std::string auth_path; // 认证接口路径
|
||||
bool auth_required = false; // 是否需要认证
|
||||
};
|
||||
|
||||
// AuthConfig 的 JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const AuthConfig& a) {
|
||||
j = json{
|
||||
{"username", a.username},
|
||||
{"password", a.password},
|
||||
{"auth_path", a.auth_path},
|
||||
{"auth_required", a.auth_required}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, AuthConfig& a) {
|
||||
j.at("username").get_to(a.username);
|
||||
j.at("password").get_to(a.password);
|
||||
j.at("auth_path").get_to(a.auth_path);
|
||||
j.at("auth_required").get_to(a.auth_required);
|
||||
}
|
||||
|
||||
// 位置数据源配置
|
||||
struct PositionDataConfig {
|
||||
std::string host; // 服务器地址
|
||||
int port; // 服务器端口
|
||||
std::string aircraft_path; // 航空器位置接口
|
||||
std::string vehicle_path; // 车辆位置接口
|
||||
int refresh_interval_ms; // 位置数据刷新间隔
|
||||
int timeout_ms; // 连接超时时间
|
||||
int read_timeout_ms; // 读取超时时间
|
||||
AuthConfig auth; // 认证配置
|
||||
};
|
||||
|
||||
// PositionDataConfig 的 JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const PositionDataConfig& p) {
|
||||
j = json{
|
||||
{"host", p.host},
|
||||
{"port", p.port},
|
||||
{"aircraft_path", p.aircraft_path},
|
||||
{"vehicle_path", p.vehicle_path},
|
||||
{"refresh_interval_ms", p.refresh_interval_ms},
|
||||
{"timeout_ms", p.timeout_ms},
|
||||
{"read_timeout_ms", p.read_timeout_ms},
|
||||
{"auth", p.auth}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, PositionDataConfig& p) {
|
||||
j.at("host").get_to(p.host);
|
||||
j.at("port").get_to(p.port);
|
||||
j.at("aircraft_path").get_to(p.aircraft_path);
|
||||
j.at("vehicle_path").get_to(p.vehicle_path);
|
||||
j.at("refresh_interval_ms").get_to(p.refresh_interval_ms);
|
||||
j.at("timeout_ms").get_to(p.timeout_ms);
|
||||
j.at("read_timeout_ms").get_to(p.read_timeout_ms);
|
||||
j.at("auth").get_to(p.auth);
|
||||
}
|
||||
|
||||
// 无人车数据源配置
|
||||
struct UnmannedVehicleConfig {
|
||||
std::string host; // 服务器地址
|
||||
int port; // 服务器端口
|
||||
std::string status_path; // 无人车状态接口
|
||||
std::string command_path; // 无人车指令接口
|
||||
int refresh_interval_ms; // 状态数据刷新间隔
|
||||
int timeout_ms; // 连接超时时间
|
||||
int read_timeout_ms; // 读取超时时间
|
||||
AuthConfig auth; // 认证配置
|
||||
};
|
||||
|
||||
// UnmannedVehicleConfig 的 JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const UnmannedVehicleConfig& u) {
|
||||
j = json{
|
||||
{"host", u.host},
|
||||
{"port", u.port},
|
||||
{"status_path", u.status_path},
|
||||
{"command_path", u.command_path},
|
||||
{"refresh_interval_ms", u.refresh_interval_ms},
|
||||
{"timeout_ms", u.timeout_ms},
|
||||
{"read_timeout_ms", u.read_timeout_ms},
|
||||
{"auth", u.auth}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, UnmannedVehicleConfig& u) {
|
||||
j.at("host").get_to(u.host);
|
||||
j.at("port").get_to(u.port);
|
||||
j.at("status_path").get_to(u.status_path);
|
||||
j.at("command_path").get_to(u.command_path);
|
||||
j.at("refresh_interval_ms").get_to(u.refresh_interval_ms);
|
||||
j.at("timeout_ms").get_to(u.timeout_ms);
|
||||
j.at("read_timeout_ms").get_to(u.read_timeout_ms);
|
||||
j.at("auth").get_to(u.auth);
|
||||
}
|
||||
|
||||
// 红绿灯数据源配置
|
||||
struct TrafficLightConfig {
|
||||
std::string host; // 服务器地址
|
||||
int port; // 服务器端口
|
||||
std::string signal_path; // 红绿灯信号接口
|
||||
int refresh_interval_ms; // 信号数据刷新间隔
|
||||
int timeout_ms; // 连接超时时间
|
||||
int read_timeout_ms; // 读取超时时间
|
||||
AuthConfig auth; // 认证配置
|
||||
};
|
||||
|
||||
// TrafficLightConfig 的 JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const TrafficLightConfig& t) {
|
||||
j = json{
|
||||
{"host", t.host},
|
||||
{"port", t.port},
|
||||
{"signal_path", t.signal_path},
|
||||
{"refresh_interval_ms", t.refresh_interval_ms},
|
||||
{"timeout_ms", t.timeout_ms},
|
||||
{"read_timeout_ms", t.read_timeout_ms},
|
||||
{"auth", t.auth}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, TrafficLightConfig& t) {
|
||||
j.at("host").get_to(t.host);
|
||||
j.at("port").get_to(t.port);
|
||||
j.at("signal_path").get_to(t.signal_path);
|
||||
j.at("refresh_interval_ms").get_to(t.refresh_interval_ms);
|
||||
j.at("timeout_ms").get_to(t.timeout_ms);
|
||||
j.at("read_timeout_ms").get_to(t.read_timeout_ms);
|
||||
j.at("auth").get_to(t.auth);
|
||||
}
|
||||
|
||||
// 数据源配置
|
||||
struct DataSourceConfig {
|
||||
PositionDataConfig position; // 位置数据源配置
|
||||
UnmannedVehicleConfig vehicle; // 无人车数据源配置
|
||||
TrafficLightConfig traffic_light; // 红绿灯数据源配置
|
||||
|
||||
// 从系统配置创建数据源配置的静态工厂方法
|
||||
static DataSourceConfig fromSystemConfig(const SystemConfig& system_config);
|
||||
};
|
||||
|
||||
// DataSourceConfig 的 JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const DataSourceConfig& d) {
|
||||
j = json{
|
||||
{"position", d.position},
|
||||
{"unmanned_vehicle", d.vehicle},
|
||||
{"traffic_light", d.traffic_light}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, DataSourceConfig& d) {
|
||||
j.at("position").get_to(d.position);
|
||||
j.at("unmanned_vehicle").get_to(d.vehicle);
|
||||
j.at("traffic_light").get_to(d.traffic_light);
|
||||
}
|
||||
|
||||
#endif
|
||||
179
src/config/AirportBounds.cpp
Normal file
179
src/config/AirportBounds.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include "AirportBounds.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
AirportBounds::AirportBounds(const std::string& configFile) {
|
||||
// 如果配置文件路径为空,不加载配置
|
||||
if (!configFile.empty()) {
|
||||
loadConfig(configFile);
|
||||
}
|
||||
}
|
||||
|
||||
void AirportBounds::loadConfig(const std::string& configFile) {
|
||||
// 检查文件是否存在
|
||||
if (!std::filesystem::exists(configFile)) {
|
||||
Logger::error("配置文件不存在: ", configFile);
|
||||
Logger::error("当前工作目录: ",
|
||||
std::filesystem::current_path().string());
|
||||
throw std::runtime_error("配置文件不存在: " + configFile);
|
||||
}
|
||||
|
||||
std::ifstream file(configFile);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("无法打开配置文件: " + configFile);
|
||||
}
|
||||
|
||||
try {
|
||||
json j;
|
||||
file >> j;
|
||||
|
||||
// 检查必要的字段是否存在
|
||||
if (!j.contains("airport") || !j["airport"].contains("bounds")) {
|
||||
throw std::runtime_error("配置文件缺少 airport.bounds 字段");
|
||||
}
|
||||
|
||||
// 加载机场旋转角度(度)和参考点
|
||||
auto& airport = j["airport"];
|
||||
if (airport.contains("rotation_angle")) {
|
||||
// 将角度转换为弧度
|
||||
rotationAngle_ =
|
||||
airport["rotation_angle"].get<double>() * M_PI / 180.0;
|
||||
}
|
||||
|
||||
if (airport.contains("reference_point")) {
|
||||
referencePoint_.x = airport["reference_point"]["x"].get<double>();
|
||||
referencePoint_.y = airport["reference_point"]["y"].get<double>();
|
||||
}
|
||||
|
||||
// 加载机场边界
|
||||
auto& airportBounds = j["airport"]["bounds"];
|
||||
airportBounds_ = { airportBounds["x"].get<double>(),
|
||||
airportBounds["y"].get<double>(),
|
||||
airportBounds["width"].get<double>(),
|
||||
airportBounds["height"].get<double>() };
|
||||
|
||||
// 检查 areas 字段
|
||||
if (!j.contains("areas")) {
|
||||
throw std::runtime_error("配置文件缺少 areas 字段");
|
||||
}
|
||||
|
||||
// 加载各区域配置
|
||||
for (const auto& [key, value] : j["areas"].items()) {
|
||||
AreaType type;
|
||||
if (key == "runway")
|
||||
type = AreaType::RUNWAY;
|
||||
else if (key == "taxiway")
|
||||
type = AreaType::TAXIWAY;
|
||||
else if (key == "gate")
|
||||
type = AreaType::GATE;
|
||||
else if (key == "service")
|
||||
type = AreaType::SERVICE;
|
||||
else if (key == "test_zone")
|
||||
type = AreaType::TEST_ZONE;
|
||||
else
|
||||
continue;
|
||||
|
||||
// 检查必要的字段
|
||||
if (!value.contains("bounds") || !value.contains("config")) {
|
||||
throw std::runtime_error("区域 " + key + " 缺少必要的字段");
|
||||
}
|
||||
|
||||
// 加载区域边界
|
||||
auto& bounds = value["bounds"];
|
||||
areaBounds_[type] = {
|
||||
bounds["x"].get<double>(), bounds["y"].get<double>(),
|
||||
bounds["width"].get<double>(), bounds["height"].get<double>() };
|
||||
|
||||
// 加载区域配置
|
||||
auto& config = value["config"];
|
||||
auto& collision_radius = config["collision_radius"];
|
||||
auto& warning_zone_radius = config["warning_zone_radius"];
|
||||
auto& alert_zone_radius = config["alert_zone_radius"];
|
||||
|
||||
RadiusConfig collision_config = {
|
||||
collision_radius["aircraft"].get<double>(),
|
||||
collision_radius["special"].get<double>(),
|
||||
collision_radius["unmanned"].get<double>() };
|
||||
|
||||
RadiusConfig warning_config = {
|
||||
warning_zone_radius["aircraft"].get<double>(),
|
||||
warning_zone_radius["special"].get<double>(),
|
||||
warning_zone_radius["unmanned"].get<double>() };
|
||||
|
||||
RadiusConfig alert_config = {
|
||||
alert_zone_radius["aircraft"].get<double>(),
|
||||
alert_zone_radius["special"].get<double>(),
|
||||
alert_zone_radius["unmanned"].get<double>() };
|
||||
|
||||
areaConfigs_[type] = { collision_config,
|
||||
config["height_threshold"].get<double>(),
|
||||
warning_config, alert_config };
|
||||
}
|
||||
} catch (const json::exception& e) {
|
||||
Logger::error("JSON解析错误: ", e.what());
|
||||
throw;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("加载配置文件失败: ", e.what());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2D AirportBounds::toAirportCoordinate(const Vector2D& point) const {
|
||||
// 1. 计算相对于参考点的偏移
|
||||
double dx = point.x - referencePoint_.x;
|
||||
double dy = point.y - referencePoint_.y;
|
||||
|
||||
// 2. 应用旋转变换
|
||||
double cos_angle = std::cos(rotationAngle_);
|
||||
double sin_angle = std::sin(rotationAngle_);
|
||||
|
||||
// 3. 返回机场坐标系中的坐标(逆时针旋转)
|
||||
Vector2D result{ dx * cos_angle - dy * sin_angle,
|
||||
dx * sin_angle + dy * cos_angle };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AreaType AirportBounds::getAreaType(const Vector2D& position) const {
|
||||
|
||||
// 按优先级检查位置所在区域
|
||||
for (const auto& [type, bounds] : areaBounds_) {
|
||||
if (bounds.contains(position)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认返回测试区
|
||||
return AreaType::TEST_ZONE;
|
||||
}
|
||||
|
||||
const AreaConfig& AirportBounds::getAreaConfig(AreaType type) const {
|
||||
auto it = areaConfigs_.find(type);
|
||||
if (it == areaConfigs_.end()) {
|
||||
throw std::runtime_error("Invalid area type");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool AirportBounds::isPointInBounds(const Vector2D& position) const {
|
||||
// 在机场坐标系中判断是否在边界内
|
||||
bool result = airportBounds_.contains(position);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AirportBounds::isPointInArea(const Vector2D& position,
|
||||
AreaType areaType) const {
|
||||
// 获取区域定义
|
||||
auto it = areaBounds_.find(areaType);
|
||||
if (it == areaBounds_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 在机场坐标系中判断是否在区域内
|
||||
return it->second.contains(position);
|
||||
}
|
||||
58
src/config/AirportBounds.h
Normal file
58
src/config/AirportBounds.h
Normal file
@ -0,0 +1,58 @@
|
||||
#ifndef AIRPORT_BOUNDS_H
|
||||
#define AIRPORT_BOUNDS_H
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include "types/BasicTypes.h"
|
||||
#include "spatial/QuadTree.h"
|
||||
#include "AreaConfig.h"
|
||||
#include <unordered_map>
|
||||
|
||||
// 机场区域定义
|
||||
class AirportBounds {
|
||||
public:
|
||||
explicit AirportBounds(const std::string& configFile = "");
|
||||
virtual ~AirportBounds() = default;
|
||||
|
||||
// 获取点所在的区域类型
|
||||
virtual AreaType getAreaType(const Vector2D& position) const;
|
||||
|
||||
// 获取区域配置
|
||||
virtual const AreaConfig& getAreaConfig(AreaType type) const;
|
||||
|
||||
// 获取整个机场边界
|
||||
virtual const Bounds& getAirportBounds() const { return airportBounds_; }
|
||||
|
||||
// 获取特定区域的边界
|
||||
virtual const Bounds& getAreaBounds(AreaType type) const {
|
||||
auto it = areaBounds_.find(type);
|
||||
if (it == areaBounds_.end()) {
|
||||
throw std::runtime_error("Invalid area type");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// 判断点是否在机场边界内
|
||||
virtual bool isPointInBounds(const Vector2D& worldPoint) const;
|
||||
|
||||
// 判断点是否在指定区域内(跑道、滑行道等)
|
||||
virtual bool isPointInArea(const Vector2D& worldPoint, AreaType areaType) const;
|
||||
|
||||
// 将点从世界坐标系转换到机场坐标系
|
||||
// 输入:世界坐标系中的点 (x, y)
|
||||
// 输出:机场坐标系中的点,原点为机场参考点,方向为机场旋转后的方向
|
||||
Vector2D toAirportCoordinate(const Vector2D& point) const;
|
||||
|
||||
protected:
|
||||
Bounds airportBounds_; // 整个机场边界
|
||||
std::unordered_map<AreaType, Bounds> areaBounds_; // 各区域边界
|
||||
std::unordered_map<AreaType, AreaConfig> areaConfigs_; // 各区域配置
|
||||
|
||||
// 从配置文件加载数据
|
||||
virtual void loadConfig(const std::string& configFile);
|
||||
|
||||
double rotationAngle_ = 0.0; // 机场与正北方向的夹角(弧度)
|
||||
Vector2D referencePoint_; // 机场参考点(旋转中心)
|
||||
};
|
||||
|
||||
#endif // AIRPORT_BOUNDS_H
|
||||
53
src/config/AreaConfig.h
Normal file
53
src/config/AreaConfig.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef AIRPORT_CONFIG_AREA_CONFIG_H
|
||||
#define AIRPORT_CONFIG_AREA_CONFIG_H
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
|
||||
// 区域类型
|
||||
enum class AreaType {
|
||||
RUNWAY, // 跑道
|
||||
TAXIWAY, // 滑行道
|
||||
GATE, // 停机位
|
||||
SERVICE, // 服务区
|
||||
TEST_ZONE // 测试区
|
||||
};
|
||||
|
||||
// 半径配置
|
||||
struct RadiusConfig {
|
||||
double aircraft; // 航空器半径
|
||||
double special; // 特勤车半径
|
||||
double unmanned; // 无人车半径
|
||||
|
||||
double getRadius(MovingObjectType type) const {
|
||||
switch (type) {
|
||||
case MovingObjectType::AIRCRAFT: return aircraft;
|
||||
case MovingObjectType::SPECIAL: return special;
|
||||
case MovingObjectType::UNMANNED: return unmanned;
|
||||
}
|
||||
return special; // 默认返回特勤车半径
|
||||
}
|
||||
};
|
||||
|
||||
// 区域配置
|
||||
struct AreaConfig {
|
||||
RadiusConfig collision_radius; // 碰撞半径
|
||||
double height_threshold; // 高度阈值
|
||||
RadiusConfig warning_zone_radius; // 预警区域半径
|
||||
RadiusConfig alert_zone_radius; // 告警区域半径
|
||||
|
||||
// 获取碰撞阈值
|
||||
struct CollisionThresholds {
|
||||
double warning; // 预警距离
|
||||
double critical; // 告警距离
|
||||
};
|
||||
|
||||
// 根据物体类型获取对应的阈值
|
||||
CollisionThresholds getThresholds(MovingObjectType type1, MovingObjectType type2) const {
|
||||
return {
|
||||
warning_zone_radius.getRadius(type1) + warning_zone_radius.getRadius(type2),
|
||||
alert_zone_radius.getRadius(type1) + alert_zone_radius.getRadius(type2)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
#endif // AIRPORT_CONFIG_AREA_CONFIG_H
|
||||
78
src/config/IntersectionConfig.cpp
Normal file
78
src/config/IntersectionConfig.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "config/IntersectionConfig.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
IntersectionConfig IntersectionConfig::load(const std::string& configFile) {
|
||||
IntersectionConfig config;
|
||||
|
||||
std::ifstream file(configFile);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open intersection config file: " + configFile);
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json jsonConfig;
|
||||
file >> jsonConfig;
|
||||
|
||||
for (const auto& item : jsonConfig["intersections"]) {
|
||||
Intersection info;
|
||||
info.id = item["id"].get<std::string>();
|
||||
info.name = item["name"].get<std::string>();
|
||||
info.trafficLightId = item["trafficLightId"].get<std::string>();
|
||||
|
||||
// 加载位置信息,检查是否为 null
|
||||
if (item["position"]["longitude"].is_null() ||
|
||||
item["position"]["latitude"].is_null() ||
|
||||
item["position"]["altitude"].is_null()) {
|
||||
throw std::runtime_error("Position values cannot be null for intersection: " + info.id);
|
||||
}
|
||||
info.position.longitude = item["position"]["longitude"].get<double>();
|
||||
info.position.latitude = item["position"]["latitude"].get<double>();
|
||||
info.position.altitude = item["position"]["altitude"].get<double>();
|
||||
|
||||
// 加载路口宽度和安全区配置,检查是否为 null
|
||||
if (item["width"].is_null() ||
|
||||
item["safetyZone"]["aircraftRadius"].is_null() ||
|
||||
item["safetyZone"]["vehicleRadius"].is_null()) {
|
||||
throw std::runtime_error("Width or safety zone values cannot be null for intersection: " + info.id);
|
||||
}
|
||||
info.width = item["width"].get<double>();
|
||||
info.safetyZone.aircraftRadius = item["safetyZone"]["aircraftRadius"].get<double>();
|
||||
info.safetyZone.vehicleRadius = item["safetyZone"]["vehicleRadius"].get<double>();
|
||||
|
||||
config.intersections_.push_back(info);
|
||||
|
||||
Logger::debug("Loaded intersection: id=", info.id,
|
||||
", name=", info.name,
|
||||
", trafficLightId=", info.trafficLightId,
|
||||
", width=", info.width,
|
||||
", aircraftRadius=", info.safetyZone.aircraftRadius,
|
||||
", vehicleRadius=", info.safetyZone.vehicleRadius);
|
||||
}
|
||||
|
||||
Logger::info("Loaded ", config.intersections_.size(), " intersections");
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error("Failed to parse intersection config: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
const Intersection* IntersectionConfig::findByTrafficLightId(const std::string& trafficLightId) const {
|
||||
auto iter = std::find_if(intersections_.begin(), intersections_.end(),
|
||||
[&](const Intersection& info) {
|
||||
return info.trafficLightId == trafficLightId;
|
||||
});
|
||||
return iter != intersections_.end() ? &(*iter) : nullptr;
|
||||
}
|
||||
|
||||
const Intersection* IntersectionConfig::findById(const std::string& intersectionId) const {
|
||||
auto iter = std::find_if(intersections_.begin(), intersections_.end(),
|
||||
[&](const Intersection& info) {
|
||||
return info.id == intersectionId;
|
||||
});
|
||||
return iter != intersections_.end() ? &(*iter) : nullptr;
|
||||
}
|
||||
43
src/config/IntersectionConfig.h
Normal file
43
src/config/IntersectionConfig.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "types/BasicTypes.h"
|
||||
|
||||
struct IntersectionPosition {
|
||||
double longitude;
|
||||
double latitude;
|
||||
double altitude;
|
||||
};
|
||||
|
||||
// 路口安全区配置
|
||||
struct SafetyZoneConfig {
|
||||
double aircraftRadius; // 航空器安全区半径
|
||||
double vehicleRadius; // 特勤车安全区半径
|
||||
};
|
||||
|
||||
struct Intersection {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string trafficLightId;
|
||||
IntersectionPosition position;
|
||||
double width; // 路口宽度,单位:米
|
||||
SafetyZoneConfig safetyZone; // 安全区配置
|
||||
};
|
||||
|
||||
class IntersectionConfig {
|
||||
public:
|
||||
static IntersectionConfig load(const std::string& configFile);
|
||||
|
||||
// 根据红绿灯ID查找路口
|
||||
const Intersection* findByTrafficLightId(const std::string& trafficLightId) const;
|
||||
|
||||
// 根据路口ID查找路口
|
||||
const Intersection* findById(const std::string& intersectionId) const;
|
||||
|
||||
// 获取所有路口配置
|
||||
const std::vector<Intersection>& getIntersections() const { return intersections_; }
|
||||
|
||||
private:
|
||||
std::vector<Intersection> intersections_;
|
||||
};
|
||||
21
src/config/SystemConfig.cpp
Normal file
21
src/config/SystemConfig.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "SystemConfig.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <fstream>
|
||||
|
||||
void SystemConfig::load(const std::string& filename) {
|
||||
try {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open config file: " + filename);
|
||||
}
|
||||
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
nlohmann::from_json(j, *this);
|
||||
|
||||
Logger::info("Loaded system configuration from ", filename);
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error("Failed to load config: " +
|
||||
std::string(e.what()));
|
||||
}
|
||||
}
|
||||
293
src/config/SystemConfig.h
Normal file
293
src/config/SystemConfig.h
Normal file
@ -0,0 +1,293 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "collector/DataSourceConfig.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
class System; // 前向声明
|
||||
|
||||
// 基础结构体
|
||||
struct CoordinatePoint {
|
||||
std::string point;
|
||||
double latitude;
|
||||
double longitude;
|
||||
};
|
||||
|
||||
// JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const CoordinatePoint& p) {
|
||||
j = json{
|
||||
{"point", p.point},
|
||||
{"latitude", p.latitude},
|
||||
{"longitude", p.longitude}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, CoordinatePoint& p) {
|
||||
j.at("point").get_to(p.point);
|
||||
j.at("latitude").get_to(p.latitude);
|
||||
j.at("longitude").get_to(p.longitude);
|
||||
}
|
||||
|
||||
// SystemConfig 类定义
|
||||
class SystemConfig {
|
||||
public:
|
||||
struct Airport {
|
||||
struct ReferencePoint {
|
||||
double latitude;
|
||||
double longitude;
|
||||
} reference_point;
|
||||
|
||||
std::string name;
|
||||
std::string iata;
|
||||
std::string icao;
|
||||
std::vector<CoordinatePoint> coordinate_points;
|
||||
} airport;
|
||||
|
||||
DataSourceConfig data_source; // 使用 DataSourceConfig 替换原有的 DataSource 结构
|
||||
|
||||
struct WebSocket {
|
||||
struct PositionUpdate {
|
||||
int aircraft_interval_ms;
|
||||
int vehicle_interval_ms;
|
||||
int traffic_light_interval_ms;
|
||||
} position_update;
|
||||
|
||||
uint16_t port;
|
||||
int max_connections;
|
||||
int ping_interval_ms;
|
||||
} websocket;
|
||||
|
||||
struct CollisionDetection {
|
||||
struct Prediction {
|
||||
double time_window;
|
||||
double vehicle_size;
|
||||
double aircraft_size;
|
||||
double min_unmanned_speed;
|
||||
} prediction;
|
||||
|
||||
int update_interval_ms;
|
||||
} collision_detection;
|
||||
|
||||
struct Logging {
|
||||
std::string level;
|
||||
std::string file;
|
||||
int max_size_mb;
|
||||
int max_files;
|
||||
bool console_output;
|
||||
} logging;
|
||||
|
||||
struct Debug {
|
||||
bool enable_mock_data;
|
||||
bool save_raw_data;
|
||||
bool profile_performance;
|
||||
} debug;
|
||||
|
||||
struct Warning {
|
||||
int warning_interval_ms;
|
||||
int log_interval_ms;
|
||||
} warning;
|
||||
|
||||
// 新增: 红绿灯 HTTP 服务器配置
|
||||
struct TrafficLightServerConfig {
|
||||
uint16_t port = 8082; // Default port if not specified in config
|
||||
int max_connections = 100;
|
||||
// Can add other relevant configs like timeout etc.
|
||||
} traffic_light_server;
|
||||
|
||||
// 新增: 模拟的移动红绿灯目标路口 ID
|
||||
std::string simulated_mobile_light_target_intersection_id = "T2路口"; // Default value
|
||||
|
||||
static SystemConfig& instance() {
|
||||
static SystemConfig instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void load(const std::string& filename);
|
||||
|
||||
SystemConfig(const SystemConfig&) = delete;
|
||||
SystemConfig& operator=(const SystemConfig&) = delete;
|
||||
|
||||
friend class System;
|
||||
|
||||
private:
|
||||
SystemConfig() = default;
|
||||
};
|
||||
|
||||
// 为每个嵌套结构体定义 JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const SystemConfig::Airport::ReferencePoint& p) {
|
||||
j = json{
|
||||
{"latitude", p.latitude},
|
||||
{"longitude", p.longitude}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::Airport::ReferencePoint& p) {
|
||||
j.at("latitude").get_to(p.latitude);
|
||||
j.at("longitude").get_to(p.longitude);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::Airport& p) {
|
||||
j = json{
|
||||
{"name", p.name},
|
||||
{"iata", p.iata},
|
||||
{"icao", p.icao},
|
||||
{"reference_point", p.reference_point},
|
||||
{"coordinate_points", p.coordinate_points}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::Airport& p) {
|
||||
j.at("name").get_to(p.name);
|
||||
j.at("iata").get_to(p.iata);
|
||||
j.at("icao").get_to(p.icao);
|
||||
j.at("reference_point").get_to(p.reference_point);
|
||||
j.at("coordinate_points").get_to(p.coordinate_points);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::WebSocket::PositionUpdate& p) {
|
||||
j = json{
|
||||
{"aircraft_interval_ms", p.aircraft_interval_ms},
|
||||
{"vehicle_interval_ms", p.vehicle_interval_ms},
|
||||
{"traffic_light_interval_ms", p.traffic_light_interval_ms}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::WebSocket::PositionUpdate& p) {
|
||||
j.at("aircraft_interval_ms").get_to(p.aircraft_interval_ms);
|
||||
j.at("vehicle_interval_ms").get_to(p.vehicle_interval_ms);
|
||||
j.at("traffic_light_interval_ms").get_to(p.traffic_light_interval_ms);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::WebSocket& p) {
|
||||
j = json{
|
||||
{"port", p.port},
|
||||
{"max_connections", p.max_connections},
|
||||
{"ping_interval_ms", p.ping_interval_ms},
|
||||
{"position_update", p.position_update}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::WebSocket& p) {
|
||||
j.at("port").get_to(p.port);
|
||||
j.at("max_connections").get_to(p.max_connections);
|
||||
j.at("ping_interval_ms").get_to(p.ping_interval_ms);
|
||||
j.at("position_update").get_to(p.position_update);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::CollisionDetection::Prediction& p) {
|
||||
j = json{
|
||||
{"time_window", p.time_window},
|
||||
{"vehicle_size", p.vehicle_size},
|
||||
{"aircraft_size", p.aircraft_size},
|
||||
{"min_unmanned_speed", p.min_unmanned_speed}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::CollisionDetection::Prediction& p) {
|
||||
j.at("time_window").get_to(p.time_window);
|
||||
j.at("vehicle_size").get_to(p.vehicle_size);
|
||||
j.at("aircraft_size").get_to(p.aircraft_size);
|
||||
j.at("min_unmanned_speed").get_to(p.min_unmanned_speed);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::CollisionDetection& p) {
|
||||
j = json{
|
||||
{"update_interval_ms", p.update_interval_ms},
|
||||
{"prediction", p.prediction}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::CollisionDetection& p) {
|
||||
j.at("update_interval_ms").get_to(p.update_interval_ms);
|
||||
j.at("prediction").get_to(p.prediction);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::Logging& p) {
|
||||
j = json{
|
||||
{"level", p.level},
|
||||
{"file", p.file},
|
||||
{"max_size_mb", p.max_size_mb},
|
||||
{"max_files", p.max_files},
|
||||
{"console_output", p.console_output}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::Logging& p) {
|
||||
j.at("level").get_to(p.level);
|
||||
j.at("file").get_to(p.file);
|
||||
j.at("max_size_mb").get_to(p.max_size_mb);
|
||||
j.at("max_files").get_to(p.max_files);
|
||||
j.at("console_output").get_to(p.console_output);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::Debug& p) {
|
||||
j = json{
|
||||
{"enable_mock_data", p.enable_mock_data},
|
||||
{"save_raw_data", p.save_raw_data},
|
||||
{"profile_performance", p.profile_performance}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::Debug& p) {
|
||||
j.at("enable_mock_data").get_to(p.enable_mock_data);
|
||||
j.at("save_raw_data").get_to(p.save_raw_data);
|
||||
j.at("profile_performance").get_to(p.profile_performance);
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig::Warning& p) {
|
||||
j = json{
|
||||
{"warning_interval_ms", p.warning_interval_ms},
|
||||
{"log_interval_ms", p.log_interval_ms}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::Warning& p) {
|
||||
j.at("warning_interval_ms").get_to(p.warning_interval_ms);
|
||||
j.at("log_interval_ms").get_to(p.log_interval_ms);
|
||||
}
|
||||
|
||||
// 新增: TrafficLightServerConfig 的 JSON 序列化/反序列化函数
|
||||
inline void to_json(json& j, const SystemConfig::TrafficLightServerConfig& p) {
|
||||
j = json{
|
||||
{"port", p.port},
|
||||
{"max_connections", p.max_connections}
|
||||
// Serialize other fields if added
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig::TrafficLightServerConfig& p) {
|
||||
// Use .value() to provide default values if keys are missing
|
||||
p.port = j.value("port", static_cast<uint16_t>(8082));
|
||||
p.max_connections = j.value("max_connections", 100);
|
||||
// Deserialize other fields if added
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const SystemConfig& config) {
|
||||
j = json{
|
||||
{"airport", config.airport},
|
||||
{"data_source", config.data_source},
|
||||
{"websocket", config.websocket},
|
||||
{"collision_detection", config.collision_detection},
|
||||
{"logging", config.logging},
|
||||
{"debug", config.debug},
|
||||
{"warning", config.warning},
|
||||
{"traffic_light_server", config.traffic_light_server},
|
||||
{"simulated_mobile_light_target_intersection_id", config.simulated_mobile_light_target_intersection_id}
|
||||
};
|
||||
}
|
||||
|
||||
inline void from_json(const json& j, SystemConfig& config) {
|
||||
j.at("airport").get_to(config.airport);
|
||||
j.at("data_source").get_to(config.data_source);
|
||||
j.at("websocket").get_to(config.websocket);
|
||||
j.at("collision_detection").get_to(config.collision_detection);
|
||||
j.at("logging").get_to(config.logging);
|
||||
j.at("debug").get_to(config.debug);
|
||||
j.at("warning").get_to(config.warning);
|
||||
config.traffic_light_server = j.value("traffic_light_server", SystemConfig::TrafficLightServerConfig{});
|
||||
config.simulated_mobile_light_target_intersection_id = j.value("simulated_mobile_light_target_intersection_id", std::string("T2路口"));
|
||||
}
|
||||
|
||||
11
src/config/WarnConfig.h
Normal file
11
src/config/WarnConfig.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef AIRPORT_NETWORK_WARN_CONFIG_H
|
||||
#define AIRPORT_NETWORK_WARN_CONFIG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
struct WarnConfig {
|
||||
int warning_interval_ms;
|
||||
int log_interval_ms;
|
||||
};
|
||||
|
||||
#endif // AIRPORT_NETWORK_WARN_CONFIG_H
|
||||
856
src/core/System.cpp
Normal file
856
src/core/System.cpp
Normal file
@ -0,0 +1,856 @@
|
||||
#include <csignal>
|
||||
#include <typeinfo>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include "System.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "collector/DataCollector.h"
|
||||
#include "spatial/CoordinateConverter.h"
|
||||
#include "network/TrafficLightHttpServer.h"
|
||||
#include "config/SystemConfig.h"
|
||||
#include "types/TrafficLightTypes.h"
|
||||
#include "network/WebSocketServer.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
System* System::instance_ = nullptr;
|
||||
|
||||
System::System()
|
||||
: controllableVehicles_(ControllableVehicles::getInstance()) {
|
||||
instance_ = this;
|
||||
std::signal(SIGINT, signalHandler);
|
||||
std::signal(SIGTERM, signalHandler);
|
||||
|
||||
// 使用单例模式获取配置实例
|
||||
auto& config = SystemConfig::instance();
|
||||
}
|
||||
|
||||
System::~System() {
|
||||
stop();
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
void System::signalHandler(int signal) {
|
||||
Logger::info("Received signal: ", signal);
|
||||
if (instance_) {
|
||||
instance_->stop();
|
||||
}
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
bool System::initialize() {
|
||||
try {
|
||||
// 配置已在 main 中加载,这里直接使用
|
||||
const auto& system_config = SystemConfig::instance();
|
||||
|
||||
// 初始化全局坐标转换器
|
||||
CoordinateConverter::instance().setReferencePoint(
|
||||
system_config.airport.reference_point.latitude,
|
||||
system_config.airport.reference_point.longitude);
|
||||
|
||||
// 加载路口配置
|
||||
intersection_config_ = IntersectionConfig::load("config/intersections.json");
|
||||
Logger::info("Loaded {} intersections", intersection_config_.getIntersections().size());
|
||||
|
||||
// 初始化 WebSocket 服务器
|
||||
ws_server_ = std::make_unique<network::WebSocketServer>(system_config.websocket.port);
|
||||
ws_thread_ = std::thread([this]() { ws_server_->start(); });
|
||||
Logger::info("WebSocket server initialized on port ", system_config.websocket.port);
|
||||
|
||||
// 新增: 初始化并启动红绿灯 HTTP 服务器
|
||||
try {
|
||||
traffic_light_http_server_ = std::make_unique<network::TrafficLightHttpServer>(
|
||||
system_config.traffic_light_server.port,
|
||||
system_config.traffic_light_server.max_connections,
|
||||
".",
|
||||
*this
|
||||
);
|
||||
// Start server in its own thread (start itself handles creating internal threads for io_context)
|
||||
traffic_light_http_server_thread_ = std::thread([this]() {
|
||||
try {
|
||||
traffic_light_http_server_->start(2); // Use 2 threads for IO context
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("TrafficLightHttpServer thread failed to start: ", e.what());
|
||||
}
|
||||
});
|
||||
Logger::info("Traffic light HTTP server initialized on port ", system_config.traffic_light_server.port);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to initialize Traffic Light HTTP Server: ", e.what());
|
||||
// Decide if this failure is critical
|
||||
return false; // Example: treat as critical
|
||||
}
|
||||
|
||||
// 加载机场区域配置
|
||||
airportBounds_ = std::make_unique<AirportBounds>("config/airport_bounds.json");
|
||||
|
||||
// 初始化冲突检测器
|
||||
collisionDetector_ = std::make_unique<CollisionDetector>(*airportBounds_, controllableVehicles_);
|
||||
|
||||
// 初始化红绿灯检测器
|
||||
trafficLightDetector_ = std::make_unique<TrafficLightDetector>(intersection_config_, controllableVehicles_, *this);
|
||||
|
||||
// 初始化安全区
|
||||
initializeSafetyZones();
|
||||
|
||||
// 加载告警配置
|
||||
WarnConfig warnConfig{
|
||||
system_config.warning.warning_interval_ms,
|
||||
system_config.warning.log_interval_ms };
|
||||
|
||||
// 创建数据采集器并初始化
|
||||
dataCollector_ = std::make_unique<DataCollector>(*airportBounds_);
|
||||
return dataCollector_->initialize(system_config.data_source, warnConfig);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to initialize system: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void System::initializeSafetyZones() {
|
||||
// 清空现有的安全区
|
||||
safetyZones_.clear();
|
||||
|
||||
// 获取所有路口配置
|
||||
const auto& intersections = intersection_config_.getIntersections();
|
||||
|
||||
// 为每个路口创建安全区
|
||||
for (const auto& intersection : intersections) {
|
||||
// 获取路口中心点的地理坐标
|
||||
Vector2D center = CoordinateConverter::instance().toLocalXY(
|
||||
intersection.position.latitude,
|
||||
intersection.position.longitude);
|
||||
|
||||
// 创建安全区
|
||||
auto safetyZone = std::make_unique<SafetyZone>(
|
||||
center,
|
||||
intersection.safetyZone.aircraftRadius, // 飞机安全区半径
|
||||
intersection.safetyZone.vehicleRadius // 特勤车安全区半径
|
||||
);
|
||||
|
||||
Logger::debug("创建路口安全区: id=", intersection.id,
|
||||
", center=(", center.x, ",", center.y, ")",
|
||||
", aircraftRadius=", intersection.safetyZone.aircraftRadius,
|
||||
", vehicleRadius=", intersection.safetyZone.vehicleRadius);
|
||||
|
||||
// 保存安全区
|
||||
safetyZones_[intersection.id] = std::move(safetyZone);
|
||||
}
|
||||
}
|
||||
|
||||
void System::start() {
|
||||
if (running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
dataCollector_->start();
|
||||
processThread_ = std::thread(&System::processLoop, this);
|
||||
Logger::info("System processing loop started");
|
||||
}
|
||||
|
||||
void System::stop() {
|
||||
Logger::info("Stopping system...");
|
||||
running_ = false;
|
||||
|
||||
// Stop data collector first
|
||||
if (dataCollector_) {
|
||||
dataCollector_->stop();
|
||||
}
|
||||
|
||||
// Stop WebSocket server
|
||||
if (ws_server_) {
|
||||
ws_server_->stop();
|
||||
}
|
||||
if (ws_thread_.joinable()) {
|
||||
ws_thread_.join();
|
||||
}
|
||||
|
||||
// 新增: Stop Traffic Light HTTP Server
|
||||
if (traffic_light_http_server_) {
|
||||
traffic_light_http_server_->stop();
|
||||
}
|
||||
if (traffic_light_http_server_thread_.joinable()) {
|
||||
traffic_light_http_server_thread_.join();
|
||||
}
|
||||
|
||||
// Stop processing loop
|
||||
if (processThread_.joinable()) {
|
||||
processThread_.join();
|
||||
}
|
||||
|
||||
Logger::info("System fully stopped.");
|
||||
}
|
||||
|
||||
void System::processLoop() {
|
||||
while (running_) {
|
||||
try {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// 获取最新数据和时间戳
|
||||
std::tie(latest_aircraft_, latest_vehicles_, latest_traffic_lights_) = dataCollector_->getLatestData();
|
||||
auto [aircraft_ts, vehicle_ts, traffic_light_ts] = dataCollector_->getLastUpdateTimestamps();
|
||||
|
||||
// 检查数据更新
|
||||
bool has_new_data = false;
|
||||
|
||||
if (aircraft_ts > last_aircraft_timestamp) {
|
||||
has_new_data = true;
|
||||
last_aircraft_timestamp = aircraft_ts;
|
||||
Logger::debug("航空器数据已更新,新时间戳: ", aircraft_ts);
|
||||
}
|
||||
|
||||
if (vehicle_ts > last_vehicle_timestamp) {
|
||||
has_new_data = true;
|
||||
last_vehicle_timestamp = vehicle_ts;
|
||||
Logger::debug("车辆数据已更新,新时间戳: ", vehicle_ts);
|
||||
}
|
||||
|
||||
if (traffic_light_ts > last_traffic_light_timestamp) {
|
||||
has_new_data = true;
|
||||
last_traffic_light_timestamp = traffic_light_ts;
|
||||
Logger::debug("红绿灯数据已更新,新时间戳: ", traffic_light_ts);
|
||||
}
|
||||
|
||||
if (has_new_data) {
|
||||
// 使用成员变量的引用构建 objects 向量
|
||||
std::vector<MovingObject*> objects;
|
||||
for (auto& ac : latest_aircraft_) {
|
||||
objects.push_back(&ac);
|
||||
}
|
||||
for (auto& veh : latest_vehicles_) {
|
||||
const auto* config = controllableVehicles_.findVehicle(veh.vehicleNo);
|
||||
if (config) {
|
||||
veh.type = config->type == "UNMANNED" ? MovingObjectType::UNMANNED : MovingObjectType::SPECIAL;
|
||||
veh.isControllable = config->type == "UNMANNED";
|
||||
}
|
||||
objects.push_back(&veh);
|
||||
}
|
||||
|
||||
// 创建当前周期的风险管理器
|
||||
std::unordered_map<std::string, RiskLevel> vehicleMaxRiskLevels; // 统一记录所有车辆的最高风险等级
|
||||
std::vector<CollisionRisk> detectedRisks; // 统一收集所有检测到的风险
|
||||
|
||||
// 检查安全区更新
|
||||
auto safety_zone_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_safety_zone_update)
|
||||
.count();
|
||||
|
||||
if (safety_zone_elapsed >= SystemConfig::instance().collision_detection.update_interval_ms) {
|
||||
updateSafetyZoneStates(objects);
|
||||
Logger::debug("开始检查安全区冲突...");
|
||||
|
||||
// 记录安全区风险检测前的风险数量
|
||||
size_t beforeSafetyZone = detectedRisks.size();
|
||||
checkUnmannedVehicleSafetyZones(latest_vehicles_, objects, vehicleMaxRiskLevels, detectedRisks);
|
||||
Logger::debug("安全区检测新增风险数量: ", detectedRisks.size() - beforeSafetyZone);
|
||||
|
||||
last_safety_zone_update = now;
|
||||
}
|
||||
|
||||
// 检查和处理常规碰撞风险
|
||||
collisionDetector_->updateTraffic(latest_aircraft_, latest_vehicles_);
|
||||
auto collisionRisks = collisionDetector_->detectCollisions();
|
||||
|
||||
Logger::debug("碰撞检测器检测到风险数量: ", collisionRisks.size());
|
||||
for (const auto& risk : collisionRisks) {
|
||||
Logger::debug("碰撞风险: id1=", risk.id1,
|
||||
", id2=", risk.id2,
|
||||
", level=", static_cast<int>(risk.level),
|
||||
", distance=", risk.distance);
|
||||
}
|
||||
|
||||
// 合并风险
|
||||
size_t beforeMerge = detectedRisks.size();
|
||||
detectedRisks.insert(detectedRisks.end(), collisionRisks.begin(), collisionRisks.end());
|
||||
Logger::debug("合并后新增风险数量: ", detectedRisks.size() - beforeMerge);
|
||||
|
||||
// 统一处理所有风险
|
||||
processCollisions(detectedRisks, vehicleMaxRiskLevels);
|
||||
|
||||
// 广播位置更新
|
||||
for (const auto& ac : latest_aircraft_) {
|
||||
broadcastPositionUpdate(ac);
|
||||
}
|
||||
for (const auto& veh : latest_vehicles_) {
|
||||
broadcastPositionUpdate(veh);
|
||||
}
|
||||
|
||||
// 处理红绿灯信号
|
||||
for (const auto& signal : latest_traffic_lights_) {
|
||||
broadcastTrafficLightStatus(signal);
|
||||
trafficLightDetector_->processSignal(signal, latest_vehicles_);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待下一次处理
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(
|
||||
SystemConfig::instance().collision_detection.update_interval_ms));
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("处理循环发生错误: ", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::checkUnmannedVehicleSafetyZones(
|
||||
const std::vector<Vehicle>& vehicles,
|
||||
const std::vector<MovingObject*>& objects,
|
||||
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels,
|
||||
std::vector<CollisionRisk>& detectedRisks) {
|
||||
|
||||
// 遍历所有无人车
|
||||
for (const auto& vehicle : vehicles) {
|
||||
// 遍历所有路口安全区
|
||||
for (const auto& [intersectionId, zone] : safetyZones_) {
|
||||
if (zone->getState() == SafetyZoneState::INACTIVE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算无人车到安全区中心的距离
|
||||
double dx = vehicle.position.x - zone->getCenter().x;
|
||||
double dy = vehicle.position.y - zone->getCenter().y;
|
||||
double distance = std::sqrt(dx * dx + dy * dy);
|
||||
double zoneRadius = zone->getCurrentRadius();
|
||||
|
||||
RiskLevel riskLevel = RiskLevel::NONE;
|
||||
if (distance <= zoneRadius) {
|
||||
riskLevel = RiskLevel::CRITICAL;
|
||||
} else if (distance <= 2 * zoneRadius) {
|
||||
riskLevel = RiskLevel::WARNING;
|
||||
}
|
||||
|
||||
if (riskLevel != RiskLevel::NONE) {
|
||||
// 查找触发安全区的目标物体
|
||||
for (const auto* obj : objects) {
|
||||
// 添加判断:跳过与自己的碰撞检测
|
||||
if (obj->id == vehicle.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((obj->isAircraft() || obj->isSpecialVehicle()) &&
|
||||
zone->isObjectInZone(*obj)) {
|
||||
// 创建风险对象
|
||||
CollisionRisk risk;
|
||||
risk.id1 = vehicle.id;
|
||||
risk.id2 = obj->id;
|
||||
risk.level = riskLevel;
|
||||
risk.distance = distance;
|
||||
risk.minDistance = distance;
|
||||
risk.relativeSpeed = std::sqrt(
|
||||
std::pow(obj->speed * std::cos(obj->heading) -
|
||||
vehicle.speed * std::cos(vehicle.heading),
|
||||
2) +
|
||||
std::pow(obj->speed * std::sin(obj->heading) -
|
||||
vehicle.speed * std::sin(vehicle.heading),
|
||||
2));
|
||||
risk.relativeMotion = {
|
||||
obj->position.x - vehicle.position.x,
|
||||
obj->position.y - vehicle.position.y };
|
||||
|
||||
detectedRisks.push_back(risk);
|
||||
|
||||
// 更新风险级别
|
||||
auto& currentRisk = vehicleMaxRiskLevels[vehicle.id];
|
||||
currentRisk = std::max(currentRisk, riskLevel);
|
||||
|
||||
Logger::debug("安全区风险: id1=", risk.id1,
|
||||
", id2=", risk.id2,
|
||||
", level=", static_cast<int>(risk.level),
|
||||
", distance=", risk.distance);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::processCollisions(
|
||||
const std::vector<CollisionRisk>& detectedRisks,
|
||||
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels) {
|
||||
|
||||
// 记录当前有风险的可控车辆
|
||||
std::unordered_set<std::string> currentVehiclesWithRisk;
|
||||
|
||||
// 修改日志输出,显示更多信息
|
||||
Logger::debug("开始处理碰撞风险,风险数量: ", detectedRisks.size());
|
||||
for (const auto& risk : detectedRisks) {
|
||||
Logger::debug("待处理风险: id1=", risk.id1,
|
||||
", id2=", risk.id2,
|
||||
", level=", static_cast<int>(risk.level),
|
||||
", distance=", risk.distance,
|
||||
", minDistance=", risk.minDistance);
|
||||
}
|
||||
|
||||
// 处理当前的碰撞风险
|
||||
for (const auto& risk : detectedRisks) {
|
||||
// 处理 id1 和 id2 中的可控车辆
|
||||
bool id1_controllable = controllableVehicles_.isControllable(risk.id1);
|
||||
bool id2_controllable = controllableVehicles_.isControllable(risk.id2);
|
||||
|
||||
auto processVehicle = [&](const std::string& vehicleId, const std::string& otherId) {
|
||||
// 发送指令
|
||||
VehicleCommand cmd;
|
||||
cmd.vehicleId = vehicleId;
|
||||
cmd.type = risk.level == RiskLevel::CRITICAL ? CommandType::ALERT : CommandType::WARNING;
|
||||
cmd.reason = otherId.substr(0, 2) == "AC" ? CommandReason::AIRCRAFT_CROSSING : CommandReason::SPECIAL_VEHICLE;
|
||||
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
// 设置目标位置
|
||||
const MovingObject* target = findVehicle(otherId);
|
||||
if (target) {
|
||||
cmd.latitude = target->geo.latitude;
|
||||
cmd.longitude = target->geo.longitude;
|
||||
cmd.relativeSpeed = risk.relativeSpeed;
|
||||
cmd.relativeMotionX = risk.relativeMotion.x;
|
||||
cmd.relativeMotionY = risk.relativeMotion.y;
|
||||
cmd.minDistance = risk.minDistance;
|
||||
}
|
||||
|
||||
Logger::debug("准备发送指令: 车辆=", vehicleId,
|
||||
", 类型=", cmd.type == CommandType::ALERT ? "ALERT" : "WARNING",
|
||||
", 原因=", cmd.reason == CommandReason::AIRCRAFT_CROSSING ? "AIRCRAFT_CROSSING" : "SPECIAL_VEHICLE",
|
||||
", 距离=", risk.distance,
|
||||
", 风险等级=", static_cast<int>(risk.level));
|
||||
|
||||
broadcastVehicleCommand(cmd);
|
||||
controllableVehicles_.sendCommand(vehicleId, cmd);
|
||||
|
||||
// 更新风险记录
|
||||
currentVehiclesWithRisk.insert(vehicleId);
|
||||
vehicleMaxRiskLevels[vehicleId] = risk.level;
|
||||
|
||||
Logger::info("碰撞风险: 车辆=", vehicleId,
|
||||
", 目标=", otherId,
|
||||
", 距离=", risk.distance, "m",
|
||||
", 最小距离=", risk.minDistance, "m",
|
||||
", 相对速度=", risk.relativeSpeed, "m/s");
|
||||
};
|
||||
|
||||
// 为每个可控车辆处理风险
|
||||
if (id1_controllable) {
|
||||
processVehicle(risk.id1, risk.id2);
|
||||
}
|
||||
if (id2_controllable) {
|
||||
processVehicle(risk.id2, risk.id1);
|
||||
}
|
||||
|
||||
// 广播碰撞预警消息
|
||||
broadcastCollisionWarning(risk);
|
||||
}
|
||||
|
||||
// 处理恢复指令
|
||||
for (const auto& vehicleId : lastVehiclesWithRisk_) {
|
||||
if (currentVehiclesWithRisk.find(vehicleId) == currentVehiclesWithRisk.end()) {
|
||||
auto riskIt = vehicleMaxRiskLevels.find(vehicleId);
|
||||
if (riskIt == vehicleMaxRiskLevels.end() || riskIt->second == RiskLevel::NONE) {
|
||||
VehicleCommand cmd;
|
||||
cmd.vehicleId = vehicleId;
|
||||
cmd.type = CommandType::RESUME;
|
||||
cmd.reason = CommandReason::RESUME_TRAFFIC;
|
||||
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
broadcastVehicleCommand(cmd);
|
||||
controllableVehicles_.sendCommand(vehicleId, cmd);
|
||||
Logger::info("发送恢复指令到车辆: ", vehicleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新风险车辆列表
|
||||
lastVehiclesWithRisk_ = std::move(currentVehiclesWithRisk);
|
||||
}
|
||||
|
||||
void System::broadcastPositionUpdate(const MovingObject& obj) {
|
||||
if (!ws_server_) {
|
||||
return;
|
||||
}
|
||||
|
||||
network::PositionUpdateMessage msg;
|
||||
msg.objectId = obj.id;
|
||||
msg.objectType = (typeid(obj) == typeid(Aircraft)) ? "aircraft" : "vehicle";
|
||||
msg.timestamp = obj.timestamp;
|
||||
msg.longitude = obj.geo.longitude;
|
||||
msg.latitude = obj.geo.latitude;
|
||||
msg.heading = obj.heading;
|
||||
msg.speed = obj.speed;
|
||||
|
||||
nlohmann::json j = {
|
||||
{"type", "position_update"},
|
||||
{"object_id", msg.objectId},
|
||||
{"object_type", msg.objectType},
|
||||
{"timestamp", msg.timestamp},
|
||||
{"position", {{"longitude", msg.longitude}, {"latitude", msg.latitude}}},
|
||||
{"heading", msg.heading},
|
||||
{"speed", msg.speed} };
|
||||
|
||||
ws_server_->broadcast(j.dump());
|
||||
Logger::debug("广播位置更新: id=", msg.objectId,
|
||||
" type=", msg.objectType,
|
||||
" pos=(", msg.longitude, ",", msg.latitude, ")",
|
||||
" heading=", msg.heading,
|
||||
" speed=", msg.speed,
|
||||
" timestamp=", msg.timestamp);
|
||||
}
|
||||
|
||||
// 新增辅助函数:将 SignalStatus 转换为字符串
|
||||
std::string signalStatusToString(SignalStatus status) {
|
||||
switch (status) {
|
||||
case SignalStatus::RED: return "RED";
|
||||
case SignalStatus::GREEN: return "GREEN";
|
||||
case SignalStatus::YELLOW: return "YELLOW";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void System::broadcastTrafficLightStatus(const TrafficLightSignal& signal) {
|
||||
if (!ws_server_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找路口信息
|
||||
const Intersection* intersection = intersection_config_.findByTrafficLightId(signal.trafficLightId);
|
||||
|
||||
nlohmann::json j = {
|
||||
{"type", "traffic_light_status"},
|
||||
{"id", signal.trafficLightId},
|
||||
// 修改: 将 status 改为一个包含 ns 和 ew 状态的对象
|
||||
{"status", {
|
||||
{"ns", signalStatusToString(signal.ns_status)},
|
||||
{"ew", signalStatusToString(signal.ew_status)}
|
||||
}},
|
||||
{"timestamp", signal.timestamp}
|
||||
};
|
||||
|
||||
// 添加路口信息
|
||||
if (intersection) {
|
||||
j["intersection"] = intersection->id;
|
||||
j["position"] = {
|
||||
{"longitude", intersection->position.longitude},
|
||||
{"latitude", intersection->position.latitude} };
|
||||
}
|
||||
|
||||
ws_server_->broadcast(j.dump());
|
||||
|
||||
// 修改日志记录
|
||||
Logger::debug("广播红绿灯状态: id=", signal.trafficLightId,
|
||||
", NS_Status=", signalStatusToString(signal.ns_status),
|
||||
", EW_Status=", signalStatusToString(signal.ew_status),
|
||||
", intersection=", intersection ? intersection->id : "",
|
||||
", timestamp=", signal.timestamp);
|
||||
}
|
||||
|
||||
std::string getRiskLevelString(RiskLevel level) {
|
||||
switch (level) {
|
||||
case RiskLevel::CRITICAL:
|
||||
return "critical";
|
||||
case RiskLevel::WARNING:
|
||||
return "warning";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
void System::broadcastCollisionWarning(const CollisionRisk& risk) {
|
||||
if (!ws_server_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造冲突预警消息
|
||||
nlohmann::json j = {
|
||||
{"type", "collision_warning"},
|
||||
{"id1", risk.id1},
|
||||
{"id2", risk.id2},
|
||||
{"distance", risk.distance},
|
||||
{"relativeSpeed", risk.relativeSpeed},
|
||||
{"warningLevel", getRiskLevelString(risk.level)},
|
||||
{"timestamp", std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count()} };
|
||||
|
||||
// 广播冲突预警消息
|
||||
ws_server_->broadcast(j.dump());
|
||||
Logger::debug("广播冲突预警: id1=", risk.id1, " id2=", risk.id2,
|
||||
" distance=", risk.distance, "m",
|
||||
" relativeSpeed=", risk.relativeSpeed, "m/s",
|
||||
" level=", getRiskLevelString(risk.level));
|
||||
}
|
||||
|
||||
void System::broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning) {
|
||||
if (ws_server_) {
|
||||
ws_server_->broadcast(warning.toJson().dump());
|
||||
|
||||
// 根据超时时间记录不同级的日志
|
||||
if (warning.elapsed_ms > 30000) { // 30秒以上
|
||||
Logger::error("Severe timeout: ", warning.source, " - ",
|
||||
warning.elapsed_ms, "ms without response");
|
||||
} else {
|
||||
Logger::warning("Connection timeout: ", warning.source, " - ",
|
||||
warning.elapsed_ms, "ms without response");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::broadcastVehicleCommand(const VehicleCommand& cmd) {
|
||||
if (!ws_server_) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json j = {
|
||||
{"type", "vehicle_command"},
|
||||
{"vehicleId", cmd.vehicleId},
|
||||
{"commandType", [&]() {
|
||||
switch (cmd.type) {
|
||||
case CommandType::SIGNAL:
|
||||
return "SIGNAL";
|
||||
case CommandType::ALERT:
|
||||
return "ALERT";
|
||||
case CommandType::WARNING:
|
||||
return "WARNING";
|
||||
case CommandType::RESUME:
|
||||
return "RESUME";
|
||||
case CommandType::PARKING:
|
||||
return "PARKING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}()},
|
||||
{"reason", [&]() {
|
||||
switch (cmd.reason) {
|
||||
case CommandReason::TRAFFIC_LIGHT:
|
||||
return "TRAFFIC_LIGHT";
|
||||
case CommandReason::AIRCRAFT_CROSSING:
|
||||
return "AIRCRAFT_CROSSING";
|
||||
case CommandReason::SPECIAL_VEHICLE:
|
||||
return "SPECIAL_VEHICLE";
|
||||
case CommandReason::AIRCRAFT_PUSH:
|
||||
return "AIRCRAFT_PUSH";
|
||||
case CommandReason::RESUME_TRAFFIC:
|
||||
return "RESUME_TRAFFIC";
|
||||
case CommandReason::PARKING_SIDE:
|
||||
return "PARKING_SIDE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}()},
|
||||
{"timestamp", cmd.timestamp} };
|
||||
|
||||
// 添加可选字段
|
||||
if (cmd.type == CommandType::SIGNAL) {
|
||||
j["signalState"] = cmd.signalState == SignalState::RED ? "RED" : "GREEN";
|
||||
j["intersectionId"] = cmd.intersectionId;
|
||||
}
|
||||
|
||||
// 添目标位置(对于所有非 RESUME 类型的指令)
|
||||
if (cmd.type != CommandType::RESUME) {
|
||||
j["targetLatitude"] = cmd.latitude;
|
||||
j["targetLongitude"] = cmd.longitude;
|
||||
}
|
||||
|
||||
ws_server_->broadcast(j.dump());
|
||||
}
|
||||
|
||||
const MovingObject* System::findVehicle(const std::string& vehicleId) const {
|
||||
if (!dataCollector_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 获取数据引用(注意:DataCollector 内部会加锁)
|
||||
const auto& aircrafts = dataCollector_->getAircraftRef();
|
||||
const auto& vehicles = dataCollector_->getVehicleRef();
|
||||
|
||||
// 在航空器中查找
|
||||
for (const auto& aircraft : aircrafts) {
|
||||
if (aircraft.flightNo == vehicleId) {
|
||||
return &aircraft;
|
||||
}
|
||||
}
|
||||
|
||||
// 在车辆中查找
|
||||
for (const auto& vehicle : vehicles) {
|
||||
if (vehicle.vehicleNo == vehicleId) {
|
||||
return &vehicle;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void System::updateSafetyZoneStates(const std::vector<MovingObject*>& objects) {
|
||||
// 重置所有安全区状态为未激活,同时重置类型
|
||||
for (auto& [id, zone] : safetyZones_) {
|
||||
zone->setState(SafetyZoneState::INACTIVE);
|
||||
zone->resetType(); // 重置安全区类型
|
||||
}
|
||||
|
||||
// 检查所有移动物体
|
||||
for (const auto& obj : objects) {
|
||||
Logger::debug("检查移动物体: id=", obj->id,
|
||||
", type=", obj->type == MovingObjectType::UNMANNED ? "无人车" : (obj->type == MovingObjectType::SPECIAL ? "特勤车" : "飞机"),
|
||||
", isAircraft=", obj->isAircraft(),
|
||||
", isSpecialVehicle=", obj->isSpecialVehicle(),
|
||||
", isUnmannedVehicle=", obj->isUnmannedVehicle());
|
||||
|
||||
// 只检查飞机和特勤车
|
||||
if (obj->isAircraft() || obj->isSpecialVehicle()) {
|
||||
checkSafetyZoneIntrusion(*obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::checkSafetyZoneIntrusion(const MovingObject& obj) {
|
||||
Logger::debug("检查安全区入侵: id=", obj.id,
|
||||
", type=", obj.isAircraft() ? "飞机" : (obj.isSpecialVehicle() ? "特勤车" : "其他"),
|
||||
", position=(", obj.position.x, ",", obj.position.y, ")");
|
||||
|
||||
// 检查每个安全区
|
||||
for (auto& [id, zone] : safetyZones_) {
|
||||
// 先检查是否在安全区内
|
||||
if (zone->isObjectInZone(obj)) {
|
||||
// 如果在区内,尝试激活安全区
|
||||
if (zone->tryActivate(obj)) {
|
||||
Logger::debug("目标 ", obj.id, " 进入路口 ", id, " 安全区, 类型: ",
|
||||
zone->getType() == SafetyZoneType::AIRCRAFT ? "飞机" : "特勤车",
|
||||
", 半径: ", zone->getCurrentRadius(),
|
||||
", 中心点=(", zone->getCenter().x, ",", zone->getCenter().y, ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool System::handleSafetyZoneRisk(const Vehicle& vehicle,
|
||||
const SafetyZone* zone,
|
||||
const std::vector<MovingObject*>& objects,
|
||||
double distance,
|
||||
const std::string& intersectionId,
|
||||
CommandType cmdType,
|
||||
const std::string& riskLevel) {
|
||||
// 检查是否为无人车
|
||||
if (!controllableVehicles_.isControllable(vehicle.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VehicleCommand cmd;
|
||||
cmd.vehicleId = vehicle.id;
|
||||
cmd.type = cmdType;
|
||||
cmd.reason = zone->getType() == SafetyZoneType::AIRCRAFT ? CommandReason::AIRCRAFT_CROSSING : CommandReason::SPECIAL_VEHICLE;
|
||||
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
// 查找触发安全区的目标物体
|
||||
const MovingObject* target = nullptr;
|
||||
for (const auto& obj : objects) {
|
||||
if ((obj->isAircraft() || obj->isSpecialVehicle()) &&
|
||||
zone->isObjectInZone(*obj)) {
|
||||
target = obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到目标物体,添加相关信息
|
||||
if (target) {
|
||||
cmd.latitude = target->geo.latitude;
|
||||
cmd.longitude = target->geo.longitude;
|
||||
|
||||
// 计算相对距离
|
||||
double rel_dx = target->position.x - vehicle.position.x;
|
||||
double rel_dy = target->position.y - vehicle.position.y;
|
||||
|
||||
// 计算相对速度
|
||||
cmd.relativeSpeed = std::sqrt(
|
||||
(target->position.x - vehicle.position.x) * (target->position.x - vehicle.position.x) +
|
||||
(target->position.y - vehicle.position.y) * (target->position.y - vehicle.position.y));
|
||||
cmd.relativeMotionX = rel_dx;
|
||||
cmd.relativeMotionY = rel_dy;
|
||||
cmd.minDistance = distance;
|
||||
|
||||
Logger::debug("安全区冲突目标信息: id=", target->id,
|
||||
", 相对距离=(", rel_dx, ",", rel_dy, ")",
|
||||
", 相对速度=", cmd.relativeSpeed,
|
||||
", 最小距离=", distance);
|
||||
}
|
||||
|
||||
broadcastVehicleCommand(cmd);
|
||||
controllableVehicles_.sendCommand(vehicle.id, cmd);
|
||||
|
||||
CollisionRisk risk;
|
||||
risk.id1 = vehicle.id;
|
||||
risk.id2 = target ? target->id : "";
|
||||
risk.level = cmdType == CommandType::ALERT ? RiskLevel::CRITICAL : RiskLevel::WARNING;
|
||||
risk.distance = distance;
|
||||
risk.relativeSpeed = cmd.relativeSpeed;
|
||||
risk.relativeMotion = { cmd.relativeMotionX, cmd.relativeMotionY };
|
||||
risk.zoneType = WarningZoneType::WARNING;
|
||||
|
||||
broadcastCollisionWarning(risk);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void System::processPushedTrafficLightData(const nlohmann::json& di_data) {
|
||||
try {
|
||||
const auto& config = SystemConfig::instance();
|
||||
const std::string targetIntersectionId = config.simulated_mobile_light_target_intersection_id;
|
||||
const Intersection* targetIntersection = intersection_config_.findById(targetIntersectionId);
|
||||
|
||||
if (!targetIntersection) {
|
||||
Logger::warning("Configured target intersection ID not found in intersection config: ", targetIntersectionId);
|
||||
return;
|
||||
}
|
||||
|
||||
SignalStatus ns_status = SignalStatus::UNKNOWN;
|
||||
SignalStatus ew_status = SignalStatus::UNKNOWN;
|
||||
|
||||
if (di_data.contains("DI-13") && di_data.at("DI-13").get<int>() == 1) {
|
||||
ns_status = SignalStatus::GREEN;
|
||||
} else if (di_data.contains("DI-12") && di_data.at("DI-12").get<int>() == 1) {
|
||||
ns_status = SignalStatus::YELLOW;
|
||||
} else if (di_data.contains("DI-11") && di_data.at("DI-11").get<int>() == 1) {
|
||||
ns_status = SignalStatus::RED;
|
||||
} else {
|
||||
Logger::warning("Received DI data has unknown or invalid N/S state: ", di_data.dump());
|
||||
}
|
||||
|
||||
if (di_data.contains("DI-16") && di_data.at("DI-16").get<int>() == 1) {
|
||||
ew_status = SignalStatus::GREEN;
|
||||
} else if (di_data.contains("DI-15") && di_data.at("DI-15").get<int>() == 1) {
|
||||
ew_status = SignalStatus::YELLOW;
|
||||
} else if (di_data.contains("DI-14") && di_data.at("DI-14").get<int>() == 1) {
|
||||
ew_status = SignalStatus::RED;
|
||||
} else {
|
||||
Logger::warning("Received DI data has unknown or invalid E/W state: ", di_data.dump());
|
||||
}
|
||||
|
||||
TrafficLightSignal signal;
|
||||
signal.trafficLightId = "DI_PUSHED_" + targetIntersectionId;
|
||||
signal.ns_status = ns_status;
|
||||
signal.ew_status = ew_status;
|
||||
signal.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
if (ws_server_) {
|
||||
nlohmann::json messageJson;
|
||||
messageJson["type"] = "intersection_traffic_light_status";
|
||||
messageJson["intersection_id"] = targetIntersection->id;
|
||||
messageJson["position"] = {
|
||||
{"latitude", targetIntersection->position.latitude},
|
||||
{"longitude", targetIntersection->position.longitude}
|
||||
};
|
||||
messageJson["ns_status"] = static_cast<int>(signal.ns_status);
|
||||
messageJson["ew_status"] = static_cast<int>(signal.ew_status);
|
||||
messageJson["timestamp"] = signal.timestamp;
|
||||
|
||||
ws_server_->broadcast(messageJson.dump());
|
||||
Logger::debug("广播路口交通灯状态: ", messageJson.dump());
|
||||
} else {
|
||||
Logger::warning("WebSocket server is not available, cannot broadcast intersection status.");
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("处理推送的交通信号灯数据时出错: ", e.what());
|
||||
}
|
||||
}
|
||||
126
src/core/System.h
Normal file
126
src/core/System.h
Normal file
@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "types/BasicTypes.h"
|
||||
#include "detector/CollisionDetector.h"
|
||||
#include "detector/TrafficLightDetector.h"
|
||||
#include "detector/SafetyZone.h"
|
||||
#include "config/AirportBounds.h"
|
||||
#include "vehicle/ControllableVehicles.h"
|
||||
#include "network/WebSocketServer.h"
|
||||
#include "network/MessageTypes.h"
|
||||
#include "config/IntersectionConfig.h"
|
||||
#include "network/TrafficLightHttpServer.h"
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
// 前向声明
|
||||
class DataCollector;
|
||||
class SystemConfig; // 添加前向声明
|
||||
|
||||
class System {
|
||||
public:
|
||||
System();
|
||||
virtual ~System();
|
||||
|
||||
bool initialize();
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
static System* instance() { return instance_; }
|
||||
static void signalHandler(int signal);
|
||||
|
||||
// 广播消息给客户端
|
||||
virtual void broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning);
|
||||
void broadcastPositionUpdate(const MovingObject& obj);
|
||||
void broadcastCollisionWarning(const CollisionRisk& risk);
|
||||
void broadcastVehicleCommand(const VehicleCommand& cmd);
|
||||
void broadcastTrafficLightStatus(const TrafficLightSignal& signal);
|
||||
|
||||
const SystemConfig& getSystemConfig() const { return SystemConfig::instance(); }
|
||||
const IntersectionConfig& getIntersectionConfig() const { return intersection_config_; }
|
||||
|
||||
// 新增: 处理推送的红绿灯数据 (改回接收 json 对象)
|
||||
void processPushedTrafficLightData(const nlohmann::json& di_data);
|
||||
|
||||
// 处理来自数据源的交通信号灯状态
|
||||
void processTrafficLightStatus(const TrafficLightSignal& signal);
|
||||
|
||||
private:
|
||||
void processLoop();
|
||||
void processCollisions(const std::vector<CollisionRisk>& detectedRisks,
|
||||
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels);
|
||||
|
||||
// 初始化安全区
|
||||
void initializeSafetyZones();
|
||||
|
||||
// 更新安全区状态
|
||||
void updateSafetyZoneStates(const std::vector<MovingObject*>& objects);
|
||||
|
||||
// 检查目标是否进入安全区
|
||||
void checkSafetyZoneIntrusion(const MovingObject& obj);
|
||||
|
||||
// 检查无人车与安全区的冲突
|
||||
void checkUnmannedVehicleSafetyZones(const std::vector<Vehicle>& vehicles,
|
||||
const std::vector<MovingObject*>& objects,
|
||||
std::unordered_map<std::string, RiskLevel>& vehicleMaxRiskLevels,
|
||||
std::vector<CollisionRisk>& detectedRisks);
|
||||
|
||||
// 处理安全区风险
|
||||
bool handleSafetyZoneRisk(const Vehicle& vehicle,
|
||||
const SafetyZone* zone,
|
||||
const std::vector<MovingObject*>& objects,
|
||||
double distance,
|
||||
const std::string& intersectionId,
|
||||
CommandType cmdType,
|
||||
const std::string& riskLevel);
|
||||
|
||||
// 记录上一次有安全区风险的无人车
|
||||
std::unordered_map<std::string, std::string> lastVehiclesWithSafetyZoneRisk_; // <vehicleId, intersectionId>
|
||||
|
||||
std::atomic<bool> running_{false};
|
||||
std::thread processThread_;
|
||||
|
||||
std::unique_ptr<AirportBounds> airportBounds_;
|
||||
ControllableVehicles& controllableVehicles_{ControllableVehicles::getInstance()};
|
||||
std::unique_ptr<CollisionDetector> collisionDetector_;
|
||||
std::unique_ptr<TrafficLightDetector> trafficLightDetector_;
|
||||
std::unique_ptr<DataCollector> dataCollector_;
|
||||
|
||||
// WebSocket 服务器
|
||||
std::unique_ptr<network::WebSocketServer> ws_server_;
|
||||
std::thread ws_thread_;
|
||||
|
||||
// 新增: 红绿灯 HTTP 服务器
|
||||
std::unique_ptr<network::TrafficLightHttpServer> traffic_light_http_server_;
|
||||
std::thread traffic_light_http_server_thread_;
|
||||
|
||||
// 路口配置
|
||||
IntersectionConfig intersection_config_;
|
||||
|
||||
// 安全区管理
|
||||
std::unordered_map<std::string, std::unique_ptr<SafetyZone>> safetyZones_;
|
||||
|
||||
static System* instance_;
|
||||
|
||||
// 记录上一次有风险的车辆列表
|
||||
std::unordered_set<std::string> lastVehiclesWithRisk_;
|
||||
|
||||
// 时间戳
|
||||
int64_t last_aircraft_timestamp{0};
|
||||
int64_t last_vehicle_timestamp{0};
|
||||
int64_t last_traffic_light_timestamp{0};
|
||||
std::chrono::steady_clock::time_point last_safety_zone_update;
|
||||
|
||||
// 辅助函数
|
||||
const MovingObject* findVehicle(const std::string& vehicleId) const;
|
||||
|
||||
// 存储最新数据的成员变量
|
||||
std::vector<Aircraft> latest_aircraft_;
|
||||
std::vector<Vehicle> latest_vehicles_;
|
||||
std::vector<TrafficLightSignal> latest_traffic_lights_;
|
||||
};
|
||||
729
src/detector/CollisionDetector.cpp
Normal file
729
src/detector/CollisionDetector.cpp
Normal file
@ -0,0 +1,729 @@
|
||||
#include "CollisionDetector.h"
|
||||
|
||||
#include "config/SystemConfig.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <cmath>
|
||||
#include <unordered_set>
|
||||
|
||||
CollisionDetector::CollisionDetector(const AirportBounds& bounds, const ControllableVehicles& controllableVehicles)
|
||||
: airportBounds_(bounds),
|
||||
vehicleTree_(bounds.getAirportBounds(), 8), // 使用机场总边界初始化四叉树
|
||||
controllableVehicles_(&controllableVehicles) {
|
||||
// 记录初始化信息
|
||||
const auto& airportBounds = bounds.getAirportBounds();
|
||||
}
|
||||
|
||||
void CollisionDetector::updateTraffic(const std::vector<Aircraft>& aircraft,
|
||||
const std::vector<Vehicle>& vehicles) {
|
||||
Logger::debug("更新交通数据: 航空器数量=", aircraft.size(),
|
||||
", 车辆数量=", vehicles.size());
|
||||
|
||||
// 更新航空器数据
|
||||
aircraftData_ = aircraft;
|
||||
|
||||
// 更新车辆四叉树
|
||||
vehicleTree_.clear();
|
||||
size_t validVehicles = 0;
|
||||
|
||||
for (const auto& vehicle : vehicles) {
|
||||
// 首先检查车辆是否在机场边界内
|
||||
if (!airportBounds_.isPointInBounds(vehicle.position)) {
|
||||
Logger::error("车辆在机场边界外: id=", vehicle.id, ", position=(",
|
||||
vehicle.position.x, ",", vehicle.position.y, ")");
|
||||
continue; // 跳过边界外的车辆
|
||||
}
|
||||
|
||||
// 根据配置设置车辆类型
|
||||
Vehicle updatedVehicle = vehicle;
|
||||
const auto* config =
|
||||
controllableVehicles_->findVehicle(vehicle.vehicleNo);
|
||||
if (config) {
|
||||
if (config->type == "UNMANNED") {
|
||||
updatedVehicle.type = MovingObjectType::UNMANNED;
|
||||
updatedVehicle.isControllable = true;
|
||||
} else if (config->type == "SPECIAL") {
|
||||
updatedVehicle.type = MovingObjectType::SPECIAL;
|
||||
updatedVehicle.isControllable = false;
|
||||
}
|
||||
} else {
|
||||
updatedVehicle.type = MovingObjectType::SPECIAL;
|
||||
updatedVehicle.isControllable = false;
|
||||
}
|
||||
|
||||
// 插入四叉树
|
||||
try {
|
||||
Logger::debug("尝试插入车辆: id=", updatedVehicle.id,
|
||||
", position=(", updatedVehicle.position.x, ",",
|
||||
updatedVehicle.position.y, ")", ", type=",
|
||||
updatedVehicle.isControllable ? "UNMANNED"
|
||||
: "SPECIAL");
|
||||
vehicleTree_.insert(updatedVehicle);
|
||||
++validVehicles;
|
||||
Logger::debug("成功插入车辆: id=", updatedVehicle.id);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("无法插入车辆到四叉树: id=", updatedVehicle.id,
|
||||
", error=", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// 记录更新后的状态
|
||||
auto bounds = vehicleTree_.getBounds();
|
||||
Logger::debug("四叉树边界: min=(", bounds.x, ",", bounds.y, "), max=(",
|
||||
bounds.x + bounds.width, ",", bounds.y + bounds.height, ")");
|
||||
auto allVehicles = vehicleTree_.queryRange(bounds);
|
||||
for (const auto& vehicle : allVehicles) {
|
||||
Logger::debug(
|
||||
"查询到车辆: id=", vehicle.id, ", position=(", vehicle.position.x,
|
||||
",", vehicle.position.y, ")",
|
||||
", type=", vehicle.isControllable ? "UNMANNED" : "SPECIAL");
|
||||
}
|
||||
Logger::debug("交通数据更新完成: 有效车辆数量=", validVehicles,
|
||||
", 四叉树中的车辆数量=", allVehicles.size());
|
||||
}
|
||||
|
||||
std::vector<CollisionRisk> CollisionDetector::detectCollisions() {
|
||||
std::vector<CollisionRisk> risks;
|
||||
|
||||
// 获取配置参数
|
||||
const auto& predictionConfig =
|
||||
SystemConfig::instance().collision_detection.prediction;
|
||||
|
||||
// 获取所有车辆
|
||||
auto allVehicles = vehicleTree_.queryRange(vehicleTree_.getBounds());
|
||||
|
||||
// 可控车辆(无人车)
|
||||
std::vector<Vehicle> unmannedVehicles;
|
||||
for (const auto& vehicle : allVehicles) {
|
||||
if (vehicle.isControllable) {
|
||||
unmannedVehicles.push_back(vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录当前检测到的冲突对
|
||||
std::unordered_set<std::pair<std::string, std::string>, CollisionPairHash>
|
||||
currentCollisions;
|
||||
|
||||
// 检测可控车辆与航空器的碰撞
|
||||
for (const auto& aircraft : aircraftData_) {
|
||||
for (const auto& vehicle : unmannedVehicles) {
|
||||
// 获取冲突记录键
|
||||
auto collisionKey = getCollisionKey(aircraft.id, vehicle.id);
|
||||
|
||||
// 获取区域配置
|
||||
const auto& areaConfig = getCollisionParams(aircraft.position);
|
||||
double safeDistance = areaConfig.warning_zone_radius.aircraft +
|
||||
areaConfig.warning_zone_radius.unmanned;
|
||||
|
||||
// 检查是否存在未解除的冲突记录
|
||||
auto it = collisionRecords_.find(collisionKey);
|
||||
bool hasUnresolvedConflict =
|
||||
(it != collisionRecords_.end() && !it->second.resolved);
|
||||
|
||||
if (hasUnresolvedConflict) {
|
||||
Logger::debug("存在未解除的冲突记录: obj1=", aircraft.id,
|
||||
", obj2=", vehicle.id, ", maxLevel=",
|
||||
static_cast<int>(it->second.maxLevel),
|
||||
", collisionPoint=(", it->second.collisionPoint.x,
|
||||
",", it->second.collisionPoint.y, ")");
|
||||
}
|
||||
|
||||
// 检测碰撞
|
||||
auto collisionResult =
|
||||
checkCollision(aircraft, vehicle, predictionConfig.time_window);
|
||||
|
||||
// 计算相对运动
|
||||
MovementVector av(aircraft.speed, aircraft.heading);
|
||||
MovementVector vv(vehicle.speed, vehicle.heading);
|
||||
double vx = av.vx - vv.vx;
|
||||
double vy = av.vy - vv.vy;
|
||||
double relativeSpeed = std::sqrt(vx * vx + vy * vy);
|
||||
|
||||
// 根据相对运动方向调整相对速度符号
|
||||
if (collisionResult.timeToCollision > 0) {
|
||||
relativeSpeed = -relativeSpeed;
|
||||
}
|
||||
|
||||
// 评估风险等级
|
||||
auto [level, zoneType] =
|
||||
evaluateRisk(collisionResult, aircraft.position, aircraft.type,
|
||||
vehicle.type);
|
||||
|
||||
if (level != RiskLevel::NONE) {
|
||||
// 记录或更新冲突信息
|
||||
auto& record = collisionRecords_[collisionKey];
|
||||
if (!record.resolved) {
|
||||
// 更新已存在的冲突记录
|
||||
record.maxLevel = std::max(record.maxLevel, level);
|
||||
record.collisionPoint =
|
||||
collisionResult.collisionPoint; // 更新冲突点
|
||||
Logger::debug("更新冲突记录: obj1=", aircraft.id,
|
||||
", obj2=", vehicle.id, ", maxLevel=",
|
||||
static_cast<int>(record.maxLevel),
|
||||
", collisionPoint=(", record.collisionPoint.x,
|
||||
",", record.collisionPoint.y, ")");
|
||||
} else {
|
||||
// 创建新的冲突记录
|
||||
record = { collisionResult
|
||||
.collisionPoint, // 使用当前检测到的冲突点
|
||||
std::chrono::system_clock::now()
|
||||
.time_since_epoch()
|
||||
.count(),
|
||||
level, false };
|
||||
Logger::debug("创建新的冲突记录: obj1=", aircraft.id,
|
||||
", obj2=", vehicle.id,
|
||||
", level=", static_cast<int>(level),
|
||||
", collisionPoint=(",
|
||||
collisionResult.collisionPoint.x, ",",
|
||||
collisionResult.collisionPoint.y, ")");
|
||||
}
|
||||
|
||||
// 添加到当前冲突集合
|
||||
currentCollisions.insert(collisionKey);
|
||||
|
||||
// 添加到风险列表
|
||||
risks.push_back({ aircraft.id,
|
||||
vehicle.id,
|
||||
level,
|
||||
collisionResult.minDistance,
|
||||
collisionResult.minDistance,
|
||||
relativeSpeed,
|
||||
{vx, vy},
|
||||
zoneType,
|
||||
collisionResult.timeToCollision,
|
||||
collisionResult.collisionPoint });
|
||||
|
||||
Logger::debug(
|
||||
"检测到碰撞风险: obj1=", aircraft.id, ", obj2=", vehicle.id,
|
||||
", minDistance=", collisionResult.minDistance, "m",
|
||||
", timeToCollision=", collisionResult.timeToCollision, "s",
|
||||
", level=", static_cast<int>(level));
|
||||
} else if (hasUnresolvedConflict) {
|
||||
// 检查是否满足解除条件
|
||||
if (checkCollisionResolved(aircraft, vehicle, it->second,
|
||||
safeDistance)) {
|
||||
it->second.resolved = true;
|
||||
Logger::debug("冲突已解除: obj1=", aircraft.id,
|
||||
", obj2=", vehicle.id);
|
||||
} else {
|
||||
// 虽然当前没有检测到风险,但由于未满足解除条件,继续保持原有风险等级
|
||||
currentCollisions.insert(collisionKey);
|
||||
risks.push_back({ aircraft.id,
|
||||
vehicle.id,
|
||||
it->second.maxLevel,
|
||||
collisionResult.minDistance,
|
||||
collisionResult.minDistance,
|
||||
relativeSpeed,
|
||||
{vx, vy},
|
||||
zoneType,
|
||||
collisionResult.timeToCollision,
|
||||
it->second.collisionPoint });
|
||||
Logger::debug(
|
||||
"保持原有风险等级: obj1=", aircraft.id,
|
||||
", obj2=", vehicle.id,
|
||||
", maxLevel=", static_cast<int>(it->second.maxLevel),
|
||||
", minDistance=", collisionResult.minDistance, "m",
|
||||
", timeToCollision=", collisionResult.timeToCollision,
|
||||
"s");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理已解除的冲突记录
|
||||
for (auto it = collisionRecords_.begin(); it != collisionRecords_.end();) {
|
||||
if (it->second.resolved) {
|
||||
it = collisionRecords_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return risks;
|
||||
}
|
||||
|
||||
bool CollisionDetector::checkCollisionResolved(const MovingObject& obj1,
|
||||
const MovingObject& obj2,
|
||||
const CollisionRecord& record,
|
||||
double safeDistance) const {
|
||||
// 计算航空器与冲突点的距离
|
||||
double dist1 =
|
||||
std::sqrt(std::pow(obj1.position.x - record.collisionPoint.x, 2) +
|
||||
std::pow(obj1.position.y - record.collisionPoint.y, 2));
|
||||
|
||||
double dist2 =
|
||||
std::sqrt(std::pow(obj2.position.x - record.collisionPoint.x, 2) +
|
||||
std::pow(obj2.position.y - record.collisionPoint.y, 2));
|
||||
|
||||
// 计算冲突点相对于航空器的位置向量与航空器航向的夹角
|
||||
double dx =
|
||||
record.collisionPoint.x - obj1.position.x; // 冲突点相对于航空器的向量
|
||||
double dy = record.collisionPoint.y - obj1.position.y;
|
||||
double pointAngle =
|
||||
std::atan2(dy, dx) * 180.0 / M_PI; // 冲突点相对于航空器的角度
|
||||
double angleDiff =
|
||||
normalizeAngle(pointAngle - (90.0 - obj1.heading)); // 修正航向角
|
||||
|
||||
Logger::debug("检查冲突解除条件: 航空器位置=(", obj1.position.x, ",",
|
||||
obj1.position.y, "), 冲突点=(", record.collisionPoint.x, ",",
|
||||
record.collisionPoint.y, "), 航向=", obj1.heading,
|
||||
", 冲突点角度=", pointAngle, ", 角度差=", angleDiff,
|
||||
", 距离=", dist1);
|
||||
|
||||
// 判断航空器是否已经通过交叉点
|
||||
if (angleDiff > 90 && angleDiff < 270) {
|
||||
// 航空器已经通过交叉点,只要航空器离开冲突点的距离大于安全距离就可以解除
|
||||
Logger::debug("航空器已通过交叉点,航空器距离=", dist1,
|
||||
"m, 安全距离=", safeDistance, "m");
|
||||
return dist1 >= safeDistance;
|
||||
} else {
|
||||
// 航空器还未通过交叉点,需要两者都在安全距离外且预计不会碰撞
|
||||
auto collisionResult =
|
||||
checkCollision(obj1, obj2, 30.0); // 使用30秒的预测窗口
|
||||
bool willCollide = collisionResult.willCollide;
|
||||
bool bothOutside = (dist1 >= safeDistance && dist2 >= safeDistance);
|
||||
|
||||
Logger::debug("航空器未通过交叉点,航空器距离=", dist1,
|
||||
"m, 无人车距离=", dist2, "m, 安全距离=", safeDistance,
|
||||
"m, 预测碰撞=", willCollide ? "是" : "否");
|
||||
|
||||
return !willCollide && bothOutside;
|
||||
}
|
||||
}
|
||||
|
||||
// 统一的风险评估函数
|
||||
std::pair<RiskLevel, WarningZoneType> CollisionDetector::evaluateRisk(
|
||||
const CollisionResult& collisionResult, const Vector2D& position,
|
||||
MovingObjectType type1, MovingObjectType type2) const {
|
||||
|
||||
// 取区域配置
|
||||
const auto& areaConfig = getCollisionParams(position);
|
||||
auto thresholds = areaConfig.getThresholds(type1, type2);
|
||||
|
||||
RiskLevel level = RiskLevel::NONE;
|
||||
WarningZoneType zoneType = WarningZoneType::NONE;
|
||||
|
||||
// 如果预测到碰撞,根据当前距离判断风险等级
|
||||
if (collisionResult.willCollide) {
|
||||
// 当前距离小于警报距离 -> 危险等级
|
||||
if (collisionResult.distance <= thresholds.critical) {
|
||||
level = RiskLevel::CRITICAL;
|
||||
zoneType = WarningZoneType::DANGER;
|
||||
}
|
||||
// 当前距离在预警范围内 -> 预警等级
|
||||
else if (collisionResult.distance <= thresholds.warning) {
|
||||
level = RiskLevel::WARNING;
|
||||
zoneType = WarningZoneType::WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改日志,添加物体类型信息
|
||||
Logger::debug("风险评估: 物体1类型=", static_cast<int>(type1),
|
||||
", 物体2类型=", static_cast<int>(type2),
|
||||
", 当前距离=", collisionResult.distance,
|
||||
"m, 预测最小距离=", collisionResult.minDistance,
|
||||
"m, 预警阈值=", thresholds.warning,
|
||||
"m, 警报阈值=", thresholds.critical,
|
||||
"m, 预测碰撞=", collisionResult.willCollide ? "是" : "否",
|
||||
", 风险等级=", static_cast<int>(level),
|
||||
", 区域类型=", static_cast<int>(zoneType));
|
||||
|
||||
return { level, zoneType };
|
||||
}
|
||||
|
||||
// 统一的碰撞检测函数
|
||||
CollisionResult CollisionDetector::checkCollision(const MovingObject& obj1,
|
||||
const MovingObject& obj2,
|
||||
double timeWindow) const {
|
||||
|
||||
// 添加物体信息日志
|
||||
Logger::debug("检查碰撞: obj1[", obj1.id, "]=(", obj1.position.x, ",",
|
||||
obj1.position.y, "), obj2[", obj2.id, "]=(", obj2.position.x,
|
||||
",", obj2.position.y, ")");
|
||||
|
||||
// 获取区域配置
|
||||
const auto& areaConfig = getCollisionParams(obj1.position);
|
||||
|
||||
// 获取各自的碰撞半径和警告半径
|
||||
double radius1 = areaConfig.collision_radius.getRadius(obj1.type);
|
||||
double radius2 = areaConfig.collision_radius.getRadius(obj2.type);
|
||||
double warning_radius1 =
|
||||
areaConfig.warning_zone_radius.getRadius(obj1.type);
|
||||
double warning_radius2 =
|
||||
areaConfig.warning_zone_radius.getRadius(obj2.type);
|
||||
|
||||
// 计算当前距离
|
||||
double dx = obj2.position.x - obj1.position.x;
|
||||
double dy = obj2.position.y - obj1.position.y;
|
||||
double current_distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 如果是静止的无人车,使用基础速度
|
||||
double speed1_calc = obj1.speed;
|
||||
double speed2_calc = obj2.speed;
|
||||
|
||||
if (obj1.type == MovingObjectType::UNMANNED && speed1_calc < 0.1) {
|
||||
speed1_calc = SystemConfig::instance()
|
||||
.collision_detection.prediction
|
||||
.min_unmanned_speed; // 使用配置的最低速度
|
||||
}
|
||||
if (obj2.type == MovingObjectType::UNMANNED && speed2_calc < 0.1) {
|
||||
speed2_calc = SystemConfig::instance()
|
||||
.collision_detection.prediction
|
||||
.min_unmanned_speed; // 使用配置的最低速度
|
||||
}
|
||||
|
||||
// 计算速度分量
|
||||
double vx1 =
|
||||
speed1_calc * std::cos((90.0 - obj1.heading) * M_PI / 180.0); // x 方向
|
||||
double vy1 =
|
||||
speed1_calc * std::sin((90.0 - obj1.heading) * M_PI / 180.0); // y 方向
|
||||
double vx2 = speed2_calc * std::cos((90.0 - obj2.heading) * M_PI / 180.0);
|
||||
double vy2 = speed2_calc * std::sin((90.0 - obj2.heading) * M_PI / 180.0);
|
||||
|
||||
// 如果当前距离大于警告区域,直接返回不会碰撞
|
||||
if (current_distance > (warning_radius1 + warning_radius2)) {
|
||||
CollisionResult result;
|
||||
result.willCollide = false;
|
||||
result.minDistance = current_distance;
|
||||
result.distance = current_distance;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 否则进行碰撞检测
|
||||
return predictCircleBasedCollision(obj1.position, radius1, obj1.speed,
|
||||
obj1.heading, obj2.position, radius2,
|
||||
obj2.speed, obj2.heading, timeWindow);
|
||||
}
|
||||
|
||||
CollisionResult CollisionDetector::predictCircleBasedCollision(
|
||||
const Vector2D& pos1, double radius1, double speed1, double heading1,
|
||||
const Vector2D& pos2, double radius2, double speed2, double heading2,
|
||||
double timeWindow) const {
|
||||
|
||||
CollisionResult result;
|
||||
|
||||
// 计算安全距离(两个圆的半径之和)
|
||||
double safe_distance = radius1 + radius2;
|
||||
|
||||
// 1. 计算当前距离
|
||||
double dx = pos2.x - pos1.x;
|
||||
double dy = pos2.y - pos1.y;
|
||||
double current_distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 初始化最小距离和当前距离
|
||||
result.minDistance = current_distance;
|
||||
result.distance = current_distance;
|
||||
result.timeToMinDistance = 0.0;
|
||||
|
||||
// 2. 计算速度分量
|
||||
double vx1 = speed1 * std::sin((90.0 - heading1) * M_PI / 180.0);
|
||||
double vy1 = speed1 * std::cos((90.0 - heading1) * M_PI / 180.0);
|
||||
double vx2 = speed2 * std::sin((90.0 - heading2) * M_PI / 180.0);
|
||||
double vy2 = speed2 * std::cos((90.0 - heading2) * M_PI / 180.0);
|
||||
|
||||
// 3. 计算相对运动
|
||||
double rel_vx = vx2 - vx1;
|
||||
double rel_vy = vy2 - vy1;
|
||||
double rel_speed = std::sqrt(rel_vx * rel_vx + rel_vy * rel_vy);
|
||||
|
||||
// 计算相对运动方向与连线方向的夹角
|
||||
double motion_angle = std::atan2(rel_vy, rel_vx);
|
||||
double connection_angle = std::atan2(dy, dx);
|
||||
double angle = std::abs(motion_angle - connection_angle);
|
||||
if (angle > M_PI) {
|
||||
angle = 2 * M_PI - angle; // 确保夹角不超过180度
|
||||
}
|
||||
|
||||
// 4. 确定碰撞类型
|
||||
if (speed1 < 0.1 && speed2 < 0.1) {
|
||||
result.type = CollisionType::STATIC;
|
||||
if (current_distance <= safe_distance) {
|
||||
result.willCollide = true;
|
||||
result.timeToCollision = 0.0;
|
||||
result.minDistance = current_distance;
|
||||
result.timeToMinDistance = 0.0;
|
||||
result.collisionPoint = { (pos1.x + pos2.x) / 2.0,
|
||||
(pos1.y + pos2.y) / 2.0 };
|
||||
result.object1State = CollisionObjectState(pos1, speed1, heading1);
|
||||
result.object2State = CollisionObjectState(pos2, speed2, heading2);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double angle_diff = getAngleDifference(heading1, heading2);
|
||||
if (angle_diff > 150.0) {
|
||||
result.type = CollisionType::HEAD_ON;
|
||||
// 对于相向运动,直接计算碰撞时间
|
||||
double closing_speed = speed1 + speed2; // 相对接近速度是两个速度之和
|
||||
if (closing_speed > 0.1) { // 避免除以零
|
||||
double collision_distance = current_distance - safe_distance;
|
||||
if (collision_distance <= 0) {
|
||||
result.willCollide = true;
|
||||
result.timeToCollision = 0.0;
|
||||
result.minDistance = current_distance;
|
||||
result.timeToMinDistance = 0.0;
|
||||
result.collisionPoint = { (pos1.x + pos2.x) / 2.0,
|
||||
(pos1.y + pos2.y) / 2.0 };
|
||||
result.object1State =
|
||||
CollisionObjectState(pos1, speed1, heading1);
|
||||
result.object2State =
|
||||
CollisionObjectState(pos2, speed2, heading2);
|
||||
} else {
|
||||
result.timeToCollision = collision_distance / closing_speed;
|
||||
result.willCollide = result.timeToCollision <= timeWindow;
|
||||
|
||||
if (result.willCollide) {
|
||||
result.minDistance = safe_distance;
|
||||
result.timeToMinDistance = result.timeToCollision;
|
||||
|
||||
// 计算碰撞时两个物体的实际位置
|
||||
double move_distance1 = speed1 * result.timeToCollision;
|
||||
double move_distance2 = speed2 * result.timeToCollision;
|
||||
|
||||
// 计算物体1的碰撞位置(向前移动到碰撞点前方)
|
||||
Vector2D collision1 = {
|
||||
pos1.x +
|
||||
move_distance1 * std::sin(heading1 * M_PI / 180.0),
|
||||
pos1.y +
|
||||
move_distance1 * std::cos(heading1 * M_PI / 180.0) };
|
||||
|
||||
// 计算物体2的碰撞位置(也是向前移动)
|
||||
Vector2D collision2 = {
|
||||
pos2.x +
|
||||
move_distance2 * std::sin(heading2 * M_PI / 180.0),
|
||||
pos2.y +
|
||||
move_distance2 * std::cos(heading2 * M_PI / 180.0) };
|
||||
|
||||
// 碰撞点两个物体的中点
|
||||
result.collisionPoint = {
|
||||
(collision1.x + collision2.x) / 2.0,
|
||||
(collision1.y + collision2.y) / 2.0 };
|
||||
|
||||
result.object1State =
|
||||
CollisionObjectState(collision1, speed1, heading1);
|
||||
result.object2State =
|
||||
CollisionObjectState(collision2, speed2, heading2);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} else if (angle_diff > 15.0 &&
|
||||
angle_diff < 165.0) { // 放宽交叉路径的角度范围
|
||||
result.type = CollisionType::CROSSING;
|
||||
|
||||
Logger::debug("交叉路径检测: angle_diff=", angle_diff, ", pos1=(",
|
||||
pos1.x, ",", pos1.y, ")", ", pos2=(", pos2.x, ",", pos2.y,
|
||||
")", ", v1=(", vx1, ",", vy1, ")", ", v2=(", vx2, ",",
|
||||
vy2, ")", ", current_distance=", current_distance,
|
||||
", safe_distance=", safe_distance);
|
||||
|
||||
// 计算行列式 (v1 x v2)
|
||||
double det = vx1 * (-vy2) - (-vx2) * vy1;
|
||||
|
||||
// 计算 det1 = (P2-P1) x (-v2)
|
||||
double det1 = dx * (-vy2) - (-vx2) * dy;
|
||||
|
||||
// 计算 det2 = v1 x (P2-P1)
|
||||
double det2 = vx1 * dy - dx * vy1;
|
||||
|
||||
// 计算时间
|
||||
double t1 = det1 / det; // 物体1的时间
|
||||
double t2 = det2 / det; // 物体2的时间
|
||||
|
||||
Logger::debug("交叉路径检测: det1=", det1, ", det2=", det2,
|
||||
", det=", det, ", t1=", t1, ", t2=", t2);
|
||||
|
||||
// 计算相交点
|
||||
Vector2D intersection = { pos1.x + vx1 * t1, pos1.y + vy1 * t1 };
|
||||
|
||||
Logger::debug("交叉路径检测: intersection=(", intersection.x, ",",
|
||||
intersection.y, ")", ", t1=", t1, ", t2=", t2);
|
||||
|
||||
// 检查时间条件
|
||||
double first_arrival_time = std::min(t1, t2);
|
||||
double first_arrival_speed = (t1 < t2) ? speed1 : speed2;
|
||||
double time_threshold = safe_distance / first_arrival_speed;
|
||||
bool valid_time =
|
||||
(t1 >= 0 && t2 >= 0 && std::max(t1, t2) <= timeWindow &&
|
||||
std::abs(t1 - t2) <= time_threshold);
|
||||
|
||||
// 如果满足所有条件,则预测为碰撞
|
||||
if (valid_time) {
|
||||
// 计算实际碰撞点(在交点之前)
|
||||
double collision_distance = safe_distance;
|
||||
double collision_time =
|
||||
first_arrival_time - collision_distance / first_arrival_speed;
|
||||
|
||||
if (collision_time < 0) {
|
||||
collision_time = 0;
|
||||
}
|
||||
|
||||
// 根据碰撞时间计算两个物体的位置
|
||||
Vector2D pos1_at_collision = { pos1.x + vx1 * collision_time,
|
||||
pos1.y + vy1 * collision_time };
|
||||
|
||||
Vector2D pos2_at_collision = { pos2.x + vx2 * collision_time,
|
||||
pos2.y + vy2 * collision_time };
|
||||
|
||||
// 碰撞点在两个物体位置的中点
|
||||
result.collisionPoint = {
|
||||
(pos1_at_collision.x + pos2_at_collision.x) / 2.0,
|
||||
(pos1_at_collision.y + pos2_at_collision.y) / 2.0 };
|
||||
|
||||
Logger::debug("碰撞时间计算: first_arrival_time=",
|
||||
first_arrival_time, ", safe_distance=", safe_distance,
|
||||
", collision_distance=", collision_distance,
|
||||
", collision_time=", collision_time);
|
||||
|
||||
result.willCollide = true;
|
||||
result.timeToCollision = collision_time;
|
||||
result.timeToMinDistance = collision_time;
|
||||
result.minDistance = safe_distance;
|
||||
|
||||
// 更新物体状态
|
||||
result.object1State =
|
||||
CollisionObjectState(pos1_at_collision, speed1, heading1);
|
||||
result.object2State =
|
||||
CollisionObjectState(pos2_at_collision, speed2, heading2);
|
||||
|
||||
Logger::debug("交叉路径碰撞: collision_time=", collision_time,
|
||||
", collision1=(", pos1_at_collision.x, ",",
|
||||
pos1_at_collision.y, ")", ", collision2=(",
|
||||
pos2_at_collision.x, ",", pos2_at_collision.y, ")",
|
||||
", collision_point=(", result.collisionPoint.x, ",",
|
||||
result.collisionPoint.y, ")");
|
||||
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
result.type = CollisionType::PARALLEL;
|
||||
// 平行运动判断:
|
||||
// 1. 航向差很小(小于30度)
|
||||
// 2. 横向距离大于安全距离
|
||||
// 3. 相对速度很小(说明速度接近)
|
||||
if (angle_diff < 30.0) {
|
||||
// 计算垂直于运动方向的向距离
|
||||
// 航向角转换为学坐标系中的旋转角度(逆时针为正)
|
||||
double rotation_angle = (90.0 - heading1) * M_PI / 180.0;
|
||||
|
||||
// 使用标准的二维坐标旋转式
|
||||
double dx_rotated =
|
||||
dx * std::cos(rotation_angle) - dy * std::sin(rotation_angle);
|
||||
double dy_rotated =
|
||||
dx * std::sin(rotation_angle) + dy * std::cos(rotation_angle);
|
||||
|
||||
// 横向距离就是旋转后的y坐标的绝对值
|
||||
double lateral_distance = std::abs(dy_rotated);
|
||||
|
||||
Logger::debug("平行运动检测: angle_diff=", angle_diff,
|
||||
", heading1=", heading1,
|
||||
", rotation_angle=", rotation_angle * 180.0 / M_PI,
|
||||
", dx=", dx, ", dy=", dy, ", dx_rotated=", dx_rotated,
|
||||
", dy_rotated=", dy_rotated,
|
||||
", lateral_distance=", lateral_distance,
|
||||
", safe_distance=", safe_distance,
|
||||
", rel_speed=", rel_speed);
|
||||
|
||||
// 如果横向距离大于等于安全距离,且相对速度很小,则不会碰撞
|
||||
if (lateral_distance >= safe_distance && rel_speed < 1.0) {
|
||||
result.willCollide = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 在时间窗口内预测碰撞
|
||||
const int STEPS = 120; // 增加采样点数以提高精度
|
||||
double dt = timeWindow / STEPS; // 时间步长
|
||||
|
||||
// 新计算速度分量(修正计算方式)
|
||||
double vx1_sample =
|
||||
speed1 * std::cos((90.0 - heading1) * M_PI / 180.0); // x 方向
|
||||
double vy1_sample =
|
||||
speed1 * std::sin((90.0 - heading1) * M_PI / 180.0); // y 方向
|
||||
double vx2_sample = speed2 * std::cos((90.0 - heading2) * M_PI / 180.0);
|
||||
double vy2_sample = speed2 * std::sin((90.0 - heading2) * M_PI / 180.0);
|
||||
|
||||
// 如果当前已经碰撞
|
||||
if (current_distance <= safe_distance) {
|
||||
result.willCollide = true;
|
||||
result.timeToCollision = 0.0;
|
||||
result.minDistance = current_distance;
|
||||
result.timeToMinDistance = 0.0;
|
||||
result.collisionPoint = { (pos1.x + pos2.x) / 2.0,
|
||||
(pos1.y + pos2.y) / 2.0 };
|
||||
result.object1State = CollisionObjectState(pos1, speed1, heading1);
|
||||
result.object2State = CollisionObjectState(pos2, speed2, heading2);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 在时间窗口内采样检查
|
||||
double prev_distance = current_distance;
|
||||
Logger::debug("开始时间窗口采样: STEPS=", STEPS, ", dt=", dt,
|
||||
", timeWindow=", timeWindow,
|
||||
", safe_distance=", safe_distance,
|
||||
", current_distance=", current_distance);
|
||||
for (int i = 1; i <= STEPS; ++i) {
|
||||
double t = i * dt;
|
||||
|
||||
// 计算t时的位置(使用修正后的速度分量)
|
||||
Vector2D future1 = { pos1.x + vx1_sample * t, pos1.y + vy1_sample * t };
|
||||
|
||||
Vector2D future2 = { pos2.x + vx2_sample * t, pos2.y + vy2_sample * t };
|
||||
|
||||
// 计算t时的距离
|
||||
double dx_t = future2.x - future1.x;
|
||||
double dy_t = future2.y - future1.y;
|
||||
double distance =
|
||||
std::sqrt(dx_t * dx_t + dy_t * dy_t); // 统一使用实际距离
|
||||
|
||||
// 更新小距离
|
||||
if (distance < result.minDistance) {
|
||||
result.minDistance = distance;
|
||||
result.timeToMinDistance = t;
|
||||
// Logger::debug(
|
||||
// "更新最小距离: min_distance=", distance,
|
||||
// ", time_to_min=", t
|
||||
// );
|
||||
}
|
||||
|
||||
// 检查是否会碰撞
|
||||
if (distance <= safe_distance) {
|
||||
result.willCollide = true;
|
||||
// 使用当前距离和安全距离做插值,提高精确度
|
||||
double progress =
|
||||
(distance - safe_distance) / (prev_distance - safe_distance);
|
||||
double t_interp = t - dt * progress;
|
||||
result.timeToCollision = t_interp;
|
||||
|
||||
Logger::debug("找到碰撞: t=", t, ", distance=", distance,
|
||||
", prev_distance=", prev_distance, ", dt=", dt,
|
||||
", t_interp=", t_interp, ", future1=(", future1.x,
|
||||
",", future1.y, ")", ", future2=(", future2.x, ",",
|
||||
future2.y, ")");
|
||||
|
||||
// 使用插值时间计算更精确的碰撞点
|
||||
Vector2D interp1 = { pos1.x + vx1_sample * t_interp,
|
||||
pos1.y + vy1_sample * t_interp };
|
||||
Vector2D interp2 = { pos2.x + vx2_sample * t_interp,
|
||||
pos2.y + vy2_sample * t_interp };
|
||||
result.collisionPoint = { (interp1.x + interp2.x) / 2.0,
|
||||
(interp1.y + interp2.y) / 2.0 };
|
||||
result.object1State =
|
||||
CollisionObjectState(interp1, speed1, heading1);
|
||||
result.object2State =
|
||||
CollisionObjectState(interp2, speed2, heading2);
|
||||
break;
|
||||
}
|
||||
|
||||
prev_distance = distance;
|
||||
|
||||
// 如果相对速度很小,且距离增加,可以提前退出
|
||||
if (rel_speed < 0.1 && i > 1) {
|
||||
if (distance > prev_distance) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
153
src/detector/CollisionDetector.h
Normal file
153
src/detector/CollisionDetector.h
Normal file
@ -0,0 +1,153 @@
|
||||
#ifndef AIRPORT_DETECTOR_COLLISION_DETECTOR_H
|
||||
#define AIRPORT_DETECTOR_COLLISION_DETECTOR_H
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
#include "spatial/QuadTree.h"
|
||||
#include "config/AirportBounds.h"
|
||||
#include "vehicle/ControllableVehicles.h"
|
||||
#include "config/SystemConfig.h"
|
||||
#include "CollisionTypes.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
// 碰撞风险等级
|
||||
enum class RiskLevel {
|
||||
NONE = 0, // 无风险
|
||||
WARNING = 1, // 预警:进入预警区域
|
||||
CRITICAL = 2 // 告警:进入危险区域
|
||||
};
|
||||
|
||||
// 预警区域类型
|
||||
enum class WarningZoneType {
|
||||
NONE = 0, // 无风险区域
|
||||
WARNING = 1, // 预警区域
|
||||
DANGER = 2 // 危险区域
|
||||
};
|
||||
|
||||
// 碰撞风险信息
|
||||
struct CollisionRisk {
|
||||
std::string id1, id2; // 碰撞物体的ID
|
||||
RiskLevel level; // 风险等级
|
||||
double distance; // 当前距离
|
||||
double minDistance; // 预测的最小距离(两个中心点之间的距离)
|
||||
double relativeSpeed; // 相对速度
|
||||
Vector2D relativeMotion; // 相对运动向量
|
||||
WarningZoneType zoneType; // 预警区域类型
|
||||
double timeToCollision; // 预计碰撞时间(秒)
|
||||
Vector2D collisionPoint; // 预计碰撞点
|
||||
};
|
||||
|
||||
// 冲突记录信息
|
||||
struct CollisionRecord {
|
||||
Vector2D collisionPoint; // 冲突点位置
|
||||
int64_t detectedTime; // 检测到冲突的时间
|
||||
RiskLevel maxLevel; // 历史最高风险等级
|
||||
bool resolved; // 是否已解除
|
||||
};
|
||||
|
||||
// 冲突对象对的哈希函数
|
||||
struct CollisionPairHash {
|
||||
std::size_t operator()(const std::pair<std::string, std::string>& p) const {
|
||||
return std::hash<std::string>()(p.first + p.second);
|
||||
}
|
||||
};
|
||||
|
||||
class CollisionDetector {
|
||||
public:
|
||||
CollisionDetector(const AirportBounds& bounds, const ControllableVehicles& controllableVehicles);
|
||||
|
||||
// 更新交通数据
|
||||
void updateTraffic(const std::vector<Aircraft>& aircraft,
|
||||
const std::vector<Vehicle>& vehicles);
|
||||
|
||||
// 检测所有可能的碰撞
|
||||
std::vector<CollisionRisk> detectCollisions();
|
||||
|
||||
// 统一的碰撞检测函数
|
||||
CollisionResult checkCollision(
|
||||
const MovingObject& obj1,
|
||||
const MovingObject& obj2,
|
||||
double timeWindow = 30.0) const;
|
||||
|
||||
private:
|
||||
const AirportBounds& airportBounds_;
|
||||
QuadTree<Vehicle> vehicleTree_;
|
||||
std::vector<Aircraft> aircraftData_;
|
||||
const ControllableVehicles* controllableVehicles_;
|
||||
|
||||
// 冲突记录映射表:<(id1,id2), CollisionRecord>
|
||||
std::unordered_map<std::pair<std::string, std::string>, CollisionRecord, CollisionPairHash> collisionRecords_;
|
||||
|
||||
// 将角度标准化到[0, 360)范围
|
||||
static double normalizeAngle(double angle) {
|
||||
while (angle >= 360.0) angle -= 360.0;
|
||||
while (angle < 0.0) angle += 360.0;
|
||||
return angle;
|
||||
}
|
||||
|
||||
// 计算两个角度之间的最小差值(范围[0, 180])
|
||||
static double getAngleDifference(double angle1, double angle2) {
|
||||
angle1 = normalizeAngle(angle1);
|
||||
angle2 = normalizeAngle(angle2);
|
||||
double diff = std::abs(angle1 - angle2);
|
||||
return std::min(diff, 360.0 - diff);
|
||||
}
|
||||
|
||||
// 根据区域获取碰撞检测参数
|
||||
AreaConfig getCollisionParams(const Vector2D& position) const {
|
||||
auto areaType = airportBounds_.getAreaType(position);
|
||||
return airportBounds_.getAreaConfig(areaType);
|
||||
}
|
||||
|
||||
// 统一的风险评估函数
|
||||
std::pair<RiskLevel, WarningZoneType> evaluateRisk(
|
||||
const CollisionResult& collisionResult,
|
||||
const Vector2D& position,
|
||||
MovingObjectType type1,
|
||||
MovingObjectType type2) const;
|
||||
|
||||
// 检查是否满足冲突解除条件
|
||||
bool checkCollisionResolved(const MovingObject& obj1,
|
||||
const MovingObject& obj2,
|
||||
const CollisionRecord& record,
|
||||
double safeDistance) const;
|
||||
|
||||
// 获取冲突记录的键
|
||||
std::pair<std::string, std::string> getCollisionKey(const std::string& id1,
|
||||
const std::string& id2) const {
|
||||
return id1 < id2 ? std::make_pair(id1, id2) : std::make_pair(id2, id1);
|
||||
}
|
||||
|
||||
// 计算相对运动
|
||||
struct MovementVector {
|
||||
double vx, vy;
|
||||
|
||||
MovementVector(double speed, double heading) {
|
||||
// 标准方位角:正北为0度,顺时针增加
|
||||
double radians = heading * M_PI / 180.0;
|
||||
|
||||
// 计算速度分量
|
||||
vx = speed * std::sin(radians); // 东向分量
|
||||
vy = speed * std::cos(radians); // 北向分量
|
||||
}
|
||||
};
|
||||
|
||||
// 预测位置的辅助函数
|
||||
Vector2D predictPosition(const Vector2D& position, double speed, double heading, double t) const {
|
||||
double vx = speed * std::sin(heading * M_PI / 180.0);
|
||||
double vy = speed * std::cos(heading * M_PI / 180.0);
|
||||
return {
|
||||
position.x + vx * t,
|
||||
position.y + vy * t
|
||||
};
|
||||
}
|
||||
|
||||
// 基于圆形的碰撞检测核心函数
|
||||
CollisionResult predictCircleBasedCollision(
|
||||
const Vector2D& pos1, double radius1, double speed1, double heading1,
|
||||
const Vector2D& pos2, double radius2, double speed2, double heading2,
|
||||
double timeWindow) const;
|
||||
};
|
||||
|
||||
#endif // AIRPORT_DETECTOR_COLLISION_DETECTOR_H
|
||||
47
src/detector/CollisionTypes.h
Normal file
47
src/detector/CollisionTypes.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
// 碰撞类型
|
||||
enum class CollisionType {
|
||||
HEAD_ON, // 相向碰撞
|
||||
CROSSING, // 交叉碰撞
|
||||
STATIC, // 静态碰撞
|
||||
PARALLEL // 平行运动
|
||||
};
|
||||
|
||||
// 碰撞时刻的物体状态
|
||||
struct CollisionObjectState {
|
||||
Vector2D position; // 位置
|
||||
double speed; // 速度
|
||||
double heading; // 航向
|
||||
|
||||
CollisionObjectState() : speed(0.0), heading(0.0) {}
|
||||
CollisionObjectState(const Vector2D& pos, double spd, double hdg)
|
||||
: position(pos), speed(spd), heading(hdg) {}
|
||||
};
|
||||
|
||||
// 碰撞预测结果
|
||||
struct CollisionResult {
|
||||
bool willCollide; // 是否会碰撞
|
||||
double timeToCollision; // 碰撞时间(从当前时刻开始的秒数)
|
||||
Vector2D collisionPoint; // 碰撞点位置
|
||||
double minDistance; // 最小距离
|
||||
double distance; // 当前距离
|
||||
double timeToMinDistance; // 到达最小距离的时间(从当前时刻开始的秒数)
|
||||
CollisionType type; // 碰撞类型
|
||||
CollisionObjectState object1State; // 物体1在碰撞时刻的状态
|
||||
CollisionObjectState object2State; // 物体2在碰撞时刻的状态
|
||||
|
||||
// 构造函数
|
||||
CollisionResult() :
|
||||
willCollide(false),
|
||||
timeToCollision(std::numeric_limits<double>::infinity()),
|
||||
collisionPoint({0, 0}),
|
||||
minDistance(std::numeric_limits<double>::infinity()),
|
||||
distance(std::numeric_limits<double>::infinity()),
|
||||
timeToMinDistance(std::numeric_limits<double>::infinity()),
|
||||
type(CollisionType::STATIC) {}
|
||||
};
|
||||
117
src/detector/RectangleCollision.h
Normal file
117
src/detector/RectangleCollision.h
Normal file
@ -0,0 +1,117 @@
|
||||
#ifndef AIRPORT_DETECTOR_RECTANGLE_COLLISION_H
|
||||
#define AIRPORT_DETECTOR_RECTANGLE_COLLISION_H
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
// 碰撞检测结果
|
||||
struct RectangleCollisionResult {
|
||||
bool willCollide = false; // 是否会发生碰撞
|
||||
double timeToCollision = 0.0; // 到碰撞的时间
|
||||
Vector2D collisionPoint = {0, 0}; // 碰撞点
|
||||
double minDistance = 0.0; // 最小距离
|
||||
};
|
||||
|
||||
// 正方形表示
|
||||
struct Rectangle {
|
||||
Vector2D center; // 中心点
|
||||
double size; // 边长
|
||||
Vector2D velocity; // 速度向量
|
||||
double heading; // 朝向(角度)
|
||||
|
||||
// 构造函数
|
||||
Rectangle(const Vector2D& c, double s, const Vector2D& v, double h = 0.0)
|
||||
: center(c), size(s), velocity(v), heading(h) {
|
||||
}
|
||||
|
||||
// 默认构造函数
|
||||
Rectangle() : center({0, 0}), size(0), velocity({0, 0}), heading(0) {}
|
||||
|
||||
// 获取正方形的AABB(轴对齐包围盒)
|
||||
// 正方形不需要考虑朝向,因为旋转后的正方形AABB保持不变
|
||||
void getAABB(double& x_min, double& x_max, double& y_min, double& y_max) const {
|
||||
double half_size = size / 2.0;
|
||||
x_min = center.x - half_size;
|
||||
x_max = center.x + half_size;
|
||||
y_min = center.y - half_size;
|
||||
y_max = center.y + half_size;
|
||||
}
|
||||
|
||||
// 预测t时刻的位置
|
||||
Rectangle predictPosition(double t) const {
|
||||
Rectangle future = *this;
|
||||
future.center.x += velocity.x * t;
|
||||
future.center.y += velocity.y * t;
|
||||
return future;
|
||||
}
|
||||
};
|
||||
|
||||
// 预测两个矩形在给定时间窗口内是否会发生碰撞
|
||||
inline bool predictRectangleBasedCollision(const Rectangle& rect1, const Rectangle& rect2, double timeWindow) {
|
||||
// 1. 首先检查当前是否碰撞
|
||||
double x1_min, x1_max, y1_min, y1_max;
|
||||
double x2_min, x2_max, y2_min, y2_max;
|
||||
|
||||
rect1.getAABB(x1_min, x1_max, y1_min, y1_max);
|
||||
rect2.getAABB(x2_min, x2_max, y2_min, y2_max);
|
||||
|
||||
// 检查当前是否重叠
|
||||
if (x1_max >= x2_min && x2_max >= x1_min &&
|
||||
y1_max >= y2_min && y2_max >= y1_min) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. 检查未来一段时间内是否会碰撞
|
||||
// 计算相对速度
|
||||
double rel_vx = rect1.velocity.x - rect2.velocity.x;
|
||||
double rel_vy = rect1.velocity.y - rect2.velocity.y;
|
||||
double rel_speed = std::sqrt(rel_vx*rel_vx + rel_vy*rel_vy);
|
||||
|
||||
// 如果相对速度接近0,使用当前距离判断
|
||||
if (rel_speed < 0.1) {
|
||||
double dx = rect1.center.x - rect2.center.x;
|
||||
double dy = rect1.center.y - rect2.center.y;
|
||||
double distance = std::sqrt(dx*dx + dy*dy);
|
||||
double safe_distance = rect1.size/2.0 + rect2.size/2.0; // 两个正方形从中心到边缘的距离之和
|
||||
|
||||
if (distance <= safe_distance) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 在时间窗口内采样检查
|
||||
const int STEPS = 60;
|
||||
double dt = timeWindow / STEPS;
|
||||
|
||||
for (int i = 1; i <= STEPS; ++i) {
|
||||
double t = i * dt;
|
||||
Rectangle future1 = rect1.predictPosition(t);
|
||||
Rectangle future2 = rect2.predictPosition(t);
|
||||
|
||||
future1.getAABB(x1_min, x1_max, y1_min, y1_max);
|
||||
future2.getAABB(x2_min, x2_max, y2_min, y2_max);
|
||||
|
||||
// 检查是否重叠
|
||||
if (x1_max >= x2_min && x2_max >= x1_min &&
|
||||
y1_max >= y2_min && y2_max >= y1_min) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 计算中心点距离
|
||||
double dx = (future1.center.x - future2.center.x);
|
||||
double dy = (future1.center.y - future2.center.y);
|
||||
double distance = std::sqrt(dx*dx + dy*dy);
|
||||
double safe_distance = rect1.size/2.0 + rect2.size/2.0; // 两个正方形从中心到边缘的距离之和
|
||||
|
||||
if (distance <= safe_distance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // AIRPORT_DETECTOR_RECTANGLE_COLLISION_H
|
||||
126
src/detector/SafetyZone.cpp
Normal file
126
src/detector/SafetyZone.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
#include "SafetyZone.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "config/SystemConfig.h"
|
||||
#include <cmath>
|
||||
|
||||
SafetyZone::SafetyZone(const Vector2D& center, double aircraftRadius, double specialVehicleRadius)
|
||||
: center_(center)
|
||||
, aircraftRadius_(aircraftRadius)
|
||||
, specialVehicleRadius_(specialVehicleRadius)
|
||||
, currentRadius_(0.0)
|
||||
, state_(SafetyZoneState::INACTIVE)
|
||||
, type_(SafetyZoneType::NONE) {
|
||||
|
||||
Logger::debug("创建安全区: center=(", center_.x, ",", center_.y,
|
||||
"), aircraftRadius=", aircraftRadius_,
|
||||
", specialVehicleRadius=", specialVehicleRadius_);
|
||||
}
|
||||
|
||||
bool SafetyZone::isInZone(const Vector2D& point) const {
|
||||
// 如果未设置类型,直接返回 false
|
||||
if (type_ == SafetyZoneType::NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算点到中心的距离
|
||||
double dx = point.x - center_.x;
|
||||
double dy = point.y - center_.y;
|
||||
double distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 使用当前设置的安全区半径
|
||||
return distance <= currentRadius_;
|
||||
}
|
||||
|
||||
void SafetyZone::resetType() {
|
||||
type_ = SafetyZoneType::NONE;
|
||||
currentRadius_ = 0.0;
|
||||
Logger::debug("重置安全区类型为 NONE");
|
||||
}
|
||||
|
||||
double SafetyZone::getObjectRadius(const MovingObject& object) const {
|
||||
const auto& config = SystemConfig::instance().collision_detection.prediction;
|
||||
if (object.isAircraft()) {
|
||||
double radius = config.aircraft_size / 2.0;
|
||||
Logger::debug("目标 ", object.id, " 是飞机,尺寸半径: ", radius);
|
||||
return radius;
|
||||
} else if (object.isSpecialVehicle()) {
|
||||
double radius = config.vehicle_size / 2.0;
|
||||
Logger::debug("目标 ", object.id, " 是特勤车,尺寸半径: ", radius);
|
||||
return radius;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
bool SafetyZone::isTypeMatched(const MovingObject& object) const {
|
||||
// 如果安全区类型未设置,返回 true
|
||||
if (type_ == SafetyZoneType::NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果是无人车,返回 true
|
||||
if (object.isUnmannedVehicle()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果安全区类型已设置,检查是否匹配
|
||||
if ((type_ == SafetyZoneType::AIRCRAFT && !object.isAircraft()) ||
|
||||
(type_ == SafetyZoneType::VEHICLE && !object.isSpecialVehicle())) {
|
||||
Logger::debug("目标 ", object.id, " 类型与安全区类型不匹配,当前安全区类型: ",
|
||||
type_ == SafetyZoneType::AIRCRAFT ? "飞机" : "特勤车");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SafetyZone::isObjectInZone(const MovingObject& object) const {
|
||||
// 计算点到中心的距离
|
||||
double dx = object.position.x - center_.x;
|
||||
double dy = object.position.y - center_.y;
|
||||
double distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 如果安全区类型未设置,使用对应的临时半径进行检查
|
||||
double checkRadius = (type_ == SafetyZoneType::NONE)
|
||||
? (object.isAircraft() ? aircraftRadius_ : specialVehicleRadius_)
|
||||
: currentRadius_;
|
||||
|
||||
Logger::debug("检查目标 ", object.id, " 是否在安全区内: ",
|
||||
"dx=", dx, ", dy=", dy, ", distance=", distance,
|
||||
", checkRadius=", checkRadius,
|
||||
", type=", type_ == SafetyZoneType::NONE ? "NONE" :
|
||||
(type_ == SafetyZoneType::AIRCRAFT ? "AIRCRAFT" : "VEHICLE"),
|
||||
", center=(", center_.x, ",", center_.y, ")",
|
||||
", object=(", object.position.x, ",", object.position.y, ")");
|
||||
|
||||
return distance <= checkRadius;
|
||||
}
|
||||
|
||||
bool SafetyZone::tryActivate(const MovingObject& object) {
|
||||
// 如果是无人车,不激活安全区
|
||||
if (object.isUnmannedVehicle()) {
|
||||
Logger::debug("目标 ", object.id, " 是无人车,不激活安全区");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果类型已设置且不匹配,不激活
|
||||
if (!isTypeMatched(object)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在区内
|
||||
bool inZone = isObjectInZone(object);
|
||||
if (!inZone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果安全区类型未设置,设置新的类型和半径
|
||||
if (type_ == SafetyZoneType::NONE) {
|
||||
type_ = object.isAircraft() ? SafetyZoneType::AIRCRAFT : SafetyZoneType::VEHICLE;
|
||||
currentRadius_ = object.isAircraft() ? aircraftRadius_ : specialVehicleRadius_;
|
||||
Logger::debug("安全区设置为", object.isAircraft() ? "飞机" : "特勤车", "类型,半径: ", currentRadius_);
|
||||
}
|
||||
|
||||
// 激活安全区
|
||||
state_ = SafetyZoneState::ACTIVE;
|
||||
return true;
|
||||
}
|
||||
66
src/detector/SafetyZone.h
Normal file
66
src/detector/SafetyZone.h
Normal file
@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
|
||||
// 安全区状态
|
||||
enum class SafetyZoneState {
|
||||
INACTIVE, // 未激活
|
||||
ACTIVE // 已激活
|
||||
};
|
||||
|
||||
// 安全区类型
|
||||
enum class SafetyZoneType {
|
||||
NONE, // 未设置
|
||||
AIRCRAFT, // 飞机安全区
|
||||
VEHICLE // 特勤车安全区
|
||||
};
|
||||
|
||||
// 安全区定义
|
||||
class SafetyZone {
|
||||
public:
|
||||
// 构造函数
|
||||
SafetyZone(const Vector2D& center, double aircraftSize, double specialVehicleSize);
|
||||
|
||||
// 获取状态
|
||||
SafetyZoneState getState() const { return state_; }
|
||||
SafetyZoneType getType() const { return type_; }
|
||||
|
||||
// 设置状态
|
||||
void setState(SafetyZoneState state) { state_ = state; }
|
||||
|
||||
// 重置类型为 NONE
|
||||
void resetType();
|
||||
|
||||
// 检查点是否在安全区内
|
||||
bool isInZone(const Vector2D& point) const;
|
||||
|
||||
// 检查目标是否在安全区内 - 纯检测函数,不修改状态
|
||||
bool isObjectInZone(const MovingObject& object) const;
|
||||
|
||||
// 尝试更新安全区状态 - 处理状态更新
|
||||
bool tryActivate(const MovingObject& object);
|
||||
|
||||
// 获取中心点
|
||||
const Vector2D& getCenter() const { return center_; }
|
||||
|
||||
// 获取安全区尺寸配置
|
||||
double getAircraftRadius() const { return aircraftRadius_; }
|
||||
double getSpecialVehicleRadius() const { return specialVehicleRadius_; }
|
||||
|
||||
// 获取当前安全区半径
|
||||
double getCurrentRadius() const { return currentRadius_; }
|
||||
|
||||
private:
|
||||
Vector2D center_; // 安全区中心点
|
||||
double aircraftRadius_; // 飞机安全区半径
|
||||
double specialVehicleRadius_; // 特勤车安全区半径
|
||||
double currentRadius_; // 当前安全区半径
|
||||
SafetyZoneState state_; // 当前状态
|
||||
SafetyZoneType type_; // 当前类型
|
||||
|
||||
// 检查物体类型是否匹配当前安全区类型
|
||||
bool isTypeMatched(const MovingObject& object) const;
|
||||
|
||||
// 获取物体的有效半径
|
||||
double getObjectRadius(const MovingObject& object) const;
|
||||
};
|
||||
72
src/detector/TrafficLightDetector.cpp
Normal file
72
src/detector/TrafficLightDetector.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "detector/TrafficLightDetector.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "types/BasicTypes.h"
|
||||
#include "core/System.h"
|
||||
#include <chrono>
|
||||
|
||||
TrafficLightDetector::TrafficLightDetector(const IntersectionConfig& intersectionConfig,
|
||||
ControllableVehicles& controllableVehicles,
|
||||
System& system)
|
||||
: intersection_config_(intersectionConfig)
|
||||
, controllable_vehicles_(controllableVehicles)
|
||||
, system_(system) {}
|
||||
|
||||
void TrafficLightDetector::processSignal(const TrafficLightSignal& signal,
|
||||
const std::vector<Vehicle>& vehicles) {
|
||||
// 根据红绿灯ID查找对应的路口
|
||||
const Intersection* intersection = intersection_config_.findByTrafficLightId(signal.trafficLightId);
|
||||
if (!intersection) {
|
||||
Logger::warning("未找到红绿灯对应的路口: ", signal.trafficLightId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存当前处理的信号
|
||||
current_signal_ = signal;
|
||||
|
||||
// 检查每个车辆
|
||||
for (const auto& vehicle : vehicles) {
|
||||
if (controllable_vehicles_.isControllable(vehicle.vehicleNo)) {
|
||||
// 根据信号灯状态发送指令
|
||||
// FIXME: 临时修改 - 仅根据南北向状态发送指令,未考虑车辆方向和东西向状态。
|
||||
// 需要根据车辆行驶方向判断应该参考 ns_status 还是 ew_status。
|
||||
switch (signal.ns_status) {
|
||||
case SignalStatus::RED:
|
||||
sendSignalCommand(vehicle.vehicleNo, SignalState::RED);
|
||||
break;
|
||||
case SignalStatus::GREEN:
|
||||
sendSignalCommand(vehicle.vehicleNo, SignalState::GREEN);
|
||||
break;
|
||||
case SignalStatus::YELLOW:
|
||||
sendSignalCommand(vehicle.vehicleNo, SignalState::YELLOW);
|
||||
break;
|
||||
default:
|
||||
Logger::warning("未知的信号灯状态");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrafficLightDetector::sendSignalCommand(const std::string& vehicleNo, SignalState state) {
|
||||
VehicleCommand cmd;
|
||||
cmd.vehicleId = vehicleNo;
|
||||
cmd.type = CommandType::SIGNAL;
|
||||
cmd.reason = CommandReason::TRAFFIC_LIGHT;
|
||||
cmd.signalState = state;
|
||||
cmd.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
// 添加路口信息
|
||||
const Intersection* intersection = intersection_config_.findByTrafficLightId(current_signal_.trafficLightId);
|
||||
if (intersection) {
|
||||
cmd.intersectionId = intersection->id;
|
||||
cmd.latitude = intersection->position.latitude;
|
||||
cmd.longitude = intersection->position.longitude;
|
||||
}
|
||||
|
||||
controllable_vehicles_.sendCommand(vehicleNo, cmd);
|
||||
system_.broadcastVehicleCommand(cmd);
|
||||
Logger::debug("发送信号灯指令到车辆: ", vehicleNo,
|
||||
" 路口: ", cmd.intersectionId,
|
||||
" 状态: ", state == SignalState::RED ? "红灯" : state == SignalState::GREEN ? "绿灯" : "黄灯");
|
||||
}
|
||||
27
src/detector/TrafficLightDetector.h
Normal file
27
src/detector/TrafficLightDetector.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "config/IntersectionConfig.h"
|
||||
#include "vehicle/ControllableVehicles.h"
|
||||
#include "types/TrafficLightTypes.h"
|
||||
#include <vector>
|
||||
|
||||
class System;
|
||||
|
||||
class TrafficLightDetector {
|
||||
public:
|
||||
TrafficLightDetector(const IntersectionConfig& intersectionConfig,
|
||||
ControllableVehicles& controllableVehicles,
|
||||
System& system);
|
||||
|
||||
void processSignal(const TrafficLightSignal& signal,
|
||||
const std::vector<Vehicle>& vehicles);
|
||||
|
||||
private:
|
||||
const IntersectionConfig& intersection_config_;
|
||||
ControllableVehicles& controllable_vehicles_;
|
||||
System& system_;
|
||||
TrafficLightSignal current_signal_;
|
||||
|
||||
// 发送信号灯指令
|
||||
void sendSignalCommand(const std::string& vehicleNo, SignalState state);
|
||||
};
|
||||
67
src/main.cpp
Normal file
67
src/main.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "core/System.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "config/SystemConfig.h"
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <csignal>
|
||||
#include <filesystem>
|
||||
|
||||
volatile sig_atomic_t running = true;
|
||||
|
||||
void signalHandler(int signum) {
|
||||
std::cout << "Interrupt signal (" << signum << ") received.\n";
|
||||
running = false;
|
||||
}
|
||||
|
||||
LogLevel getLogLevelFromString(const std::string& level) {
|
||||
if (level == "debug") return LogLevel::DEBUG;
|
||||
if (level == "info") return LogLevel::INFO;
|
||||
if (level == "warning") return LogLevel::WARNING;
|
||||
if (level == "error") return LogLevel::ERROR;
|
||||
return LogLevel::INFO; // 默认级别
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
try {
|
||||
// 设置信号处理
|
||||
signal(SIGINT, signalHandler);
|
||||
signal(SIGTERM, signalHandler);
|
||||
|
||||
// 创建日志目录
|
||||
std::filesystem::create_directories("logs");
|
||||
|
||||
// 加载系统配置
|
||||
SystemConfig::instance().load("config/system_config.json");
|
||||
|
||||
// 初始化日志系统
|
||||
Logger::initialize(SystemConfig::instance().logging.file, getLogLevelFromString(SystemConfig::instance().logging.level));
|
||||
Logger::info("Starting system...");
|
||||
Logger::debug("Log level set to: ", SystemConfig::instance().logging.level);
|
||||
|
||||
// 初始化系统
|
||||
System system;
|
||||
if (!system.initialize()) {
|
||||
Logger::error("Failed to initialize system");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 启动系统
|
||||
system.start();
|
||||
Logger::info("System running...");
|
||||
|
||||
// 主循环
|
||||
while (running) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
// 停止系统
|
||||
system.stop();
|
||||
Logger::info("System stopped");
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "Fatal error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
168
src/mock/MockDataService.cpp
Normal file
168
src/mock/MockDataService.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
#include "mock/MockDataService.h"
|
||||
#include <format>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
MockDataService::MockDataService(const AirportBounds& bounds)
|
||||
: bounds_(bounds)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<Aircraft> MockDataService::generateAircraftData() {
|
||||
std::vector<Aircraft> aircraft;
|
||||
|
||||
// 生成跑道上的航空器
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Aircraft a;
|
||||
a.flightNo = generateAircraftId();
|
||||
a.position = generatePosition(AreaType::RUNWAY);
|
||||
a.speed = generateSpeed(AreaType::RUNWAY);
|
||||
a.heading = generateHeading(AreaType::RUNWAY);
|
||||
a.altitude = generateAltitude(AreaType::RUNWAY);
|
||||
aircraft.push_back(a);
|
||||
}
|
||||
|
||||
// 生成滑行道上的航空器
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Aircraft a;
|
||||
a.flightNo = generateAircraftId();
|
||||
a.position = generatePosition(AreaType::TAXIWAY);
|
||||
a.speed = generateSpeed(AreaType::TAXIWAY);
|
||||
a.heading = generateHeading(AreaType::TAXIWAY);
|
||||
a.altitude = generateAltitude(AreaType::TAXIWAY);
|
||||
aircraft.push_back(a);
|
||||
}
|
||||
|
||||
// 生成停机位的航空器
|
||||
for (int i = 0; i < 15; ++i) {
|
||||
Aircraft a;
|
||||
a.flightNo = generateAircraftId();
|
||||
a.position = generatePosition(AreaType::GATE);
|
||||
a.speed = generateSpeed(AreaType::GATE);
|
||||
a.heading = generateHeading(AreaType::GATE);
|
||||
a.altitude = generateAltitude(AreaType::GATE);
|
||||
aircraft.push_back(a);
|
||||
}
|
||||
|
||||
return aircraft;
|
||||
}
|
||||
|
||||
std::vector<Vehicle> MockDataService::generateVehicleData() {
|
||||
std::vector<Vehicle> vehicles;
|
||||
|
||||
// 生成服务区的车辆
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
Vehicle v;
|
||||
v.vehicleNo = generateVehicleId();
|
||||
v.position = generatePosition(AreaType::SERVICE);
|
||||
v.speed = generateSpeed(AreaType::SERVICE);
|
||||
v.heading = generateHeading(AreaType::SERVICE);
|
||||
vehicles.push_back(v);
|
||||
}
|
||||
|
||||
// 生成停机位的车辆
|
||||
for (int i = 0; i < 15; ++i) {
|
||||
Vehicle v;
|
||||
v.vehicleNo = generateVehicleId();
|
||||
v.position = generatePosition(AreaType::GATE);
|
||||
v.speed = generateSpeed(AreaType::GATE);
|
||||
v.heading = generateHeading(AreaType::GATE);
|
||||
vehicles.push_back(v);
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
Vector2D MockDataService::generatePosition(AreaType areaType) {
|
||||
const auto& config = bounds_.getAreaConfig(areaType);
|
||||
const auto& areaBounds = bounds_.getAreaBounds(areaType);
|
||||
|
||||
std::uniform_real_distribution<double> x_dist(areaBounds.x,
|
||||
areaBounds.x + areaBounds.width);
|
||||
std::uniform_real_distribution<double> y_dist(areaBounds.y,
|
||||
areaBounds.y + areaBounds.height);
|
||||
|
||||
return {x_dist(rng_), y_dist(rng_)};
|
||||
}
|
||||
|
||||
double MockDataService::generateSpeed(AreaType areaType) {
|
||||
// 根据区域类型返回合适的速度范围
|
||||
switch (areaType) {
|
||||
case AreaType::RUNWAY:
|
||||
return std::uniform_real_distribution<double>{30.0, 80.0}(rng_);
|
||||
case AreaType::TAXIWAY:
|
||||
return std::uniform_real_distribution<double>{5.0, 15.0}(rng_);
|
||||
case AreaType::GATE:
|
||||
return std::uniform_real_distribution<double>{0.0, 5.0}(rng_);
|
||||
case AreaType::SERVICE:
|
||||
return std::uniform_real_distribution<double>{0.0, 10.0}(rng_);
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
double MockDataService::generateHeading(AreaType areaType) {
|
||||
// 根据区域类型返回合适的航向范围
|
||||
switch (areaType) {
|
||||
case AreaType::RUNWAY:
|
||||
// 跑道通常只有两个方向
|
||||
return std::uniform_int_distribution<int>{0, 1}(rng_) * 180.0;
|
||||
default:
|
||||
// 其他区域可以是任意方向
|
||||
return std::uniform_real_distribution<double>{0.0, 360.0}(rng_);
|
||||
}
|
||||
}
|
||||
|
||||
double MockDataService::generateAltitude(AreaType areaType) {
|
||||
// 根据区域类型返回合适的高度范围
|
||||
switch (areaType) {
|
||||
case AreaType::RUNWAY:
|
||||
return std::uniform_real_distribution<double>{0.0, 15.0}(rng_);
|
||||
case AreaType::TAXIWAY:
|
||||
return std::uniform_real_distribution<double>{0.0, 5.0}(rng_);
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string MockDataService::generateAircraftId() {
|
||||
std::stringstream ss;
|
||||
ss << "AC" << std::setw(4) << std::setfill('0') << ++aircraftCounter_;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string MockDataService::generateVehicleId() {
|
||||
std::stringstream ss;
|
||||
ss << "VH" << std::setw(4) << std::setfill('0') << ++vehicleCounter_;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::vector<TrafficLightSignal> MockDataService::generateTrafficLightData() {
|
||||
std::vector<TrafficLightSignal> signals;
|
||||
|
||||
// 生成 5-10 个红绿灯信号
|
||||
std::uniform_int_distribution<> numSignals(5, 10);
|
||||
int count = numSignals(rng_);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
TrafficLightSignal signal;
|
||||
signal.trafficLightId = generateTrafficLightId();
|
||||
signal.status = generateTrafficLightState();
|
||||
signal.timestamp = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
signals.push_back(signal);
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
std::string MockDataService::generateTrafficLightId() {
|
||||
std::stringstream ss;
|
||||
ss << "TL" << std::setw(4) << std::setfill('0') << ++trafficLightCounter_;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
SignalStatus MockDataService::generateTrafficLightState() {
|
||||
std::uniform_int_distribution<> dist(0, 2);
|
||||
int state = dist(rng_);
|
||||
return static_cast<SignalStatus>(state);
|
||||
}
|
||||
43
src/mock/MockDataService.h
Normal file
43
src/mock/MockDataService.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef AIRPORT_MOCK_MOCKDATASERVICE_H
|
||||
#define AIRPORT_MOCK_MOCKDATASERVICE_H
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
#include "config/AirportBounds.h"
|
||||
#include "types/TrafficLightTypes.h"
|
||||
#include <vector>
|
||||
#include <random>
|
||||
|
||||
class MockDataService {
|
||||
public:
|
||||
explicit MockDataService(const AirportBounds& bounds);
|
||||
|
||||
std::vector<Aircraft> generateAircraftData();
|
||||
std::vector<Vehicle> generateVehicleData();
|
||||
std::vector<TrafficLightSignal> generateTrafficLightData();
|
||||
|
||||
private:
|
||||
const AirportBounds& bounds_;
|
||||
std::mt19937 rng_{std::random_device{}()};
|
||||
|
||||
// 在指定区域内生成随机位置
|
||||
Vector2D generatePosition(AreaType areaType);
|
||||
|
||||
// 根据区域生成合适的速度和航向
|
||||
double generateSpeed(AreaType areaType);
|
||||
double generateHeading(AreaType areaType);
|
||||
double generateAltitude(AreaType areaType);
|
||||
|
||||
// 生成ID
|
||||
std::string generateAircraftId();
|
||||
std::string generateVehicleId();
|
||||
std::string generateTrafficLightId();
|
||||
|
||||
// 生成红绿灯状态
|
||||
SignalStatus generateTrafficLightState();
|
||||
|
||||
int aircraftCounter_ = 0;
|
||||
int vehicleCounter_ = 0;
|
||||
int trafficLightCounter_ = 0;
|
||||
};
|
||||
|
||||
#endif // AIRPORT_MOCK_MOCKDATASERVICE_H
|
||||
142
src/network/HTTPClient.cpp
Normal file
142
src/network/HTTPClient.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
#include "HTTPClient.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
std::string getSignalStateString(SignalState state) {
|
||||
switch (state) {
|
||||
case SignalState::RED:
|
||||
return "RED";
|
||||
case SignalState::YELLOW:
|
||||
return "YELLOW";
|
||||
case SignalState::GREEN:
|
||||
return "GREEN";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
HTTPClient::HTTPClient() {
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
curl_ = curl_easy_init();
|
||||
if (!curl_) {
|
||||
throw std::runtime_error("Failed to initialize CURL");
|
||||
}
|
||||
}
|
||||
|
||||
HTTPClient::~HTTPClient() {
|
||||
if (curl_) {
|
||||
curl_easy_cleanup(curl_);
|
||||
}
|
||||
curl_global_cleanup();
|
||||
}
|
||||
|
||||
size_t HTTPClient::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
size_t total_size = size * nmemb;
|
||||
std::string* response = static_cast<std::string*>(userp);
|
||||
response->append(static_cast<char*>(contents), total_size);
|
||||
return total_size;
|
||||
}
|
||||
|
||||
bool HTTPClient::sendCommand(const std::string& ip, int port, const VehicleCommand& command) const {
|
||||
if (!curl_) {
|
||||
Logger::error("CURL not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构造请求URL
|
||||
std::stringstream url;
|
||||
url << "http://" << ip << ":" << port << "/api/VehicleCommandInfo";
|
||||
|
||||
// 生成消息唯一 id
|
||||
std::stringstream transId;
|
||||
transId << std::hex << std::chrono::system_clock::now().time_since_epoch().count();
|
||||
|
||||
// 构造请求体(必填字段)
|
||||
nlohmann::json request = {
|
||||
{"transId", transId.str()},
|
||||
{"timestamp", command.timestamp},
|
||||
{"vehicleID", command.vehicleId},
|
||||
{"commandType", [&]() {
|
||||
switch (command.type) {
|
||||
case CommandType::SIGNAL: return "SIGNAL";
|
||||
case CommandType::ALERT: return "ALERT";
|
||||
case CommandType::WARNING: return "WARNING";
|
||||
case CommandType::RESUME: return "RESUME";
|
||||
case CommandType::PARKING: return "PARKING";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}()},
|
||||
{"commandReason", [&]() {
|
||||
switch (command.reason) {
|
||||
case CommandReason::TRAFFIC_LIGHT: return "TRAFFIC_LIGHT";
|
||||
case CommandReason::AIRCRAFT_CROSSING: return "AIRCRAFT_CROSSING";
|
||||
case CommandReason::SPECIAL_VEHICLE: return "SPECIAL_VEHICLE";
|
||||
case CommandReason::AIRCRAFT_PUSH: return "AIRCRAFT_PUSH";
|
||||
case CommandReason::RESUME_TRAFFIC: return "RESUME_TRAFFIC";
|
||||
case CommandReason::PARKING_SIDE: return "PARKING_SIDE";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}()},
|
||||
{"latitude", command.latitude},
|
||||
{"longitude", command.longitude},
|
||||
{"signalState", getSignalStateString(command.signalState)},
|
||||
{"intersectionId", command.intersectionId},
|
||||
{"relativeSpeed", command.relativeSpeed},
|
||||
{"relativeMotionX", command.relativeMotionX},
|
||||
{"relativeMotionY", command.relativeMotionY},
|
||||
{"minDistance", command.minDistance}
|
||||
};
|
||||
|
||||
std::string request_body = request.dump();
|
||||
response_buffer_.clear();
|
||||
|
||||
// 设置CURL选项
|
||||
curl_easy_setopt(curl_, CURLOPT_URL, url.str().c_str());
|
||||
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, request_body.c_str());
|
||||
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response_buffer_);
|
||||
|
||||
// 设置请求头
|
||||
struct curl_slist* headers = nullptr;
|
||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers);
|
||||
|
||||
// 发送请求
|
||||
CURLcode res = curl_easy_perform(curl_);
|
||||
curl_slist_free_all(headers);
|
||||
|
||||
if (res != CURLE_OK) {
|
||||
Logger::error("Failed to send command: ", curl_easy_strerror(res));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查响应状态码
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
if (http_code == 200) {
|
||||
try {
|
||||
// 解析响应
|
||||
nlohmann::json response = nlohmann::json::parse(response_buffer_);
|
||||
int code = response["code"].get<int>();
|
||||
std::string msg = response["msg"].get<std::string>();
|
||||
|
||||
if (code == 200) {
|
||||
Logger::debug("Command sent successfully: ", request_body);
|
||||
return true;
|
||||
} else {
|
||||
Logger::error("Command failed with code: ", code, " message: ", msg);
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to parse response: ", e.what());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Logger::error("Command failed with HTTP code: ", http_code, " response: ", response_buffer_);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
20
src/network/HTTPClient.h
Normal file
20
src/network/HTTPClient.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <curl/curl.h>
|
||||
#include "types/VehicleCommand.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
class HTTPClient {
|
||||
public:
|
||||
HTTPClient();
|
||||
~HTTPClient();
|
||||
|
||||
// 发送控制指令
|
||||
bool sendCommand(const std::string& ip, int port, const VehicleCommand& command) const;
|
||||
|
||||
private:
|
||||
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp);
|
||||
CURL* curl_;
|
||||
mutable std::string response_buffer_;
|
||||
};
|
||||
605
src/network/HTTPDataSource.cpp
Normal file
605
src/network/HTTPDataSource.cpp
Normal file
@ -0,0 +1,605 @@
|
||||
#include "HTTPDataSource.h"
|
||||
#include "HTTPClient.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
size_t HTTPDataSource::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
size_t total_size = size * nmemb;
|
||||
std::string* response = static_cast<std::string*>(userp);
|
||||
response->append(static_cast<char*>(contents), total_size);
|
||||
return total_size;
|
||||
}
|
||||
|
||||
size_t HTTPDataSource::DebugCallback(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
HTTPDataSource::HTTPDataSource(const DataSourceConfig& config)
|
||||
: config_(config)
|
||||
, last_health_check_(std::chrono::steady_clock::now()) {
|
||||
curl_ = curl_easy_init();
|
||||
if (!curl_) {
|
||||
Logger::error("Failed to initialize CURL");
|
||||
}
|
||||
}
|
||||
|
||||
HTTPDataSource::~HTTPDataSource() {
|
||||
disconnect();
|
||||
if (curl_) {
|
||||
curl_easy_cleanup(curl_);
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::connect() {
|
||||
if (!curl_) {
|
||||
curl_ = curl_easy_init();
|
||||
if (!curl_) {
|
||||
Logger::error("Failed to initialize CURL");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HTTPDataSource::disconnect() {
|
||||
if (curl_) {
|
||||
curl_easy_cleanup(curl_);
|
||||
curl_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::isAvailable() const {
|
||||
return curl_ != nullptr;
|
||||
}
|
||||
|
||||
bool HTTPDataSource::tryReconnect() {
|
||||
if (is_reconnecting_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
is_reconnecting_ = true;
|
||||
|
||||
try {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_connect_attempt_).count();
|
||||
|
||||
if (elapsed < config_.position.timeout_ms) {
|
||||
is_reconnecting_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
last_connect_attempt_ = now;
|
||||
|
||||
for (int retry = 0; retry < MAX_RETRIES; ++retry) {
|
||||
if (connect()) {
|
||||
is_reconnecting_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (retry < MAX_RETRIES - 1) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(config_.position.timeout_ms / 3));
|
||||
}
|
||||
}
|
||||
|
||||
Logger::error("Failed to reconnect after ", MAX_RETRIES, " attempts");
|
||||
is_reconnecting_ = false;
|
||||
return false;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Error during reconnection: ", e.what());
|
||||
is_reconnecting_ = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::checkConnectionHealth() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now - last_health_check_).count();
|
||||
|
||||
if (elapsed < HEALTH_CHECK_INTERVAL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
last_health_check_ = now;
|
||||
return curl_ != nullptr;
|
||||
}
|
||||
|
||||
bool HTTPDataSource::ensureConnected() {
|
||||
if (isAvailable() && checkConnectionHealth()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return tryReconnect();
|
||||
}
|
||||
|
||||
bool HTTPDataSource::sendRequest(const std::string& url, const AuthState* auth_state,
|
||||
std::string& response, HttpMethod method, const std::string& body) {
|
||||
if (!curl_) {
|
||||
Logger::error("CURL not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_easy_reset(curl_);
|
||||
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, config_.position.timeout_ms / 1000);
|
||||
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, config_.position.timeout_ms / 1000);
|
||||
curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L);
|
||||
|
||||
if (method == HttpMethod::GET) {
|
||||
curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1L);
|
||||
curl_easy_setopt(curl_, CURLOPT_POST, 0L);
|
||||
} else if (method == HttpMethod::POST) {
|
||||
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl_, CURLOPT_HTTPGET, 0L);
|
||||
if (!body.empty()) {
|
||||
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, body.c_str());
|
||||
} else {
|
||||
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, "");
|
||||
}
|
||||
}
|
||||
|
||||
struct curl_slist* headers = nullptr;
|
||||
headers = curl_slist_append(headers, "Accept: application/json");
|
||||
headers = curl_slist_append(headers, "Cache-Control: no-cache");
|
||||
headers = curl_slist_append(headers, "Expect:");
|
||||
|
||||
if (method == HttpMethod::POST) {
|
||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||
}
|
||||
|
||||
if (auth_state && auth_state->is_authenticated && !auth_state->token.empty()) {
|
||||
std::string auth_header = "Authorization: " + auth_state->token;
|
||||
headers = curl_slist_append(headers, auth_header.c_str());
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers);
|
||||
|
||||
response.clear();
|
||||
CURLcode res = curl_easy_perform(curl_);
|
||||
curl_slist_free_all(headers);
|
||||
|
||||
if (res != CURLE_OK) {
|
||||
Logger::error("请求失败: ", curl_easy_strerror(res));
|
||||
return false;
|
||||
}
|
||||
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
if (http_code != 200) {
|
||||
Logger::error("HTTP 请求失败,状态码: ", http_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HTTPDataSource::fetchPositionAircraftData(std::vector<Aircraft>& aircraft) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!ensureConnected()) {
|
||||
Logger::error("连接失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ensureAuthenticated(config_.position.auth,
|
||||
config_.position.host,
|
||||
config_.position.port,
|
||||
position_auth_,
|
||||
DataSourceType::POSITION)) {
|
||||
Logger::error("认证失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = "http://" + config_.position.host + ":" +
|
||||
std::to_string(config_.position.port) +
|
||||
config_.position.aircraft_path;
|
||||
|
||||
std::string response;
|
||||
if (!sendRequest(url, &position_auth_, response, HttpMethod::GET)) {
|
||||
Logger::error("请求失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsePositionAircraftResponse(response, aircraft);
|
||||
}
|
||||
|
||||
bool HTTPDataSource::fetchPositionVehicleData(std::vector<Vehicle>& vehicles) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!ensureConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ensureAuthenticated(config_.position.auth,
|
||||
config_.position.host,
|
||||
config_.position.port,
|
||||
position_auth_,
|
||||
DataSourceType::POSITION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = "http://" + config_.position.host + ":" +
|
||||
std::to_string(config_.position.port) +
|
||||
config_.position.vehicle_path;
|
||||
|
||||
std::string response;
|
||||
if (!sendRequest(url, &position_auth_, response, HttpMethod::GET)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsePositionVehicleResponse(response, vehicles);
|
||||
}
|
||||
|
||||
bool HTTPDataSource::fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!ensureConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ensureAuthenticated(config_.traffic_light.auth,
|
||||
config_.traffic_light.host,
|
||||
config_.traffic_light.port,
|
||||
traffic_light_auth_,
|
||||
DataSourceType::TRAFFIC_LIGHT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = "http://" + config_.traffic_light.host + ":" +
|
||||
std::to_string(config_.traffic_light.port) +
|
||||
config_.traffic_light.signal_path;
|
||||
|
||||
std::string response;
|
||||
if (!sendRequest(url, &traffic_light_auth_, response, HttpMethod::GET)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parseTrafficLightResponse(response, signals);
|
||||
}
|
||||
|
||||
std::string getSignalStateString(SignalState state) {
|
||||
switch (state) {
|
||||
case SignalState::RED:
|
||||
return "RED";
|
||||
case SignalState::YELLOW:
|
||||
return "YELLOW";
|
||||
case SignalState::GREEN:
|
||||
return "GREEN";
|
||||
default:
|
||||
return "GREEN"; // 默认为绿灯
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::sendUnmannedVehicleCommand(const std::string& vehicle_id, const VehicleCommand& command) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!ensureConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ensureAuthenticated(config_.vehicle.auth,
|
||||
config_.vehicle.host,
|
||||
config_.vehicle.port,
|
||||
unmanned_vehicle_auth_,
|
||||
DataSourceType::UNMANNED)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = "http://" + config_.vehicle.host + ":" +
|
||||
std::to_string(config_.vehicle.port) +
|
||||
config_.vehicle.command_path;
|
||||
|
||||
std::stringstream transId;
|
||||
transId << std::hex << std::chrono::system_clock::now().time_since_epoch().count();
|
||||
|
||||
nlohmann::json request = {
|
||||
{"transId", transId.str()},
|
||||
{"timestamp", command.timestamp},
|
||||
{"vehicleID", command.vehicleId},
|
||||
{"commandType", [&]() {
|
||||
switch (command.type) {
|
||||
case CommandType::SIGNAL: return "SIGNAL";
|
||||
case CommandType::ALERT: return "ALERT";
|
||||
case CommandType::WARNING: return "WARNING";
|
||||
case CommandType::RESUME: return "RESUME";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}()},
|
||||
{"commandReason", [&]() {
|
||||
switch (command.reason) {
|
||||
case CommandReason::TRAFFIC_LIGHT: return "TRAFFIC_LIGHT";
|
||||
case CommandReason::AIRCRAFT_CROSSING: return "AIRCRAFT_CROSSING";
|
||||
case CommandReason::SPECIAL_VEHICLE: return "SPECIAL_VEHICLE";
|
||||
case CommandReason::AIRCRAFT_PUSH: return "AIRCRAFT_PUSH";
|
||||
case CommandReason::RESUME_TRAFFIC: return "RESUME_TRAFFIC";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}()},
|
||||
{"latitude", command.latitude},
|
||||
{"longitude", command.longitude},
|
||||
{"signalState", getSignalStateString(command.signalState)},
|
||||
{"intersectionId", command.intersectionId}
|
||||
};
|
||||
|
||||
if (command.type == CommandType::ALERT || command.type == CommandType::WARNING) {
|
||||
request["relativeSpeed"] = command.relativeSpeed;
|
||||
request["relativeMotionX"] = command.relativeMotionX;
|
||||
request["relativeMotionY"] = command.relativeMotionY;
|
||||
request["minDistance"] = command.minDistance;
|
||||
}
|
||||
|
||||
std::string response;
|
||||
if (!sendRequest(url, &unmanned_vehicle_auth_, response, HttpMethod::POST, request.dump())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json j = nlohmann::json::parse(response);
|
||||
if (!j.contains("code") || !j.contains("msg")) {
|
||||
Logger::error("Invalid command response format");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j["code"].get<int>() != 200) {
|
||||
Logger::error("Command failed: ", j["msg"].get<std::string>());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to parse command response: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!ensureConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ensureAuthenticated(config_.vehicle.auth,
|
||||
config_.vehicle.host,
|
||||
config_.vehicle.port,
|
||||
unmanned_vehicle_auth_,
|
||||
DataSourceType::UNMANNED)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = "http://" + config_.vehicle.host + ":" +
|
||||
std::to_string(config_.vehicle.port) +
|
||||
config_.vehicle.status_path + "?vehicleId=" + vehicle_id;
|
||||
|
||||
std::string response;
|
||||
if (!sendRequest(url, &unmanned_vehicle_auth_, response, HttpMethod::GET)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json j = nlohmann::json::parse(response);
|
||||
if (!j.contains("status") || !j.contains("data")) {
|
||||
Logger::error("Invalid vehicle status response format");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j["status"].get<int>() != 200) {
|
||||
Logger::error("Failed to get vehicle status");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = j["data"].dump();
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to parse vehicle status response: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::authenticatePosition(const AuthConfig& auth_config,
|
||||
const std::string& host, int port, AuthState& auth_state) {
|
||||
if (!auth_config.auth_required) {
|
||||
auth_state.is_authenticated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(auth_mutex_);
|
||||
|
||||
if (!curl_) {
|
||||
Logger::error("CURL not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = "http://" + host + ":" + std::to_string(port) +
|
||||
auth_config.auth_path + "?username=" + auth_config.username +
|
||||
"&password=" + auth_config.password;
|
||||
|
||||
std::string response;
|
||||
if (!sendRequest(url, nullptr, response, HttpMethod::POST, "")) {
|
||||
Logger::error("认证请求失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json j = nlohmann::json::parse(response);
|
||||
if (!j.contains("status") || !j.contains("msg") || !j.contains("data")) {
|
||||
Logger::error("认证响应缺少必需字段");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j["status"].get<int>() != 200) {
|
||||
Logger::error("认证失败: ", j["msg"].get<std::string>());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string token = j["data"].get<std::string>();
|
||||
if (token.substr(0, 7) != "Bearer ") {
|
||||
token = "Bearer " + token;
|
||||
}
|
||||
auth_state.token = token;
|
||||
auth_state.is_authenticated = true;
|
||||
auth_state.last_auth_time = std::chrono::steady_clock::now();
|
||||
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("解析认证响应失败: ", e.what(), ", 响应内容: ", response);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::authenticateUnmanned(const AuthConfig& auth_config,
|
||||
const std::string& host, int port, AuthState& auth_state) {
|
||||
auth_state.is_authenticated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HTTPDataSource::authenticateTrafficLight(const AuthConfig& auth_config,
|
||||
const std::string& host, int port, AuthState& auth_state) {
|
||||
auth_state.is_authenticated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HTTPDataSource::ensureAuthenticated(const AuthConfig& auth_config, const std::string& host,
|
||||
int port, AuthState& auth_state, DataSourceType type) {
|
||||
if (!auth_config.auth_required) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!auth_state.is_authenticated) {
|
||||
switch (type) {
|
||||
case DataSourceType::POSITION:
|
||||
return authenticatePosition(auth_config, host, port, auth_state);
|
||||
case DataSourceType::UNMANNED:
|
||||
return authenticateUnmanned(auth_config, host, port, auth_state);
|
||||
case DataSourceType::TRAFFIC_LIGHT:
|
||||
return authenticateTrafficLight(auth_config, host, port, auth_state);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HTTPDataSource::parsePositionAircraftResponse(const std::string& response, std::vector<Aircraft>& aircraft) {
|
||||
try {
|
||||
nlohmann::json j = nlohmann::json::parse(response);
|
||||
if (!j.contains("status") || !j.contains("data")) {
|
||||
Logger::error("Invalid aircraft response format");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j["status"].get<int>() != 200) {
|
||||
Logger::error("Failed to get aircraft data: ", j["msg"].get<std::string>());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& data = j["data"];
|
||||
aircraft.clear();
|
||||
for (const auto& item : data) {
|
||||
Aircraft ac;
|
||||
ac.flightNo = item["flightNo"].get<std::string>();
|
||||
ac.id = ac.flightNo;
|
||||
ac.geo.longitude = item["longitude"].get<double>();
|
||||
ac.geo.latitude = item["latitude"].get<double>();
|
||||
ac.timestamp = item["time"].get<uint64_t>();
|
||||
|
||||
if (item.contains("altitude")) {
|
||||
ac.altitude = item["altitude"].get<double>();
|
||||
}
|
||||
if (item.contains("trackNumber")) {
|
||||
ac.trackNumber = item["trackNumber"].get<int64_t>();
|
||||
}
|
||||
aircraft.push_back(ac);
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to parse aircraft response: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::parsePositionVehicleResponse(const std::string& response, std::vector<Vehicle>& vehicles) {
|
||||
try {
|
||||
nlohmann::json j = nlohmann::json::parse(response);
|
||||
if (!j.contains("status") || !j.contains("data")) {
|
||||
Logger::error("Invalid vehicle response format");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j["status"].get<int>() != 200) {
|
||||
Logger::error("Failed to get vehicle data: ", j["msg"].get<std::string>());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& data = j["data"];
|
||||
vehicles.clear();
|
||||
for (const auto& item : data) {
|
||||
Vehicle vehicle;
|
||||
vehicle.vehicleNo = item["vehicleNo"].get<std::string>();
|
||||
vehicle.id = vehicle.vehicleNo;
|
||||
vehicle.geo.longitude = item["longitude"].get<double>();
|
||||
vehicle.geo.latitude = item["latitude"].get<double>();
|
||||
vehicle.timestamp = item["time"].get<uint64_t>();
|
||||
|
||||
if (item.contains("direction")) {
|
||||
vehicle.heading = item["direction"].get<double>();
|
||||
}
|
||||
if (item.contains("speed")) {
|
||||
vehicle.speed = item["speed"].get<double>();
|
||||
}
|
||||
vehicles.push_back(vehicle);
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to parse vehicle response: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPDataSource::parseTrafficLightResponse(const std::string& response, std::vector<TrafficLightSignal>& signals) {
|
||||
try {
|
||||
nlohmann::json j = nlohmann::json::parse(response);
|
||||
if (!j.contains("status") || !j.contains("data")) {
|
||||
Logger::error("Invalid traffic light response format");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j["status"].get<int>() != 200) {
|
||||
Logger::error("Failed to get traffic light data: ", j["msg"].get<std::string>());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& data = j["data"];
|
||||
signals.clear();
|
||||
for (const auto& item : data) {
|
||||
TrafficLightSignal signal;
|
||||
signal.trafficLightId = item["id"].get<std::string>();
|
||||
signal.ns_status = [&]() {
|
||||
int state = item["ns_status"].get<int>();
|
||||
switch (state) {
|
||||
case 0: return SignalStatus::RED;
|
||||
case 1: return SignalStatus::GREEN;
|
||||
case 2: return SignalStatus::YELLOW;
|
||||
default: return SignalStatus::UNKNOWN;
|
||||
}
|
||||
}();
|
||||
signal.timestamp = item["timestamp"].get<uint64_t>();
|
||||
signal.intersectionId = item["intersection"].get<std::string>();
|
||||
signal.latitude = item["position"]["latitude"].get<double>();
|
||||
signal.longitude = item["position"]["longitude"].get<double>();
|
||||
signals.push_back(signal);
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to parse traffic light response: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
96
src/network/HTTPDataSource.h
Normal file
96
src/network/HTTPDataSource.h
Normal file
@ -0,0 +1,96 @@
|
||||
#ifndef AIRPORT_NETWORK_HTTP_DATA_SOURCE_H
|
||||
#define AIRPORT_NETWORK_HTTP_DATA_SOURCE_H
|
||||
|
||||
#include "collector/DataSource.h"
|
||||
#include "spatial/CoordinateConverter.h"
|
||||
#include <string>
|
||||
#include "collector/DataSourceConfig.h"
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include "core/System.h"
|
||||
#include <curl/curl.h>
|
||||
#include "types/VehicleCommand.h"
|
||||
|
||||
class HTTPDataSource : public DataSource {
|
||||
public:
|
||||
enum class HttpMethod {
|
||||
GET,
|
||||
POST
|
||||
};
|
||||
|
||||
explicit HTTPDataSource(const DataSourceConfig& config);
|
||||
~HTTPDataSource() override;
|
||||
|
||||
const DataSourceConfig& getConfig() const { return config_; }
|
||||
|
||||
DataSourceConfig config_;
|
||||
bool connect() override;
|
||||
void disconnect() override;
|
||||
bool isAvailable() const override;
|
||||
|
||||
// 位置数据接口
|
||||
bool fetchPositionAircraftData(std::vector<Aircraft>& aircraft) override;
|
||||
bool fetchPositionVehicleData(std::vector<Vehicle>& vehicles) override;
|
||||
|
||||
// 无人车接口
|
||||
bool sendUnmannedVehicleCommand(const std::string& vehicle_id, const VehicleCommand& command) override;
|
||||
bool fetchUnmannedVehicleStatus(const std::string& vehicle_id, std::string& status) override;
|
||||
|
||||
// 红绿灯接口
|
||||
bool fetchTrafficLightSignals(std::vector<TrafficLightSignal>& signals) override;
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
CURL* curl_;
|
||||
|
||||
std::chrono::steady_clock::time_point last_connect_attempt_;
|
||||
std::chrono::steady_clock::time_point last_health_check_; // 上次健康检查时间
|
||||
static constexpr int MAX_RETRIES = 3; // 单次连接最大重试次数
|
||||
static constexpr int HEALTH_CHECK_INTERVAL = 60; // 健康检查间隔(秒)
|
||||
std::atomic<bool> is_reconnecting_{false}; // 重连状态标志
|
||||
|
||||
// 每个数据源的认证状态
|
||||
struct AuthState {
|
||||
bool is_authenticated = false;
|
||||
std::string token;
|
||||
std::chrono::steady_clock::time_point last_auth_time;
|
||||
};
|
||||
|
||||
AuthState position_auth_; // 位置数据源认证状态
|
||||
AuthState unmanned_vehicle_auth_; // 无人车数据源认证状态
|
||||
AuthState traffic_light_auth_; // 红绿灯数据源认证状态
|
||||
std::mutex auth_mutex_; // 认证相关的互斥锁
|
||||
|
||||
// 认证相关方法
|
||||
bool ensureAuthenticated(const AuthConfig& auth_config, const std::string& host,
|
||||
int port, AuthState& auth_state, DataSourceType type);
|
||||
|
||||
bool tryReconnect();
|
||||
bool ensureConnected();
|
||||
bool checkConnectionHealth(); // 检查连接健康状况
|
||||
|
||||
bool sendRequest(const std::string& url, const AuthState* auth_state,
|
||||
std::string& response, HttpMethod method = HttpMethod::GET,
|
||||
const std::string& body = "");
|
||||
|
||||
// 位置数据响应解析
|
||||
bool parsePositionAircraftResponse(const std::string& response, std::vector<Aircraft>& aircraft);
|
||||
bool parsePositionVehicleResponse(const std::string& response, std::vector<Vehicle>& vehicles);
|
||||
|
||||
// 红绿灯响应解析
|
||||
bool parseTrafficLightResponse(const std::string& response, std::vector<TrafficLightSignal>& signals);
|
||||
|
||||
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp);
|
||||
static size_t DebugCallback(CURL* handle, curl_infotype type, char* data, size_t size, void* userp);
|
||||
|
||||
// 不同数据源的认证方法
|
||||
bool authenticatePosition(const AuthConfig& auth_config, const std::string& host,
|
||||
int port, AuthState& auth_state);
|
||||
bool authenticateUnmanned(const AuthConfig& auth_config, const std::string& host,
|
||||
int port, AuthState& auth_state);
|
||||
bool authenticateTrafficLight(const AuthConfig& auth_config, const std::string& host,
|
||||
int port, AuthState& auth_state);
|
||||
};
|
||||
#endif // AIRPORT_NETWORK_HTTP_DATA_SOURCE_H
|
||||
|
||||
71
src/network/MessageTypes.h
Normal file
71
src/network/MessageTypes.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace network {
|
||||
|
||||
// 位置更新消息
|
||||
struct PositionUpdateMessage {
|
||||
std::string type = "position_update";
|
||||
std::string objectId;
|
||||
std::string objectType; // "aircraft" 或 "vehicle"
|
||||
double longitude;
|
||||
double latitude;
|
||||
double heading;
|
||||
double speed;
|
||||
uint64_t timestamp;
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
return {
|
||||
{"type", type},
|
||||
{"objectId", objectId},
|
||||
{"objectType", objectType},
|
||||
{"longitude", longitude},
|
||||
{"latitude", latitude},
|
||||
{"heading", heading},
|
||||
{"speed", speed},
|
||||
{"timestamp", timestamp}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 碰撞告警消息
|
||||
struct CollisionWarningMessage {
|
||||
std::string type = "collision_warning";
|
||||
std::string id1;
|
||||
std::string id2;
|
||||
std::string warningLevel; // "low", "medium", "high"
|
||||
double distance; // 当前距离
|
||||
double relativeSpeed; // 相对速度
|
||||
double threshold; // 预警距离阈值
|
||||
uint64_t timestamp;
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
return {
|
||||
{"type", type},
|
||||
{"id1", id1},
|
||||
{"id2", id2},
|
||||
{"warningLevel", warningLevel},
|
||||
{"distance", distance},
|
||||
{"relativeSpeed", relativeSpeed},
|
||||
{"threshold", threshold},
|
||||
{"timestamp", timestamp}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
struct TimeoutWarningMessage {
|
||||
std::string source; // 超时来源
|
||||
int64_t elapsed_ms; // 超时时长
|
||||
bool is_read_timeout; // 是否是读取超时
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
return {
|
||||
{"source", source},
|
||||
{"elapsed_ms", elapsed_ms},
|
||||
{"is_read_timeout", is_read_timeout}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace network
|
||||
17
src/network/NetworkInterface.h
Normal file
17
src/network/NetworkInterface.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef AIRPORT_NETWORK_INTERFACE_H
|
||||
#define AIRPORT_NETWORK_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "types/VehicleCommand.h"
|
||||
|
||||
class NetworkInterface {
|
||||
public:
|
||||
virtual ~NetworkInterface() = default;
|
||||
virtual bool connect() = 0;
|
||||
virtual void disconnect() = 0;
|
||||
virtual bool send(const VehicleCommand& cmd) = 0;
|
||||
virtual bool isConnected() const = 0;
|
||||
};
|
||||
|
||||
#endif // AIRPORT_NETWORK_INTERFACE_H
|
||||
377
src/network/TrafficLightHttpServer.cpp
Normal file
377
src/network/TrafficLightHttpServer.cpp
Normal file
@ -0,0 +1,377 @@
|
||||
#include "TrafficLightHttpServer.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "core/System.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/bind/bind.hpp>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace network {
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
namespace net = boost::asio;
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
using json = nlohmann::json;
|
||||
|
||||
// --- Forward Declarations within namespace ---
|
||||
// 不再需要,因为类定义直接在下面
|
||||
// class TrafficLightSession;
|
||||
// class TrafficLightListener;
|
||||
|
||||
//---------------- Helper Functions for HTTP Responses ------------------
|
||||
|
||||
// Returns a bad request response
|
||||
http::response<http::string_body> bad_request(const http::request<http::string_body>& req, beast::string_view why) {
|
||||
http::response<http::string_body> res{ http::status::bad_request, req.version() };
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = std::string(why);
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns a not found response
|
||||
http::response<http::string_body> not_found(const http::request<http::string_body>& req) {
|
||||
http::response<http::string_body> res{ http::status::not_found, req.version() };
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = "The resource '" + std::string(req.target()) + "' was not found.";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns a server error response
|
||||
http::response<http::string_body> server_error(const http::request<http::string_body>& req, beast::string_view what) {
|
||||
http::response<http::string_body> res{ http::status::internal_server_error, req.version() };
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = "An error occurred: '" + std::string(what) + "'";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns a method not allowed response
|
||||
http::response<http::string_body> method_not_allowed(const http::request<http::string_body>& req) {
|
||||
http::response<http::string_body> res{ http::status::method_not_allowed, req.version() };
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.set(http::field::allow, "POST");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = "Method Not Allowed. Allowed methods: POST";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns a success response (200 OK)
|
||||
http::response<http::string_body> ok_response(const http::request<http::string_body>& req, const std::string& message = "OK") {
|
||||
http::response<http::string_body> res{ http::status::ok, req.version() };
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "application/json");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = "{\"status\": \"success\", \"message\": \"" + message + "\"}";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
//---------------- Internal Session Class (Definition & Implementation) ----------------------
|
||||
// Handles an HTTP server connection. Defined entirely within the .cpp file.
|
||||
class TrafficLightSession : public std::enable_shared_from_this<TrafficLightSession>
|
||||
{
|
||||
tcp::socket socket_;
|
||||
beast::flat_buffer buffer_;
|
||||
std::string const& doc_root_; // Keep for potential future use
|
||||
http::request<http::string_body> req_;
|
||||
// Strand to ensure sequential execution of handlers for this session.
|
||||
net::strand<net::io_context::executor_type> strand_;
|
||||
net::io_context& ioc_; // 新增: 持有 io_context 引用
|
||||
System& system_; // 新增: System 引用
|
||||
|
||||
public:
|
||||
// Take ownership of the socket
|
||||
explicit TrafficLightSession(tcp::socket&& socket, std::string const& doc_root, net::io_context& ioc, System& system_ref)
|
||||
: socket_(std::move(socket)),
|
||||
doc_root_(doc_root),
|
||||
strand_(ioc.get_executor()),
|
||||
ioc_(ioc),
|
||||
system_(system_ref) {}
|
||||
|
||||
void run() {
|
||||
net::dispatch(strand_, [self = shared_from_this()]() {
|
||||
self->do_read();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void do_read() {
|
||||
req_ = {}; // 清空请求对象以便重用
|
||||
http::async_read(socket_, buffer_, req_,
|
||||
net::bind_executor(strand_,
|
||||
[self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
self->on_read(ec, bytes_transferred);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
void on_read(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
// Connection closed by peer
|
||||
if (ec == http::error::end_of_stream) return do_close();
|
||||
if (ec) return Logger::error("Session Read Error: ", ec.message());
|
||||
handle_request();
|
||||
}
|
||||
|
||||
void handle_request() {
|
||||
// Respond to HEAD request
|
||||
if (req_.method() == http::verb::head) {
|
||||
auto res = ok_response(req_, "");
|
||||
res.body() = ""; // No body for HEAD
|
||||
return send_response(std::move(res));
|
||||
}
|
||||
|
||||
// Only allow POST requests to the specific endpoint
|
||||
if (req_.method() != http::verb::post) {
|
||||
return send_response(method_not_allowed(req_));
|
||||
}
|
||||
|
||||
if (req_.target() != "/trafficlight") {
|
||||
return send_response(not_found(req_));
|
||||
}
|
||||
|
||||
// Attempt to parse the JSON body
|
||||
try {
|
||||
json body_json = json::parse(req_.body());
|
||||
|
||||
// Log the received data
|
||||
Logger::info("接收到红绿灯数据:");
|
||||
std::string log_msg = "红绿灯状态: ";
|
||||
bool first = true;
|
||||
for (const auto& key : { "DI-11", "DI-12", "DI-13", "DI-14", "DI-15", "DI-16" }) {
|
||||
if (body_json.contains(key)) {
|
||||
if (!first) log_msg += ", ";
|
||||
log_msg += key;
|
||||
log_msg += ":";
|
||||
// Safely convert JSON value to string, handling potential non-string types
|
||||
try {
|
||||
log_msg += body_json[key].dump();
|
||||
} catch (const json::type_error& te) {
|
||||
log_msg += "[invalid_type]";
|
||||
Logger::warning("JSON value for key ", key, " is not easily dumpable: ", te.what());
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
if (first) { // If no relevant keys were found
|
||||
log_msg += body_json.dump(); // Log the full body
|
||||
}
|
||||
Logger::info(log_msg);
|
||||
|
||||
// TODO: Integrate data processing (e.g., pass to DataCollector)
|
||||
// For now, just logging and sending OK.
|
||||
|
||||
system_.processPushedTrafficLightData(body_json);
|
||||
return send_response(ok_response(req_, "Data received and processed"));
|
||||
} catch (const json::parse_error& e) {
|
||||
Logger::error("JSON Parsing Error: ", e.what(), ", Body: ", req_.body());
|
||||
return send_response(bad_request(req_, "Invalid JSON format"));
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Error processing request: ", e.what());
|
||||
return send_response(server_error(req_, "Internal server error processing request"));
|
||||
}
|
||||
}
|
||||
|
||||
void send_response(http::response<http::string_body>&& res) {
|
||||
auto sp = std::make_shared<http::response<http::string_body>>(std::move(res));
|
||||
http::async_write(socket_, *sp,
|
||||
net::bind_executor(strand_,
|
||||
[self = shared_from_this(), sp](beast::error_code ec, std::size_t bytes) {
|
||||
self->on_write(sp->need_eof(), ec, bytes);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
void on_write(bool close, beast::error_code ec, std::size_t bytes_transferred) {
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
if (ec) return Logger::error("Session Write Error: ", ec.message());
|
||||
if (close) return do_close();
|
||||
do_read();
|
||||
}
|
||||
|
||||
void do_close() {
|
||||
beast::error_code ec;
|
||||
socket_.shutdown(tcp::socket::shutdown_send, ec);
|
||||
}
|
||||
};
|
||||
|
||||
//---------------- Internal Listener Class (Definition & Implementation) ---------------------
|
||||
// Accepts incoming connections and launches sessions. Defined entirely within the .cpp file.
|
||||
class TrafficLightListener : public std::enable_shared_from_this<TrafficLightListener>
|
||||
{
|
||||
net::io_context& ioc_;
|
||||
tcp::acceptor acceptor_;
|
||||
std::string const& doc_root_;
|
||||
int max_connections_; // 保留这个,用于拒绝连接
|
||||
System& system_; // 新增: System 引用
|
||||
|
||||
public:
|
||||
TrafficLightListener(
|
||||
net::io_context& ioc,
|
||||
tcp::endpoint endpoint,
|
||||
int max_connections,
|
||||
std::string const& doc_root,
|
||||
System& system_ref)
|
||||
: ioc_(ioc),
|
||||
acceptor_(ioc),
|
||||
doc_root_(doc_root),
|
||||
max_connections_(max_connections),
|
||||
system_(system_ref) {
|
||||
beast::error_code ec;
|
||||
|
||||
// Open the acceptor
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if (ec) { Logger::error("Listener Open Error: ", ec.message()); throw beast::system_error{ ec }; }
|
||||
|
||||
// Allow address reuse
|
||||
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
|
||||
if (ec) { Logger::error("Listener Set Option Error: ", ec.message()); throw beast::system_error{ ec }; }
|
||||
|
||||
// Bind to the server address
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if (ec) { Logger::error("Listener Bind Error: ", ec.message()); throw beast::system_error{ ec }; }
|
||||
|
||||
// Start listening for connections
|
||||
acceptor_.listen(net::socket_base::max_listen_connections, ec);
|
||||
if (ec) { Logger::error("Listener Listen Error: ", ec.message()); throw beast::system_error{ ec }; }
|
||||
}
|
||||
|
||||
void run() {
|
||||
do_accept();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
beast::error_code ec_cancel, ec_close;
|
||||
// 取消所有挂起的异步接受操作
|
||||
acceptor_.cancel(ec_cancel);
|
||||
// 关闭 acceptor
|
||||
acceptor_.close(ec_close);
|
||||
if (ec_cancel) Logger::warning("Listener cancel error: ", ec_cancel.message());
|
||||
if (ec_close) Logger::warning("Listener close error: ", ec_close.message());
|
||||
}
|
||||
|
||||
private:
|
||||
void do_accept() {
|
||||
acceptor_.async_accept(
|
||||
net::bind_executor(acceptor_.get_executor(),
|
||||
[self = shared_from_this()](beast::error_code ec, tcp::socket socket) { // socket 按值传递
|
||||
if (ec) {
|
||||
if (ec == net::error::operation_aborted) {
|
||||
return; // 服务器停止,不再接受
|
||||
}
|
||||
Logger::error("Listener Accept Error: ", ec.message());
|
||||
// 对于其他错误,我们仍然尝试继续接受,除非 acceptor 关闭
|
||||
} else {
|
||||
Logger::info("Accepted connection from ", socket.remote_endpoint());
|
||||
std::make_shared<TrafficLightSession>(
|
||||
std::move(socket), // 从 lambda 的参数 socket 移动
|
||||
self->doc_root_, // 访问 listener 的成员
|
||||
self->ioc_, // 访问 listener 的成员
|
||||
self->system_ // 访问 listener 的成员
|
||||
)->run();
|
||||
}
|
||||
|
||||
if (self->acceptor_.is_open()) {
|
||||
self->do_accept(); // 继续接受下一个连接
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//---------------- TrafficLightHttpServer Implementation -------------------
|
||||
|
||||
TrafficLightHttpServer::TrafficLightHttpServer(uint16_t port, int max_connections, const std::string& doc_root, System& system_ref)
|
||||
: port_(port),
|
||||
doc_root_(doc_root),
|
||||
max_connections_(max_connections),
|
||||
ioc_(1),
|
||||
system_(system_ref) {}
|
||||
|
||||
TrafficLightHttpServer::~TrafficLightHttpServer() {
|
||||
if (running_.load()) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void TrafficLightHttpServer::start(int num_threads) {
|
||||
if (running_.exchange(true)) {
|
||||
Logger::warning("TrafficLightHttpServer already started.");
|
||||
return;
|
||||
}
|
||||
// ... (确保 num_threads >= 1)
|
||||
auto const address = net::ip::make_address("0.0.0.0");
|
||||
auto const port = port_;
|
||||
try {
|
||||
listener_ = std::make_shared<TrafficLightListener>(
|
||||
ioc_,
|
||||
tcp::endpoint{ address, port },
|
||||
max_connections_,
|
||||
doc_root_,
|
||||
system_
|
||||
);
|
||||
listener_->run();
|
||||
Logger::info("TrafficLightHttpServer starting on ", address.to_string(), ":", port, " with ", num_threads, " threads.");
|
||||
threads_.reserve(num_threads);
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
threads_.emplace_back([this] { run_ioc(); });
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to start TrafficLightHttpServer: ", e.what());
|
||||
running_ = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void TrafficLightHttpServer::stop() {
|
||||
if (!running_.exchange(false)) { return; }
|
||||
Logger::info("Stopping TrafficLightHttpServer...");
|
||||
net::post(ioc_, [this]() {
|
||||
if (listener_) {
|
||||
listener_->stop();
|
||||
listener_.reset();
|
||||
}
|
||||
});
|
||||
ioc_.stop();
|
||||
for (auto& thread : threads_) {
|
||||
if (thread.joinable()) { thread.join(); }
|
||||
}
|
||||
threads_.clear();
|
||||
Logger::info("TrafficLightHttpServer stopped.");
|
||||
}
|
||||
|
||||
void TrafficLightHttpServer::run_ioc() {
|
||||
Logger::debug("TrafficLightHttpServer I/O thread started.");
|
||||
try {
|
||||
ioc_.run();
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Exception in TrafficLightHttpServer I/O thread: ", e.what());
|
||||
} catch (...) {
|
||||
Logger::error("Unknown exception in TrafficLightHttpServer I/O thread.");
|
||||
}
|
||||
Logger::debug("TrafficLightHttpServer I/O thread finished.");
|
||||
}
|
||||
|
||||
} // namespace network
|
||||
|
||||
64
src/network/TrafficLightHttpServer.h
Normal file
64
src/network/TrafficLightHttpServer.h
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <cstdint> // For uint16_t
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// Forward declare Logger if header is heavy, or include if needed often
|
||||
// #include "utils/Logger.h"
|
||||
|
||||
// 前向声明 System 类
|
||||
class System;
|
||||
|
||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||
namespace net = boost::asio; // from <boost/asio.hpp>
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace network {
|
||||
|
||||
// 前向声明内部类 (如果 TrafficLightHttpServer 需要引用它)
|
||||
class TrafficLightListener;
|
||||
// class TrafficLightSession; // Session 通常由 Listener 创建和管理,Server 可能不需要直接引用
|
||||
|
||||
// Main server class (只保留这个类的声明)
|
||||
class TrafficLightHttpServer
|
||||
{
|
||||
uint16_t port_;
|
||||
std::string doc_root_;
|
||||
int max_connections_;
|
||||
net::io_context ioc_;
|
||||
// 使用指向内部 Listener 实现的指针
|
||||
std::shared_ptr<TrafficLightListener> listener_;
|
||||
std::vector<std::thread> threads_;
|
||||
std::atomic<bool> running_{ false };
|
||||
System& system_; // 新增: System 引用
|
||||
|
||||
public:
|
||||
TrafficLightHttpServer(uint16_t port, int max_connections, const std::string& doc_root, System& system_ref);
|
||||
~TrafficLightHttpServer();
|
||||
|
||||
TrafficLightHttpServer(const TrafficLightHttpServer&) = delete;
|
||||
TrafficLightHttpServer& operator=(const TrafficLightHttpServer&) = delete;
|
||||
|
||||
void start(int num_threads = 1);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void run_ioc();
|
||||
};
|
||||
|
||||
}
|
||||
141
src/network/WebSocketServer.cpp
Normal file
141
src/network/WebSocketServer.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include "network/WebSocketServer.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace network {
|
||||
|
||||
WebSocketServer::WebSocketServer(uint16_t port)
|
||||
: acceptor_(ioc_, { boost::asio::ip::tcp::v4(), port }) {}
|
||||
|
||||
WebSocketServer::~WebSocketServer() {
|
||||
// 关闭所有连接
|
||||
std::lock_guard<std::mutex> lock(sessions_mutex_);
|
||||
for (auto& session : sessions_) {
|
||||
try {
|
||||
session.lock()->close(boost::beast::websocket::close_code::normal);
|
||||
} catch (...) {
|
||||
// 忽略关闭时的错误
|
||||
}
|
||||
}
|
||||
sessions_.clear();
|
||||
|
||||
// 停止 io_context
|
||||
ioc_.stop();
|
||||
}
|
||||
|
||||
void WebSocketServer::start() {
|
||||
Logger::info("WebSocket 服务器启动,监听端口: ", acceptor_.local_endpoint().port());
|
||||
handleAccept();
|
||||
ioc_.run();
|
||||
}
|
||||
|
||||
void WebSocketServer::broadcast(const std::string& message) {
|
||||
std::lock_guard<std::mutex> lock(sessions_mutex_);
|
||||
auto it = sessions_.begin();
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
while (it != sessions_.end()) {
|
||||
if (auto session = it->lock()) { // 获取 shared_ptr
|
||||
try {
|
||||
session->write(boost::asio::buffer(message));
|
||||
++successCount;
|
||||
++it;
|
||||
} catch (...) {
|
||||
Logger::warning("广播消息到客户端失败");
|
||||
++failCount;
|
||||
it = sessions_.erase(it); // 移除失败的会话
|
||||
}
|
||||
} else {
|
||||
Logger::debug("移除失效的会话");
|
||||
it = sessions_.erase(it); // 移除已失效的会话
|
||||
}
|
||||
}
|
||||
|
||||
Logger::debug("广播消息完成: ", successCount, " 个成功, ", failCount, " 个失败, 当前共 ", sessions_.size(), " 个连接");
|
||||
}
|
||||
|
||||
void WebSocketServer::handleAccept() {
|
||||
acceptor_.async_accept(
|
||||
[this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
|
||||
if (!ec) {
|
||||
Logger::info("接受新的 WebSocket 连接: ", socket.remote_endpoint().address().to_string());
|
||||
|
||||
// 创建新的 WebSocket 会话
|
||||
auto ws = std::make_shared<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>(std::move(socket));
|
||||
|
||||
// 异步完成 WebSocket 握手
|
||||
ws->async_accept(
|
||||
[this, ws](boost::system::error_code ec) {
|
||||
if (!ec) {
|
||||
// 握手成功,保存会话并开始读取消息
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(sessions_mutex_);
|
||||
sessions_.push_back(ws); // 存储 weak_ptr
|
||||
Logger::info("WebSocket 握手成功,当前连接数: ", sessions_.size());
|
||||
}
|
||||
doRead(ws);
|
||||
} else {
|
||||
Logger::error("WebSocket 握手失败: ", ec.message());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Logger::error("接受 WebSocket 连接失败: ", ec.message());
|
||||
}
|
||||
|
||||
// 继续接受新的连接
|
||||
handleAccept();
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketServer::doRead(std::shared_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> ws) {
|
||||
// 为每个会话创建一个专用的缓冲区
|
||||
auto buffer = std::make_shared<boost::beast::multi_buffer>();
|
||||
|
||||
// 异步读取消息
|
||||
ws->async_read(
|
||||
*buffer,
|
||||
[this, ws, buffer = buffer](boost::system::error_code ec, std::size_t bytes_transferred) {
|
||||
if (!ec) {
|
||||
// 成功读取消息,继续读取下一条
|
||||
buffer->consume(buffer->size()); // 清空缓冲区
|
||||
doRead(ws);
|
||||
} else {
|
||||
// 发生错误或连接关闭,移除会话
|
||||
std::lock_guard<std::mutex> lock(sessions_mutex_);
|
||||
auto it = std::find_if(sessions_.begin(), sessions_.end(),
|
||||
[ws](const std::weak_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>& weak) {
|
||||
return !weak.expired() && weak.lock() == ws;
|
||||
});
|
||||
if (it != sessions_.end()) {
|
||||
sessions_.erase(it);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketServer::stop() {
|
||||
running_ = false;
|
||||
Logger::info("正在停止 WebSocket 服务器...");
|
||||
ioc_.stop(); // 停止 io_context
|
||||
|
||||
// 关闭所有连接
|
||||
std::lock_guard<std::mutex> lock(sessions_mutex_);
|
||||
for (auto& session : sessions_) {
|
||||
if (auto ws = session.lock()) {
|
||||
try {
|
||||
ws->close(boost::beast::websocket::close_code::normal);
|
||||
Logger::debug("关闭 WebSocket 连接");
|
||||
} catch (...) {
|
||||
Logger::warning("关闭 WebSocket 连接时发生错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
sessions_.clear();
|
||||
Logger::info("WebSocket 服务器已停止");
|
||||
}
|
||||
|
||||
} // namespace network
|
||||
32
src/network/WebSocketServer.h
Normal file
32
src/network/WebSocketServer.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
namespace network {
|
||||
|
||||
class WebSocketServer {
|
||||
public:
|
||||
WebSocketServer(uint16_t port);
|
||||
~WebSocketServer();
|
||||
|
||||
void start();
|
||||
void broadcast(const std::string& message);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void handleAccept();
|
||||
void doRead(std::shared_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> ws);
|
||||
|
||||
boost::asio::io_context ioc_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
std::vector<std::weak_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>> sessions_;
|
||||
std::mutex sessions_mutex_;
|
||||
std::atomic<bool> running_{true};
|
||||
};
|
||||
|
||||
} // namespace network
|
||||
52
src/spatial/CoordinateConverter.cpp
Normal file
52
src/spatial/CoordinateConverter.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "spatial/CoordinateConverter.h"
|
||||
#include "config/SystemConfig.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <cmath>
|
||||
|
||||
// 地球半径(米)
|
||||
const double EARTH_RADIUS = 6378137.0;
|
||||
|
||||
CoordinateConverter::CoordinateConverter() noexcept {
|
||||
const auto& refPoint = SystemConfig::instance().airport.reference_point;
|
||||
setReferencePoint(refPoint.latitude, refPoint.longitude);
|
||||
}
|
||||
|
||||
void CoordinateConverter::setReferencePoint(double lat, double lon) noexcept {
|
||||
ref_lat_ = lat * M_PI / 180.0; // 转换为弧度
|
||||
ref_lon_ = lon * M_PI / 180.0;
|
||||
cos_ref_lat_ = std::cos(ref_lat_); // 预计算参考点的余弦值
|
||||
}
|
||||
|
||||
Vector2D CoordinateConverter::toLocalXY(double lat, double lon) const noexcept {
|
||||
// 将经纬度转换为弧度
|
||||
double lat_rad = lat * M_PI / 180.0;
|
||||
double lon_rad = lon * M_PI / 180.0;
|
||||
|
||||
// 计算相对于参考点的经纬度差(弧度)
|
||||
double d_lon = lon_rad - ref_lon_;
|
||||
double d_lat = lat_rad - ref_lat_;
|
||||
|
||||
// 计算东西方向距离(x)和南北方向距离(y)
|
||||
Vector2D result;
|
||||
result.x = EARTH_RADIUS * cos_ref_lat_ * d_lon; // 使用预计算的余弦值
|
||||
result.y = EARTH_RADIUS * d_lat;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GeoPosition CoordinateConverter::toLatLon(const Vector2D& pos) const noexcept {
|
||||
// 计算经纬度差(弧度)
|
||||
double d_lon = pos.x / (EARTH_RADIUS * cos_ref_lat_); // 使用预计算的余弦值
|
||||
double d_lat = pos.y / EARTH_RADIUS;
|
||||
|
||||
// 计算实际经纬度(弧度)
|
||||
double lon_rad = d_lon + ref_lon_;
|
||||
double lat_rad = d_lat + ref_lat_;
|
||||
|
||||
// 转换为度
|
||||
GeoPosition result;
|
||||
result.latitude = lat_rad * 180.0 / M_PI;
|
||||
result.longitude = lon_rad * 180.0 / M_PI;
|
||||
|
||||
return result;
|
||||
}
|
||||
39
src/spatial/CoordinateConverter.h
Normal file
39
src/spatial/CoordinateConverter.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef AIRPORT_SPATIAL_COORDINATE_CONVERTER_H
|
||||
#define AIRPORT_SPATIAL_COORDINATE_CONVERTER_H
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
class CoordinateConverter {
|
||||
public:
|
||||
// 删除拷贝构造和赋值操作
|
||||
CoordinateConverter(const CoordinateConverter&) = delete;
|
||||
CoordinateConverter& operator=(const CoordinateConverter&) = delete;
|
||||
|
||||
// 获取全局单例
|
||||
static CoordinateConverter& instance() {
|
||||
static CoordinateConverter instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 设置参考点(经纬度)
|
||||
void setReferencePoint(double lat, double lon) noexcept;
|
||||
|
||||
// 将经纬度转换为本地平面坐标系(米)
|
||||
Vector2D toLocalXY(double lat, double lon) const noexcept;
|
||||
|
||||
// 将本地平面坐标转换为经纬度
|
||||
GeoPosition toLatLon(const Vector2D& pos) const noexcept;
|
||||
|
||||
private:
|
||||
// 私有构造函数
|
||||
CoordinateConverter() noexcept;
|
||||
|
||||
static constexpr double EARTH_RADIUS = 6378137.0; // WGS84椭球体赤道半径(米)
|
||||
double ref_lat_{0.0}; // 参考点纬度(弧度)
|
||||
double ref_lon_{0.0}; // 参考点经度(弧度)
|
||||
double cos_ref_lat_{1.0}; // 参考点纬度的余弦值(预计算)
|
||||
};
|
||||
|
||||
#endif // AIRPORT_SPATIAL_COORDINATE_CONVERTER_H
|
||||
5
src/spatial/QuadTree.cpp
Normal file
5
src/spatial/QuadTree.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
#include "QuadTree.h"
|
||||
|
||||
// 显式实例化常用类型
|
||||
template class QuadTree<Vehicle>;
|
||||
template class QuadTree<Aircraft>;
|
||||
196
src/spatial/QuadTree.h
Normal file
196
src/spatial/QuadTree.h
Normal file
@ -0,0 +1,196 @@
|
||||
#ifndef AIRPORT_SPATIAL_QUAD_TREE_H
|
||||
#define AIRPORT_SPATIAL_QUAD_TREE_H
|
||||
|
||||
#include "types/BasicTypes.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
// 边界定义
|
||||
struct Bounds {
|
||||
double x; // 左上角 x 坐标
|
||||
double y; // 左上角 y 坐标
|
||||
double width; // 宽度
|
||||
double height; // 高度
|
||||
|
||||
Bounds() = default;
|
||||
|
||||
// 使用左上角点和宽高构造
|
||||
Bounds(double x_, double y_, double width_, double height_)
|
||||
: x(x_), y(y_), width(width_), height(height_) {}
|
||||
|
||||
// 检查点是否在边界内
|
||||
bool contains(const Vector2D& point) const {
|
||||
return point.x >= x && point.x <= (x + width) &&
|
||||
point.y >= y && point.y <= (y + height);
|
||||
}
|
||||
|
||||
// 检查是否与另一个边界相交
|
||||
bool intersects(const Bounds& other) const {
|
||||
return !(other.x > (x + width) || (other.x + other.width) < x ||
|
||||
other.y > (y + height) || (other.y + other.height) < y);
|
||||
}
|
||||
|
||||
// 获取中心点
|
||||
Vector2D getCenter() const {
|
||||
return {x + width/2, y + height/2};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class QuadTree {
|
||||
public:
|
||||
explicit QuadTree(const Bounds& bounds, int capacity = 4)
|
||||
: bounds_(bounds), capacity_(capacity) {}
|
||||
|
||||
const Bounds& getBounds() const { return bounds_; }
|
||||
|
||||
void insert(const T& item) { insertInternal(item); }
|
||||
void insert(T&& item) { insertInternal(std::move(item)); }
|
||||
|
||||
std::vector<T> queryRange(const Bounds& range) const {
|
||||
std::vector<T> found;
|
||||
if (!bounds_.intersects(range)) {
|
||||
return found;
|
||||
}
|
||||
|
||||
for (const auto& item : items_) {
|
||||
if (range.contains(getPosition(item))) {
|
||||
found.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (divided_) {
|
||||
auto nw = northwest_->queryRange(range);
|
||||
auto ne = northeast_->queryRange(range);
|
||||
auto sw = southwest_->queryRange(range);
|
||||
auto se = southeast_->queryRange(range);
|
||||
|
||||
found.insert(found.end(), nw.begin(), nw.end());
|
||||
found.insert(found.end(), ne.begin(), ne.end());
|
||||
found.insert(found.end(), sw.begin(), sw.end());
|
||||
found.insert(found.end(), se.begin(), se.end());
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
std::vector<T> queryNearby(const Vector2D& point, double radius) const {
|
||||
// 创建一个以point为中心,边长为radius*2的正方形边界
|
||||
Bounds range(
|
||||
point.x - radius, // x
|
||||
point.y - radius, // y
|
||||
radius * 2, // width
|
||||
radius * 2 // height
|
||||
);
|
||||
|
||||
return queryRange(range);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
items_.clear();
|
||||
divided_ = false;
|
||||
northwest_.reset();
|
||||
northeast_.reset();
|
||||
southwest_.reset();
|
||||
southeast_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
Bounds bounds_;
|
||||
int capacity_;
|
||||
std::vector<T> items_;
|
||||
bool divided_ = false;
|
||||
|
||||
std::unique_ptr<QuadTree> northwest_;
|
||||
std::unique_ptr<QuadTree> northeast_;
|
||||
std::unique_ptr<QuadTree> southwest_;
|
||||
std::unique_ptr<QuadTree> southeast_;
|
||||
|
||||
template <typename U>
|
||||
void insertInternal(U&& item) {
|
||||
const Vector2D point = getPosition(item);
|
||||
|
||||
// 如果点不在当前树的边界内,忽略该对象
|
||||
if (!bounds_.contains(point)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 叶子节点:未分裂且未达到容量,直接存放
|
||||
if (!divided_ && items_.size() < static_cast<size_t>(capacity_)) {
|
||||
items_.emplace_back(std::forward<U>(item));
|
||||
return;
|
||||
}
|
||||
|
||||
// 达到容量后分裂,并将已有 items_ 重新分发到子节点
|
||||
if (!divided_) {
|
||||
subdivide();
|
||||
|
||||
auto oldItems = std::move(items_);
|
||||
items_.clear();
|
||||
items_.shrink_to_fit();
|
||||
|
||||
for (auto& old : oldItems) {
|
||||
insertIntoChildren(std::move(old));
|
||||
}
|
||||
}
|
||||
|
||||
insertIntoChildren(std::forward<U>(item));
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
void insertIntoChildren(U&& item) {
|
||||
const Vector2D point = getPosition(item);
|
||||
|
||||
// 由于 contains 使用 <= 边界,点可能落在多象限边界上;这里固定按顺序选择一个子象限
|
||||
if (northwest_ && northwest_->bounds_.contains(point)) {
|
||||
northwest_->insertInternal(std::forward<U>(item));
|
||||
return;
|
||||
}
|
||||
if (northeast_ && northeast_->bounds_.contains(point)) {
|
||||
northeast_->insertInternal(std::forward<U>(item));
|
||||
return;
|
||||
}
|
||||
if (southwest_ && southwest_->bounds_.contains(point)) {
|
||||
southwest_->insertInternal(std::forward<U>(item));
|
||||
return;
|
||||
}
|
||||
if (southeast_ && southeast_->bounds_.contains(point)) {
|
||||
southeast_->insertInternal(std::forward<U>(item));
|
||||
return;
|
||||
}
|
||||
|
||||
// 理论上不会走到这里(已检查 bounds_ contains),兜底放回当前节点
|
||||
items_.emplace_back(std::forward<U>(item));
|
||||
}
|
||||
|
||||
void subdivide() {
|
||||
double w = bounds_.width / 2;
|
||||
double h = bounds_.height / 2;
|
||||
|
||||
// 创建四个子区域
|
||||
Bounds nw(bounds_.x, bounds_.y, w, h);
|
||||
northwest_ = std::make_unique<QuadTree>(nw, capacity_);
|
||||
|
||||
Bounds ne(bounds_.x + w, bounds_.y, w, h);
|
||||
northeast_ = std::make_unique<QuadTree>(ne, capacity_);
|
||||
|
||||
Bounds sw(bounds_.x, bounds_.y + h, w, h);
|
||||
southwest_ = std::make_unique<QuadTree>(sw, capacity_);
|
||||
|
||||
Bounds se(bounds_.x + w, bounds_.y + h, w, h);
|
||||
southeast_ = std::make_unique<QuadTree>(se, capacity_);
|
||||
|
||||
divided_ = true;
|
||||
}
|
||||
|
||||
static Vector2D getPosition(const T& item) {
|
||||
if constexpr (std::is_same_v<T, Vehicle> || std::is_same_v<T, Aircraft>) {
|
||||
return item.position; // 直接返回 position 成员
|
||||
} else {
|
||||
return item.getPosition(); // 对于其他类型,假设有getPosition方法
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // AIRPORT_SPATIAL_QUAD_TREE_H
|
||||
151
src/types/BasicTypes.cpp
Normal file
151
src/types/BasicTypes.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include "types/BasicTypes.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "spatial/CoordinateConverter.h"
|
||||
#include <cmath>
|
||||
|
||||
double Vector2D::magnitude() const {
|
||||
return std::sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
double Vector2D::direction() const {
|
||||
double angle = std::atan2(x, y) * 180.0 / M_PI;
|
||||
if (angle < 0) {
|
||||
angle += 360.0;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
double MovingObject::calculateDistance(const GeoPosition& pos1, const GeoPosition& pos2) {
|
||||
// 使用 Haversine 公式计算实际距离
|
||||
const double EARTH_RADIUS = 6371000.0; // 地球半径(米)
|
||||
|
||||
// 转换为弧度
|
||||
double lat1 = pos1.latitude * M_PI / 180.0;
|
||||
double lon1 = pos1.longitude * M_PI / 180.0;
|
||||
double lat2 = pos2.latitude * M_PI / 180.0;
|
||||
double lon2 = pos2.longitude * M_PI / 180.0;
|
||||
|
||||
// 计算差值
|
||||
double dlat = lat2 - lat1;
|
||||
double dlon = lon2 - lon1;
|
||||
|
||||
// Haversine 公式
|
||||
double a = std::sin(dlat/2) * std::sin(dlat/2) +
|
||||
std::cos(lat1) * std::cos(lat2) *
|
||||
std::sin(dlon/2) * std::sin(dlon/2);
|
||||
double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1-a));
|
||||
|
||||
return EARTH_RADIUS * c;
|
||||
}
|
||||
|
||||
double MovingObject::calculateHeading(const GeoPosition& from, const GeoPosition& to) {
|
||||
// 计算经纬度差值
|
||||
double dlon = to.longitude - from.longitude;
|
||||
double dlat = to.latitude - from.latitude;
|
||||
|
||||
// 将经度差转换为实际距离(考虑纬度影响)
|
||||
double lat_rad = from.latitude * M_PI / 180.0;
|
||||
double dx = dlon * std::cos(lat_rad); // 东西方向的距离(经度)
|
||||
double dy = dlat; // 南北方向的距离(纬度)
|
||||
|
||||
// 计算航向角(正北为0度,顺时针增加)
|
||||
double angle = std::atan2(dx, dy) * 180.0 / M_PI;
|
||||
if (angle < 0) {
|
||||
angle += 360.0;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
bool Aircraft::isValidPosition(const GeoPosition& newPos) const {
|
||||
if (positionHistory.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double distance = calculateDistance(positionHistory.back().geo, newPos);
|
||||
return distance <= MAX_POSITION_JUMP;
|
||||
}
|
||||
|
||||
bool Aircraft::isValidSpeed(double speed) const {
|
||||
return speed <= MAX_SPEED;
|
||||
}
|
||||
|
||||
bool Vehicle::isValidPosition(const GeoPosition& newPos) const {
|
||||
if (positionHistory.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double distance = calculateDistance(positionHistory.back().geo, newPos);
|
||||
return distance <= MAX_POSITION_JUMP;
|
||||
}
|
||||
|
||||
bool Vehicle::isValidSpeed(double speed) const {
|
||||
return speed <= MAX_SPEED;
|
||||
}
|
||||
|
||||
void MovingObject::updateMotion(const GeoPosition& newPos, uint64_t newTime) {
|
||||
// 检查是否是新数据
|
||||
bool isNewData = positionHistory.empty() || newTime > positionHistory.back().timestamp;
|
||||
if (isNewData) {
|
||||
// 更新位置历史
|
||||
positionHistory.emplace_back(newPos, newTime);
|
||||
if (positionHistory.size() > MAX_HISTORY) {
|
||||
positionHistory.pop_front();
|
||||
}
|
||||
|
||||
// 计算速度和航向
|
||||
if (positionHistory.size() >= 2) {
|
||||
// 使用最近的两个点来计算速度和航向
|
||||
const auto& curr = positionHistory.back();
|
||||
const auto& prev = positionHistory[positionHistory.size() - 2];
|
||||
|
||||
// 计算距离和时间差
|
||||
double distance = calculateDistance(prev.geo, curr.geo); // 单位:米
|
||||
double timeDiff = static_cast<double>(curr.timestamp - prev.timestamp) / 1000.0; // 转换为秒
|
||||
|
||||
// 只有当位置变化足够大且时间差足够长时才更新速度和航向
|
||||
static const double MIN_DISTANCE = 0.1; // 最小位置变化阈值(米)
|
||||
static const double MIN_TIME = 0.05; // 最小时间差阈值(秒)
|
||||
|
||||
if (distance > MIN_DISTANCE && timeDiff > MIN_TIME) {
|
||||
// 计算新的速度
|
||||
double newSpeed = distance / timeDiff; // 米/秒
|
||||
|
||||
if (isValidSpeed(newSpeed)) {
|
||||
// 使用指数移动平均来平滑速度,增大平滑因子以加快更新
|
||||
const double alpha = 0.8; // 增大平滑因子
|
||||
if (speed == 0) {
|
||||
speed = newSpeed; // 第一次计算
|
||||
} else {
|
||||
speed = speed * (1 - alpha) + newSpeed * alpha; // 平滑更新
|
||||
}
|
||||
}
|
||||
|
||||
// 计算并直接更新航向,不需要平滑处理
|
||||
heading = calculateHeading(prev.geo, curr.geo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前位置
|
||||
geo = newPos;
|
||||
timestamp = newTime;
|
||||
position = CoordinateConverter::instance().toLocalXY(geo.latitude, geo.longitude);
|
||||
}
|
||||
|
||||
void MovingObject::copyHistoryFrom(const MovingObject& other) {
|
||||
positionHistory = other.positionHistory;
|
||||
}
|
||||
|
||||
bool MovingObject::hasPositionChanged() const {
|
||||
if (positionHistory.size() < 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& latest = positionHistory.back();
|
||||
const auto& previous = positionHistory[positionHistory.size() - 2];
|
||||
|
||||
// 计算位置变化
|
||||
double distance = calculateDistance(latest.geo, previous.geo);
|
||||
return distance > 0.1; // 位置变化超过0.1米才认为发生了变化
|
||||
}
|
||||
113
src/types/BasicTypes.h
Normal file
113
src/types/BasicTypes.h
Normal file
@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <cstdint>
|
||||
|
||||
// 移动物体类型
|
||||
enum class MovingObjectType {
|
||||
AIRCRAFT, // 航空器
|
||||
SPECIAL, // 特勤车
|
||||
UNMANNED // 无人车(可控车辆)
|
||||
};
|
||||
|
||||
// 基础数据类型
|
||||
struct Vector2D {
|
||||
double x;
|
||||
double y;
|
||||
|
||||
Vector2D() : x(0), y(0) {}
|
||||
Vector2D(double x_, double y_) : x(x_), y(y_) {}
|
||||
|
||||
double magnitude() const;
|
||||
double direction() const;
|
||||
};
|
||||
|
||||
struct GeoPosition {
|
||||
double latitude;
|
||||
double longitude;
|
||||
};
|
||||
|
||||
struct PositionRecord {
|
||||
GeoPosition geo;
|
||||
uint64_t timestamp;
|
||||
|
||||
PositionRecord(const GeoPosition& g, uint64_t t) : geo(g), timestamp(t) {}
|
||||
};
|
||||
|
||||
// 移动物体基类
|
||||
class MovingObject {
|
||||
public:
|
||||
MovingObject() : heading(0.0), speed(0.0), timestamp(0), type(MovingObjectType::SPECIAL) {}
|
||||
virtual ~MovingObject() = default;
|
||||
|
||||
std::string id;
|
||||
Vector2D position;
|
||||
GeoPosition geo;
|
||||
double heading;
|
||||
double speed;
|
||||
int64_t timestamp;
|
||||
MovingObjectType type; // 添加类型字段
|
||||
|
||||
std::deque<PositionRecord> positionHistory;
|
||||
|
||||
static double calculateDistance(const GeoPosition& pos1, const GeoPosition& pos2);
|
||||
static double calculateHeading(const GeoPosition& from, const GeoPosition& to);
|
||||
|
||||
virtual bool isValidPosition(const GeoPosition& newPos) const = 0;
|
||||
virtual bool isValidSpeed(double speed) const = 0;
|
||||
virtual double getSpeedSmoothingFactor() const { return 0.3; }
|
||||
|
||||
void updateMotion(const GeoPosition& newPos, uint64_t newTime);
|
||||
void copyHistoryFrom(const MovingObject& other);
|
||||
bool hasPositionChanged() const;
|
||||
|
||||
// 添加类型识别虚函数
|
||||
virtual bool isAircraft() const { return type == MovingObjectType::AIRCRAFT; }
|
||||
virtual bool isSpecialVehicle() const { return type == MovingObjectType::SPECIAL; }
|
||||
virtual bool isUnmannedVehicle() const { return type == MovingObjectType::UNMANNED; }
|
||||
|
||||
static constexpr size_t MAX_HISTORY = 10;
|
||||
};
|
||||
|
||||
// 航空器类
|
||||
class Aircraft : public MovingObject {
|
||||
public:
|
||||
Aircraft() { type = MovingObjectType::AIRCRAFT; }
|
||||
|
||||
std::string flightNo;
|
||||
std::string trackNumber;
|
||||
double altitude;
|
||||
|
||||
bool isValidPosition(const GeoPosition& newPos) const override;
|
||||
bool isValidSpeed(double speed) const override;
|
||||
|
||||
static constexpr double MAX_SPEED = 100.0; // 最大速度(米/秒)
|
||||
static constexpr double MAX_POSITION_JUMP = 50.0; // 最大位置跳变(米)
|
||||
|
||||
bool isAircraft() const override { return true; }
|
||||
bool isSpecialVehicle() const override { return false; }
|
||||
};
|
||||
|
||||
// 车辆类
|
||||
class Vehicle : public MovingObject {
|
||||
public:
|
||||
Vehicle() {
|
||||
type = MovingObjectType::UNMANNED; // 默认为无人车类型
|
||||
isControllable = true; // 默认为可控
|
||||
}
|
||||
|
||||
std::string vehicleNo;
|
||||
bool isControllable; // true 表示无人车,false 表示特勤车
|
||||
|
||||
bool isValidPosition(const GeoPosition& newPos) const override;
|
||||
bool isValidSpeed(double speed) const override;
|
||||
|
||||
static constexpr double MAX_SPEED = 20.0; // 最大速度(米/秒)
|
||||
static constexpr double MAX_POSITION_JUMP = 10.0; // 最大位置跳变(米)
|
||||
|
||||
bool isAircraft() const override { return false; }
|
||||
bool isSpecialVehicle() const override {
|
||||
return type == MovingObjectType::SPECIAL; // 只判断类型
|
||||
}
|
||||
};
|
||||
21
src/types/TrafficLightTypes.h
Normal file
21
src/types/TrafficLightTypes.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
enum class SignalStatus {
|
||||
RED,
|
||||
GREEN,
|
||||
YELLOW,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
struct TrafficLightSignal {
|
||||
std::string trafficLightId; // 红绿灯设备 ID
|
||||
SignalStatus ns_status = SignalStatus::UNKNOWN; // 南北向状态
|
||||
SignalStatus ew_status = SignalStatus::UNKNOWN; // 东西向状态
|
||||
uint64_t timestamp; // 时间戳
|
||||
std::string intersectionId; // 路口编号
|
||||
double latitude; // 路口纬度
|
||||
double longitude; // 路口经度
|
||||
};
|
||||
60
src/types/VehicleCommand.h
Normal file
60
src/types/VehicleCommand.h
Normal file
@ -0,0 +1,60 @@
|
||||
#ifndef AIRPORT_TYPES_VEHICLE_COMMAND_H
|
||||
#define AIRPORT_TYPES_VEHICLE_COMMAND_H
|
||||
|
||||
#include <string>
|
||||
#include "VehicleData.h"
|
||||
|
||||
// 指令类型
|
||||
enum class CommandType {
|
||||
ALERT, // 告警指令
|
||||
SIGNAL, // 信号灯指令
|
||||
WARNING, // 预警指令
|
||||
RESUME, // 恢复指令
|
||||
PARKING // 安全停靠
|
||||
};
|
||||
|
||||
// 信号灯状态
|
||||
enum class SignalState {
|
||||
RED, // 红灯
|
||||
GREEN, // 绿灯
|
||||
YELLOW // 黄灯
|
||||
};
|
||||
|
||||
// 指令原因
|
||||
enum class CommandReason {
|
||||
TRAFFIC_LIGHT, // 红绿灯控制
|
||||
AIRCRAFT_CROSSING, // 航空器交叉
|
||||
SPECIAL_VEHICLE, // 特勤车辆
|
||||
AIRCRAFT_PUSH, // 航空器推出
|
||||
RESUME_TRAFFIC, // 恢复通行
|
||||
PARKING_SIDE // 安全停靠
|
||||
};
|
||||
|
||||
struct VehicleCommand {
|
||||
std::string vehicleId; // 车辆ID
|
||||
CommandType type; // 指令类型
|
||||
CommandReason reason; // 指令原因
|
||||
uint64_t timestamp; // 时间戳
|
||||
SignalState signalState; // 信号灯状态(仅当 type 为 SIGNAL 时有效)
|
||||
std::string intersectionId; // 路口ID(仅当 type 为 SIGNAL 时有效)
|
||||
double latitude; // 目标位置纬度(路口/航空器/特勤车)
|
||||
double longitude; // 目标位置经度(路口/航空器/特勤车)
|
||||
double relativeSpeed; // 相对速度(仅当 type 为 ALERT/WARNING 时有效)
|
||||
double relativeMotionX; // 相对运动 X 分量(仅当 type 为 ALERT/WARNING 时有效)
|
||||
double relativeMotionY; // 相对运动 Y 分量(仅当 type 为 ALERT/WARNING 时有效)
|
||||
double minDistance; // 最小距离(仅当 type 为 ALERT/WARNING 时有效)
|
||||
|
||||
VehicleCommand() :
|
||||
type(CommandType::RESUME),
|
||||
reason(CommandReason::RESUME_TRAFFIC),
|
||||
timestamp(0),
|
||||
signalState(SignalState::GREEN),
|
||||
latitude(0),
|
||||
longitude(0),
|
||||
relativeSpeed(0),
|
||||
relativeMotionX(0),
|
||||
relativeMotionY(0),
|
||||
minDistance(0) {}
|
||||
};
|
||||
|
||||
#endif // AIRPORT_TYPES_VEHICLE_COMMAND_H
|
||||
6
src/types/VehicleData.cpp
Normal file
6
src/types/VehicleData.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "VehicleData.h"
|
||||
#include "spatial/CoordinateConverter.h"
|
||||
|
||||
void VehicleData::updateLocalPosition(const CoordinateConverter& converter) {
|
||||
position = converter.toLocalXY(geo.latitude, geo.longitude);
|
||||
}
|
||||
30
src/types/VehicleData.h
Normal file
30
src/types/VehicleData.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef AIRPORT_TYPES_VEHICLE_DATA_H
|
||||
#define AIRPORT_TYPES_VEHICLE_DATA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "BasicTypes.h"
|
||||
|
||||
// 前向声明
|
||||
class CoordinateConverter;
|
||||
|
||||
enum class VehicleType {
|
||||
AIRCRAFT,
|
||||
VEHICLE
|
||||
};
|
||||
|
||||
struct VehicleData {
|
||||
std::string id; // 唯一标识
|
||||
VehicleType type; // 类型
|
||||
GeoPosition geo; // 地理坐标(经纬度)
|
||||
Vector2D position; // 平面坐标(米)
|
||||
Vector2D velocity; // 速度(米/秒)
|
||||
double heading; // 航向角(度)
|
||||
uint64_t timestamp; // 时间戳
|
||||
double altitude{0.0}; // 高度(米,仅飞行器有效)
|
||||
|
||||
// 只保留声明
|
||||
void updateLocalPosition(const CoordinateConverter& converter);
|
||||
};
|
||||
|
||||
#endif // AIRPORT_TYPES_VEHICLE_DATA_H
|
||||
7
src/utils/Logger.cpp
Normal file
7
src/utils/Logger.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include "Logger.h"
|
||||
|
||||
// 显式实例化模板,以支持常用类型
|
||||
template void Logger::log(const char* level, const char* msg);
|
||||
template void Logger::log(const char* level, std::string msg);
|
||||
template void Logger::log(const char* level, int msg);
|
||||
template void Logger::log(const char* level, double msg);
|
||||
99
src/utils/Logger.h
Normal file
99
src/utils/Logger.h
Normal file
@ -0,0 +1,99 @@
|
||||
#ifndef AIRPORT_UTILS_LOGGER_H
|
||||
#define AIRPORT_UTILS_LOGGER_H
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <mutex>
|
||||
|
||||
enum class LogLevel {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR
|
||||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
static void initialize(const std::string& filename, LogLevel level = LogLevel::INFO) {
|
||||
std::lock_guard<std::mutex> lock(getMutex());
|
||||
currentLevel() = level;
|
||||
logFile().open(filename, std::ios::app);
|
||||
}
|
||||
|
||||
static void setLogLevel(LogLevel level) {
|
||||
std::lock_guard<std::mutex> lock(getMutex());
|
||||
currentLevel() = level;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void debug(Args... args) {
|
||||
if (currentLevel() <= LogLevel::DEBUG) {
|
||||
log("DEBUG", args...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void info(Args... args) {
|
||||
if (currentLevel() <= LogLevel::INFO) {
|
||||
log("INFO", args...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void warning(Args... args) {
|
||||
if (currentLevel() <= LogLevel::WARNING) {
|
||||
log("WARNING", args...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void error(Args... args) {
|
||||
if (currentLevel() <= LogLevel::ERROR) {
|
||||
log("ERROR", args...);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static LogLevel& currentLevel() {
|
||||
static LogLevel level = LogLevel::INFO;
|
||||
return level;
|
||||
}
|
||||
|
||||
static std::ofstream& logFile() {
|
||||
static std::ofstream file;
|
||||
return file;
|
||||
}
|
||||
|
||||
static std::mutex& getMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void log(const char* level, Args... args) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto now_c = std::chrono::system_clock::to_time_t(now);
|
||||
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()) % 1000;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S")
|
||||
<< '.' << std::setfill('0') << std::setw(3) << now_ms.count()
|
||||
<< " [" << level << "] ";
|
||||
|
||||
ss << std::fixed << std::setprecision(14);
|
||||
(ss << ... << args) << std::endl;
|
||||
|
||||
std::lock_guard<std::mutex> lock(getMutex());
|
||||
std::cerr << ss.str();
|
||||
if (logFile().is_open()) {
|
||||
logFile() << ss.str();
|
||||
logFile().flush();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // AIRPORT_UTILS_LOGGER_H
|
||||
111
src/vehicle/ControllableVehicles.cpp
Normal file
111
src/vehicle/ControllableVehicles.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
#include "ControllableVehicles.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
// 定义静态成员变量
|
||||
ControllableVehicles* ControllableVehicles::instance_ = nullptr;
|
||||
|
||||
ControllableVehicles& ControllableVehicles::getInstance() {
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new ControllableVehicles("config/unmanned_vehicles.json");
|
||||
}
|
||||
return *instance_;
|
||||
}
|
||||
|
||||
ControllableVehicles::ControllableVehicles(const std::string& configFile)
|
||||
: http_client_(std::make_unique<HTTPClient>()) {
|
||||
if (!configFile.empty()) {
|
||||
loadConfig(configFile);
|
||||
}
|
||||
}
|
||||
|
||||
ControllableVehicles::~ControllableVehicles() {
|
||||
if (instance_ != nullptr) {
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ControllableVehicleConfig>& ControllableVehicles::getVehicles() const {
|
||||
return vehicles_;
|
||||
}
|
||||
|
||||
const ControllableVehicleConfig* ControllableVehicles::findVehicle(const std::string& vehicleNo) const {
|
||||
auto iter = std::find_if(vehicles_.begin(), vehicles_.end(),
|
||||
[&](const ControllableVehicleConfig& config) {
|
||||
return config.vehicleNo == vehicleNo;
|
||||
});
|
||||
|
||||
return iter != vehicles_.end() ? &(*iter) : nullptr;
|
||||
}
|
||||
|
||||
bool ControllableVehicles::isControllable(const std::string& vehicleNo) const {
|
||||
// 查找车辆配置
|
||||
auto* config = findVehicle(vehicleNo);
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
// 只有无人车类型是可控的
|
||||
return config->type == "UNMANNED";
|
||||
}
|
||||
|
||||
bool ControllableVehicles::loadConfig(const std::string& configFile) {
|
||||
try {
|
||||
std::ifstream file(configFile);
|
||||
if (!file.is_open()) {
|
||||
Logger::error("Failed to open controllable vehicles config file: ", configFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
nlohmann::json jsonConfig;
|
||||
file >> jsonConfig;
|
||||
|
||||
for (const auto& item : jsonConfig["vehicles"]) {
|
||||
ControllableVehicleConfig config;
|
||||
config.vehicleNo = item["vehicleNo"].get<std::string>();
|
||||
config.type = item["type"].get<std::string>();
|
||||
config.ip = item["ip"].get<std::string>();
|
||||
config.port = item["port"].get<int>();
|
||||
vehicles_.push_back(config);
|
||||
Logger::info("Added vehicle: ", config.vehicleNo, ", type: ", config.type);
|
||||
}
|
||||
|
||||
Logger::info("Loaded ", vehicles_.size(), " controllable vehicles");
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to parse controllable vehicles config: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ControllableVehicles::sendCommand(const std::string& vehicleNo, const VehicleCommand& command) {
|
||||
auto vehicle = findVehicle(vehicleNo);
|
||||
if (!vehicle) {
|
||||
Logger::error("Vehicle ", vehicleNo, " not found in controllable vehicles");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
bool success = http_client_->sendCommand(vehicle->ip, vehicle->port, command);
|
||||
if (success) {
|
||||
Logger::info("Successfully sent command to vehicle ", vehicleNo, ": ",
|
||||
command.type == CommandType::SIGNAL ? "SIGNAL" :
|
||||
command.type == CommandType::ALERT ? "ALERT" :
|
||||
command.type == CommandType::WARNING ? "WARNING" :
|
||||
command.type == CommandType::PARKING ? "PARKING" : "RESUME",
|
||||
"/",
|
||||
command.reason == CommandReason::TRAFFIC_LIGHT ? "TRAFFIC_LIGHT" :
|
||||
command.reason == CommandReason::AIRCRAFT_CROSSING ? "AIRCRAFT_CROSSING" :
|
||||
command.reason == CommandReason::SPECIAL_VEHICLE ? "SPECIAL_VEHICLE" :
|
||||
command.reason == CommandReason::AIRCRAFT_PUSH ? "AIRCRAFT_PUSH" :
|
||||
command.reason == CommandReason::PARKING_SIDE ? "PARKING_SIDE" : "RESUME_TRAFFIC");
|
||||
} else {
|
||||
Logger::error("Failed to send command to vehicle ", vehicleNo);
|
||||
}
|
||||
return success;
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Exception while sending command to vehicle ", vehicleNo, ": ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
34
src/vehicle/ControllableVehicles.h
Normal file
34
src/vehicle/ControllableVehicles.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "types/VehicleCommand.h"
|
||||
#include "network/HTTPClient.h"
|
||||
|
||||
struct ControllableVehicleConfig {
|
||||
std::string vehicleNo; // 车牌号
|
||||
std::string type; // 车辆类型(UNMANNED 或 SPECIAL)
|
||||
std::string ip; // IP地址
|
||||
int port; // 端口号
|
||||
};
|
||||
|
||||
class ControllableVehicles {
|
||||
private:
|
||||
static ControllableVehicles* instance_;
|
||||
ControllableVehicles(const std::string& configFile);
|
||||
ControllableVehicles(const ControllableVehicles&) = delete;
|
||||
ControllableVehicles& operator=(const ControllableVehicles&) = delete;
|
||||
~ControllableVehicles();
|
||||
|
||||
std::vector<ControllableVehicleConfig> vehicles_;
|
||||
std::unique_ptr<HTTPClient> http_client_;
|
||||
bool loadConfig(const std::string& configFile);
|
||||
|
||||
public:
|
||||
static ControllableVehicles& getInstance();
|
||||
const std::vector<ControllableVehicleConfig>& getVehicles() const;
|
||||
const ControllableVehicleConfig* findVehicle(const std::string& vehicleNo) const;
|
||||
bool isControllable(const std::string& vehicleNo) const;
|
||||
bool sendCommand(const std::string& vehicleNo, const VehicleCommand& command);
|
||||
};
|
||||
246
tests/AirportBoundsTest.cpp
Normal file
246
tests/AirportBoundsTest.cpp
Normal file
@ -0,0 +1,246 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "config/AirportBounds.h"
|
||||
#include "utils/Logger.h"
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
// 测试用的 AirportBounds 类,暴露 protected 成员以便测试
|
||||
class TestAirportBounds : public AirportBounds {
|
||||
public:
|
||||
using AirportBounds::AirportBounds;
|
||||
using AirportBounds::toAirportCoordinate;
|
||||
using AirportBounds::airportBounds_;
|
||||
using AirportBounds::areaBounds_;
|
||||
using AirportBounds::referencePoint_;
|
||||
using AirportBounds::rotationAngle_;
|
||||
|
||||
// 设置测试数据
|
||||
void setTestData(const Vector2D& refPoint, double angle, const Bounds& bounds) {
|
||||
referencePoint_ = refPoint;
|
||||
rotationAngle_ = angle * M_PI / 180.0; // 转换为弧度
|
||||
airportBounds_ = bounds;
|
||||
}
|
||||
};
|
||||
|
||||
class AirportBoundsTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 设置日志级别
|
||||
Logger::setLogLevel(LogLevel::DEBUG);
|
||||
|
||||
// 创建测试实例
|
||||
bounds_ = std::make_unique<TestAirportBounds>();
|
||||
|
||||
// 设置基本测试数据
|
||||
// 参考点设为原点 (0, 0)
|
||||
// 旋转角度设为 90 度,便于验证
|
||||
bounds_->setTestData(
|
||||
Vector2D{ 0, 0 }, // 参考点
|
||||
90.0, // 旋转角度(度)
|
||||
Bounds(-2000, -2000, 4000, 4000) // 4km x 4km 的区域
|
||||
);
|
||||
}
|
||||
|
||||
// 辅助函数:检查两个点是否足够接近(考虑浮点误差)
|
||||
bool pointsAreClose(const Vector2D& p1, const Vector2D& p2, double tolerance = 0.1) {
|
||||
return std::abs(p1.x - p2.x) < tolerance && std::abs(p1.y - p2.y) < tolerance;
|
||||
}
|
||||
|
||||
std::unique_ptr<TestAirportBounds> bounds_;
|
||||
};
|
||||
|
||||
// 测试坐标转换 - 参考点
|
||||
TEST_F(AirportBoundsTest, ReferencePointTransformation) {
|
||||
// 参考点应该转换为原点 (0,0)
|
||||
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 0, 0 });
|
||||
EXPECT_TRUE(pointsAreClose(result, Vector2D{ 0, 0 }))
|
||||
<< "参考点转换结果: (" << result.x << ", " << result.y << ")";
|
||||
}
|
||||
|
||||
// 测试坐标转换 - 基本位移(90度逆时针旋转)
|
||||
TEST_F(AirportBoundsTest, BasicTranslation) {
|
||||
// 向右移动 100 米,逆时针旋转 90 度后,应该在 y 轴正方向
|
||||
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 100, 0 });
|
||||
EXPECT_NEAR(result.x, 0, 0.001);
|
||||
EXPECT_NEAR(result.y, 100, 0.001)
|
||||
<< "向右100米的点逆时针旋转90度后应该在 (0, 100),实际在: ("
|
||||
<< result.x << ", " << result.y << ")";
|
||||
|
||||
// 向上移动 100 米,逆时针旋转 90 度后,应该在 x 轴负方向
|
||||
result = bounds_->toAirportCoordinate(Vector2D{ 0, 100 });
|
||||
EXPECT_NEAR(result.x, -100, 0.001);
|
||||
EXPECT_NEAR(result.y, 0, 0.001)
|
||||
<< "向上100米的点逆时针旋转90度后应该在 (-100, 0),实际在: ("
|
||||
<< result.x << ", " << result.y << ")";
|
||||
}
|
||||
|
||||
// 测试坐标转换 - 复合变换(90度逆时针旋转)
|
||||
TEST_F(AirportBoundsTest, CompositeTransformation) {
|
||||
// 向右上方移动 (100, 100),逆时针旋转 90 度
|
||||
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 100, 100 });
|
||||
EXPECT_NEAR(result.x, -100, 0.001);
|
||||
EXPECT_NEAR(result.y, 100, 0.001)
|
||||
<< "点(100,100)逆时针旋转90度后应该在 (-100, 100),实际在: ("
|
||||
<< result.x << ", " << result.y << ")";
|
||||
}
|
||||
|
||||
// 测试边界检查 - 边界内的点
|
||||
TEST_F(AirportBoundsTest, PointInBounds) {
|
||||
// 测试参考点(一定在边界内)
|
||||
EXPECT_TRUE(bounds_->isPointInBounds(Vector2D{ 0, 0 }))
|
||||
<< "参考点应该在边界内";
|
||||
|
||||
// 测试边界内的点
|
||||
EXPECT_TRUE(bounds_->isPointInBounds(Vector2D{ 1000, 1000 }))
|
||||
<< "距离参考点1km的点应该在边界内";
|
||||
}
|
||||
|
||||
// 测试边界检查 - 边界外的点
|
||||
TEST_F(AirportBoundsTest, PointOutOfBounds) {
|
||||
// 测试边界外的点
|
||||
EXPECT_FALSE(bounds_->isPointInBounds(Vector2D{ 3000, 3000 }))
|
||||
<< "距离参考点3km的点应该在边界外";
|
||||
|
||||
// 测试另一个边界外的点
|
||||
EXPECT_FALSE(bounds_->isPointInBounds(Vector2D{ -2500, -2500 }))
|
||||
<< "距离参考点2.5km的点应该在边界外";
|
||||
}
|
||||
|
||||
// 测试不同旋转角度(45度逆时针旋转)
|
||||
TEST_F(AirportBoundsTest, DifferentRotations) {
|
||||
// 重新设置为45度逆时针旋转
|
||||
bounds_->setTestData(
|
||||
Vector2D{ 0, 0 }, // 参考点
|
||||
45.0, // 旋转角度(度)
|
||||
Bounds(-2000, -2000, 4000, 4000)
|
||||
);
|
||||
|
||||
// 向右移动 100 米,逆时针旋转 45 度
|
||||
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 100, 0 });
|
||||
double sqrt2_2 = std::sqrt(2.0) / 2.0; // cos(45°) = sin(45°) = √2/2
|
||||
EXPECT_NEAR(result.x, 100 * sqrt2_2, 0.001);
|
||||
EXPECT_NEAR(result.y, 100 * sqrt2_2, 0.001)
|
||||
<< "向右100米的点逆时针旋转45度后应该在 (70.71, 70.71),实际在: ("
|
||||
<< result.x << ", " << result.y << ")";
|
||||
|
||||
// 向上移动 100 米,逆时针旋转 45 度
|
||||
result = bounds_->toAirportCoordinate(Vector2D{ 0, 100 });
|
||||
EXPECT_NEAR(result.x, -100 * sqrt2_2, 0.001);
|
||||
EXPECT_NEAR(result.y, 100 * sqrt2_2, 0.001)
|
||||
<< "向上100米的点逆时针旋转45度后应该在 (-70.71, 70.71),实际在: ("
|
||||
<< result.x << ", " << result.y << ")";
|
||||
}
|
||||
|
||||
// 测试旋转变换(30度逆时针旋转)
|
||||
TEST_F(AirportBoundsTest, RotationTransformation) {
|
||||
// 重新设置为30度逆时针旋转
|
||||
bounds_->setTestData(
|
||||
Vector2D{ 0, 0 }, // 参考点
|
||||
30.0, // 旋转角度(度)
|
||||
Bounds(-2000, -2000, 4000, 4000)
|
||||
);
|
||||
|
||||
// 在世界坐标系中向右移动1000米,逆时针旋转 30 度
|
||||
Vector2D result = bounds_->toAirportCoordinate(Vector2D{ 1000, 0 });
|
||||
double cos30 = std::cos(M_PI / 6); // cos(30°) ≈ 0.866025
|
||||
double sin30 = std::sin(M_PI / 6); // sin(30°) ≈ 0.5
|
||||
|
||||
// 在机场坐标系中的期望结果:
|
||||
// x = 1000 * cos(30°) ≈ 866.025
|
||||
// y = 1000 * sin(30°) ≈ 500
|
||||
EXPECT_NEAR(result.x, 1000 * cos30, 0.1);
|
||||
EXPECT_NEAR(result.y, 1000 * sin30, 0.1)
|
||||
<< "向右1000米的点逆时针旋转30度后应该在 (866.025, 500),实际在: ("
|
||||
<< result.x << ", " << result.y << ")";
|
||||
|
||||
// 在世界坐标系中向上移动1000米,逆时针旋转 30 度
|
||||
result = bounds_->toAirportCoordinate(Vector2D{ 0, 1000 });
|
||||
|
||||
// 在机场坐标系中的期望结果:
|
||||
// x = -1000 * sin(30°) ≈ -500
|
||||
// y = 1000 * cos(30°) ≈ 866.025
|
||||
EXPECT_NEAR(result.x, -1000 * sin30, 0.1);
|
||||
EXPECT_NEAR(result.y, 1000 * cos30, 0.1)
|
||||
<< "向上1000米的点逆时针旋转30度后应该在 (-500, 866.025),实际在: ("
|
||||
<< result.x << ", " << result.y << ")";
|
||||
}
|
||||
|
||||
// 测试区域检查
|
||||
TEST_F(AirportBoundsTest, AreaCheck) {
|
||||
// 重新设置为0度旋转,便于测试
|
||||
bounds_->setTestData(
|
||||
Vector2D{ 0, 0 }, // 参考点
|
||||
0.0, // 旋转角度(度)
|
||||
Bounds(-2000, -2000, 4000, 4000)
|
||||
);
|
||||
|
||||
// 设置测试区域
|
||||
bounds_->areaBounds_[AreaType::RUNWAY] = Bounds(-1000, -100, 2000, 200);
|
||||
bounds_->areaBounds_[AreaType::TAXIWAY] = Bounds(-800, -300, 1600, 600);
|
||||
|
||||
// 测试跑道上的点
|
||||
Vector2D runwayPoint{ 500, 0 }; // 跑道中心偏右500米
|
||||
EXPECT_TRUE(bounds_->isPointInArea(runwayPoint, AreaType::RUNWAY))
|
||||
<< "跑道上的点应该在跑道区域内";
|
||||
|
||||
// 测试滑行道上的点
|
||||
Vector2D taxiwayPoint{ 0, 200 }; // 滑行道中心向上200米
|
||||
EXPECT_TRUE(bounds_->isPointInArea(taxiwayPoint, AreaType::TAXIWAY))
|
||||
<< "滑行道上的点应该在滑行道区域内";
|
||||
|
||||
// 测试区域外的点
|
||||
Vector2D outPoint{ 3000, 3000 }; // 远离机场的点
|
||||
EXPECT_FALSE(bounds_->isPointInArea(outPoint, AreaType::RUNWAY))
|
||||
<< "远离机场的点不应该在任何区域内";
|
||||
EXPECT_FALSE(bounds_->isPointInArea(outPoint, AreaType::TAXIWAY))
|
||||
<< "远离机场的点不应该在任何区域内";
|
||||
}
|
||||
|
||||
// 测试配置文件加载
|
||||
TEST_F(AirportBoundsTest, ConfigLoading) {
|
||||
// 使用项目中的配置文件
|
||||
AirportBounds bounds("config/airport_bounds.json");
|
||||
|
||||
// 验证配置是否正确加载
|
||||
const auto& airportBounds = bounds.getAirportBounds();
|
||||
EXPECT_TRUE(airportBounds.width > 0 && airportBounds.height > 0)
|
||||
<< "机场边界的宽度和高度应该为正值";
|
||||
|
||||
// 验证区域配置
|
||||
const auto& runwayBounds = bounds.getAreaBounds(AreaType::RUNWAY);
|
||||
EXPECT_TRUE(runwayBounds.width > 0 && runwayBounds.height > 0)
|
||||
<< "跑道区域的宽度和高度应该为正值";
|
||||
EXPECT_TRUE(runwayBounds.width < airportBounds.width && runwayBounds.height < airportBounds.height)
|
||||
<< "跑道区域应该小于机场边界";
|
||||
}
|
||||
|
||||
// 测试配置文件错误处理
|
||||
TEST_F(AirportBoundsTest, ConfigErrorHandling) {
|
||||
// 测试文件不存在
|
||||
EXPECT_THROW(AirportBounds("nonexistent.json"), std::runtime_error);
|
||||
|
||||
// 测试配置文件格式错误
|
||||
EXPECT_THROW(AirportBounds("tests/data/invalid_airport_bounds.json"), std::runtime_error);
|
||||
}
|
||||
|
||||
// 测试边界条件
|
||||
TEST_F(AirportBoundsTest, EdgeCases) {
|
||||
// 重新设置为0度旋转,便于测试
|
||||
bounds_->setTestData(
|
||||
Vector2D{ 0, 0 }, // 参考点
|
||||
0.0, // 旋转角度(度)
|
||||
Bounds(-2000, -2000, 4000, 4000)
|
||||
);
|
||||
|
||||
// 测试正好在边界上的点
|
||||
Vector2D edge{ 2000, 2000 }; // 右上角边界点
|
||||
EXPECT_TRUE(bounds_->isPointInBounds(edge))
|
||||
<< "边界上的点应该被认为在边界内";
|
||||
|
||||
// 测试边界外的点
|
||||
Vector2D outside{ 2001, 2001 }; // 刚好超出边界
|
||||
EXPECT_FALSE(bounds_->isPointInBounds(outside))
|
||||
<< "边界外的点应该被认为在边界外";
|
||||
}
|
||||
539
tests/BasicCollisionTest.cpp
Normal file
539
tests/BasicCollisionTest.cpp
Normal file
@ -0,0 +1,539 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <memory>
|
||||
|
||||
#include "detector/CollisionDetector.h"
|
||||
#include "vehicle/ControllableVehicles.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "config/SystemConfig.h"
|
||||
#include "config/AirportBounds.h"
|
||||
|
||||
// Mock ControllableVehicles 类
|
||||
class MockControllableVehicles {
|
||||
public:
|
||||
MockControllableVehicles() = default;
|
||||
|
||||
MOCK_METHOD(bool, isControllable, (const std::string& vehicleNo), (const));
|
||||
MOCK_METHOD(const ControllableVehicleConfig*, findVehicle, (const std::string& vehicleNo), (const));
|
||||
MOCK_METHOD(void, sendCommand, (const std::string& vehicleNo, const VehicleCommand& cmd));
|
||||
|
||||
// 转换为 ControllableVehicles& 的操作符
|
||||
operator const ControllableVehicles&() const {
|
||||
return ControllableVehicles::getInstance();
|
||||
}
|
||||
};
|
||||
|
||||
// Mock AirportBounds 类
|
||||
class MockAirportBounds : public AirportBounds {
|
||||
public:
|
||||
MockAirportBounds() : AirportBounds("") {
|
||||
// 设置测试区域边界
|
||||
airportBounds_ = Bounds(0, 0, 5000, 4000);
|
||||
areaBounds_[AreaType::TEST_ZONE] = airportBounds_;
|
||||
|
||||
// 设置测试区域配置
|
||||
AreaConfig config;
|
||||
config.collision_radius = {50.0, 50.0, 25.0}; // aircraft, special, unmanned
|
||||
config.height_threshold = 10.0;
|
||||
config.warning_zone_radius = {100.0, 100.0, 50.0};
|
||||
config.alert_zone_radius = {50.0, 50.0, 25.0};
|
||||
areaConfigs_[AreaType::TEST_ZONE] = config;
|
||||
|
||||
// 设置系统配置
|
||||
auto& system_config = SystemConfig::instance();
|
||||
system_config.collision_detection.prediction.time_window = 30.0; // 设置预测时间窗口为30秒
|
||||
}
|
||||
|
||||
MOCK_METHOD(AreaType, getAreaType, (const Vector2D& position), (const));
|
||||
|
||||
const AreaConfig& getAreaConfig(AreaType type) const override {
|
||||
auto it = areaConfigs_.find(type);
|
||||
if (it == areaConfigs_.end()) {
|
||||
throw std::runtime_error("Invalid area type");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
};
|
||||
|
||||
class BasicCollisionTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
Logger::setLogLevel(LogLevel::DEBUG); // 设置日志级别为 DEBUG
|
||||
|
||||
airportBounds_ = std::make_unique<MockAirportBounds>();
|
||||
mockControllableVehicles_ = std::make_unique<MockControllableVehicles>();
|
||||
|
||||
// 设置 Mock 对象的行为
|
||||
EXPECT_CALL(*airportBounds_, getAreaType(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(AreaType::TEST_ZONE));
|
||||
|
||||
// 设置 Mock ControllableVehicles 的行为
|
||||
EXPECT_CALL(*mockControllableVehicles_, isControllable(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(true));
|
||||
|
||||
detector_ = std::make_unique<CollisionDetector>(*airportBounds_, *mockControllableVehicles_);
|
||||
}
|
||||
|
||||
std::unique_ptr<MockAirportBounds> airportBounds_;
|
||||
std::unique_ptr<MockControllableVehicles> mockControllableVehicles_;
|
||||
std::unique_ptr<CollisionDetector> detector_;
|
||||
};
|
||||
|
||||
// 1. 静态碰撞测试
|
||||
TEST_F(BasicCollisionTest, StaticCollision) {
|
||||
// 创建两个静止且重叠的物体
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "V1";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 0.0;
|
||||
v1.heading = 0.0;
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "V2";
|
||||
v2.position = {120.0, 100.0}; // 距离20米,小于碰撞半径25米
|
||||
v2.speed = 0.0;
|
||||
v2.heading = 0.0;
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
|
||||
auto result = detector_->checkCollision(v1, v2, 30.0);
|
||||
|
||||
EXPECT_TRUE(result.willCollide) << "距离小于碰撞半径的静止物体应该检测为碰撞";
|
||||
EXPECT_DOUBLE_EQ(result.timeToCollision, 0.0) << "静止物体的碰撞时间应该为0";
|
||||
EXPECT_EQ(result.type, CollisionType::STATIC) << "应该识别为静态碰撞";
|
||||
|
||||
// 增加更多验证
|
||||
EXPECT_DOUBLE_EQ(result.minDistance, 20.0) << "最小距离应该是当前距离20米";
|
||||
EXPECT_DOUBLE_EQ(result.timeToMinDistance, 0.0) << "静止物体最小距离时间应该为0";
|
||||
EXPECT_NEAR(result.collisionPoint.x, 110.0, 0.1) << "碰撞点应该在两车中点";
|
||||
EXPECT_NEAR(result.collisionPoint.y, 100.0, 0.1) << "碰撞点应该在同一水平线上";
|
||||
|
||||
// 验证物体状态
|
||||
EXPECT_NEAR(result.object1State.position.x, 100.0, 0.1);
|
||||
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.speed, 0.0);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.heading, 0.0);
|
||||
|
||||
EXPECT_NEAR(result.object2State.position.x, 120.0, 0.1);
|
||||
EXPECT_NEAR(result.object2State.position.y, 100.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.speed, 0.0);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.heading, 0.0);
|
||||
}
|
||||
|
||||
// 2. 相向碰撞测试
|
||||
TEST_F(BasicCollisionTest, HeadOnCollision) {
|
||||
// 创建两个相向运动的物体
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "V1";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 10.0;
|
||||
v1.heading = 90.0; // 向东
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "V2";
|
||||
v2.position = {200.0, 100.0}; // 在v1东边100米
|
||||
v2.speed = 10.0;
|
||||
v2.heading = 270.0; // 向西
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
|
||||
auto result = detector_->checkCollision(v1, v2, 30.0);
|
||||
|
||||
EXPECT_TRUE(result.willCollide) << "相向运动的物体应该检测为碰撞";
|
||||
EXPECT_NEAR(result.timeToCollision, 2.5, 0.1) << "碰撞时间应该接近2.5秒((100-50)米/20米每秒)";
|
||||
EXPECT_EQ(result.type, CollisionType::HEAD_ON) << "应该识别为相向碰撞";
|
||||
EXPECT_NEAR(result.collisionPoint.x, 150.0, 0.1) << "碰撞点应该在两车中点处(100+200)/2=150";
|
||||
EXPECT_NEAR(result.collisionPoint.y, 100.0, 0.1) << "碰撞点应该在同一水平线上";
|
||||
|
||||
// 增加更多验证
|
||||
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离该是碰撞半径之和50米";
|
||||
EXPECT_NEAR(result.timeToMinDistance, 2.5, 0.1) << "最小距离时间应该等于碰撞时间";
|
||||
|
||||
// 验证物体状态
|
||||
EXPECT_NEAR(result.object1State.position.x, 125.0, 0.1);
|
||||
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.speed, 10.0);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.heading, 90.0);
|
||||
|
||||
EXPECT_NEAR(result.object2State.position.x, 175.0, 0.1);
|
||||
EXPECT_NEAR(result.object2State.position.y, 100.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.speed, 10.0);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.heading, 270.0);
|
||||
}
|
||||
|
||||
// 3. 平行运动测试
|
||||
TEST_F(BasicCollisionTest, ParallelMotion) {
|
||||
// 创建两个平行运动的物体
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "V1";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 10.0;
|
||||
v1.heading = 90.0; // 向东
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "V2";
|
||||
v2.position = {100.0, 150.0}; // 在v1北边50米,大于碰撞半径
|
||||
v2.speed = 10.0;
|
||||
v2.heading = 90.0; // 向东
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
|
||||
auto result = detector_->checkCollision(v1, v2, 30.0);
|
||||
|
||||
EXPECT_FALSE(result.willCollide) << "平行运动且距离大于碰撞半径的物体不应该检测为碰撞";
|
||||
EXPECT_EQ(result.type, CollisionType::PARALLEL) << "应该识别为平行运动";
|
||||
|
||||
// 增加更多验证
|
||||
EXPECT_DOUBLE_EQ(result.minDistance, 50.0) << "最小距离应该是初始距离50米";
|
||||
EXPECT_DOUBLE_EQ(result.timeToMinDistance, 0.0) << "平行运动的最小距离时间应该为0";
|
||||
EXPECT_DOUBLE_EQ(result.timeToCollision, std::numeric_limits<double>::infinity()) << "平行运动不会碰撞";
|
||||
|
||||
// 不验证碰撞点,因为不会发生碰撞
|
||||
}
|
||||
|
||||
// 4. 交叉路径测试
|
||||
TEST_F(BasicCollisionTest, CrossingPaths) {
|
||||
// 创建两个倾斜交叉运动的物体
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "V1";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 15.0;
|
||||
v1.heading = 75.0; // 向东北偏东方向运动
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "V2";
|
||||
v2.position = {150.0, 150.0};
|
||||
v2.speed = 10.0;
|
||||
v2.heading = 105.0; // 向东南偏东方向运动,与 v1 夹角为 30 度
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
|
||||
// 添加航向角度差的调试日志
|
||||
double angle_diff = std::abs(v1.heading - v2.heading);
|
||||
Logger::debug("航向角度差检查:");
|
||||
Logger::debug("v1 航向: " + std::to_string(v1.heading));
|
||||
Logger::debug("v2 航向: " + std::to_string(v2.heading));
|
||||
Logger::debug("航向角度差: " + std::to_string(angle_diff));
|
||||
|
||||
auto result = detector_->checkCollision(v1, v2, 30.0);
|
||||
|
||||
EXPECT_TRUE(result.willCollide) << "交叉路径的物体应该检测为碰撞";
|
||||
EXPECT_EQ(result.type, CollisionType::CROSSING) << "应该识别为交叉碰撞";
|
||||
|
||||
// 计算碰撞时间和位置:
|
||||
// v1: 速度分量 (3.882, 14.489) m/s // 15 m/s * (cos(15°), sin(15°))
|
||||
// v2: 速度分量 (-2.588, 9.659) m/s // 10 m/s * (cos(75°), sin(75°))
|
||||
// 相对速度: (-6.47, -4.83) m/s
|
||||
// 相对速度大小: 8.06 m/s
|
||||
// 碰撞点在两车位置的中点
|
||||
double collision_time = 2.07; // 根据实际计算得到
|
||||
Vector2D collision_point = {126.34, 150.006}; // 两车位置的中点
|
||||
|
||||
EXPECT_NEAR(result.timeToCollision, collision_time, 0.1) << "考虑碰撞半径25米,碰撞时间应该接近2.07秒";
|
||||
EXPECT_NEAR(result.collisionPoint.x, collision_point.x, 0.1) << "碰撞点x坐标应该在126.34";
|
||||
EXPECT_NEAR(result.collisionPoint.y, collision_point.y, 0.1) << "碰撞点y坐标应该在150.006";
|
||||
|
||||
// 增加更多验证
|
||||
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离应该是碰撞半径之和50米";
|
||||
EXPECT_NEAR(result.timeToMinDistance, collision_time, 0.1) << "最小距离时间应该等于碰撞时间";
|
||||
|
||||
// 验证物体状态
|
||||
EXPECT_NEAR(result.object1State.position.x, 108.04, 0.1);
|
||||
EXPECT_NEAR(result.object1State.position.y, 130.007, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.speed, 15.0);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.heading, 75.0);
|
||||
|
||||
EXPECT_NEAR(result.object2State.position.x, 144.64, 0.1);
|
||||
EXPECT_NEAR(result.object2State.position.y, 170.005, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.speed, 10.0);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.heading, 105.0);
|
||||
}
|
||||
|
||||
// 5. 垂直交叉路径测试
|
||||
TEST_F(BasicCollisionTest, PerpendicularCrossingPaths) {
|
||||
// 创建两个垂直交叉运动的物体
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "V1";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 10.0;
|
||||
v1.heading = 90.0; // 向东运动
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "V2";
|
||||
v2.position = {150.0, 150.0};
|
||||
v2.speed = 10.0;
|
||||
v2.heading = 180.0; // 向南运动,与 v1 夹角为 90 度
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
|
||||
auto result = detector_->checkCollision(v1, v2, 30.0);
|
||||
|
||||
EXPECT_TRUE(result.willCollide) << "垂直交叉路径的物体应该检测为碰撞";
|
||||
EXPECT_EQ(result.type, CollisionType::CROSSING) << "应该识别为交叉碰撞";
|
||||
|
||||
// 计算碰撞时间和位置:
|
||||
// v1: 速度分量 (10.0, 0.0) m/s
|
||||
// v2: 速度分量 (0.0, -10.0) m/s
|
||||
// 相对速度: (-10.0, -10.0) m/s
|
||||
// 相对速度大小: 14.14 m/s
|
||||
// 当前距离小于安全距离时,立即判定为碰撞
|
||||
double collision_time = 0.0; // 当前距离小于安全距离,立即碰撞
|
||||
Vector2D collision_point = {125.0, 125.0}; // 两车当前位置的中点
|
||||
|
||||
EXPECT_NEAR(result.timeToCollision, collision_time, 0.1) << "当前距离小于安全距离,碰撞时间应该为0";
|
||||
EXPECT_NEAR(result.collisionPoint.x, collision_point.x, 0.1) << "碰撞点x坐标应该在125.0";
|
||||
EXPECT_NEAR(result.collisionPoint.y, collision_point.y, 0.1) << "碰撞点y坐标应该在125.0";
|
||||
|
||||
// 增加更多验证
|
||||
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离应该是碰撞半径之和50米";
|
||||
EXPECT_NEAR(result.timeToMinDistance, collision_time, 0.1) << "最小距离时间应该等于碰撞时间";
|
||||
|
||||
// 验证物体状态
|
||||
EXPECT_NEAR(result.object1State.position.x, 100.0, 0.1);
|
||||
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.speed, 10.0);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.heading, 90.0);
|
||||
|
||||
EXPECT_NEAR(result.object2State.position.x, 150.0, 0.1);
|
||||
EXPECT_NEAR(result.object2State.position.y, 150.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.speed, 10.0);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.heading, 180.0);
|
||||
}
|
||||
|
||||
TEST_F(BasicCollisionTest, DivergentMotion) {
|
||||
// 设置两个物体背向运动的场景
|
||||
// 物体1在(150,100),向右运动,航向90度
|
||||
// 物体2在(200,100),向右运动,航向90度
|
||||
// 两物体都在远离对方,不应该发生碰撞
|
||||
|
||||
Vehicle obj1;
|
||||
obj1.vehicleNo = "V1";
|
||||
obj1.position = {150, 100};
|
||||
obj1.speed = 10;
|
||||
obj1.heading = 90; // 右运动
|
||||
obj1.type = MovingObjectType::UNMANNED;
|
||||
|
||||
Vehicle obj2;
|
||||
obj2.vehicleNo = "V2";
|
||||
obj2.position = {300, 100}; // 增加初始距离到 150m
|
||||
obj2.speed = 10;
|
||||
obj2.heading = 90; // 也向右运动
|
||||
obj2.type = MovingObjectType::SPECIAL;
|
||||
|
||||
auto result = detector_->checkCollision(obj1, obj2, 10.0);
|
||||
|
||||
// 验证结果
|
||||
EXPECT_FALSE(result.willCollide) << "同向运动且距离大于安全距离的物体不应该发生碰撞";
|
||||
EXPECT_EQ(result.type, CollisionType::PARALLEL) << "应该识别为平行运动";
|
||||
|
||||
// 初始距离应该是 150m
|
||||
EXPECT_NEAR(result.minDistance, 150.0, 0.1);
|
||||
|
||||
// 由于两车都以相同速度向右运动,最小距离应该保持不变
|
||||
EXPECT_NEAR(result.timeToMinDistance, 0.0, 0.1);
|
||||
}
|
||||
|
||||
// 6. 追尾场景测试
|
||||
TEST_F(BasicCollisionTest, TailgatingMotion) {
|
||||
// 创建两个同向运动的物体,后车速度大于前车
|
||||
Vehicle v1; // 前车
|
||||
v1.vehicleNo = "V1";
|
||||
v1.position = {60.0, 100.0}; // 前车在前方60米处
|
||||
v1.speed = 10.0; // 前车速度10m/s
|
||||
v1.heading = 90.0; // 向东运动
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
|
||||
Vehicle v2; // 后车
|
||||
v2.vehicleNo = "V2";
|
||||
v2.position = {0.0, 100.0}; // 后车在原点
|
||||
v2.speed = 15.0; // 后车速度15m/s
|
||||
v2.heading = 90.0; // 向东运动
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
|
||||
auto result = detector_->checkCollision(v1, v2, 30.0);
|
||||
|
||||
// 验证碰撞类型
|
||||
EXPECT_EQ(result.type, CollisionType::PARALLEL) << "应该识别为平行运动";
|
||||
|
||||
// 验证会发生碰撞
|
||||
EXPECT_TRUE(result.willCollide) << "后车速度大于前车,应该预测到碰撞";
|
||||
|
||||
// 验证碰撞时间(初始距离60米,相对速度5m/s,安全距离50米,需要缩短10米,所以碰撞时间应该是2秒)
|
||||
EXPECT_NEAR(result.timeToCollision, 2.0, 0.1) << "碰撞时间应该接近2秒";
|
||||
|
||||
// 验证最小距离(应该是安全距离)
|
||||
EXPECT_NEAR(result.minDistance, 50.0, 0.1) << "最小距离应该是安全距离50米";
|
||||
|
||||
// 验证最小距离时间(应该等于碰撞时间)
|
||||
EXPECT_NEAR(result.timeToMinDistance, 2.0, 0.1) << "最小距离时间应该等于碰撞时间";
|
||||
|
||||
// 验证碰撞点(在两车碰撞时的中点)
|
||||
// 前车:初始位置 60 + 10 * 2 = 80
|
||||
// 后车:初始位置 0 + 15 * 2 = 30
|
||||
// 碰撞点应该在 (80 + 30) / 2 = 55
|
||||
EXPECT_NEAR(result.collisionPoint.x, 55.0, 0.1) << "碰撞点x坐标应该在55米处";
|
||||
EXPECT_NEAR(result.collisionPoint.y, 100.0, 0.1) << "碰撞点y坐标应该保持在100米";
|
||||
|
||||
// 验证碰撞时刻的物体状态
|
||||
// 前车位置:60 + 10 * 2 = 80
|
||||
EXPECT_NEAR(result.object1State.position.x, 80.0, 0.1);
|
||||
EXPECT_NEAR(result.object1State.position.y, 100.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.speed, 10.0);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.heading, 90.0);
|
||||
|
||||
// 后车位置:0 + 15 * 2 = 30
|
||||
EXPECT_NEAR(result.object2State.position.x, 30.0, 0.1);
|
||||
EXPECT_NEAR(result.object2State.position.y, 100.0, 0.1);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.speed, 15.0);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.heading, 90.0);
|
||||
}
|
||||
|
||||
// 6. QN002 与 AC001 交叉路径测试(实际场景)
|
||||
TEST_F(BasicCollisionTest, QN002AircraftCrossing) {
|
||||
// 创建 AC001 航空器,从 T7 出发
|
||||
Aircraft aircraft;
|
||||
aircraft.flightNo = "AC001";
|
||||
aircraft.position = {0.0, 0.0}; // T7 点的相对坐标
|
||||
aircraft.speed = 50.0 / 3.6; // 转换为米/秒
|
||||
aircraft.heading = 45.0; // 根据 T7 到 T11 的方向计算
|
||||
aircraft.type = MovingObjectType::AIRCRAFT;
|
||||
|
||||
// 创建 QN002 无人车,从 T4 到 T8 路径上的一点发
|
||||
Vehicle qn002;
|
||||
qn002.vehicleNo = "QN002";
|
||||
qn002.position = {-2.0, 120.0}; // 在 T4-T8 路径上,距离 T7 约 120 米(小于检测范围 150 米)
|
||||
qn002.speed = 36.0 / 3.6; // 转换为米/秒
|
||||
qn002.heading = 135.0; // 根据 T4 到 T8 的方向计算
|
||||
qn002.type = MovingObjectType::UNMANNED;
|
||||
qn002.isControllable = true;
|
||||
|
||||
// 检测碰撞
|
||||
auto result = detector_->checkCollision(aircraft, qn002, 30.0);
|
||||
|
||||
// 验证是否检测到碰撞
|
||||
EXPECT_TRUE(result.willCollide) << "QN002 与 AC001 的交叉路径应该检测为碰撞";
|
||||
EXPECT_EQ(result.type, CollisionType::CROSSING) << "应该识别为交叉碰撞";
|
||||
|
||||
// 记录详细的测试信息
|
||||
Logger::debug("QN002-AC001 碰撞测试结果:");
|
||||
Logger::debug("碰撞类型: ", static_cast<int>(result.type));
|
||||
Logger::debug("最小距离: ", result.minDistance, "m");
|
||||
Logger::debug("碰撞时间: ", result.timeToCollision, "s");
|
||||
Logger::debug("碰撞点: (", result.collisionPoint.x, ",", result.collisionPoint.y, ")");
|
||||
|
||||
// 验证碰撞参数
|
||||
EXPECT_GT(result.timeToCollision, 0.0) << "碰撞时间应该大于0";
|
||||
EXPECT_LT(result.timeToCollision, 30.0) << "碰撞时间应该在预测窗口内";
|
||||
EXPECT_LE(result.minDistance, 75.0) << "小距离应该小于预警距离";
|
||||
|
||||
// 验证物体状态
|
||||
EXPECT_DOUBLE_EQ(result.object1State.speed, 50.0 / 3.6);
|
||||
EXPECT_DOUBLE_EQ(result.object1State.heading, 45.0);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.speed, 36.0 / 3.6);
|
||||
EXPECT_DOUBLE_EQ(result.object2State.heading, 135.0);
|
||||
}
|
||||
|
||||
TEST_F(BasicCollisionTest, CrossingBeforeAndAfter) {
|
||||
// 设置 Mock 对象的预期行为
|
||||
EXPECT_CALL(*mockControllableVehicles_, isControllable(::testing::StrEq("QN002")))
|
||||
.WillRepeatedly(::testing::Return(true));
|
||||
|
||||
// 创建一个航空器和一个无人车
|
||||
Aircraft aircraft;
|
||||
aircraft.id = "AC001";
|
||||
aircraft.type = MovingObjectType::AIRCRAFT;
|
||||
aircraft.speed = 10.0; // 10米/秒
|
||||
aircraft.heading = 90.0; // 向东
|
||||
|
||||
Vehicle vehicle;
|
||||
vehicle.id = "QN002";
|
||||
vehicle.vehicleNo = "QN002";
|
||||
vehicle.type = MovingObjectType::UNMANNED;
|
||||
vehicle.isControllable = true;
|
||||
vehicle.speed = 10.0; // 10米/秒
|
||||
vehicle.heading = 0.0; // 向北
|
||||
|
||||
// 设置初始位置:航空器在交叉点西侧,无人车在南侧
|
||||
Vector2D crossPoint{300.0, 300.0}; // 交叉点坐标
|
||||
|
||||
// 1. 测试交叉前的情况
|
||||
// 航空器在交叉点西侧,无人车在南侧
|
||||
aircraft.position = {crossPoint.x - 80.0, crossPoint.y}; // 航空器在交叉点西侧80
|
||||
vehicle.position = {crossPoint.x, crossPoint.y - 80.0}; // 无人车在交叉点南侧80米
|
||||
|
||||
// 更新交通数据
|
||||
std::vector<Aircraft> aircrafts = {aircraft};
|
||||
std::vector<Vehicle> vehicles = {vehicle};
|
||||
detector_->updateTraffic(aircrafts, vehicles);
|
||||
|
||||
// 检测碰撞(使用30秒的预测窗口)
|
||||
auto risks = detector_->detectCollisions();
|
||||
|
||||
// 记录详细的测试信息
|
||||
Logger::debug("交叉前碰撞检测:");
|
||||
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
|
||||
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
|
||||
Logger::debug("交叉点位置: (", crossPoint.x, ",", crossPoint.y, ")");
|
||||
|
||||
ASSERT_FALSE(risks.empty()) << "交叉前应该检测到碰撞风险";
|
||||
EXPECT_EQ(risks[0].level, RiskLevel::WARNING) << "交叉前应该是预警级别";
|
||||
|
||||
// 2. 测试交叉过程中
|
||||
// 航空器在交叉点西侧20米,无人车在南侧20米
|
||||
aircraft.position = {crossPoint.x - 20.0, crossPoint.y};
|
||||
vehicle.position = {crossPoint.x, crossPoint.y - 20.0};
|
||||
|
||||
// 更新交通数据
|
||||
aircrafts = {aircraft};
|
||||
vehicles = {vehicle};
|
||||
detector_->updateTraffic(aircrafts, vehicles);
|
||||
|
||||
// 检测碰撞
|
||||
risks = detector_->detectCollisions();
|
||||
|
||||
// 记录详细的测试信息
|
||||
Logger::debug("交叉过程碰撞检测:");
|
||||
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
|
||||
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
|
||||
|
||||
ASSERT_FALSE(risks.empty()) << "交叉过程中应该检测到碰撞风险";
|
||||
EXPECT_EQ(risks[0].level, RiskLevel::CRITICAL) << "交叉过程中应该是告警级别";
|
||||
|
||||
// 3. 测试交叉后的情况
|
||||
// 航空器已经通过交叉点并远离安全距离,无人车还在交叉点附近
|
||||
aircraft.position = {crossPoint.x + 160.0, crossPoint.y}; // 航空器在交叉点东侧160米(大于安全距离150米)
|
||||
vehicle.position = {crossPoint.x, crossPoint.y - 10.0}; // 无人车还在交叉点附近
|
||||
|
||||
// 更新交通数据
|
||||
aircrafts = {aircraft};
|
||||
vehicles = {vehicle};
|
||||
detector_->updateTraffic(aircrafts, vehicles);
|
||||
|
||||
// 检测碰撞
|
||||
risks = detector_->detectCollisions();
|
||||
|
||||
// 记录详细的测试信息
|
||||
Logger::debug("交叉后碰撞检测:");
|
||||
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
|
||||
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
|
||||
|
||||
EXPECT_TRUE(risks.empty()) << "航空器通过远离安全距离后,应该解除冲突";
|
||||
|
||||
// 4. 测试无人车继续运动
|
||||
// 无人车向北移动到交叉点
|
||||
vehicle.position = {crossPoint.x, crossPoint.y};
|
||||
|
||||
// 更新交通数据
|
||||
vehicles = {vehicle};
|
||||
detector_->updateTraffic(aircrafts, vehicles);
|
||||
|
||||
// 检测碰撞
|
||||
risks = detector_->detectCollisions();
|
||||
|
||||
// 记录详细的测试信息
|
||||
Logger::debug("无人车继续运动碰撞检测:");
|
||||
Logger::debug("航空器位置: (", aircraft.position.x, ",", aircraft.position.y, ")");
|
||||
Logger::debug("无人车位置: (", vehicle.position.x, ",", vehicle.position.y, ")");
|
||||
|
||||
EXPECT_TRUE(risks.empty()) << "航空器已远离,无人车继续运动不应产生新的冲突";
|
||||
}
|
||||
128
tests/BasicTypesTest.cpp
Normal file
128
tests/BasicTypesTest.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "types/BasicTypes.h"
|
||||
#include <ctime>
|
||||
|
||||
class BasicTypesTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 测试设置
|
||||
}
|
||||
};
|
||||
|
||||
// 测试 Vector2D
|
||||
TEST_F(BasicTypesTest, Vector2DMagnitude) {
|
||||
Vector2D v(3.0, 4.0);
|
||||
EXPECT_DOUBLE_EQ(v.magnitude(), 5.0);
|
||||
}
|
||||
|
||||
TEST_F(BasicTypesTest, Vector2DDirection) {
|
||||
Vector2D v(1.0, 1.0); // 45度角
|
||||
EXPECT_NEAR(v.direction(), 45.0, 0.001);
|
||||
}
|
||||
|
||||
// 测试 MovingObject 的速度计算
|
||||
class TestMovingObject : public MovingObject {
|
||||
protected:
|
||||
bool isValidPosition(const GeoPosition& newPos) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isValidSpeed(double speed) const override {
|
||||
return speed <= 100.0;
|
||||
}
|
||||
|
||||
double getSpeedSmoothingFactor() const override {
|
||||
return 0.3;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BasicTypesTest, MovingObjectSpeedCalculation) {
|
||||
TestMovingObject obj;
|
||||
|
||||
// 使用毫秒级时间戳
|
||||
uint64_t now = 1000000;
|
||||
|
||||
// 第一个位置
|
||||
GeoPosition pos1{36.0, 120.0};
|
||||
obj.updateMotion(pos1, now);
|
||||
EXPECT_DOUBLE_EQ(obj.speed, 0.0); // 第一个点速度应该为0
|
||||
|
||||
// 第二个位置(向东移动约90米/秒)
|
||||
GeoPosition pos2{36.0, 120.001};
|
||||
double distance = obj.calculateDistance(pos1, pos2);
|
||||
printf("Distance: %.2f meters\n", distance);
|
||||
|
||||
obj.updateMotion(pos2, now + 1000); // 1000毫秒后
|
||||
printf("Speed: %.2f m/s\n", obj.speed);
|
||||
|
||||
EXPECT_NEAR(obj.speed, 90.0, 2.0); // 使用 Haversine 公式计算的实际距离
|
||||
|
||||
// 检查航向角
|
||||
EXPECT_NEAR(obj.heading, 90.0, 0.1); // 应该朝向正东
|
||||
}
|
||||
|
||||
// 测试 Aircraft
|
||||
TEST_F(BasicTypesTest, AircraftValidation) {
|
||||
Aircraft aircraft;
|
||||
aircraft.flightNo = "CES2501";
|
||||
|
||||
// 使用毫秒级时间戳
|
||||
uint64_t now = 1000000;
|
||||
|
||||
// 测试位置更新
|
||||
GeoPosition pos1{36.0, 120.0};
|
||||
aircraft.updateMotion(pos1, now);
|
||||
|
||||
GeoPosition pos2{36.0, 120.001}; // 约90米
|
||||
aircraft.updateMotion(pos2, now + 2000); // 2000毫秒后
|
||||
|
||||
// 速度应该约为45米/秒
|
||||
EXPECT_NEAR(aircraft.speed, 45.0, 2.0);
|
||||
}
|
||||
|
||||
// 测试 Vehicle
|
||||
TEST_F(BasicTypesTest, VehicleValidation) {
|
||||
Vehicle vehicle;
|
||||
vehicle.vehicleNo = "VEH001";
|
||||
|
||||
// 使用毫秒级时间戳
|
||||
uint64_t now = 1000000;
|
||||
|
||||
// 测试位置更新
|
||||
GeoPosition pos1{36.0, 120.0};
|
||||
vehicle.updateMotion(pos1, now);
|
||||
|
||||
// 使用约9米的位置变化(小于10米的跳变限制)
|
||||
GeoPosition pos2{36.00008, 120.0}; // 约9米
|
||||
double distance = vehicle.calculateDistance(pos1, pos2);
|
||||
printf("Vehicle distance: %.2f meters\n", distance);
|
||||
|
||||
vehicle.updateMotion(pos2, now + 1000); // 1000毫秒后
|
||||
printf("Vehicle speed: %.2f m/s\n", vehicle.speed);
|
||||
|
||||
// 速度应该约为9米/秒(小于20米/秒的限制)
|
||||
EXPECT_NEAR(vehicle.speed, 9.0, 1.0);
|
||||
}
|
||||
|
||||
// 添加边界条件测试
|
||||
TEST_F(BasicTypesTest, VectorZeroMagnitude) {
|
||||
Vector2D v(0.0, 0.0);
|
||||
EXPECT_DOUBLE_EQ(v.magnitude(), 0.0);
|
||||
}
|
||||
|
||||
// 测试距离计算
|
||||
TEST_F(BasicTypesTest, DistanceCalculation) {
|
||||
TestMovingObject obj;
|
||||
|
||||
// 测试经度方向的距离(约90米)
|
||||
GeoPosition pos1{36.0, 120.0};
|
||||
GeoPosition pos2{36.0, 120.001};
|
||||
double distance1 = obj.calculateDistance(pos1, pos2);
|
||||
EXPECT_NEAR(distance1, 90.0, 1.0);
|
||||
|
||||
// 测试纬度方向的距离(约22米)
|
||||
GeoPosition pos3{36.0, 120.0};
|
||||
GeoPosition pos4{36.0002, 120.0};
|
||||
double distance2 = obj.calculateDistance(pos3, pos4);
|
||||
EXPECT_NEAR(distance2, 22.0, 1.0);
|
||||
}
|
||||
26
tests/CMakeLists.txt
Normal file
26
tests/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
||||
# 设置测试源文件
|
||||
set(TEST_SOURCES
|
||||
AirportBoundsTest.cpp
|
||||
BasicCollisionTest.cpp
|
||||
CollisionDetectorTest.cpp
|
||||
TrafficLightDetectorTest.cpp
|
||||
SafetyZoneTest.cpp
|
||||
BasicTypesTest.cpp
|
||||
HTTPDataSourceTest.cpp
|
||||
DataCollectorTest.cpp
|
||||
)
|
||||
|
||||
# 创建测试可执行文件
|
||||
add_executable(unit_tests ${TEST_SOURCES})
|
||||
|
||||
# 链接需要的库
|
||||
target_link_libraries(unit_tests
|
||||
PRIVATE
|
||||
${PROJECT_NAME}_lib
|
||||
GTest::gtest_main
|
||||
GTest::gmock_main
|
||||
)
|
||||
|
||||
# 添加测试
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(unit_tests)
|
||||
322
tests/CollisionDetectorTest.cpp
Normal file
322
tests/CollisionDetectorTest.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
#include "detector/CollisionDetector.h"
|
||||
#include "vehicle/ControllableVehicles.h"
|
||||
#include "config/AirportBounds.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "utils/Logger.h"
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
// Mock ControllableVehicles 类
|
||||
class MockControllableVehicles {
|
||||
public:
|
||||
MockControllableVehicles() = default;
|
||||
|
||||
MOCK_METHOD(bool, isControllable, (const std::string& vehicleNo), (const));
|
||||
MOCK_METHOD(const ControllableVehicleConfig*, findVehicle, (const std::string& vehicleNo), (const));
|
||||
MOCK_METHOD(void, sendCommand, (const std::string& vehicleNo, const VehicleCommand& cmd));
|
||||
|
||||
// 转换为 ControllableVehicles& 的操作符
|
||||
operator const ControllableVehicles&() const {
|
||||
return ControllableVehicles::getInstance();
|
||||
}
|
||||
};
|
||||
|
||||
// Mock AirportBounds 类
|
||||
class MockAirportBounds : public AirportBounds {
|
||||
public:
|
||||
MockAirportBounds() : AirportBounds("") {
|
||||
// 设置测试区域边界
|
||||
airportBounds_ = Bounds(0, 0, 5000, 4000);
|
||||
areaBounds_[AreaType::TEST_ZONE] = airportBounds_;
|
||||
|
||||
// 设置测试区域配置
|
||||
AreaConfig config;
|
||||
config.collision_radius = {50.0, 50.0, 25.0}; // aircraft, special, unmanned
|
||||
config.height_threshold = 10.0;
|
||||
config.warning_zone_radius = {100.0, 100.0, 50.0};
|
||||
config.alert_zone_radius = {50.0, 50.0, 25.0};
|
||||
areaConfigs_[AreaType::TEST_ZONE] = config;
|
||||
}
|
||||
|
||||
MOCK_CONST_METHOD1(getAreaType, AreaType(const Vector2D& position));
|
||||
|
||||
const AreaConfig& getAreaConfig(AreaType type) const override {
|
||||
auto it = areaConfigs_.find(type);
|
||||
if (it == areaConfigs_.end()) {
|
||||
throw std::runtime_error("Invalid area type");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const Bounds& getAirportBounds() const override {
|
||||
return airportBounds_;
|
||||
}
|
||||
};
|
||||
|
||||
class CollisionDetectorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 设置日志级别为 DEBUG
|
||||
Logger::initialize("", LogLevel::DEBUG);
|
||||
|
||||
// 打印当前工作目录
|
||||
std::cout << "Current working directory: " << std::filesystem::current_path() << std::endl;
|
||||
|
||||
// 创建 Mock 对象
|
||||
airportBounds_ = std::make_unique<MockAirportBounds>();
|
||||
mockControllableVehicles_ = std::make_unique<MockControllableVehicles>();
|
||||
|
||||
// 设置 Mock 对象的行为
|
||||
EXPECT_CALL(*airportBounds_, getAreaType(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(AreaType::TEST_ZONE));
|
||||
|
||||
// 创建冲突检测器
|
||||
detector_ = std::make_unique<CollisionDetector>(*airportBounds_, *mockControllableVehicles_);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
Logger::initialize("", LogLevel::INFO);
|
||||
}
|
||||
|
||||
std::unique_ptr<MockAirportBounds> airportBounds_;
|
||||
std::unique_ptr<MockControllableVehicles> mockControllableVehicles_;
|
||||
std::unique_ptr<CollisionDetector> detector_;
|
||||
};
|
||||
|
||||
// 测试可控车辆(无人车)与航空器的碰撞检测
|
||||
TEST_F(CollisionDetectorTest, UnmannedVehicleAircraftCollision) {
|
||||
// 设置测试数据
|
||||
Aircraft aircraft;
|
||||
aircraft.flightNo = "TEST001";
|
||||
aircraft.position = {100.0, 100.0};
|
||||
aircraft.speed = 15.0;
|
||||
aircraft.heading = 90.0; // 向东行驶
|
||||
aircraft.type = MovingObjectType::AIRCRAFT;
|
||||
|
||||
Vehicle vehicle;
|
||||
vehicle.vehicleNo = "VEH001";
|
||||
vehicle.position = {120.0, 105.0}; // 在航空器前方偏北5米处
|
||||
vehicle.speed = 8.0;
|
||||
vehicle.heading = 270.0; // 向西行驶(与航空器相向而行)
|
||||
vehicle.type = MovingObjectType::UNMANNED;
|
||||
vehicle.isControllable = true;
|
||||
|
||||
// 测试1:相向而行且距离较近
|
||||
auto collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "相向而行且距离较近的航空器和无人车应该检测到碰撞";
|
||||
|
||||
// 测试2:增加距离到边界值
|
||||
vehicle.position = {150.0, 105.0}; // 增加到50米距离
|
||||
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "在警告距离内应该检测到碰撞";
|
||||
|
||||
// 测试3:超出碰撞检测范围
|
||||
vehicle.position = {900.0, 100.0}; // 增加到800米距离
|
||||
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "距离较远时不应该检测到碰撞";
|
||||
}
|
||||
|
||||
// 测试无人车与特勤车的碰撞检测
|
||||
TEST_F(CollisionDetectorTest, UnmannedVehicleSpecialVehicleCollision) {
|
||||
Vehicle unmanned;
|
||||
unmanned.vehicleNo = "VEH001";
|
||||
unmanned.position = {100.0, 100.0};
|
||||
unmanned.speed = 10.0;
|
||||
unmanned.heading = 90.0; // 向东行驶
|
||||
unmanned.type = MovingObjectType::UNMANNED;
|
||||
unmanned.isControllable = true;
|
||||
|
||||
Vehicle special;
|
||||
special.vehicleNo = "VEH002";
|
||||
special.position = {120.0, 100.0}; // 距离20米
|
||||
special.speed = 5.0;
|
||||
special.heading = 270.0; // 向西行驶
|
||||
special.type = MovingObjectType::SPECIAL;
|
||||
special.isControllable = false;
|
||||
|
||||
// 测试1:相向而行且距离较近
|
||||
auto collisionResult = detector_->checkCollision(unmanned, special, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "相向而行且距离较近的无人车和特勤车应该检测到碰撞";
|
||||
|
||||
// 测试2:在警告距离边界
|
||||
special.position = {150.0, 100.0}; // 增加到50米距离
|
||||
collisionResult = detector_->checkCollision(unmanned, special, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "在警告距离应该检测到碰撞";
|
||||
|
||||
// 测试3:超出碰撞检测范围
|
||||
special.position = {300.0, 100.0}; // 增加到200米距离
|
||||
collisionResult = detector_->checkCollision(unmanned, special, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "超出警告距离时不应该检测到碰撞";
|
||||
}
|
||||
|
||||
// 测试斜向叉路径碰撞
|
||||
TEST_F(CollisionDetectorTest, DiagonalCrossingPathsCollision) {
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "VEH001";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 10.0;
|
||||
v1.heading = 45.0; // 北行驶
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
v1.isControllable = true;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "VEH002";
|
||||
v2.position = {150.0, 150.0};
|
||||
v2.speed = 10.0;
|
||||
v2.heading = 225.0; // 向西南行驶(与v1相向)
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
v2.isControllable = true;
|
||||
|
||||
// 测试1:路径交叉且距离较近
|
||||
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "斜向相向而行且路径交叉的车辆应该检测到碰撞";
|
||||
|
||||
// 测试2:不同速度比
|
||||
v1.speed = 5.0; // 降低速度
|
||||
v2.speed = 15.0; // 提高速度
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "不同速度比的斜向相向车辆应该检测到碰撞";
|
||||
|
||||
// 测试3:超出碰撞检测范围
|
||||
v2.position = {300.0, 300.0}; // 增加到较远距离
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "距离较远时不应该检测到碰撞";
|
||||
}
|
||||
|
||||
// 测试垂直交叉路径碰撞
|
||||
TEST_F(CollisionDetectorTest, PerpendicularCrossingPathsCollision) {
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "VEH001";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 10.0;
|
||||
v1.heading = 90.0; // 向东行驶
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
v1.isControllable = true;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "VEH002";
|
||||
v2.position = {120.0, 120.0}; // 在v1东北方20米处
|
||||
v2.speed = 10.0;
|
||||
v2.heading = 180.0; // 向南行驶
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
v2.isControllable = true;
|
||||
|
||||
// 测试1:垂直相交且距离较近
|
||||
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "垂直交叉路径的车辆应该检测到碰撞";
|
||||
|
||||
// 测试2:不同速度比
|
||||
v1.speed = 5.0; // 降低速度
|
||||
v2.speed = 15.0; // 提高速度
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "不同速度的垂直交叉路径车辆应该检测到碰撞";
|
||||
|
||||
// 测试3:超出碰撞检测范围
|
||||
v2.position = {200.0, 200.0}; // 增加到较远距离
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "距离较远时应该检测到碰撞";
|
||||
}
|
||||
|
||||
// 测试静止辆的碰撞检测
|
||||
TEST_F(CollisionDetectorTest, StationaryVehiclesCollision) {
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "VEH001";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 0.0; // 静止
|
||||
v1.heading = 90.0;
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
v1.isControllable = true;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "VEH002";
|
||||
v2.position = {105.0, 100.0}; // 距离5米
|
||||
v2.speed = 0.0; // 静止
|
||||
v2.heading = 90.0;
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
v2.isControllable = true;
|
||||
|
||||
// 测试1:两个静止车辆距离小于安全距离
|
||||
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "距离小于安全距离的静止车辆应该检测到碰撞";
|
||||
|
||||
// 测试2:一动一静,移动车辆接近静止车辆
|
||||
v2.position = {120.0, 100.0}; // 增加距离到20米
|
||||
v2.speed = 5.0; // 开始移动
|
||||
v2.heading = 270.0; // 向西行驶(朝向v1)
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "移动车辆接近静止车辆时应该检测到碰撞";
|
||||
|
||||
// 测试3:一动一静,移动车辆远离静止车辆
|
||||
v2.position = {160.0, 100.0}; // 增加到60米,大于安全距离(50米)
|
||||
v2.speed = 5.0; // 开始移动
|
||||
v2.heading = 90.0; // 向东行驶(远离v1)
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "移动车辆远离静止车辆且距离于安全距离时不应该检测到碰撞";
|
||||
}
|
||||
|
||||
// 测试同向运动的碰撞检测
|
||||
TEST_F(CollisionDetectorTest, TailgatingCollision) {
|
||||
Vehicle v1;
|
||||
v1.vehicleNo = "VEH001";
|
||||
v1.position = {100.0, 100.0};
|
||||
v1.speed = 10.0;
|
||||
v1.heading = 90.0; // 向东行驶
|
||||
v1.type = MovingObjectType::UNMANNED;
|
||||
v1.isControllable = true;
|
||||
|
||||
Vehicle v2;
|
||||
v2.vehicleNo = "VEH002";
|
||||
v2.position = {40.0, 100.0}; // 在v1西侧60米处(100-60=40)
|
||||
v2.speed = 10.0;
|
||||
v2.heading = 90.0; // 也向东行驶
|
||||
v2.type = MovingObjectType::UNMANNED;
|
||||
v2.isControllable = true;
|
||||
|
||||
// 测试1:同向同速
|
||||
auto collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "同向同速的车辆不应该检测到碰撞";
|
||||
|
||||
// 测试2:同向不同速(v2速度更快,会追上v1)
|
||||
v2.speed = 15.0;
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "同向但速度较快的车辆追上前车时应该检测到碰撞";
|
||||
|
||||
// 测试3:同向不同速但距离较远
|
||||
v2.position = {0.0, 100.0}; // 在原点
|
||||
v2.speed = 11.0; // 减小速度差,从 15m/s 改为 11m/s
|
||||
collisionResult = detector_->checkCollision(v1, v2, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "相对速度小且距离较远时不应该检测到碰撞";
|
||||
}
|
||||
|
||||
// 测试航空器与静止车辆的碰撞检测
|
||||
TEST_F(CollisionDetectorTest, AircraftStationaryVehicleCollision) {
|
||||
Aircraft aircraft;
|
||||
aircraft.flightNo = "TEST001";
|
||||
aircraft.position = {100.0, 100.0};
|
||||
aircraft.speed = 15.0;
|
||||
aircraft.heading = 90.0; // 向东行驶
|
||||
aircraft.type = MovingObjectType::AIRCRAFT;
|
||||
|
||||
Vehicle vehicle;
|
||||
vehicle.vehicleNo = "VEH001";
|
||||
vehicle.position = {150.0, 100.0}; // 在航空器前方50米处
|
||||
vehicle.speed = 0.0; // 静止
|
||||
vehicle.heading = 90.0;
|
||||
vehicle.type = MovingObjectType::UNMANNED;
|
||||
vehicle.isControllable = true;
|
||||
|
||||
// 测试1:航空器接近静止车辆
|
||||
auto collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
|
||||
EXPECT_TRUE(collisionResult.willCollide) << "航空器接近静止车辆时应该检测到碰撞";
|
||||
|
||||
// 测试2:静止车辆在航空器航向偏离处
|
||||
vehicle.position = {200.0, 200.0}; // 在航空器前方偏北,距离约100米(大于安全距离75米)
|
||||
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "航空器与不在航向上的静止车辆不应该检测到碰撞";
|
||||
|
||||
// 测试3:静止车辆在航空器后方
|
||||
vehicle.position = {0.0, 100.0}; // 在航空器后方100米(大于安全距离75米)
|
||||
collisionResult = detector_->checkCollision(aircraft, vehicle, 30.0);
|
||||
EXPECT_FALSE(collisionResult.willCollide) << "航空器与后方的静止车辆不应该检测到碰撞";
|
||||
}
|
||||
906
tests/DataCollectorTest.cpp
Normal file
906
tests/DataCollectorTest.cpp
Normal file
@ -0,0 +1,906 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "collector/DataCollector.h"
|
||||
#include "config/AirportBounds.h"
|
||||
#include "spatial/CoordinateConverter.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "network/MessageTypes.h"
|
||||
#include "core/System.h"
|
||||
|
||||
// 创建一个 Mock DataSource 类
|
||||
class MockDataSource : public DataSource {
|
||||
public:
|
||||
MOCK_METHOD0(connect, bool());
|
||||
MOCK_METHOD0(disconnect, void());
|
||||
MOCK_CONST_METHOD0(isAvailable, bool());
|
||||
MOCK_METHOD1(fetchPositionAircraftData, bool(std::vector<Aircraft>&));
|
||||
MOCK_METHOD1(fetchPositionVehicleData, bool(std::vector<Vehicle>&));
|
||||
MOCK_METHOD1(fetchTrafficLightSignals, bool(std::vector<TrafficLightSignal>&));
|
||||
MOCK_METHOD2(sendUnmannedVehicleCommand, bool(const std::string&, const VehicleCommand&));
|
||||
MOCK_METHOD2(fetchUnmannedVehicleStatus, bool(const std::string&, std::string&));
|
||||
};
|
||||
|
||||
// 创建测试专用的 AirportBounds 类
|
||||
class TestAirportBounds : public AirportBounds {
|
||||
public:
|
||||
using AirportBounds::AirportBounds; // 继承构造函数
|
||||
|
||||
void setBounds(const Bounds& bounds) {
|
||||
this->airportBounds_ = bounds;
|
||||
}
|
||||
|
||||
void setReferencePoint(const Vector2D& point) {
|
||||
this->referencePoint_ = point;
|
||||
}
|
||||
|
||||
void setRotationAngle(double angle) {
|
||||
this->rotationAngle_ = angle * M_PI / 180.0; // 转换为弧度
|
||||
}
|
||||
|
||||
protected:
|
||||
using AirportBounds::airportBounds_;
|
||||
using AirportBounds::referencePoint_;
|
||||
using AirportBounds::rotationAngle_;
|
||||
};
|
||||
|
||||
class DataCollectorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 设置日志级别
|
||||
Logger::setLogLevel(LogLevel::DEBUG);
|
||||
|
||||
// 初始化系统配置
|
||||
SystemConfig::instance().load("config/system_config.json");
|
||||
|
||||
// 创建测试实例
|
||||
bounds_ = std::make_unique<TestAirportBounds>();
|
||||
|
||||
// 设置机场边界参数
|
||||
bounds_->setReferencePoint(Vector2D{0, 0}); // 世界坐标系中的参考点
|
||||
bounds_->setRotationAngle(68.53); // 与 airport_bounds.json 保持一致
|
||||
bounds_->setBounds(Bounds(-100, -200, 800, 400)); // 与 airport_bounds.json 保持一致
|
||||
|
||||
// 初始化坐标转换器
|
||||
const auto& refPoint = SystemConfig::instance().airport.reference_point;
|
||||
CoordinateConverter::instance().setReferencePoint(refPoint.latitude, refPoint.longitude);
|
||||
|
||||
collector = std::make_unique<DataCollector>(*bounds_);
|
||||
mockSource = std::make_shared<::testing::NiceMock<MockDataSource>>();
|
||||
collector->setDataSource(mockSource);
|
||||
|
||||
// 初始化配置
|
||||
dataSourceConfig_.position.refresh_interval_ms = 100;
|
||||
dataSourceConfig_.position.timeout_ms = 5000; // 添加连接超时配置
|
||||
dataSourceConfig_.position.read_timeout_ms = 2000; // 添加读取超时配置
|
||||
|
||||
dataSourceConfig_.vehicle.refresh_interval_ms = 100;
|
||||
dataSourceConfig_.vehicle.timeout_ms = 5000;
|
||||
dataSourceConfig_.vehicle.read_timeout_ms = 2000;
|
||||
|
||||
dataSourceConfig_.traffic_light.refresh_interval_ms = 100;
|
||||
dataSourceConfig_.traffic_light.timeout_ms = 5000;
|
||||
dataSourceConfig_.traffic_light.read_timeout_ms = 2000;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 500; // 设置警告间隔
|
||||
EXPECT_TRUE(collector->initialize(dataSourceConfig_, warnConfig));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
collector.reset();
|
||||
}
|
||||
|
||||
// 创建测试用的航空器
|
||||
Aircraft createTestAircraft(const std::string& id, double lat, double lon) {
|
||||
Aircraft aircraft;
|
||||
aircraft.id = id;
|
||||
aircraft.flightNo = id;
|
||||
aircraft.geo.latitude = lat;
|
||||
aircraft.geo.longitude = lon;
|
||||
aircraft.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
return aircraft;
|
||||
}
|
||||
|
||||
// 创建测试用的车辆
|
||||
Vehicle createTestVehicle(const std::string& id, double lat, double lon) {
|
||||
Vehicle vehicle;
|
||||
vehicle.id = id;
|
||||
vehicle.vehicleNo = id;
|
||||
vehicle.geo.latitude = lat;
|
||||
vehicle.geo.longitude = lon;
|
||||
vehicle.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
return vehicle;
|
||||
}
|
||||
|
||||
// 创建测试用的红绿灯信号
|
||||
TrafficLightSignal createTestTrafficLight(const std::string& id, int state) {
|
||||
TrafficLightSignal signal;
|
||||
signal.trafficLightId = id;
|
||||
signal.ns_status = static_cast<SignalStatus>(state);
|
||||
signal.ew_status = static_cast<SignalStatus>(state);
|
||||
signal.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
return signal;
|
||||
}
|
||||
|
||||
// 创建边界内的航空器
|
||||
Aircraft createOutOfBoundsAircraft(const std::string& id) {
|
||||
// 计算偏移量,考虑机场旋转角度
|
||||
double angle = 68.53 * M_PI / 180.0; // 转换为弧度
|
||||
double offset = 0.027; // 约 3km
|
||||
double lat_offset = offset * std::cos(angle);
|
||||
double lon_offset = offset * std::sin(angle);
|
||||
|
||||
const auto& refPoint = SystemConfig::instance().airport.reference_point;
|
||||
return createTestAircraft(id, refPoint.latitude + lat_offset, refPoint.longitude + lon_offset);
|
||||
}
|
||||
|
||||
// 创建边界内的车辆
|
||||
Vehicle createOutOfBoundsVehicle(const std::string& id) {
|
||||
// 计算偏移量,考虑机场旋转角度
|
||||
double angle = 68.53 * M_PI / 180.0; // 转换为弧度
|
||||
double offset = 0.0225; // 约 2.5km
|
||||
double lat_offset = -offset * std::cos(angle); // 负值表示向相反方向偏移
|
||||
double lon_offset = -offset * std::sin(angle);
|
||||
|
||||
const auto& refPoint = SystemConfig::instance().airport.reference_point;
|
||||
return createTestVehicle(id, refPoint.latitude + lat_offset, refPoint.longitude + lon_offset);
|
||||
}
|
||||
|
||||
std::unique_ptr<DataCollector> collector;
|
||||
std::shared_ptr<MockDataSource> mockSource;
|
||||
std::unique_ptr<TestAirportBounds> bounds_;
|
||||
std::unique_ptr<CoordinateConverter> converter_;
|
||||
DataSourceConfig dataSourceConfig_;
|
||||
};
|
||||
|
||||
// 修改初始化测试用例
|
||||
TEST_F(DataCollectorTest, Initialization) {
|
||||
DataSourceConfig dataSourceConfig;
|
||||
|
||||
// 设置位置数据配置
|
||||
dataSourceConfig.position.host = "localhost";
|
||||
dataSourceConfig.position.port = 8080;
|
||||
dataSourceConfig.position.aircraft_path = "/api/getCurrentFlightPositions";
|
||||
dataSourceConfig.position.refresh_interval_ms = 1000;
|
||||
dataSourceConfig.position.timeout_ms = 5000;
|
||||
dataSourceConfig.position.read_timeout_ms = 2000;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 1000;
|
||||
warnConfig.log_interval_ms = 2000;
|
||||
|
||||
EXPECT_TRUE(collector->initialize(dataSourceConfig, warnConfig));
|
||||
}
|
||||
|
||||
// 测试数据采集
|
||||
TEST_F(DataCollectorTest, DataCollectionTest) {
|
||||
// 设置数据源配置
|
||||
DataSourceConfig config;
|
||||
|
||||
// 设置位置数据配置
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8080;
|
||||
config.position.refresh_interval_ms = 100;
|
||||
config.position.timeout_ms = 1000;
|
||||
config.position.read_timeout_ms = 500;
|
||||
|
||||
// 设置无人车数据配置
|
||||
config.vehicle.host = "localhost";
|
||||
config.vehicle.port = 8081;
|
||||
config.vehicle.refresh_interval_ms = 100;
|
||||
config.vehicle.timeout_ms = 1000;
|
||||
config.vehicle.read_timeout_ms = 500;
|
||||
|
||||
// 设置红绿灯数据配置
|
||||
config.traffic_light.host = "localhost";
|
||||
config.traffic_light.port = 8082;
|
||||
config.traffic_light.refresh_interval_ms = 100;
|
||||
config.traffic_light.timeout_ms = 1000;
|
||||
config.traffic_light.read_timeout_ms = 500;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 200;
|
||||
|
||||
collector->initialize(config, warnConfig);
|
||||
|
||||
std::vector<Aircraft> testAircraft = {
|
||||
createTestAircraft("TEST1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
|
||||
createTestAircraft("TEST2", 36.354483470 + 0.0002 * std::cos(68.53 * M_PI / 180.0),
|
||||
120.08502054 + 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向偏移约 20m
|
||||
};
|
||||
|
||||
std::vector<Vehicle> testVehicles = {
|
||||
createTestVehicle("VEH1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
|
||||
createTestVehicle("VEH2", 36.354483470 - 0.0002 * std::cos(68.53 * M_PI / 180.0),
|
||||
120.08502054 - 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向反向偏移约 20m
|
||||
};
|
||||
|
||||
std::vector<TrafficLightSignal> testSignals = {
|
||||
createTestTrafficLight("TL001", 0),
|
||||
createTestTrafficLight("TL002", 1)
|
||||
};
|
||||
|
||||
// 设置 Mock 数据返回
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testAircraft),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testVehicles),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testSignals),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
std::string testStatus = "NORMAL";
|
||||
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<1>(testStatus),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 启动数据采集
|
||||
collector->start();
|
||||
|
||||
// 等待数据更新(至少 1000ms,确保数据被采集)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// 获取最新数据
|
||||
auto [aircraft, vehicles, traffic_lights] = collector->getLatestData();
|
||||
auto [aircraft_ts, vehicle_ts, traffic_light_ts] = collector->getLastUpdateTimestamps();
|
||||
|
||||
// 验证数据
|
||||
EXPECT_EQ(aircraft.size(), 2);
|
||||
if (!aircraft.empty()) {
|
||||
EXPECT_EQ(aircraft[0].flightNo, "TEST1");
|
||||
EXPECT_EQ(aircraft[1].flightNo, "TEST2");
|
||||
}
|
||||
|
||||
EXPECT_EQ(vehicles.size(), 2);
|
||||
if (!vehicles.empty()) {
|
||||
EXPECT_EQ(vehicles[0].vehicleNo, "VEH1");
|
||||
EXPECT_EQ(vehicles[1].vehicleNo, "VEH2");
|
||||
}
|
||||
|
||||
EXPECT_EQ(traffic_lights.size(), 2);
|
||||
if (!traffic_lights.empty()) {
|
||||
EXPECT_EQ(traffic_lights[0].trafficLightId, "TL001");
|
||||
EXPECT_EQ(traffic_lights[1].trafficLightId, "TL002");
|
||||
}
|
||||
|
||||
// 验证时间戳
|
||||
EXPECT_GT(aircraft_ts, 0);
|
||||
EXPECT_GT(vehicle_ts, 0);
|
||||
EXPECT_GT(traffic_light_ts, 0);
|
||||
|
||||
// 停止数据采集
|
||||
collector->stop();
|
||||
}
|
||||
|
||||
// 测试数据采集循环
|
||||
TEST_F(DataCollectorTest, DataCollectionLoop) {
|
||||
std::vector<Aircraft> testAircraft = {
|
||||
createTestAircraft("TEST1", 36.354483470, 120.08502054) // 参考点
|
||||
};
|
||||
|
||||
std::vector<Vehicle> testVehicles = {
|
||||
createTestVehicle("VEH1", 36.354483470, 120.08502054) // 参考点
|
||||
};
|
||||
|
||||
// 设置 Mock 数据返回
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testAircraft),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testVehicles),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 启动采集
|
||||
collector->start();
|
||||
|
||||
// 等待数据采集
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
// 停止采集
|
||||
collector->stop();
|
||||
|
||||
// 验证数据
|
||||
auto aircraft = collector->getAircraftData();
|
||||
EXPECT_EQ(aircraft.size(), 1);
|
||||
|
||||
auto vehicles = collector->getVehicleData();
|
||||
EXPECT_EQ(vehicles.size(), 1);
|
||||
}
|
||||
|
||||
// 测试错误处理
|
||||
TEST_F(DataCollectorTest, ErrorHandling) {
|
||||
// 设置 Mock 返回错误
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
// 启动数据采集
|
||||
collector->start();
|
||||
|
||||
// 等待数据更新(至少三个采集周期)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(
|
||||
dataSourceConfig_.position.refresh_interval_ms * 3));
|
||||
|
||||
// 获取最新数据
|
||||
auto [aircraft, vehicles, traffic_lights] = collector->getLatestData();
|
||||
|
||||
// 验证数据为空
|
||||
EXPECT_TRUE(aircraft.empty());
|
||||
EXPECT_TRUE(vehicles.empty());
|
||||
|
||||
// 停止数据采集
|
||||
collector->stop();
|
||||
}
|
||||
|
||||
// 测试红绿灯信号获取
|
||||
TEST_F(DataCollectorTest, TrafficLightSignalsTest) {
|
||||
std::vector<TrafficLightSignal> testSignals = {
|
||||
createTestTrafficLight("TL001", 0), // RED = 0
|
||||
createTestTrafficLight("TL002", 1), // GREEN = 1
|
||||
createTestTrafficLight("TL003", 2) // YELLOW = 2
|
||||
};
|
||||
|
||||
// 设置 Mock 数据返回
|
||||
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testSignals),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 启动数据采集
|
||||
collector->start();
|
||||
|
||||
// 等待数据更新(至少三个采集周期)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(
|
||||
dataSourceConfig_.position.refresh_interval_ms * 3));
|
||||
|
||||
// 验证数据
|
||||
auto signals = collector->getTrafficLightSignals();
|
||||
EXPECT_EQ(signals.size(), 3);
|
||||
if (signals.size() >= 3) {
|
||||
EXPECT_EQ(signals[0].trafficLightId, "TL001");
|
||||
EXPECT_EQ(signals[0].ns_status, SignalStatus::RED); // RED = 0
|
||||
EXPECT_EQ(signals[1].trafficLightId, "TL002");
|
||||
EXPECT_EQ(signals[1].ns_status, SignalStatus::GREEN); // GREEN = 1
|
||||
}
|
||||
|
||||
// 停止数据采集
|
||||
collector->stop();
|
||||
}
|
||||
|
||||
// 测试红绿灯信号错误处理
|
||||
TEST_F(DataCollectorTest, TrafficLightSignalsErrorTest) {
|
||||
// 设置 Mock 返回错误
|
||||
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
// 启动数据采集
|
||||
collector->start();
|
||||
|
||||
// 等待数据更新(至少三个采集周期)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(
|
||||
dataSourceConfig_.position.refresh_interval_ms * 3));
|
||||
|
||||
// 验证数据为空
|
||||
auto signals = collector->getTrafficLightSignals();
|
||||
EXPECT_TRUE(signals.empty());
|
||||
|
||||
// 停止数据采集
|
||||
collector->stop();
|
||||
}
|
||||
|
||||
// 测试连接健康检查和超时告警
|
||||
TEST_F(DataCollectorTest, ConnectionHealthAndTimeout) {
|
||||
DataSourceConfig dataSourceConfig;
|
||||
|
||||
// 设置位置数据配置
|
||||
dataSourceConfig.position.host = "localhost";
|
||||
dataSourceConfig.position.port = 8080;
|
||||
dataSourceConfig.position.timeout_ms = 1000; // 1秒超时
|
||||
dataSourceConfig.position.read_timeout_ms = 500; // 0.5秒读取超时
|
||||
dataSourceConfig.position.refresh_interval_ms = 100; // 100ms刷新间隔
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 500; // 500ms告警间隔
|
||||
|
||||
collector->initialize(dataSourceConfig, warnConfig);
|
||||
|
||||
// 模拟连接健康检查失败
|
||||
EXPECT_CALL(*mockSource, isAvailable())
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
// 模拟数据获取失败
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
// 启动采集
|
||||
collector->start();
|
||||
|
||||
// 等待超过超时时间
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||
|
||||
// 停止采集
|
||||
collector->stop();
|
||||
|
||||
// 验证数据为空
|
||||
auto aircraft = collector->getAircraftData();
|
||||
EXPECT_TRUE(aircraft.empty());
|
||||
auto vehicles = collector->getVehicleData();
|
||||
EXPECT_TRUE(vehicles.empty());
|
||||
}
|
||||
|
||||
// 测试连接恢复
|
||||
TEST_F(DataCollectorTest, ConnectionRecovery) {
|
||||
DataSourceConfig config;
|
||||
|
||||
// 设置位置数据配置
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8080;
|
||||
config.position.refresh_interval_ms = 100;
|
||||
config.position.timeout_ms = 1000;
|
||||
config.position.read_timeout_ms = 500;
|
||||
|
||||
// 设置无人车数据配置
|
||||
config.vehicle.host = "localhost";
|
||||
config.vehicle.port = 8081;
|
||||
config.vehicle.refresh_interval_ms = 100;
|
||||
config.vehicle.timeout_ms = 1000;
|
||||
config.vehicle.read_timeout_ms = 500;
|
||||
|
||||
// 设置红绿灯数据配置
|
||||
config.traffic_light.host = "localhost";
|
||||
config.traffic_light.port = 8082;
|
||||
config.traffic_light.refresh_interval_ms = 100;
|
||||
config.traffic_light.timeout_ms = 1000;
|
||||
config.traffic_light.read_timeout_ms = 500;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 200;
|
||||
|
||||
collector->initialize(config, warnConfig);
|
||||
|
||||
std::vector<Aircraft> testAircraft = {
|
||||
createTestAircraft("TEST1", 36.354483470, 120.08502054) // 参考点
|
||||
};
|
||||
|
||||
// 设置 Mock 行为: 前两次失败,然后成功
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testAircraft),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 添加车辆数据的 mock 行为
|
||||
std::vector<Vehicle> testVehicles;
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testVehicles),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 设置无人车数据 Mock 行为
|
||||
std::string testStatus = "NORMAL";
|
||||
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<1>(testStatus),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 设置红绿灯数据 Mock 行为
|
||||
std::vector<TrafficLightSignal> testSignals = {
|
||||
createTestTrafficLight("TL001", 0)
|
||||
};
|
||||
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillOnce(::testing::Return(false))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testSignals),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 启动数据采集
|
||||
collector->start();
|
||||
|
||||
// 等待足够长的时间以确保连接恢复
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// 获取最新数据
|
||||
auto [aircraft, vehicles, traffic_lights] = collector->getLatestData();
|
||||
|
||||
// 验证数据
|
||||
EXPECT_EQ(aircraft.size(), 1);
|
||||
if (!aircraft.empty()) {
|
||||
EXPECT_EQ(aircraft[0].flightNo, "TEST1");
|
||||
}
|
||||
|
||||
// 验证红绿灯数据
|
||||
EXPECT_EQ(traffic_lights.size(), 1);
|
||||
if (!traffic_lights.empty()) {
|
||||
EXPECT_EQ(traffic_lights[0].trafficLightId, "TL001");
|
||||
}
|
||||
|
||||
// 停止数据采集
|
||||
collector->stop();
|
||||
}
|
||||
|
||||
// 测试长连接下的数据获取
|
||||
TEST_F(DataCollectorTest, LongConnectionDataFetch) {
|
||||
DataSourceConfig dataSourceConfig;
|
||||
|
||||
// 设置位置数据配置
|
||||
dataSourceConfig.position.host = "localhost";
|
||||
dataSourceConfig.position.port = 8080;
|
||||
dataSourceConfig.position.refresh_interval_ms = 100;
|
||||
dataSourceConfig.position.timeout_ms = 1000;
|
||||
dataSourceConfig.position.read_timeout_ms = 500;
|
||||
|
||||
collector->initialize(dataSourceConfig, WarnConfig{});
|
||||
|
||||
std::vector<Aircraft> testAircraft = {
|
||||
createTestAircraft("TEST1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
|
||||
createTestAircraft("TEST2", 36.354483470 + 0.0002 * std::cos(68.53 * M_PI / 180.0),
|
||||
120.08502054 + 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向偏移约 20m
|
||||
};
|
||||
|
||||
std::vector<Vehicle> testVehicles = {
|
||||
createTestVehicle("VEH1", 36.354483470, 120.08502054), // 参考点,转换后是 (0, 0)
|
||||
createTestVehicle("VEH2", 36.354483470 - 0.0002 * std::cos(68.53 * M_PI / 180.0),
|
||||
120.08502054 - 0.0002 * std::sin(68.53 * M_PI / 180.0)) // 沿机场方向反向偏移约 20m
|
||||
};
|
||||
|
||||
std::vector<TrafficLightSignal> testSignals = {
|
||||
createTestTrafficLight("TL001", 0)
|
||||
};
|
||||
|
||||
// 模拟连接保持可用
|
||||
EXPECT_CALL(*mockSource, isAvailable())
|
||||
.WillRepeatedly(::testing::Return(true));
|
||||
|
||||
// 模拟多次成功获取数据
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.Times(::testing::AtLeast(3))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testAircraft),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 添加其他数据源的 mock 行为
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testVehicles),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testSignals),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 添加无人车状态获取的 mock 行为
|
||||
std::string testStatus = "NORMAL";
|
||||
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
|
||||
.WillRepeatedly(::testing::DoAll(
|
||||
::testing::SetArgReferee<1>(testStatus),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 启动采集
|
||||
collector->start();
|
||||
|
||||
// 等待多个刷新周期
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 增加等待时间确保执行次数
|
||||
|
||||
// 验证数据
|
||||
auto aircraft = collector->getAircraftData();
|
||||
EXPECT_FALSE(aircraft.empty());
|
||||
if (!aircraft.empty()) {
|
||||
EXPECT_EQ(aircraft[0].flightNo, "TEST1");
|
||||
}
|
||||
|
||||
auto vehicles = collector->getVehicleData();
|
||||
EXPECT_FALSE(vehicles.empty());
|
||||
if (!vehicles.empty()) {
|
||||
EXPECT_EQ(vehicles[0].vehicleNo, "VEH1");
|
||||
}
|
||||
|
||||
auto signals = collector->getTrafficLightSignals();
|
||||
EXPECT_FALSE(signals.empty());
|
||||
if (!signals.empty()) {
|
||||
EXPECT_EQ(signals[0].trafficLightId, "TL001");
|
||||
}
|
||||
|
||||
// 停止采集
|
||||
collector->stop();
|
||||
}
|
||||
|
||||
TEST_F(DataCollectorTest, InitializeWithTimeoutConfig) {
|
||||
DataSourceConfig config;
|
||||
|
||||
// 设置位置数据配置
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8080;
|
||||
config.position.timeout_ms = 5000; // 连接超时 5 秒
|
||||
config.position.read_timeout_ms = 2000; // 读取超时 2 秒
|
||||
config.position.refresh_interval_ms = 100;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 500;
|
||||
|
||||
EXPECT_NO_THROW(collector->initialize(config, warnConfig));
|
||||
}
|
||||
|
||||
TEST_F(DataCollectorTest, TimeoutWarningTest) {
|
||||
DataSourceConfig config;
|
||||
|
||||
// 设置位置数据配置
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8080;
|
||||
config.position.timeout_ms = 1000; // 连接超时 1 秒
|
||||
config.position.read_timeout_ms = 500; // 读取超时 0.5 秒
|
||||
config.position.refresh_interval_ms = 100;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 200; // 警告间隔 0.2 秒
|
||||
|
||||
collector->initialize(config, warnConfig);
|
||||
|
||||
// 模拟连接成功但读取超时
|
||||
EXPECT_CALL(*mockSource, isAvailable())
|
||||
.WillRepeatedly(::testing::Return(true));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false)); // 持续读取超时
|
||||
|
||||
// 启动采集
|
||||
collector->start();
|
||||
|
||||
// 等待足够长的时间以触发多次警告
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// 停止采集
|
||||
collector->stop();
|
||||
}
|
||||
|
||||
// 测试超时警告机制
|
||||
TEST_F(DataCollectorTest, TimeoutWarningMechanism) {
|
||||
DataSourceConfig config;
|
||||
|
||||
// 设置位置数据配置
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8080;
|
||||
config.position.timeout_ms = 200; // 连接超时 200ms
|
||||
config.position.read_timeout_ms = 100; // 读取超时 100ms
|
||||
config.position.refresh_interval_ms = 10;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 50; // 警告间隔 50ms
|
||||
|
||||
// 创建一个计数器来记录收到的警告数量
|
||||
int warningCount = 0;
|
||||
|
||||
// 创建一个 mock System 来捕获警告
|
||||
class MockSystem : public System {
|
||||
public:
|
||||
MOCK_METHOD1(broadcastTimeoutWarning, void(const network::TimeoutWarningMessage&));
|
||||
};
|
||||
|
||||
auto mockSystem = std::make_shared<MockSystem>();
|
||||
|
||||
// 设置期望接收到超时警告
|
||||
EXPECT_CALL(*mockSystem, broadcastTimeoutWarning(::testing::_))
|
||||
.WillRepeatedly(::testing::Invoke([&warningCount](const network::TimeoutWarningMessage& warning) {
|
||||
warningCount++;
|
||||
// 验证警告消息的内容
|
||||
EXPECT_THAT(warning.source, ::testing::AnyOf(
|
||||
::testing::StrEq("position"),
|
||||
::testing::StrEq("unmanned"),
|
||||
::testing::StrEq("traffic_light")
|
||||
));
|
||||
EXPECT_GT(warning.elapsed_ms, 0);
|
||||
}));
|
||||
|
||||
// 设置 mock system 到 collector
|
||||
collector->setSystem(mockSystem);
|
||||
collector->initialize(config, warnConfig);
|
||||
|
||||
// 启动采集器
|
||||
collector->start();
|
||||
|
||||
// 模拟所有数据源获取失败
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
// 等待足够长的时间以确保警告被发送
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
|
||||
// 停止采集器
|
||||
collector->stop();
|
||||
|
||||
// 验证是否收到了警告
|
||||
EXPECT_GT(warningCount, 0);
|
||||
}
|
||||
|
||||
// 测试读取超时机制
|
||||
TEST_F(DataCollectorTest, ReadTimeoutMechanism) {
|
||||
DataSourceConfig config;
|
||||
|
||||
// 设置位置数据配置
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8080;
|
||||
config.position.timeout_ms = 1000; // 连接超时 1000ms
|
||||
config.position.read_timeout_ms = 500; // 读取超时 500ms
|
||||
config.position.refresh_interval_ms = 100;
|
||||
|
||||
// 设置无人车数据配置
|
||||
config.vehicle.host = "localhost";
|
||||
config.vehicle.port = 8081;
|
||||
config.vehicle.timeout_ms = 1000;
|
||||
config.vehicle.read_timeout_ms = 500;
|
||||
config.vehicle.refresh_interval_ms = 100;
|
||||
|
||||
// 设置红绿灯数据配置
|
||||
config.traffic_light.host = "localhost";
|
||||
config.traffic_light.port = 8082;
|
||||
config.traffic_light.timeout_ms = 1000;
|
||||
config.traffic_light.read_timeout_ms = 500;
|
||||
config.traffic_light.refresh_interval_ms = 100;
|
||||
|
||||
WarnConfig warnConfig;
|
||||
warnConfig.warning_interval_ms = 200; // 警告间隔 200ms
|
||||
|
||||
// 创建一个计数器来记录收到的警告数量
|
||||
int warningCount = 0;
|
||||
|
||||
// 创建一个 mock System 来捕获警告
|
||||
class MockSystem : public System {
|
||||
public:
|
||||
MOCK_METHOD1(broadcastTimeoutWarning, void(const network::TimeoutWarningMessage&));
|
||||
};
|
||||
|
||||
auto mockSystem = std::make_shared<MockSystem>();
|
||||
|
||||
// 设置期望接收到超时警告
|
||||
EXPECT_CALL(*mockSystem, broadcastTimeoutWarning(::testing::_))
|
||||
.WillRepeatedly(::testing::Invoke([&warningCount](const network::TimeoutWarningMessage& warning) {
|
||||
warningCount++;
|
||||
// 验证警告消息的内容
|
||||
EXPECT_THAT(warning.source, ::testing::AnyOf(
|
||||
::testing::StrEq("position"),
|
||||
::testing::StrEq("unmanned"),
|
||||
::testing::StrEq("traffic_light")
|
||||
));
|
||||
EXPECT_GT(warning.elapsed_ms, 0);
|
||||
EXPECT_TRUE(warning.is_read_timeout);
|
||||
}));
|
||||
|
||||
// 设置 mock system 到 collector
|
||||
collector->setSystem(mockSystem);
|
||||
collector->initialize(config, warnConfig);
|
||||
|
||||
// 启动采集器
|
||||
collector->start();
|
||||
|
||||
// 模拟所有数据源获取失败
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchTrafficLightSignals(::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchUnmannedVehicleStatus(::testing::_, ::testing::_))
|
||||
.WillRepeatedly(::testing::Return(false));
|
||||
|
||||
// 等待足够长的时间以确保警告被发送
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||
|
||||
// 停止采集器
|
||||
collector->stop();
|
||||
|
||||
// 验证是否收到了警告
|
||||
EXPECT_GT(warningCount, 0);
|
||||
}
|
||||
|
||||
// 添加新的测试用例:测试边界外数据过滤
|
||||
TEST_F(DataCollectorTest, OutOfBoundsDataFiltering) {
|
||||
DataSourceConfig dataSourceConfig;
|
||||
WarnConfig warnConfig;
|
||||
EXPECT_TRUE(collector->initialize(dataSourceConfig, warnConfig));
|
||||
|
||||
// 创建测试数据:混合边界内外的数据
|
||||
const auto& refPoint = SystemConfig::instance().airport.reference_point;
|
||||
|
||||
// 创建边界内的航空器
|
||||
std::vector<Aircraft> testAircraft = {
|
||||
createTestAircraft("IN_BOUNDS_1", refPoint.latitude, refPoint.longitude), // 参考点
|
||||
createTestAircraft("IN_BOUNDS_2",
|
||||
refPoint.latitude + 0.001 * std::cos(68.53 * M_PI / 180.0), // 沿机场方向偏移约 100m
|
||||
refPoint.longitude + 0.001 * std::sin(68.53 * M_PI / 180.0)),
|
||||
createOutOfBoundsAircraft("OUT_BOUNDS_1"), // 边界外偏移约 3km
|
||||
createOutOfBoundsAircraft("OUT_BOUNDS_2") // 边界外偏移约 3km
|
||||
};
|
||||
|
||||
// 创建边界内的车辆
|
||||
std::vector<Vehicle> testVehicles = {
|
||||
createTestVehicle("VEH_IN_1", refPoint.latitude, refPoint.longitude), // 参考点
|
||||
createTestVehicle("VEH_IN_2",
|
||||
refPoint.latitude + 0.001 * std::cos(68.53 * M_PI / 180.0), // 沿机场方向偏移约 100m
|
||||
refPoint.longitude + 0.001 * std::sin(68.53 * M_PI / 180.0)),
|
||||
createOutOfBoundsVehicle("VEH_OUT_1"), // 边界外偏移约 2.5km
|
||||
createOutOfBoundsVehicle("VEH_OUT_2") // 边界外偏移约 2.5km
|
||||
};
|
||||
|
||||
// 设置 Mock 数据返回
|
||||
EXPECT_CALL(*mockSource, fetchPositionAircraftData(::testing::_))
|
||||
.WillOnce(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testAircraft),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
EXPECT_CALL(*mockSource, fetchPositionVehicleData(::testing::_))
|
||||
.WillOnce(::testing::DoAll(
|
||||
::testing::SetArgReferee<0>(testVehicles),
|
||||
::testing::Return(true)
|
||||
));
|
||||
|
||||
// 执行刷新
|
||||
collector->fetchPositionData();
|
||||
|
||||
// 验证过滤结果
|
||||
const auto& filteredAircraft = collector->getAircraftData();
|
||||
const auto& filteredVehicles = collector->getVehicleData();
|
||||
|
||||
// 检查过滤后的数量
|
||||
EXPECT_EQ(filteredAircraft.size(), 2) << "应该只保留2架在边界内的航空器";
|
||||
EXPECT_EQ(filteredVehicles.size(), 2) << "应该只保留2辆在边界内的车辆";
|
||||
|
||||
// 检查保留的是正确的航空器
|
||||
if (filteredAircraft.size() >= 2) {
|
||||
EXPECT_EQ(filteredAircraft[0].flightNo, "IN_BOUNDS_1");
|
||||
EXPECT_EQ(filteredAircraft[1].flightNo, "IN_BOUNDS_2");
|
||||
}
|
||||
|
||||
// 检查保留的是正确的车辆
|
||||
if (filteredVehicles.size() >= 2) {
|
||||
EXPECT_EQ(filteredVehicles[0].vehicleNo, "VEH_IN_1");
|
||||
EXPECT_EQ(filteredVehicles[1].vehicleNo, "VEH_IN_2");
|
||||
}
|
||||
}
|
||||
286
tests/HTTPDataSourceTest.cpp
Normal file
286
tests/HTTPDataSourceTest.cpp
Normal file
@ -0,0 +1,286 @@
|
||||
#include "network/HTTPDataSource.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "utils/Logger.h"
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
class HTTPDataSourceTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {}
|
||||
void TearDown() override {}
|
||||
};
|
||||
|
||||
TEST_F(HTTPDataSourceTest, BasicFunctionality) {
|
||||
DataSourceConfig config;
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8081;
|
||||
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
|
||||
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
|
||||
config.position.auth.auth_required = true;
|
||||
config.position.auth.auth_path = "/login";
|
||||
config.position.auth.username = "dianxin";
|
||||
config.position.auth.password = "dianxin@123";
|
||||
config.position.timeout_ms = 5000;
|
||||
config.position.read_timeout_ms = 2000;
|
||||
config.position.refresh_interval_ms = 100;
|
||||
|
||||
HTTPDataSource source(config);
|
||||
ASSERT_TRUE(source.connect()) << "连接失败";
|
||||
|
||||
std::vector<Aircraft> aircraft;
|
||||
bool success = source.fetchPositionAircraftData(aircraft);
|
||||
ASSERT_TRUE(success) << "获取航空器数据失败";
|
||||
ASSERT_FALSE(aircraft.empty()) << "航空器数据为空";
|
||||
|
||||
std::vector<Vehicle> vehicles;
|
||||
ASSERT_TRUE(source.fetchPositionVehicleData(vehicles)) << "获取车辆数据失败";
|
||||
ASSERT_FALSE(vehicles.empty()) << "车辆数据为空";
|
||||
}
|
||||
|
||||
TEST_F(HTTPDataSourceTest, AuthenticationFailure) {
|
||||
DataSourceConfig config;
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8081;
|
||||
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
|
||||
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
|
||||
config.position.refresh_interval_ms = 1000;
|
||||
config.position.timeout_ms = 1000;
|
||||
config.position.read_timeout_ms = 500;
|
||||
|
||||
config.position.auth.auth_required = true;
|
||||
config.position.auth.username = "wrong_user";
|
||||
config.position.auth.password = "wrong_pass";
|
||||
config.position.auth.auth_path = "/login";
|
||||
|
||||
HTTPDataSource source(config);
|
||||
std::vector<Aircraft> aircraft;
|
||||
EXPECT_FALSE(source.fetchPositionAircraftData(aircraft));
|
||||
}
|
||||
|
||||
TEST_F(HTTPDataSourceTest, Reconnection) {
|
||||
DataSourceConfig config;
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8081;
|
||||
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
|
||||
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
|
||||
config.position.refresh_interval_ms = 1000;
|
||||
config.position.timeout_ms = 1000;
|
||||
config.position.read_timeout_ms = 500;
|
||||
|
||||
config.position.auth.auth_required = true;
|
||||
config.position.auth.username = "dianxin";
|
||||
config.position.auth.password = "dianxin@123";
|
||||
config.position.auth.auth_path = "/login";
|
||||
|
||||
HTTPDataSource source(config);
|
||||
EXPECT_TRUE(source.connect());
|
||||
|
||||
std::vector<Aircraft> aircraft;
|
||||
EXPECT_TRUE(source.fetchPositionAircraftData(aircraft));
|
||||
|
||||
source.disconnect();
|
||||
EXPECT_TRUE(source.connect());
|
||||
|
||||
aircraft.clear();
|
||||
EXPECT_TRUE(source.fetchPositionAircraftData(aircraft));
|
||||
}
|
||||
|
||||
TEST_F(HTTPDataSourceTest, AuthenticationOnly) {
|
||||
DataSourceConfig config;
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8081;
|
||||
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
|
||||
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
|
||||
config.position.refresh_interval_ms = 1000;
|
||||
config.position.timeout_ms = 1000;
|
||||
config.position.read_timeout_ms = 500;
|
||||
|
||||
config.position.auth.auth_required = true;
|
||||
config.position.auth.username = "dianxin";
|
||||
config.position.auth.password = "dianxin@123";
|
||||
config.position.auth.auth_path = "/login";
|
||||
|
||||
HTTPDataSource source(config);
|
||||
EXPECT_TRUE(source.connect());
|
||||
|
||||
std::vector<Aircraft> aircraft;
|
||||
EXPECT_TRUE(source.fetchPositionAircraftData(aircraft));
|
||||
}
|
||||
|
||||
TEST_F(HTTPDataSourceTest, TrafficLightSignals) {
|
||||
DataSourceConfig config;
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8081;
|
||||
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
|
||||
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
|
||||
config.position.auth.auth_required = true;
|
||||
config.position.auth.auth_path = "/login";
|
||||
config.position.auth.username = "dianxin";
|
||||
config.position.auth.password = "dianxin@123";
|
||||
config.position.timeout_ms = 5000;
|
||||
config.position.read_timeout_ms = 2000;
|
||||
config.position.refresh_interval_ms = 100;
|
||||
|
||||
config.traffic_light.host = "localhost";
|
||||
config.traffic_light.port = 8081;
|
||||
config.traffic_light.signal_path = "/api/VehicleCommandInfo";
|
||||
config.vehicle.host = "localhost";
|
||||
config.vehicle.port = 8081;
|
||||
config.vehicle.command_path = "/api/VehicleCommandInfo";
|
||||
|
||||
HTTPDataSource source(config);
|
||||
ASSERT_TRUE(source.connect());
|
||||
|
||||
// 先获取车辆状态来初始化车辆
|
||||
std::vector<Vehicle> vehicles;
|
||||
ASSERT_TRUE(source.fetchPositionVehicleData(vehicles)) << "获取车辆状态失败";
|
||||
ASSERT_FALSE(vehicles.empty()) << "车辆数据为空";
|
||||
|
||||
// 发送绿灯指令(优先级 2)
|
||||
VehicleCommand green_command;
|
||||
green_command.vehicleId = "QN001";
|
||||
green_command.type = CommandType::SIGNAL;
|
||||
green_command.reason = CommandReason::TRAFFIC_LIGHT;
|
||||
green_command.signalState = SignalState::GREEN;
|
||||
green_command.intersectionId = "INTERSECTION_001";
|
||||
green_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
ASSERT_TRUE(source.sendUnmannedVehicleCommand(green_command.vehicleId, green_command)) << "发送绿灯指令失败";
|
||||
|
||||
// 等待绿灯指令生效
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
// 发送红灯指令(优先级 4)
|
||||
VehicleCommand red_command;
|
||||
red_command.vehicleId = "QN001";
|
||||
red_command.type = CommandType::SIGNAL;
|
||||
red_command.reason = CommandReason::TRAFFIC_LIGHT;
|
||||
red_command.signalState = SignalState::RED;
|
||||
red_command.intersectionId = "INTERSECTION_001";
|
||||
red_command.latitude = 36.36;
|
||||
red_command.longitude = 120.08;
|
||||
red_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
ASSERT_TRUE(source.sendUnmannedVehicleCommand(red_command.vehicleId, red_command)) << "发送红灯指令失败";
|
||||
|
||||
// 等待红灯指令生效
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
|
||||
// 最后发送绿灯指令
|
||||
green_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
ASSERT_TRUE(source.sendUnmannedVehicleCommand(green_command.vehicleId, green_command)) << "发送最终绿灯指令失败";
|
||||
}
|
||||
|
||||
TEST_F(HTTPDataSourceTest, UnmannedVehicleCommand) {
|
||||
DataSourceConfig config;
|
||||
config.position.host = "localhost";
|
||||
config.position.port = 8081;
|
||||
config.position.aircraft_path = "/openApi/getCurrentFlightPositions";
|
||||
config.position.vehicle_path = "/openApi/getCurrentVehiclePositions";
|
||||
config.position.auth.auth_required = true;
|
||||
config.position.auth.auth_path = "/login";
|
||||
config.position.auth.username = "dianxin";
|
||||
config.position.auth.password = "dianxin@123";
|
||||
config.position.timeout_ms = 5000;
|
||||
config.position.read_timeout_ms = 2000;
|
||||
config.position.refresh_interval_ms = 100;
|
||||
|
||||
config.vehicle.host = "localhost";
|
||||
config.vehicle.port = 8081;
|
||||
config.vehicle.command_path = "/api/VehicleCommandInfo";
|
||||
config.vehicle.auth.auth_required = true;
|
||||
config.vehicle.auth.auth_path = "/login";
|
||||
config.vehicle.auth.username = "dianxin";
|
||||
config.vehicle.auth.password = "dianxin@123";
|
||||
config.vehicle.timeout_ms = 5000;
|
||||
config.vehicle.read_timeout_ms = 2000;
|
||||
|
||||
HTTPDataSource source(config);
|
||||
ASSERT_TRUE(source.connect()) << "连接失败";
|
||||
|
||||
// 先获取车辆状态来初始化车辆
|
||||
std::vector<Vehicle> vehicles;
|
||||
ASSERT_TRUE(source.fetchPositionVehicleData(vehicles)) << "获取车辆状态失败";
|
||||
ASSERT_FALSE(vehicles.empty()) << "车辆数据为空";
|
||||
|
||||
// 先发送 RESUME 指令清除所有状态
|
||||
VehicleCommand resume_command;
|
||||
resume_command.vehicleId = "QN001";
|
||||
resume_command.type = CommandType::RESUME;
|
||||
resume_command.reason = CommandReason::RESUME_TRAFFIC;
|
||||
resume_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
ASSERT_TRUE(source.sendUnmannedVehicleCommand(resume_command.vehicleId, resume_command)) << "发送恢复指令失败";
|
||||
|
||||
// 等待 RESUME 指令生效
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
// 发送 WARNING 指令(优先级 3)
|
||||
VehicleCommand warning_command;
|
||||
warning_command.vehicleId = "QN001";
|
||||
warning_command.type = CommandType::WARNING;
|
||||
warning_command.reason = CommandReason::AIRCRAFT_CROSSING;
|
||||
warning_command.relativeSpeed = 50;
|
||||
warning_command.relativeMotionX = 50;
|
||||
warning_command.relativeMotionY = 50;
|
||||
warning_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
ASSERT_TRUE(source.sendUnmannedVehicleCommand(warning_command.vehicleId, warning_command)) << "发送预警指令失败";
|
||||
|
||||
// 等待 WARNING 指令生效
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
// 发送 ALERT 指令(优先级 5)
|
||||
VehicleCommand alert_command;
|
||||
alert_command.vehicleId = "QN001";
|
||||
alert_command.type = CommandType::ALERT;
|
||||
alert_command.reason = CommandReason::AIRCRAFT_CROSSING;
|
||||
alert_command.relativeSpeed = 100;
|
||||
alert_command.relativeMotionX = 100;
|
||||
alert_command.relativeMotionY = 100;
|
||||
alert_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
ASSERT_TRUE(source.sendUnmannedVehicleCommand(alert_command.vehicleId, alert_command)) << "发送告警指令失败";
|
||||
|
||||
// 等待 ALERT 指令生效
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
// 最后发送 RESUME 指令清除状态
|
||||
resume_command.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
ASSERT_TRUE(source.sendUnmannedVehicleCommand(resume_command.vehicleId, resume_command)) << "发送最终恢复指令失败";
|
||||
}
|
||||
|
||||
TEST_F(HTTPDataSourceTest, TrafficLightAndUnmannedAuthFailure) {
|
||||
DataSourceConfig config;
|
||||
config.traffic_light.host = "localhost";
|
||||
config.traffic_light.port = 8081;
|
||||
config.traffic_light.signal_path = "/api/VehicleCommandInfo";
|
||||
config.traffic_light.auth.auth_required = true;
|
||||
config.traffic_light.auth.username = "wrong_user";
|
||||
config.traffic_light.auth.password = "wrong_pass";
|
||||
|
||||
config.vehicle.host = "localhost";
|
||||
config.vehicle.port = 8081;
|
||||
config.vehicle.command_path = "/api/VehicleCommandInfo";
|
||||
config.vehicle.auth.auth_required = true;
|
||||
config.vehicle.auth.username = "wrong_user";
|
||||
config.vehicle.auth.password = "wrong_pass";
|
||||
|
||||
HTTPDataSource source(config);
|
||||
|
||||
std::vector<TrafficLightSignal> signals;
|
||||
EXPECT_FALSE(source.fetchTrafficLightSignals(signals));
|
||||
|
||||
VehicleCommand command;
|
||||
command.vehicleId = "TEST_VEHICLE_001";
|
||||
command.type = CommandType::SIGNAL;
|
||||
command.reason = CommandReason::RESUME_TRAFFIC;
|
||||
command.signalState = SignalState::GREEN;
|
||||
command.timestamp = 0;
|
||||
EXPECT_FALSE(source.sendUnmannedVehicleCommand(command.vehicleId, command));
|
||||
}
|
||||
190
tests/SafetyZoneTest.cpp
Normal file
190
tests/SafetyZoneTest.cpp
Normal file
@ -0,0 +1,190 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "detector/SafetyZone.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "config/SystemConfig.h"
|
||||
|
||||
class SafetyZoneTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 确保 SystemConfig 已初始化
|
||||
SystemConfig::instance();
|
||||
// 创建一个安全区,中心点在(0,0),飞机安全区半径50米,特勤车安全区半径30米
|
||||
safetyZone = std::make_unique<SafetyZone>(Vector2D{0, 0}, 50.0, 30.0);
|
||||
}
|
||||
|
||||
std::unique_ptr<SafetyZone> safetyZone;
|
||||
|
||||
// 创建测试用的移动对象
|
||||
Aircraft createAircraft(const std::string& id, double x, double y) {
|
||||
Aircraft obj;
|
||||
obj.id = id;
|
||||
obj.position = Vector2D{x, y};
|
||||
obj.geo.latitude = 0; // 这里的经纬度不重要
|
||||
obj.geo.longitude = 0;
|
||||
obj.type = MovingObjectType::AIRCRAFT; // 设置类型为航空器
|
||||
return obj;
|
||||
}
|
||||
|
||||
Vehicle createSpecialVehicle(const std::string& id, double x, double y) {
|
||||
Vehicle obj;
|
||||
obj.id = id;
|
||||
obj.position = Vector2D{x, y};
|
||||
obj.geo.latitude = 0;
|
||||
obj.geo.longitude = 0;
|
||||
obj.type = MovingObjectType::SPECIAL; // 设置类型为特勤车
|
||||
obj.isControllable = false; // 设置为非可控车辆(即特勤车)
|
||||
return obj;
|
||||
}
|
||||
|
||||
Vehicle createUnmannedVehicle(const std::string& id, double x, double y) {
|
||||
Vehicle obj;
|
||||
obj.id = id;
|
||||
obj.position = Vector2D{x, y};
|
||||
obj.geo.latitude = 0;
|
||||
obj.geo.longitude = 0;
|
||||
obj.type = MovingObjectType::UNMANNED; // 设置类型为无人车
|
||||
obj.isControllable = true; // 设置为可控车辆(即无人车)
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 获取飞机有效距离(安全区半径 + 飞机半长度)
|
||||
double getAircraftEffectiveDistance() const {
|
||||
const auto& config = SystemConfig::instance().collision_detection.prediction;
|
||||
return safetyZone->getAircraftRadius() + config.aircraft_size / 2.0;
|
||||
}
|
||||
|
||||
// 获取特勤车有效距离(安全区半径 + 车辆半长度)
|
||||
double getVehicleEffectiveDistance() const {
|
||||
const auto& config = SystemConfig::instance().collision_detection.prediction;
|
||||
return safetyZone->getSpecialVehicleRadius() + config.vehicle_size / 2.0;
|
||||
}
|
||||
};
|
||||
|
||||
// 测试初始状态
|
||||
TEST_F(SafetyZoneTest, InitialState) {
|
||||
EXPECT_EQ(safetyZone->getState(), SafetyZoneState::INACTIVE);
|
||||
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::NONE);
|
||||
EXPECT_EQ(safetyZone->getCurrentRadius(), 0.0);
|
||||
}
|
||||
|
||||
// 测试飞机进入安全区
|
||||
TEST_F(SafetyZoneTest, AircraftEntering) {
|
||||
double effectiveDistance = getAircraftEffectiveDistance();
|
||||
|
||||
// 创建一个飞机,位置在安全区边缘内侧
|
||||
auto aircraft = createAircraft("AC001", effectiveDistance - 1.0, 0.0);
|
||||
|
||||
// 检查飞机是否在安全区内
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft));
|
||||
|
||||
// 尝试激活安全区
|
||||
EXPECT_TRUE(safetyZone->tryActivate(aircraft));
|
||||
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
|
||||
EXPECT_EQ(safetyZone->getCurrentRadius(), 50.0);
|
||||
|
||||
// 测试飞机完全离开安全区
|
||||
aircraft = createAircraft("AC001", effectiveDistance + 1.0, 0.0);
|
||||
EXPECT_FALSE(safetyZone->isObjectInZone(aircraft));
|
||||
}
|
||||
|
||||
// 测试特勤车进入安全区
|
||||
TEST_F(SafetyZoneTest, SpecialVehicleEntering) {
|
||||
double effectiveDistance = getVehicleEffectiveDistance();
|
||||
|
||||
// 创建一个特勤车,位置在安全区边缘内侧
|
||||
auto vehicle = createSpecialVehicle("TQ001", effectiveDistance - 1.0, 0.0);
|
||||
|
||||
// 检查特勤车是否在安全区内
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle));
|
||||
|
||||
// 尝试激活安全区
|
||||
EXPECT_TRUE(safetyZone->tryActivate(vehicle));
|
||||
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::VEHICLE);
|
||||
EXPECT_EQ(safetyZone->getCurrentRadius(), 30.0);
|
||||
|
||||
// 测试特勤车完全离开安全区
|
||||
vehicle = createSpecialVehicle("TQ001", effectiveDistance + 1.0, 0.0);
|
||||
EXPECT_FALSE(safetyZone->isObjectInZone(vehicle));
|
||||
}
|
||||
|
||||
// 测试无人车进入安全区
|
||||
TEST_F(SafetyZoneTest, UnmannedVehicleEntering) {
|
||||
// 创建一个无人车,位置在安全区内
|
||||
auto vehicle = createUnmannedVehicle("UV001", 10.0, 0.0);
|
||||
|
||||
// 检查无人车是否在安全区内(应该返回false,因为无人车不能设置安全区类型)
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle));
|
||||
EXPECT_FALSE(safetyZone->tryActivate(vehicle));
|
||||
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::NONE);
|
||||
EXPECT_EQ(safetyZone->getCurrentRadius(), 0.0);
|
||||
}
|
||||
|
||||
// 测试安全区类型锁定
|
||||
TEST_F(SafetyZoneTest, SafetyZoneTypeLocking) {
|
||||
double aircraftEffectiveDistance = getAircraftEffectiveDistance();
|
||||
double vehicleEffectiveDistance = getVehicleEffectiveDistance();
|
||||
|
||||
// 先让飞机进入安全区
|
||||
auto aircraft = createAircraft("AC001", aircraftEffectiveDistance - 5.0, 0.0);
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft));
|
||||
EXPECT_TRUE(safetyZone->tryActivate(aircraft));
|
||||
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
|
||||
|
||||
// 尝试让特勤车进入已经被飞机设置类型的安全区
|
||||
auto vehicle = createSpecialVehicle("TQ001", vehicleEffectiveDistance - 5.0, 0.0);
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle));
|
||||
EXPECT_FALSE(safetyZone->tryActivate(vehicle));
|
||||
|
||||
// 确认安全区类型和尺寸没有改变
|
||||
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
|
||||
EXPECT_EQ(safetyZone->getCurrentRadius(), 50.0);
|
||||
}
|
||||
|
||||
// 测试边界条件
|
||||
TEST_F(SafetyZoneTest, BoundaryConditions) {
|
||||
double aircraftEffectiveDistance = getAircraftEffectiveDistance();
|
||||
double vehicleEffectiveDistance = getVehicleEffectiveDistance();
|
||||
|
||||
// 测试飞机在边界上
|
||||
auto aircraft1 = createAircraft("AC001", aircraftEffectiveDistance, 0.0);
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft1));
|
||||
EXPECT_TRUE(safetyZone->tryActivate(aircraft1));
|
||||
|
||||
// 测试飞机在边界外
|
||||
auto aircraft2 = createAircraft("AC002", aircraftEffectiveDistance + 0.1, 0.0);
|
||||
EXPECT_FALSE(safetyZone->isObjectInZone(aircraft2));
|
||||
|
||||
// 重新创建安全区用于测试特勤车
|
||||
safetyZone = std::make_unique<SafetyZone>(Vector2D{0, 0}, 50.0, 30.0);
|
||||
|
||||
// 测试特勤车在边界上
|
||||
auto vehicle1 = createSpecialVehicle("TQ001", vehicleEffectiveDistance, 0.0);
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(vehicle1));
|
||||
EXPECT_TRUE(safetyZone->tryActivate(vehicle1));
|
||||
|
||||
// 测试特勤车在边界外
|
||||
auto vehicle2 = createSpecialVehicle("TQ002", vehicleEffectiveDistance + 0.1, 0.0);
|
||||
EXPECT_FALSE(safetyZone->isObjectInZone(vehicle2));
|
||||
}
|
||||
|
||||
// 测试状态重置
|
||||
TEST_F(SafetyZoneTest, StateReset) {
|
||||
double effectiveDistance = getAircraftEffectiveDistance();
|
||||
|
||||
// 先让飞机进入安全区
|
||||
auto aircraft = createAircraft("AC001", effectiveDistance - 5.0, 0.0);
|
||||
EXPECT_TRUE(safetyZone->isObjectInZone(aircraft));
|
||||
EXPECT_TRUE(safetyZone->tryActivate(aircraft));
|
||||
|
||||
// 设置状态为激活
|
||||
safetyZone->setState(SafetyZoneState::ACTIVE);
|
||||
EXPECT_EQ(safetyZone->getState(), SafetyZoneState::ACTIVE);
|
||||
|
||||
// 重置状态
|
||||
safetyZone->setState(SafetyZoneState::INACTIVE);
|
||||
EXPECT_EQ(safetyZone->getState(), SafetyZoneState::INACTIVE);
|
||||
|
||||
// 确认类型和尺寸保持不变
|
||||
EXPECT_EQ(safetyZone->getType(), SafetyZoneType::AIRCRAFT);
|
||||
EXPECT_EQ(safetyZone->getCurrentRadius(), 50.0);
|
||||
}
|
||||
141
tests/TrafficLightDetectorTest.cpp
Normal file
141
tests/TrafficLightDetectorTest.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "detector/TrafficLightDetector.h"
|
||||
#include "config/IntersectionConfig.h"
|
||||
#include "vehicle/ControllableVehicles.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "types/VehicleCommand.h"
|
||||
#include "types/TrafficLightTypes.h"
|
||||
#include "core/System.h"
|
||||
|
||||
// Mock System 类
|
||||
class MockSystem : public System {
|
||||
public:
|
||||
MOCK_METHOD(void, broadcastTrafficLightCommand, (const std::string&, const VehicleCommand&));
|
||||
static MockSystem& getInstance() {
|
||||
static MockSystem instance;
|
||||
return instance;
|
||||
}
|
||||
private:
|
||||
MockSystem() = default;
|
||||
MockSystem(const MockSystem&) = delete;
|
||||
MockSystem& operator=(const MockSystem&) = delete;
|
||||
};
|
||||
|
||||
class TrafficLightDetectorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// 创建测试用的交叉口配置
|
||||
Intersection intersection;
|
||||
intersection.id = "INT001";
|
||||
intersection.name = "Test Intersection";
|
||||
intersection.position.latitude = 36.36;
|
||||
intersection.position.longitude = 120.08;
|
||||
intersection.position.altitude = 9.5;
|
||||
intersection.width = 50.0;
|
||||
intersection.trafficLightId = "TL001";
|
||||
|
||||
// 创建临时配置文件
|
||||
std::ofstream config_file("test_intersections.json");
|
||||
nlohmann::json j;
|
||||
j["intersections"] = nlohmann::json::array();
|
||||
j["intersections"].push_back({
|
||||
{"id", intersection.id},
|
||||
{"name", intersection.name},
|
||||
{"position", {
|
||||
{"latitude", intersection.position.latitude},
|
||||
{"longitude", intersection.position.longitude},
|
||||
{"altitude", intersection.position.altitude}
|
||||
}},
|
||||
{"width", intersection.width},
|
||||
{"trafficLightId", intersection.trafficLightId},
|
||||
{"safetyZone", {
|
||||
{"aircraftRadius", 50.0},
|
||||
{"vehicleRadius", 50.0}
|
||||
}}
|
||||
});
|
||||
config_file << j.dump(4);
|
||||
config_file.close();
|
||||
|
||||
intersectionConfig = IntersectionConfig::load("test_intersections.json");
|
||||
detector = std::make_unique<TrafficLightDetector>(
|
||||
intersectionConfig,
|
||||
ControllableVehicles::getInstance(),
|
||||
MockSystem::getInstance()
|
||||
);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
detector.reset();
|
||||
std::remove("test_intersections.json");
|
||||
}
|
||||
|
||||
// 创建测试用的红绿灯信号
|
||||
TrafficLightSignal createTestSignal(const std::string& id, SignalStatus status) {
|
||||
TrafficLightSignal signal;
|
||||
signal.trafficLightId = id;
|
||||
signal.ns_status = status;
|
||||
signal.ew_status = status;
|
||||
signal.timestamp = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
return signal;
|
||||
}
|
||||
|
||||
// 创建测试用的车辆数据
|
||||
Vehicle createTestVehicle(const std::string& id, double lat, double lon) {
|
||||
Vehicle v;
|
||||
v.id = id;
|
||||
v.vehicleNo = id;
|
||||
v.geo.latitude = lat;
|
||||
v.geo.longitude = lon;
|
||||
v.timestamp = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
return v;
|
||||
}
|
||||
|
||||
std::unique_ptr<TrafficLightDetector> detector;
|
||||
IntersectionConfig intersectionConfig;
|
||||
};
|
||||
|
||||
// 测试红绿灯信号处理
|
||||
TEST_F(TrafficLightDetectorTest, SignalProcessing) {
|
||||
// 创建一个在交叉口范围内的车辆
|
||||
Vehicle vehicle = createTestVehicle("V001", 36.36, 120.08);
|
||||
std::vector<Vehicle> vehicles = {vehicle};
|
||||
|
||||
// 测试红灯
|
||||
TrafficLightSignal redSignal = createTestSignal("TL001", SignalStatus::RED);
|
||||
|
||||
detector->processSignal(redSignal, vehicles);
|
||||
|
||||
// 测试绿灯
|
||||
TrafficLightSignal greenSignal = createTestSignal("TL001", SignalStatus::GREEN);
|
||||
|
||||
detector->processSignal(greenSignal, vehicles);
|
||||
}
|
||||
|
||||
// 测试无效的红绿灯信号
|
||||
TEST_F(TrafficLightDetectorTest, InvalidSignal) {
|
||||
Vehicle vehicle = createTestVehicle("V001", 36.36, 120.08);
|
||||
std::vector<Vehicle> vehicles = {vehicle};
|
||||
|
||||
// 测试无效的红绿灯ID
|
||||
TrafficLightSignal invalidSignal = createTestSignal("TL999", SignalStatus::RED);
|
||||
|
||||
detector->processSignal(invalidSignal, vehicles);
|
||||
}
|
||||
|
||||
// 测试边界情况
|
||||
TEST_F(TrafficLightDetectorTest, EdgeCases) {
|
||||
// 测试空车辆列表
|
||||
std::vector<Vehicle> emptyVehicles;
|
||||
TrafficLightSignal signal = createTestSignal("TL001", SignalStatus::RED);
|
||||
|
||||
detector->processSignal(signal, emptyVehicles);
|
||||
|
||||
// 测试交叉口范围外的车辆
|
||||
Vehicle farVehicle = createTestVehicle("V002", 36.40, 120.12);
|
||||
std::vector<Vehicle> vehicles = {farVehicle};
|
||||
|
||||
detector->processSignal(signal, vehicles);
|
||||
}
|
||||
5
tests/data/invalid_airport_bounds.json
Normal file
5
tests/data/invalid_airport_bounds.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"airport": {
|
||||
"rotation_angle": 68.53
|
||||
}
|
||||
}
|
||||
303
tools/map.html
Normal file
303
tools/map.html
Normal file
@ -0,0 +1,303 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>机场路口地图</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f0f0f0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
canvas {
|
||||
border: 1px solid #ddd;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.legend {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
background: #f8f8f8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.legend-item {
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>机场路口地图</h1>
|
||||
<canvas id="mapCanvas" width="1000" height="800"></canvas>
|
||||
<div class="legend">
|
||||
<h3>图例</h3>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #666;"></div>
|
||||
<span>道路</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #f00;"></div>
|
||||
<span>路口</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 路口坐标数据
|
||||
const intersections = [
|
||||
{point: "T1", longitude: 120.0868853, latitude: 36.35496367},
|
||||
{point: "T2", longitude: 120.08502054, latitude: 36.35448347},
|
||||
{point: "T3", longitude: 120.08341044, latitude: 36.35406879},
|
||||
{point: "T4", longitude: 120.08558121, latitude: 36.35305878},
|
||||
{point: "T5", longitude: 120.08400957, latitude: 36.35265197},
|
||||
{point: "T6", longitude: 120.08649105, latitude: 36.35074527},
|
||||
{point: "T7", longitude: 120.08562915, latitude: 36.35052372},
|
||||
{point: "T8", longitude: 120.08676664, latitude: 36.35004529},
|
||||
{point: "T9", longitude: 120.08520616, latitude: 36.34964473},
|
||||
{point: "T10", longitude: 120.08710569, latitude: 36.34917893},
|
||||
{point: "T11", longitude: 120.0873865, latitude: 36.3509885},
|
||||
{point: "T12", longitude: 120.08603613, latitude: 36.35190217}
|
||||
];
|
||||
|
||||
// 参考点
|
||||
const referencePoint = {
|
||||
latitude: 36.34807893,
|
||||
longitude: 120.08201044
|
||||
};
|
||||
|
||||
// 获取 Canvas 上下文
|
||||
const canvas = document.getElementById('mapCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 计算坐标范围
|
||||
let minLat = Math.min(...intersections.map(p => p.latitude));
|
||||
let maxLat = Math.max(...intersections.map(p => p.latitude));
|
||||
let minLon = Math.min(...intersections.map(p => p.longitude));
|
||||
let maxLon = Math.max(...intersections.map(p => p.longitude));
|
||||
|
||||
// 添加边距
|
||||
const margin = 0.0001;
|
||||
minLat -= margin;
|
||||
maxLat += margin;
|
||||
minLon -= margin;
|
||||
maxLon += margin;
|
||||
|
||||
// 计算中心纬度
|
||||
const centerLat = (minLat + maxLat) / 2;
|
||||
// 地球半径(米)
|
||||
const EARTH_RADIUS = 6378137.0;
|
||||
// 计算1度经度和纬度对应的实际距离(米)
|
||||
const metersPerLatDegree = (Math.PI / 180) * EARTH_RADIUS;
|
||||
const metersPerLonDegree = metersPerLatDegree * Math.cos(centerLat * Math.PI / 180);
|
||||
|
||||
// 计算实际距离范围(米)
|
||||
const totalWidthMeters = (maxLon - minLon) * metersPerLonDegree;
|
||||
const totalHeightMeters = (maxLat - minLat) * metersPerLatDegree;
|
||||
|
||||
// 计算画布缩放比例,保持宽高比
|
||||
const scale = Math.min(canvas.width / totalWidthMeters, canvas.height / totalHeightMeters);
|
||||
|
||||
// 坐标转换函数
|
||||
function convertToCanvas(lon, lat) {
|
||||
// 将经纬度转换为实际距离(米)
|
||||
const x = (lon - minLon) * metersPerLonDegree;
|
||||
const y = (lat - minLat) * metersPerLatDegree;
|
||||
|
||||
// 使用统一的缩放比例转换为画布坐标
|
||||
const canvasX = x * scale;
|
||||
const canvasY = canvas.height - (y * scale);
|
||||
|
||||
// 居中显示
|
||||
const offsetX = (canvas.width - totalWidthMeters * scale) / 2;
|
||||
const offsetY = (canvas.height - totalHeightMeters * scale) / 2;
|
||||
|
||||
return {
|
||||
x: canvasX + offsetX,
|
||||
y: canvasY - offsetY
|
||||
};
|
||||
}
|
||||
|
||||
// 绘制道路
|
||||
function drawRoad(start, end) {
|
||||
const startPos = convertToCanvas(start.longitude, start.latitude);
|
||||
const endPos = convertToCanvas(end.longitude, end.latitude);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startPos.x, startPos.y);
|
||||
ctx.lineTo(endPos.x, endPos.y);
|
||||
ctx.strokeStyle = '#666';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 绘制路口
|
||||
function drawIntersection(intersection) {
|
||||
const pos = convertToCanvas(intersection.longitude, intersection.latitude);
|
||||
|
||||
// 绘制圆点
|
||||
ctx.beginPath();
|
||||
ctx.arc(pos.x, pos.y, 5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#f00';
|
||||
ctx.fill();
|
||||
|
||||
// 绘制标签
|
||||
ctx.font = '12px Arial';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(intersection.point, pos.x, pos.y - 10);
|
||||
}
|
||||
|
||||
// 计算 T2-T6 的旋转角度
|
||||
const t2 = intersections.find(p => p.point === "T2");
|
||||
const t6 = intersections.find(p => p.point === "T6");
|
||||
const angle = Math.atan2(
|
||||
convertToCanvas(t6.longitude, t6.latitude).y - convertToCanvas(t2.longitude, t2.latitude).y,
|
||||
convertToCanvas(t6.longitude, t6.latitude).x - convertToCanvas(t2.longitude, t2.latitude).x
|
||||
);
|
||||
|
||||
// 设置画布中心点
|
||||
const centerX = canvas.width / 2;
|
||||
const centerY = canvas.height / 2;
|
||||
|
||||
// 清空画布
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 绘制网格
|
||||
const gridSize = 25; // 网格大小为25像素
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = '#ccc';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// 绘制垂直线
|
||||
for(let x = 0; x <= canvas.width; x += gridSize) {
|
||||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, canvas.height);
|
||||
}
|
||||
|
||||
// 绘制水平线
|
||||
for(let y = 0; y <= canvas.height; y += gridSize) {
|
||||
ctx.moveTo(0, y);
|
||||
ctx.lineTo(canvas.width, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// 保存当前状态
|
||||
ctx.save();
|
||||
|
||||
// 将原点移到画布中心
|
||||
ctx.translate(centerX, centerY);
|
||||
|
||||
// 旋转画布
|
||||
ctx.rotate(-angle);
|
||||
|
||||
// 将原点移回去
|
||||
ctx.translate(-centerX, -centerY);
|
||||
|
||||
// 计算并显示道路的偏离角度
|
||||
function calculateRoadAngle(start, end) {
|
||||
const startPos = convertToCanvas(start.longitude, start.latitude);
|
||||
const endPos = convertToCanvas(end.longitude, end.latitude);
|
||||
|
||||
// 将坐标点转换到旋转后的坐标系
|
||||
const rotatedStartX = (startPos.x - centerX) * Math.cos(-angle) - (startPos.y - centerY) * Math.sin(-angle);
|
||||
const rotatedStartY = (startPos.x - centerX) * Math.sin(-angle) + (startPos.y - centerY) * Math.cos(-angle);
|
||||
const rotatedEndX = (endPos.x - centerX) * Math.cos(-angle) - (endPos.y - centerY) * Math.sin(-angle);
|
||||
const rotatedEndY = (endPos.x - centerX) * Math.sin(-angle) + (endPos.y - centerY) * Math.cos(-angle);
|
||||
|
||||
// 计算在旋转坐标系中的方向向量(从起点指向终点)
|
||||
const dx = rotatedEndX - rotatedStartX;
|
||||
const dy = rotatedEndY - rotatedStartY;
|
||||
|
||||
// 计算与垂直方向的夹角(弧度)
|
||||
const angleRad = Math.atan2(-dx, dy);
|
||||
// 转换为角度并取绝对值
|
||||
const deviationAngle = Math.abs(angleRad * 180 / Math.PI);
|
||||
|
||||
// 显示角度(文字方向跟随旋转后的坐标系)
|
||||
ctx.save();
|
||||
// 将文字位置移到终点附近
|
||||
ctx.translate(endPos.x, endPos.y + 15);
|
||||
ctx.rotate(angle);
|
||||
ctx.font = '12px Arial';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(deviationAngle.toFixed(1) + '°', 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// 绘制道路网络
|
||||
// 计算指定道路的角度
|
||||
drawRoad(intersections[0], intersections[2]); // T1-T3
|
||||
calculateRoadAngle(intersections[0], intersections[2]);
|
||||
|
||||
drawRoad(intersections[3], intersections[4]); // T4-T5
|
||||
calculateRoadAngle(intersections[3], intersections[4]);
|
||||
|
||||
drawRoad(intersections[10], intersections[6]); // T11-T7 (修改顺序)
|
||||
calculateRoadAngle(intersections[10], intersections[6]);
|
||||
|
||||
drawRoad(intersections[7], intersections[8]); // T8-T9
|
||||
calculateRoadAngle(intersections[7], intersections[8]);
|
||||
|
||||
// 绘制其他道路
|
||||
drawRoad(intersections[1], intersections[9]); // T2-T10
|
||||
drawRoad(intersections[5], intersections[7]); // T6-T8
|
||||
drawRoad(intersections[11], intersections[3]); // T12-T4
|
||||
|
||||
// 绘制所有路口
|
||||
intersections.forEach(drawIntersection);
|
||||
|
||||
// 恢复画布状态
|
||||
ctx.restore();
|
||||
|
||||
// 绘制比例尺
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(gridSize * 2, canvas.height - 20);
|
||||
ctx.lineTo(gridSize * 5, canvas.height - 20);
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制刻度
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(gridSize * 2, canvas.height - 15);
|
||||
ctx.lineTo(gridSize * 2, canvas.height - 25);
|
||||
ctx.moveTo(gridSize * 3, canvas.height - 15);
|
||||
ctx.lineTo(gridSize * 3, canvas.height - 25);
|
||||
ctx.moveTo(gridSize * 4, canvas.height - 15);
|
||||
ctx.lineTo(gridSize * 4, canvas.height - 25);
|
||||
ctx.moveTo(gridSize * 5, canvas.height - 15);
|
||||
ctx.lineTo(gridSize * 5, canvas.height - 25);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
// 计算两个网格单位代表的实际距离(米)
|
||||
const gridMeters = gridSize / scale;
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'center';
|
||||
// 在刻度处显示距离
|
||||
ctx.fillText('0', gridSize * 2, canvas.height - 5);
|
||||
ctx.fillText(Math.round(gridMeters) + '', gridSize * 3, canvas.height - 5);
|
||||
ctx.fillText(Math.round(gridMeters * 1) + '', gridSize * 4, canvas.height - 5);
|
||||
ctx.fillText(Math.round(gridMeters * 2) + '米', gridSize * 5, canvas.height - 5);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
740
tools/map_websocket.html
Normal file
740
tools/map_websocket.html
Normal file
@ -0,0 +1,740 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>机场车辆监控</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="https://unpkg.com/leaflet-rotatedmarker/leaflet.rotatedMarker.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 800px;
|
||||
width: 60%;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#messages {
|
||||
width: 40%;
|
||||
height: 800px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.position {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #f90;
|
||||
}
|
||||
|
||||
.command {
|
||||
color: #800080;
|
||||
}
|
||||
|
||||
.vehicle-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: black;
|
||||
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.aircraft-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: rgba(128, 0, 128, 0.5);
|
||||
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
||||
border: 2px solid white;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.aircraft-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: black;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.special-vehicle-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: orange;
|
||||
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.intersection-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: #666;
|
||||
clip-path: polygon(40% 0%, 60% 0%, 60% 40%, 100% 40%, 100% 60%, 60% 60%, 60% 100%, 40% 100%, 40% 60%, 0% 60%, 0% 40%, 40% 40%);
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.traffic-light {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.traffic-light-red {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.traffic-light-green {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.traffic-light-yellow {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.countdown-label {
|
||||
background: #000;
|
||||
border: 1px solid #666;
|
||||
border-radius: 2px;
|
||||
font-family: "Digital-7", "DSEG7 Classic", Monaco, monospace;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
padding: 1px 2px;
|
||||
line-height: 14px;
|
||||
min-width: 28px;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.countdown-red {
|
||||
color: #ff3333;
|
||||
}
|
||||
|
||||
.countdown-green {
|
||||
color: #33ff33;
|
||||
}
|
||||
|
||||
.countdown-yellow {
|
||||
color: #ffff33;
|
||||
}
|
||||
|
||||
.distance-label {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.command-text {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.safety-border {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 2px solid;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.emergency-border {
|
||||
border-color: rgba(255, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.core-border {
|
||||
border-color: rgba(255, 165, 0, 0.6);
|
||||
}
|
||||
|
||||
.warning-border {
|
||||
border-color: rgba(255, 255, 0, 0.4);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>机场车辆监控系统</h2>
|
||||
<div class="container">
|
||||
<div id="map"></div>
|
||||
<div id="messages"></div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button onclick="connect()">连接</button>
|
||||
<button onclick="disconnect()">断开</button>
|
||||
<button onclick="clearMessages()">清空日志</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let ws = null;
|
||||
const messagesDiv = document.getElementById('messages');
|
||||
|
||||
// 初始化地图
|
||||
const map = L.map('map').setView([36.35305878, 120.08558121], 17);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
// 定义路口坐标
|
||||
const T1_INTERSECTION = {
|
||||
latitude: 36.35496367,
|
||||
longitude: 120.0868853
|
||||
};
|
||||
|
||||
const T2_INTERSECTION = {
|
||||
latitude: 36.35448347,
|
||||
longitude: 120.08502054
|
||||
};
|
||||
|
||||
const T3_INTERSECTION = {
|
||||
latitude: 36.35406879,
|
||||
longitude: 120.08341044
|
||||
};
|
||||
|
||||
const T4_INTERSECTION = {
|
||||
latitude: 36.35305878,
|
||||
longitude: 120.08558121
|
||||
};
|
||||
|
||||
const T6_INTERSECTION = {
|
||||
latitude: 36.35074527,
|
||||
longitude: 120.08649105
|
||||
};
|
||||
|
||||
const T7_INTERSECTION = {
|
||||
latitude: 36.35052372,
|
||||
longitude: 120.08562915
|
||||
};
|
||||
|
||||
const T8_INTERSECTION = {
|
||||
latitude: 36.35004529,
|
||||
longitude: 120.08676664
|
||||
};
|
||||
|
||||
const T10_INTERSECTION = {
|
||||
latitude: 36.34917893,
|
||||
longitude: 120.08710569
|
||||
};
|
||||
|
||||
const T11_INTERSECTION = {
|
||||
latitude: 36.3509885,
|
||||
longitude: 120.0873865
|
||||
};
|
||||
|
||||
// 存储所有标记
|
||||
const markers = new Map();
|
||||
const intersectionMarkers = new Map();
|
||||
const intersections = new Map();
|
||||
|
||||
// 创建自定义图标
|
||||
function createIcon(className, command = '') {
|
||||
let size;
|
||||
if (className.includes('aircraft')) {
|
||||
size = [50, 50]; // 50米正方形
|
||||
} else if (className.includes('vehicle')) {
|
||||
size = [20, 20]; // 10米正方形
|
||||
} else if (className.includes('traffic-light')) {
|
||||
size = [12, 12]; // 10像素的红绿灯
|
||||
} else {
|
||||
size = [20, 20]; // 其他图标保持原样
|
||||
}
|
||||
|
||||
// 如果有指令,创建带指令的图标
|
||||
if (command && className === 'vehicle-icon') {
|
||||
const html = `
|
||||
<div style="width:${size[0]}px;height:${size[1]}px;position:relative;">
|
||||
<div class="${className}"></div>
|
||||
<div class="command-text">${command}</div>
|
||||
</div>`;
|
||||
return L.divIcon({
|
||||
html: html,
|
||||
className: '',
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1] / 2]
|
||||
});
|
||||
}
|
||||
|
||||
// 没有指令时,创建普通图标
|
||||
return L.divIcon({
|
||||
className: className,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1] / 2]
|
||||
});
|
||||
}
|
||||
|
||||
function log(message, type = 'info') {
|
||||
const div = document.createElement('div');
|
||||
div.className = type;
|
||||
// 将换行符转换为 HTML 换行
|
||||
div.innerHTML = `${new Date().toLocaleTimeString()} - ${message.replace(/\n/g, '<br>')}`;
|
||||
messagesDiv.appendChild(div);
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function clearMessages() {
|
||||
messagesDiv.innerHTML = '';
|
||||
}
|
||||
|
||||
function updatePosition(data) {
|
||||
const id = data.object_id;
|
||||
const position = [data.position.latitude, data.position.longitude];
|
||||
let iconClass;
|
||||
|
||||
// 根据ID前缀确定图标类型
|
||||
if (data.object_type === 'aircraft') {
|
||||
iconClass = 'aircraft-icon';
|
||||
|
||||
// 创建或更新安全边框
|
||||
const borders = [
|
||||
{ size: 250, class: 'warning-border' }, // 外围预警区
|
||||
{ size: 150, class: 'core-border' }, // 核心安全区
|
||||
{ size: 100, class: 'emergency-border' } // 紧急制动区
|
||||
];
|
||||
|
||||
borders.forEach(border => {
|
||||
const borderId = id + '_' + border.class;
|
||||
let borderMarker = markers.get(borderId);
|
||||
|
||||
if (!borderMarker) {
|
||||
// 创建边框标记
|
||||
borderMarker = L.marker(position, {
|
||||
icon: L.divIcon({
|
||||
className: 'safety-border ' + border.class,
|
||||
iconSize: [border.size, border.size],
|
||||
iconAnchor: [border.size / 2, border.size / 2]
|
||||
}),
|
||||
rotationAngle: data.heading || 0,
|
||||
rotationOrigin: 'center center'
|
||||
}).addTo(map);
|
||||
markers.set(borderId, borderMarker);
|
||||
} else {
|
||||
// 更新边框位置和方向
|
||||
borderMarker.setLatLng(position);
|
||||
if (data.heading !== undefined) {
|
||||
borderMarker.setRotationAngle(data.heading);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (id.startsWith('TQ')) {
|
||||
iconClass = 'special-vehicle-icon';
|
||||
} else {
|
||||
iconClass = 'vehicle-icon';
|
||||
}
|
||||
|
||||
let marker = markers.get(id);
|
||||
if (!marker) {
|
||||
// 创建新标记
|
||||
marker = L.marker(position, {
|
||||
icon: createIcon(iconClass),
|
||||
rotationAngle: data.heading || 0,
|
||||
rotationOrigin: 'center center'
|
||||
}).addTo(map);
|
||||
marker.bindTooltip(id);
|
||||
markers.set(id, marker);
|
||||
} else {
|
||||
// 更新现有标记位置和航向
|
||||
marker.setLatLng(position);
|
||||
if (data.heading !== undefined) {
|
||||
marker.setRotationAngle(data.heading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加红绿灯状态和倒计时变量
|
||||
// let lastTrafficLightState = null;
|
||||
// let countdownInterval = null;
|
||||
// let countdown = 10;
|
||||
|
||||
// New function to update intersection traffic lights
|
||||
function updateIntersectionTrafficLights(data) {
|
||||
const intersectionId = data.intersection_id;
|
||||
const position = [data.position.latitude, data.position.longitude];
|
||||
const nsStatus = data.ns_status; // 0: red, 1: green, 2: yellow
|
||||
const ewStatus = data.ew_status; // 0: red, 1: green, 2: yellow
|
||||
|
||||
const nsColor = nsStatus === 0 ? 'red' : nsStatus === 1 ? 'green' : 'yellow';
|
||||
const ewColor = ewStatus === 0 ? 'red' : ewStatus === 1 ? 'green' : 'yellow';
|
||||
|
||||
// Calculate offset positions for NS and EW lights
|
||||
const nsPositionOffset = [position[0] + 0.00003, position[1]]; // Slightly north
|
||||
const ewPositionOffset = [position[0], position[1] + 0.00003]; // Slightly east
|
||||
|
||||
let currentMarkers = intersectionMarkers.get(intersectionId);
|
||||
if (!currentMarkers) {
|
||||
// Create new marker group
|
||||
currentMarkers = {};
|
||||
currentMarkers.nsMarker = L.marker(nsPositionOffset, {
|
||||
icon: createIcon(`traffic-light traffic-light-${nsColor}`),
|
||||
pane: 'markerPane' // Ensure lights are above roads
|
||||
}).addTo(map).bindTooltip(`Intersection ${intersectionId} (NS)`);
|
||||
|
||||
currentMarkers.ewMarker = L.marker(ewPositionOffset, {
|
||||
icon: createIcon(`traffic-light traffic-light-${ewColor}`),
|
||||
pane: 'markerPane'
|
||||
}).addTo(map).bindTooltip(`Intersection ${intersectionId} (EW)`);
|
||||
|
||||
intersectionMarkers.set(intersectionId, currentMarkers);
|
||||
} else {
|
||||
// Update existing markers
|
||||
currentMarkers.nsMarker.setLatLng(nsPositionOffset);
|
||||
currentMarkers.nsMarker.setIcon(createIcon(`traffic-light traffic-light-${nsColor}`));
|
||||
|
||||
currentMarkers.ewMarker.setLatLng(ewPositionOffset);
|
||||
currentMarkers.ewMarker.setIcon(createIcon(`traffic-light traffic-light-${ewColor}`));
|
||||
}
|
||||
}
|
||||
|
||||
function updateVehicleCommand(vehicleId, commandType) {
|
||||
console.log('更新车辆指令:', vehicleId, commandType);
|
||||
|
||||
// 只处理无人车
|
||||
if (!vehicleId.startsWith('QN')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是 SIGNAL 指令,不更新显示
|
||||
if (commandType === 'SIGNAL') {
|
||||
console.log('忽略 SIGNAL 指令');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取指令字母
|
||||
let commandText = '';
|
||||
switch (commandType) {
|
||||
case 'ALERT':
|
||||
commandText = 'A';
|
||||
break;
|
||||
case 'WARNING':
|
||||
commandText = 'W';
|
||||
break;
|
||||
case 'RESUME':
|
||||
commandText = 'R';
|
||||
break;
|
||||
default:
|
||||
commandText = '';
|
||||
}
|
||||
|
||||
console.log('指令文本:', commandText);
|
||||
|
||||
// 更新图标
|
||||
const marker = markers.get(vehicleId);
|
||||
if (marker && commandText) {
|
||||
console.log('设置新标:', vehicleId, commandText);
|
||||
marker.setIcon(createIcon('vehicle-icon', commandText));
|
||||
} else if (marker) {
|
||||
marker.setIcon(createIcon('vehicle-icon'));
|
||||
}
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (ws) {
|
||||
log('已经连接,请先断开', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket('ws://localhost:8010');
|
||||
|
||||
ws.onopen = () => {
|
||||
log('连接成功', 'success');
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
log('连接关闭', 'info');
|
||||
ws = null;
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
log('发生错误: ' + error, 'error');
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
// 忽略心跳响应
|
||||
if (data.type === 'heartbeat') {
|
||||
return;
|
||||
}
|
||||
|
||||
let type = 'info'; // 默认类型
|
||||
let message = '';
|
||||
|
||||
switch (data.type) {
|
||||
case 'position_update':
|
||||
type = 'position';
|
||||
updatePosition(data);
|
||||
message = `位置更新: ${data.object_id} (${data.object_type})\n` +
|
||||
`位置: (${data.position.longitude.toFixed(6)}, ${data.position.latitude.toFixed(6)})\n` +
|
||||
`航向: ${data.heading !== undefined ? data.heading.toFixed(2) + '°' : 'N/A'}\n` +
|
||||
`速度: ${data.speed !== undefined ? data.speed.toFixed(2) + ' m/s' : 'N/A'}`;
|
||||
break;
|
||||
case 'intersection_traffic_light_status':
|
||||
type = 'info';
|
||||
updateIntersectionTrafficLights(data);
|
||||
message = `路口交通灯状态更新:\n` +
|
||||
`路口 ID: ${data.intersection_id}\n` +
|
||||
`南北状态: ${data.ns_status === 0 ? '红' : data.ns_status === 1 ? '绿' : '黄'}\n` +
|
||||
`东西状态: ${data.ew_status === 0 ? '红' : data.ew_status === 1 ? '绿' : '黄'}`;
|
||||
break;
|
||||
case 'collision_warning':
|
||||
type = 'warning';
|
||||
message = '收到碰撞预警:\n' + JSON.stringify(data, null, 2);
|
||||
break;
|
||||
case 'vehicle_command':
|
||||
type = 'command';
|
||||
console.log('收到指令消息:', data); // 调试日志
|
||||
updateVehicleCommand(data.vehicleId, data.commandType);
|
||||
// 为控制指令添加中文描述
|
||||
const commandTypes = {
|
||||
'SIGNAL': '信号灯',
|
||||
'ALERT': '告警',
|
||||
'WARNING': '预警',
|
||||
'RESUME': '恢复',
|
||||
'PARKING': '安全停靠'
|
||||
};
|
||||
const reasons = {
|
||||
'TRAFFIC_LIGHT': '红绿灯控制',
|
||||
'AIRCRAFT_CROSSING': '航空器交叉',
|
||||
'SPECIAL_VEHICLE': '特勤车辆',
|
||||
'AIRCRAFT_PUSH': '航空器推出',
|
||||
'RESUME_TRAFFIC': '恢复通行',
|
||||
'PARKING_SIDE': '安全停靠'
|
||||
};
|
||||
message = `收到车辆控制指令:\n车辆: ${data.vehicleId}\n` +
|
||||
`指令类型: ${commandTypes[data.commandType] || data.commandType}\n` +
|
||||
`原因: ${reasons[data.reason] || data.reason}\n` +
|
||||
(data.targetLatitude !== undefined ? `目标位置: (${data.targetLatitude}, ${data.targetLongitude})\n` : '') +
|
||||
(data.signalState ? `信号灯状态: ${data.signalState}\n` : '') +
|
||||
(data.intersectionId ? `路口ID: ${data.intersectionId}\n` : '') +
|
||||
`时间戳: ${new Date(data.timestamp / 1000000).toLocaleString()}`;
|
||||
break;
|
||||
default:
|
||||
message = '收到未知类型消息:\n' + JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
log(message, type);
|
||||
} catch (e) {
|
||||
log('消息解析错误: ' + e.message, 'error');
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
log('连接失败: ' + error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (!ws) {
|
||||
log('未连接', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除倒计时
|
||||
// if (countdownInterval) {
|
||||
// clearInterval(countdownInterval);
|
||||
// countdownInterval = null;
|
||||
// }
|
||||
// lastTrafficLightState = null;
|
||||
// countdown = 10;
|
||||
|
||||
// 清除倒计时标记
|
||||
// trafficLights.forEach(light => {
|
||||
// if (light.countdownMarker) {
|
||||
// map.removeLayer(light.countdownMarker);
|
||||
// light.countdownMarker = null;
|
||||
// }
|
||||
// });
|
||||
|
||||
ws.close();
|
||||
ws = null;
|
||||
|
||||
// 清除所有车辆和安全边框标记
|
||||
markers.forEach((marker, key) => {
|
||||
map.removeLayer(marker);
|
||||
});
|
||||
markers.clear();
|
||||
|
||||
// 清除旧的红绿灯标记
|
||||
// trafficLights.forEach(light => map.removeLayer(light));
|
||||
// trafficLights.clear();
|
||||
|
||||
// 清除路口红绿灯标记
|
||||
intersectionMarkers.forEach((markers, id) => {
|
||||
if (markers.nsMarker) map.removeLayer(markers.nsMarker);
|
||||
if (markers.ewMarker) map.removeLayer(markers.ewMarker);
|
||||
});
|
||||
intersectionMarkers.clear();
|
||||
}
|
||||
|
||||
// 添加道路刻度标记函数
|
||||
function addRoadMarks(startPoint, endPoint) {
|
||||
// 计算点之间的距离(米)
|
||||
const lat1 = startPoint[0];
|
||||
const lon1 = startPoint[1];
|
||||
const lat2 = endPoint[0];
|
||||
const lon2 = endPoint[1];
|
||||
|
||||
// 计算道路角度(考虑经纬度投影)
|
||||
const latMid = (lat1 + lat2) / 2; // 使用中点纬度来算经度缩放
|
||||
const lonScale = Math.cos(latMid * Math.PI / 180); // 经度缩放因子
|
||||
const dx = (lon2 - lon1) * lonScale;
|
||||
const dy = lat2 - lat1;
|
||||
const angle = Math.atan2(dy, dx);
|
||||
|
||||
// 计算垂直于道路的方向(只在右侧显示刻度)
|
||||
const perpAngle = angle + Math.PI / 2;
|
||||
const markLength = 0.00005; // 保持您设置的较短刻度线长度
|
||||
const offset = 0.00004; // 向右偏移一点,避免与道路重叠
|
||||
|
||||
// 计算总距离
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 每50米一个刻度
|
||||
const step = 0.0005; // 约50米
|
||||
const steps = Math.floor(dist / step);
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
// 计算刻度位置
|
||||
const ratio = i / steps;
|
||||
// 在经纬度坐标系中正插值
|
||||
const pos = [
|
||||
lat1 + dy * ratio,
|
||||
lon1 + (dx / lonScale) * ratio
|
||||
];
|
||||
|
||||
// 计算垂直偏移(考虑经纬度投影)
|
||||
const offsetPos = [
|
||||
pos[0] + Math.sin(perpAngle) * offset,
|
||||
pos[1] + Math.cos(perpAngle) * offset / lonScale
|
||||
];
|
||||
|
||||
// 计算刻度线终点(考虑经纬度投影)
|
||||
const markEnd = [
|
||||
offsetPos[0] + Math.sin(perpAngle) * markLength,
|
||||
offsetPos[1] + Math.cos(perpAngle) * markLength / lonScale
|
||||
];
|
||||
|
||||
// 添加刻度线
|
||||
L.polyline([offsetPos, markEnd], {
|
||||
color: '#666',
|
||||
weight: 1.5
|
||||
}).addTo(map);
|
||||
|
||||
// 添加距离标签
|
||||
const distance = Math.round(i * 50);
|
||||
if (distance > 0) {
|
||||
const label = L.divIcon({
|
||||
className: 'distance-label',
|
||||
html: distance + 'm',
|
||||
iconSize: [40, 20],
|
||||
iconAnchor: [-5, 10]
|
||||
});
|
||||
|
||||
L.marker(markEnd, {
|
||||
icon: label,
|
||||
interactive: false
|
||||
}).addTo(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加道路
|
||||
// T2 到 T10主路
|
||||
const mainRoadEW = L.polyline([
|
||||
[T2_INTERSECTION.latitude, T2_INTERSECTION.longitude],
|
||||
[T10_INTERSECTION.latitude, T10_INTERSECTION.longitude]
|
||||
], {
|
||||
color: '#999',
|
||||
weight: 8
|
||||
}).addTo(map);
|
||||
|
||||
// T1 到 T3道路
|
||||
const westRoadNS = L.polyline([
|
||||
[T1_INTERSECTION.latitude, T1_INTERSECTION.longitude],
|
||||
[T3_INTERSECTION.latitude, T3_INTERSECTION.longitude]
|
||||
], {
|
||||
color: '#999',
|
||||
weight: 8
|
||||
}).addTo(map);
|
||||
|
||||
// T7 到 T11道路
|
||||
const eastRoadNS = L.polyline([
|
||||
[T7_INTERSECTION.latitude, T7_INTERSECTION.longitude],
|
||||
[T11_INTERSECTION.latitude, T11_INTERSECTION.longitude]
|
||||
], {
|
||||
color: '#999',
|
||||
weight: 8
|
||||
}).addTo(map);
|
||||
|
||||
// 添加刻度标记
|
||||
// T1 到 T3路刻度
|
||||
addRoadMarks(
|
||||
[T1_INTERSECTION.latitude, T1_INTERSECTION.longitude],
|
||||
[T3_INTERSECTION.latitude, T3_INTERSECTION.longitude]
|
||||
);
|
||||
|
||||
// T7 到 T11路刻度
|
||||
addRoadMarks(
|
||||
[T7_INTERSECTION.latitude, T7_INTERSECTION.longitude],
|
||||
[T11_INTERSECTION.latitude, T11_INTERSECTION.longitude]
|
||||
);
|
||||
|
||||
// T2 到 T10路刻度
|
||||
addRoadMarks(
|
||||
[T2_INTERSECTION.latitude, T2_INTERSECTION.longitude],
|
||||
[T10_INTERSECTION.latitude, T10_INTERSECTION.longitude]
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1162
tools/mock_server.py
Normal file
1162
tools/mock_server.py
Normal file
File diff suppressed because it is too large
Load Diff
109
tools/mock_traffic_light_client.py
Normal file
109
tools/mock_traffic_light_client.py
Normal file
@ -0,0 +1,109 @@
|
||||
import time
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# --- 配置 ---
|
||||
CPP_SERVER_URL = "http://localhost:8082/trafficlight" # C++ TrafficLightHttpServer 的地址和端口
|
||||
SEND_INTERVAL = 1.0 # 发送间隔(秒)
|
||||
|
||||
# 红绿灯循环周期 (North/South State, East/West State, Duration in seconds)
|
||||
TRAFFIC_LIGHT_CYCLE = [
|
||||
("GREEN", "RED", 10),
|
||||
("YELLOW", "RED", 2),
|
||||
("RED", "GREEN", 10),
|
||||
("RED", "YELLOW", 2),
|
||||
]
|
||||
|
||||
# --- 日志设置 ---
|
||||
LOG_DIR = 'logs'
|
||||
if not os.path.exists(LOG_DIR):
|
||||
try:
|
||||
os.makedirs(LOG_DIR)
|
||||
except OSError as e:
|
||||
print(f"Error creating log directory {LOG_DIR}: {e}", file=sys.stderr)
|
||||
LOG_DIR = '.' # Fallback to current directory
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(os.path.join(LOG_DIR, 'mock_traffic_light_client.log')),
|
||||
logging.StreamHandler() # 同时输出到控制台
|
||||
]
|
||||
)
|
||||
|
||||
# --- 全局状态 ---
|
||||
current_cycle_index = 0
|
||||
last_cycle_switch_time = time.time()
|
||||
|
||||
# --- 辅助函数 ---
|
||||
def generate_di_payload(ns_state, ew_state):
|
||||
"""根据南北和东西状态生成 DI 信号 Payload。"""
|
||||
# 初始化所有 DI 信号为 0
|
||||
payload = {f"DI-{i:02d}": 0 for i in range(1, 19)}
|
||||
# 设置南北向状态 (DI-11: 北红, DI-12: 北黄, DI-13: 北绿)
|
||||
if ns_state == "RED": payload["DI-11"] = 1
|
||||
elif ns_state == "YELLOW": payload["DI-12"] = 1
|
||||
elif ns_state == "GREEN": payload["DI-13"] = 1
|
||||
# 设置东西向状态 (DI-14: 东红, DI-15: 东黄, DI-16: 东绿)
|
||||
if ew_state == "RED": payload["DI-14"] = 1
|
||||
elif ew_state == "YELLOW": payload["DI-15"] = 1
|
||||
elif ew_state == "GREEN": payload["DI-16"] = 1
|
||||
return payload
|
||||
|
||||
def send_traffic_light_update(url, payload):
|
||||
"""使用 urllib 发送 POST 请求。"""
|
||||
try:
|
||||
data = json.dumps(payload).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'}, method='POST')
|
||||
# 设置一个较短的超时时间,避免长时间阻塞
|
||||
with urllib.request.urlopen(req, timeout=0.8) as response:
|
||||
logging.debug(f"Sent: {payload}, Status: {response.status}")
|
||||
# 可以选择性地读取响应内容
|
||||
# response.read()
|
||||
except (urllib.error.URLError, TimeoutError, ConnectionRefusedError) as e:
|
||||
logging.error(f"Failed to send to {url}: {e}")
|
||||
except Exception as e:
|
||||
logging.error(f"Unexpected error during send: {e}")
|
||||
|
||||
# --- 主循环 ---
|
||||
def traffic_light_sender_loop():
|
||||
"""主循环,管理状态切换并发送数据。"""
|
||||
global current_cycle_index, last_cycle_switch_time
|
||||
logging.info(f"Starting traffic light client, sending to {CPP_SERVER_URL}")
|
||||
while True:
|
||||
try:
|
||||
now = time.time()
|
||||
|
||||
# 检查是否需要切换循环阶段
|
||||
current_ns_state, current_ew_state, duration = TRAFFIC_LIGHT_CYCLE[current_cycle_index]
|
||||
if now - last_cycle_switch_time >= duration:
|
||||
current_cycle_index = (current_cycle_index + 1) % len(TRAFFIC_LIGHT_CYCLE)
|
||||
last_cycle_switch_time = now
|
||||
next_ns_state, next_ew_state, _ = TRAFFIC_LIGHT_CYCLE[current_cycle_index]
|
||||
logging.info(f"Switching phase: {current_ns_state}/{current_ew_state} -> {next_ns_state}/{next_ew_state}")
|
||||
# 更新当前状态以供本次发送
|
||||
current_ns_state, current_ew_state, _ = TRAFFIC_LIGHT_CYCLE[current_cycle_index]
|
||||
|
||||
# 生成 Payload 并发送
|
||||
payload = generate_di_payload(current_ns_state, current_ew_state)
|
||||
send_traffic_light_update(CPP_SERVER_URL, payload)
|
||||
|
||||
# 等待指定间隔
|
||||
time.sleep(SEND_INTERVAL)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Traffic light client stopped by user.")
|
||||
break
|
||||
except Exception as e:
|
||||
logging.error(f"Error in main loop: {e}", exc_info=True)
|
||||
# 等待一段时间再重试,避免快速失败循环
|
||||
time.sleep(5)
|
||||
|
||||
# --- 启动入口 ---
|
||||
if __name__ == '__main__':
|
||||
traffic_light_sender_loop()
|
||||
135
tools/test_websocket.html
Normal file
135
tools/test_websocket.html
Normal file
@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebSocket 测试</title>
|
||||
<style>
|
||||
#messages {
|
||||
width: 100%;
|
||||
height: 800px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.error { color: red; }
|
||||
.success { color: green; }
|
||||
.info { color: blue; }
|
||||
.position { color: #666; }
|
||||
.warning { color: #f90; }
|
||||
.command { color: #800080; } /* 紫色显示控制指令 */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>WebSocket 测试客户端</h2>
|
||||
<div id="messages"></div>
|
||||
<div>
|
||||
<button onclick="connect()">连接</button>
|
||||
<button onclick="disconnect()">断开</button>
|
||||
<button onclick="clearMessages()">清空日志</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let ws = null;
|
||||
const messagesDiv = document.getElementById('messages');
|
||||
|
||||
function log(message, type = 'info') {
|
||||
const div = document.createElement('div');
|
||||
div.className = type;
|
||||
div.textContent = `${new Date().toLocaleTimeString()} - ${message}`;
|
||||
messagesDiv.appendChild(div);
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function clearMessages() {
|
||||
messagesDiv.innerHTML = '';
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (ws) {
|
||||
log('已经连接,请先断开', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket('ws://localhost:8010');
|
||||
|
||||
ws.onopen = () => {
|
||||
log('连接成功', 'success');
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
log('连接关闭', 'info');
|
||||
ws = null;
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
log('发生错误: ' + error, 'error');
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
const formattedData = JSON.stringify(data, null, 2);
|
||||
|
||||
// 根据消息类型使用不同的样式
|
||||
let type = 'info';
|
||||
let message = '收到消息:\n' + formattedData;
|
||||
|
||||
switch (data.type) {
|
||||
case 'position_update':
|
||||
type = 'position';
|
||||
break;
|
||||
case 'collision_warning':
|
||||
type = 'warning';
|
||||
break;
|
||||
case 'vehicle_command':
|
||||
type = 'command';
|
||||
// 为控制指令添加中文描述
|
||||
const commandTypes = {
|
||||
'SIGNAL': '信号灯',
|
||||
'ALERT': '告警',
|
||||
'WARNING': '预警',
|
||||
'RESUME': '恢复',
|
||||
'PARKING': '安全停靠'
|
||||
};
|
||||
const reasons = {
|
||||
'TRAFFIC_LIGHT': '红绿灯控制',
|
||||
'AIRCRAFT_CROSSING': '航空器交叉',
|
||||
'SPECIAL_VEHICLE': '特勤车辆',
|
||||
'AIRCRAFT_PUSH': '航空器推出',
|
||||
'RESUME_TRAFFIC': '恢复通行',
|
||||
'PARKING_SIDE': '安全停靠'
|
||||
};
|
||||
message = `收到车辆控制指令:\n车辆: ${data.vehicleId}\n` +
|
||||
`指令类型: ${commandTypes[data.commandType] || data.commandType}\n` +
|
||||
`原因: ${reasons[data.reason] || data.reason}\n` +
|
||||
(data.targetLatitude !== undefined ? `目标位置: (${data.targetLatitude}, ${data.targetLongitude})\n` : '') +
|
||||
(data.signalState ? `信号灯状态: ${data.signalState}\n` : '') +
|
||||
(data.intersectionId ? `路口ID: ${data.intersectionId}\n` : '') +
|
||||
`时间戳: ${new Date(data.timestamp/1000000).toLocaleString()}`;
|
||||
break;
|
||||
}
|
||||
|
||||
log(message, type);
|
||||
} catch (e) {
|
||||
log('收到消息: ' + event.data, 'info');
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
log('连接失败: ' + error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (!ws) {
|
||||
log('未连接', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user