CollisionAvoidance/docs/design.md

18 KiB
Raw Blame History

机场碰撞预警系统设计文档

1. 系统概述

1.1 系统目标

实现机场内航空器与车辆的实时位置监控和碰撞预警。

1.2 主要功能

  • 实时获取航空器位置数据
  • 实时获取车辆位置数据
  • 碰撞风险检测
  • 预警信息输出

2. 系统架构

2.1 整体架构

  • 数据采集层:负责从外部系统获取位置数据
  • 数据处理层:进行坐标转换和数据规范化
  • 碰撞检测层:执行碰撞风险分析
  • 预警输出层:生成和发送预警信息

2.2 核心模块

  1. System系统总控模块
  2. DataCollector数据采集模块
  3. CollisionDetector碰撞检测模块
  4. CoordinateConverter坐标转换模块

3. 数据模型

3.1 基础数据类型

// 二维向量
struct Vector2D {
    double x;  // 东西方向(米)
    double y;  // 南北方向(米)
};

// 地理坐标
struct GeoPosition {
    double latitude;   // 纬度
    double longitude;  // 经度
};

// 基础移动物体
struct MovingObject {
    std::string id;          // 唯一标识
    GeoPosition geo;         // 地理坐标
    Vector2D position;       // 平面坐标
    double heading;          // 航向角(度)
    uint64_t timestamp;      // 时间戳
};

3.2 业务数据类型

// 航空器数据
struct Aircraft : MovingObject {
    std::string flightNo;    // 航班号
    std::string trackNumber; // 航迹号
    double altitude;         // 高度(米)
};

// 车辆数据
struct Vehicle : MovingObject {
    std::string vehicleNo;   // 车牌号
    double speed;            // 速度
    double direction;        // 方向
    bool controllable;       // 是否可控
};

3.3 配置数据类型

// 可控车辆配置
struct ControllableVehicleConfig {
    std::string vehicleNo;  // 车牌号
    std::string ip;         // 车辆IP地址
    int port;               // 车辆端口号
};

4. 接口设计

4.1 数据源接口

class DataSource {
    virtual std::vector<Aircraft> getAircraftData() = 0;
    virtual std::vector<Vehicle> getVehicleData() = 0;
};

4.2 碰撞检测接口

class CollisionDetector {
    bool detectAircraft(const Aircraft& a1, const Aircraft& a2);
    bool detectAircraftVehicle(const Aircraft& aircraft, const Vehicle& vehicle);
};

5. 碰撞检测算法

5.1 基本原理

碰撞检测基于以下三个核心要素:

  1. 距离检测
  2. 相对运动分析
  3. 区域特定阈值

5.2 检测流程

5.2.1 距离检测

  1. 直接报警条件:
if (distance < threshold * 0.5) {  // 距离小于阈值的一半
    return true;  // 直接报警
}
  1. 进一步检测条件:
if (distance < threshold) {  // 距离在阈值范围内
    // 进行相对运动分析
}

5.2.2 相对运动分析

  1. 速度分量计算:
// 考虑航向角,转换为数学坐标系
double vx = speed * std::cos((90 - heading) * M_PI / 180.0);
double vy = speed * std::sin((90 - heading) * M_PI / 180.0);
  1. 相对运动计算:
// 计算相对速度
double relativeVx = v1x - v2x;
double relativeVy = v1y - v2y;

// 计算相对运动
double relativeMotion = dx*relativeVx + dy*relativeVy;
  1. 碰撞判定:
if (relativeMotion <= 0) {  // 物体正在接近或相对静止
    return true;  // 报警
}

5.3 区域特定阈值

不同区域采用不同的安全距离阈值:

区域 航空器地面阈值 车辆阈值
跑道 100米 50米
滑行道 50米 30米
停机位 40米 20米
服务区 30米 15米

5.4 空间优化

使用四叉树进行空间索引,优化查询性能:

  1. 四叉树构建:
QuadTree<Vehicle> vehicleTree_(bounds, 8);  // 容量为8的四叉树
  1. 邻近查询:
auto nearbyVehicles = vehicleTree_.queryNearby(
    position,    // 中心点
    threshold    // 查询半径
);

5.5 性能考虑

  1. 空间复杂度:

    • 四叉树O(n),其中 n 为车辆数量
    • 航空器列表O(m),其中 m 为航空器数量
  2. 时间复杂度:

    • 四叉树查询O(log n)
    • 总体碰撞检测O(m * log n)

5.6 安全保障

  1. 距离冗余:

    • 使用阈值的一半作为直接报警条件
    • 为不同区域设置合适的安全距离
  2. 运动预测:

    • 考虑物体的相对运动
    • 提前发现潜在碰撞风险
  3. 降级处理:

    • 当无法计算相对运动时,仅使用距离判断
    • 保证基本的安全检测功能

5.7 碰撞检测主流程

// 加载可控车辆配置
std::vector<ControllableVehicleConfig> controllableVehicles = loadControllableVehicleConfig();

for (const auto& aircraft : aircrafts) {
    for (const auto& vehicle : vehicles) {
        if (detectCollision(aircraft, vehicle)) {
            // 检查是否为可控车辆
            auto iter = std::find_if(controllableVehicles.begin(), controllableVehicles.end(), 
                [&](const ControllableVehicleConfig& config) {
                    return config.vehicleNo == vehicle.vehicleNo;
                });
            
            if (iter != controllableVehicles.end()) {
                // 生成控制指令
                auto command = generateCommand(aircraft, vehicle);
                // 发送控制指令
                sendCommand(command, iter->ip, iter->port);
            } else {
                // 发送普通预警
                sendWarning(aircraft, vehicle);
            }
        }
    }
}

5.8 可控车辆配置加载

从配置文件加载可控车辆信息:

std::vector<ControllableVehicleConfig> loadControllableVehicleConfig() {
    std::vector<ControllableVehicleConfig> configs;
    
    // 读取配置文件
    std::ifstream file("controllable_vehicles.json");
    nlohmann::json jsonConfig;
    file >> jsonConfig;
    
    // 解析配置项
    for (const auto& item : jsonConfig["vehicles"]) {
        ControllableVehicleConfig config;
        config.vehicleNo = item["vehicleNo"].get<std::string>();
        config.ip = item["ip"].get<std::string>();
        config.port = item["port"].get<int>();
        configs.push_back(config);
    }
    
    return configs;
}

配置文件 controllable_vehicles.json 的格式如下:

{
    "vehicles": [
        {
            "vehicleNo": "VEH001",
            "ip": "192.168.1.101",
            "port": 8080
        },
        {
            "vehicleNo": "VEH002",
            "ip": "192.168.1.102",
            "port": 8080
        },
        {
            "vehicleNo": "VEH003",
            "ip": "192.168.1.103",
            "port": 8080
        }
    ]
}

6. 坐标转换

6.1 转换方法

  • 使用等角投影方法
  • 基于机场参考点青岛胶东国际机场北纬36°21'43.2"东经120°05'16.8"
  • 考虑地球曲率影响
  • WGS84椭球体参数

7. 数据更新策略

7.1 数据采集

  • 定时轮询默认1秒
  • 独立的数据采集线程
  • 分别缓存航空器和车辆数据

7.2 碰撞检测

  • 实时检测默认100ms
  • 独立的检测线程
  • 分类处理不同类型的碰撞风险

8. 开发规范

8.1 代码规范

  • Google C++ Style
  • 智能指针管理
  • 异常安全
  • RAII原则

8.2 性能规范

  • 避免虚函数滥用
  • 减少动态内存分配
  • 合理使用模板
  • 注意数据对齐

8.3 网络编程规范

8.3.1 数据读取策略

  • 使用分块读取代替一次性读取
  • 合理设置分块大小如1KB
  • 正确处理部分读取的情况
  • 验证数据完整性

8.3.2 错误处理

  • 实现请求重试机制
  • 设置合理的超时时间
  • 正确处理连接断开
  • 完善的错误日志记录

8.3.3 连接管理

  • 及时关闭无用连接
  • 正确处理连接状态
  • 实现连接池(如需要)
  • 处理并发连接

8.3.4 性能考虑

  • 使用异步操作处理并发
  • 实现数据缓存机制
  • 免频繁建立连接
  • 合理控制缓冲区大小

8.3.5 最佳实践

// 分块读取示例
const size_t chunk_size = 1024;  // 1KB per chunk
while (total_read < content_length) {
    size_t to_read = std::min(chunk_size, content_length - total_read);
    // 读取数据块
    // 处理错误
    // 更新计数
}

// 重试机制示例
const int max_retries = 3;
for (int retry = 0; retry < max_retries; ++retry) {
    try {
        // 尝试操作
        break;  // 成功则退出
    } catch (...) {
        if (retry == max_retries - 1) throw;  // 最后一次重试失败
        // 等待后重试
    }
}

8.4 HTTP 数据读取规范

8.4.1 问题描述

在使用 Boost.Asio 读取 HTTP 响应时,可能会遇到 "End of file" 错误。这个错误通常发生在:

  • 读取响应体时
  • 服务器已关闭连接
  • 数据未完全读取

8.4.2 错误原因分析

  1. HTTP 响应头读取时可能已包含部分响应体数据
  2. 忽略缓冲区中的这部分数据会导致:
    • 数据丢失
    • 读取位置错误
    • 连接提前关闭

8.4.3 解决方案

  1. 正确处理响应数据:
// 1. 读取响应头
size_t header_length = asio::read_until(*socket_, response_buf, "\r\n\r\n");

// 2. 处理缓冲区中的响应体数据
size_t body_part = response_buf.size() - header_length;
if (body_part > 0) {
    // 提取已缓冲的响应体数据
    body.append(
        asio::buffers_begin(response_buf.data()) + header_length,
        asio::buffers_end(response_buf.data())
    );
    total_read += body_part;
}

// 3. 继续读取剩余数据
while (total_read < content_length) {
    // 读取剩余的响应体
}

8.4.4 最佳实践

  1. 数据读取原则:

    • 使用简单直接的读取逻辑
    • 准确跟踪已读取的数据量
    • 正确处理缓冲区中的所有数据
    • 避免复杂的重试机制
  2. 错误处理:

    • 及时检查错误码
    • 提供详细的错误日志
    • 在错误发生时及时关闭连接
    • 保持连接状态的一致性
  3. 数据完整性:

    • 验证 Content-Length
    • 确保读取完整的响应体
    • 避免数据丢失或重复
    • 正确处理分块数据
  4. 日志记录:

    • 记录关键操作步骤
    • 包含详细的错误信息
    • 记录数据读取进度
    • 便于问题诊断和调试

9. 测试策略

9.1 单元测试

  • 模块功能测试
  • 边界条件测试
  • 异常处理测试
  • 内存泄漏测试
  • 坐标转换精度测试

9.2 性能测试

  • 延迟测试
  • 并发测试
  • 压力测试
  • 内存使用测试
  • 坐标转换性能测试

9. 测试场景

9.1 碰撞风险场景模拟

9.1.1 场景描述

模拟一个典型的地面碰撞风险场景:

  • 航空器在跑道上滑行
  • 地面车辆垂直接近跑道
  • 两者轨迹存在交叉点
  • 如不采取措施将发生碰撞

9.1.2 场景参数

  1. 航空器参数:

    • 初始位置:跑道西端 (AIRPORT_LON - 0.002, AIRPORT_LAT)
    • 运动方向:向东滑行
    • 运动速度0.0002经度/秒
    • 高度5米地面滑行高度
  2. 车辆参数:

    • 初始位置:跑道南侧 (AIRPORT_LON, AIRPORT_LAT - 0.002)
    • 运动方向:向北行驶
    • 运动速度0.0002纬度/秒
    • 与航空器轨迹垂直相交
  3. 时间参数:

    • 更新频率:每秒一次
    • 场景周期10秒
    • 碰撞风险时间约5秒场景中点

9.1.3 数据特点

  1. 位置数据:

    • 只提供实时位置信息
    • 不包含速度信息
    • 不包含方向信息
    • 位置精度小数点后6位
  2. 数据格式:

// 航空器数据
{
    "flightNo": "CES2501",
    "longitude": 120.088003,
    "latitude": 36.361999,
    "time": 1700123456,
}

// 车辆数据
{
    "vehicleNo": "VEH001",
    "longitude": 120.088003,
    "latitude": 36.361999,
    "time": 1700123456
}

9.1.4 预期结果

  1. 碰撞检测:

    • 在交叉点附近应触发预警
    • 预警时间碰撞前约5秒
    • 持续时间:直到危险解除
  2. 风险等级:

    • 距离大于100米低风险
    • 距离50-100米中等风险
    • 距离小于50米高风险
  3. 系统响应:

    • 实时计算相对距离
    • 及时发出预警信息
    • 记录完整的碰撞风险过程

10. 风险控制

10.1 技术风险

  • 实时性保证
  • 内存管理
  • 多线程同步
  • 系统稳定性
  • 坐标转换精度

10.2 解决方案

  • 性能优化
  • 资源监控
  • 故障恢复
  • 降级处理
  • 坐标转换验证

11. 后续优化

  1. 引入GPU加速计算
  2. 优化空间索引算法
  3. 改进故障检测机制
  4. 增加自适应负载均衡
  5. 完善监控告警系统
  6. 优化坐标转换算法
  7. 支持多种投影方式

使用纯C++实现可以获得:

  1. 最佳的实时性能
  2. 直接的硬件控制
  3. 精确的内存管理
  4. 最小的系统开销
  5. 可预测的行为表现

10. 单元测试

10.1 测试框架

  • 使用 Google Test 框架
  • 支持 EXPECT 和 ASSERT 断言
  • 支持 Mock 对象
  • 支持测试夹具Test Fixtures

10.2 测试用例组织

10.2.1 基础类型测试 (BasicTypesTest)

  1. Vector2D 测试

    • 测试向量大小计算
    • 测试方向角计算
  2. MovingObject 测试

    • 测试速度计算
    • 测试航向角计算
    • 验证位置历史记录
  3. 数据有效性测试

    • 测试航空器数据
    • 测试车辆数据
    • 验证速度和位置限制

10.2.2 碰撞检测测试 (CollisionDetectorTest)

  1. 安全距离测试

    • 测试水平安全距离
    • 测试垂直安全距离
  2. 边界条件测试

    • 测试临界距离
    • 测试高度边界
  3. 特殊情况测试

    • 测试零距离情况
    • 测试对角线距离

10.2.3 数据采集测试 (DataCollectorTest)

  1. 速度计算测试

    // 测试数据生成
    double lon = 120.08 + (LON_CHANGE_PER_SEC * i);  // 每秒经度变化
    uint64_t timestamp = baseTime + (i * 1);         // 每秒一个点
    
    // 验证速度计算
    EXPECT_NEAR(aircraft.speed, 55.0, 5.0);  // 允许5m/s误差
    
  2. Mock 数据源

    class MockDataSource : public DataSource {
        MOCK_METHOD(bool, connect, (), (override));
        MOCK_METHOD(bool, fetchAircraftData, (std::vector<Aircraft>&), (override));
        MOCK_METHOD(bool, fetchVehicleData, (std::vector<Vehicle>&), (override));
    };
    

10.3 测试数据生成

10.3.1 位置数据

  1. 航空器数据

    • 基于青岛胶东机场坐标
    • 模拟<EFBFBD><EFBFBD>面滑行场景
    • 生成连续位置点
  2. 车辆数据

    • 模拟地面车辆运动
    • 生成可能的碰撞场景

10.3.2 时间序列

  1. 时间戳生成

    • 使用 Unix 时间戳
    • 每秒更新一次
    • 保证时间连性
  2. 数据更新频率

    • 模拟实际系统的更新频率
    • 确保数据连续性
    • 避免时间戳重复

10.4 测试覆盖率

  1. 基础功能测试

    • 坐标转换
    • 速度计算
    • 航向计算
  2. 错误处理测试

    • 无效数据处理
    • 连接错误处理
    • 数据解析错误
  3. 边界条件测试

    • 最大/最小速度
    • 位置跳变限制
    • 时间戳异常

10.5 测试最佳实践

  1. 测试数据准备

    • 使用实际的地理坐标
    • 模拟真实的运动场景
    • 考虑数据精度
  2. Mock 对象使用

    • 模拟外部依赖
    • 控制测试环境
    • 验证交互逻辑
  3. 日志记录

    • 记录测试过程
    • 输出中间结果
    • 便于问题诊断

11. 可控车辆通信

11.1 通信方式

使用 HTTP 协议与可控车辆通信:

  • 碰撞检测系统作为 HTTP 客户端,向可控车辆发送 POST 请求
  • 请求地址格式为: http://<vehicle_ip>:<port>/command
  • 请求 Body 中包含控制指令的 JSON 表示
  • 可控车辆作为 HTTP 服务器,接收并处理控制指令

11.2 指令格式

使用 JSON 格式表示控制指令:

POST /command HTTP/1.1
Host: <vehicle_ip>:<port>
Content-Type: application/json

{
    "command": "stop",
    "timestamp": 1700123456
}

或者:

POST /command HTTP/1.1
Host: <vehicle_ip>:<port>  
Content-Type: application/json

{
    "command": "change_route",
    "route": [
        {"longitude": 120.08, "latitude": 36.36},
        {"longitude": 120.09, "latitude": 36.37},
        {"longitude": 120.10, "latitude": 36.38}  
    ],
    "timestamp": 1700123456
}

11.3 可控车辆响应

可控车辆收到指令后,需要及时响应:

  1. 立即执行停车、减速等指令
  2. 如果是改变路线,则规划新的路径并切换
  3. 将执行结果以 JSON 格式返回:
HTTP/1.1 200 OK
Content-Type: application/json

{
    "status": "ok",
    "message": "Command executed successfully",
    "timestamp": 1700123456
}

或者:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
    "status": "error",
    "message": "Failed to execute command",
    "error": "No route found",
    "timestamp": 1700123456  
}

11.4 安全考虑

由于 HTTP 是明文传输协议,存在安全隐患。需要采取以下措施:

  1. 使用 HTTPS 代替 HTTP,实现加密通信
  2. 对可控车辆的访问进行身份验证,防止非法控制
  3. 对控制指令进行数字签名,防止指令被篡改
  4. 对敏感数据(如路径点坐标)进行加密,防止泄露

11.5 可用性考虑

为保证可控车辆的控制链路高可用,需要:

  1. 监控可控车辆的 HTTP 服务可用性,发现异常及时告警
  2. 对控制指令的发送进行重试和超时处理,避免单次请求失败导致控制中断
  3. 考虑引入备用的控制方式(如 MQTT),作为 HTTP 不可用时的降级方案

11.6 指令发送

根据可控车辆的IP地址和端口号,发送控制指令:

void sendCommand(const ControlCommand& command, const std::string& ip, int port) {
    // 创建HTTP客户端
    HttpClient client(ip, port);
    
    // 构造请求
    HttpRequest request;
    request.setMethod(HttpRequest::POST);
    request.setPath("/command");
    request.setHeader("Content-Type", "application/json");
    request.setBody(command.toJson());
    
    // 发送请求
    HttpResponse response = client.send(request);
    
    // 处理响应
    if (response.getStatus() == HttpResponse::OK) {
        // 指令发送成功
        logger.info("Command sent successfully");
    } else {
        // 指令发送失败
        logger.error("Failed to send command, status code: {}", response.getStatus());
    }
}

使用 HTTP 客户端库,向可控车辆发送 POST 请求。请求路径为 /command,请求体为控制指令的 JSON 表示。根据响应状态码判断指令是否发送成功。