Qt AFSim雷达探测显示

Qt AFSim雷达探测显示

目录

  1. 概述
  2. 核心类介绍
  3. 数据结构
  4. 实现原理
  5. 关键方法解析
  6. 使用示例
  7. 注意事项

概述

雷达探测范围显示功能用于在战术地图上可视化显示雷达传感器的探测覆盖区域。该功能基于Qt Graphics Framework实现,通过绘制扇形区域来表示雷达波束的探测范围。


主要特性

  • 支持多传感器、多波束的探测范围显示
  • 根据平台航向自动调整波束方向
  • 支持比例尺缩放,自动适应地图缩放级别
  • 半透明显示,不遮挡其他地图元素
  • 可通过右键菜单控制显示/隐藏

核心类介绍

GraphicsSensorCoverageItem

GraphicsSensorCoverageItem 是雷达探测范围显示的核心类,继承自 QGraphicsObject

类定义位置:

1:56:radarmap/source/graphicsitems/graphicssensorcoverageitem.h 复制代码
#pragma once

#include <QGraphicsObject>
#include <QPainterPath>
#include <QColor>
#include <QRectF>
#include <QtGlobal>

#include "situation_types.h"

/**
 * @brief 雷达传感器探测范围显示 Item
 *
 * 规则:
 * - 使用 EntityData::sensors 中当前模式(current_mode_index)下
 *   的 beams 列表信息。
 * - 每个 beam 使用 max_range 与 receiver.azimuth_fov_[min,max]
 *   绘制扇形区域。
 * - 同一模式及多个传感器下的所有 beam 扇形做"叠加"显示,
 *   不做几何并集运算,便于看出多波束重叠区域。
 */
class GraphicsSensorCoverageItem : public QGraphicsObject
{
    Q_OBJECT
public:
    explicit GraphicsSensorCoverageItem(QGraphicsItem *parent = nullptr);
    ~GraphicsSensorCoverageItem() override;

    /// 设置比例尺(像素/千米)
    void setScaleFactor(qreal pixelsPerKm);

    /// 根据目标传感器数据更新探测范围
    void updateSensorCoverage(const diting::EntityData &target);

    /// 设置显示颜色
    void setColor(const QColor &color);

public:
    QRectF boundingRect() const override;
    QPainterPath shape() const override;

protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;

private:
    /// 基于单个 beam 构造扇形路径
    QPainterPath createBeamSectorPath(double maxRangeKm,
                                      double azimuthMinDeg,
                                      double azimuthMaxDeg) const;

private:
    QPainterPath m_coveragePath;   ///< 全部 beam 合并后的覆盖区域
    qreal        m_pixelsPerKm;    ///< 像素/千米
    QColor       m_color;          ///< 填充/边框颜色
};

主要成员变量:

  • m_coveragePath: 存储所有波束合并后的覆盖区域路径
  • m_pixelsPerKm: 比例尺因子,用于将千米转换为像素
  • m_color: 显示颜色(包含透明度)

数据结构

EntityData

实体数据,包含平台的所有信息,包括传感器数据。

301:339:situation/include/situation_types.h 复制代码
// 实体数据(与IDL同步)
struct EntityData {
    QString name;               // 实体名称
    QString type;               // 实体类型(与IDL一致使用字符串)
    QString side;               // 所属方(红方/蓝方/中立)
    Position pos;               // 位置信息
    Orientation orient;         // 姿态信息
    Velocity vel;               // 速度信息
    EntityStatus status = EntityStatus::ACTIVE; // ACTIVE/BROKEN/DELETED
    QVector<KeyValue> attributes; // 扩展属性

    // 状态(动态)
    QVector<Sensor> sensors;        // 传感器实例
    QVector<Weapon> weapons;        // 武器实例列表
    QVector<LocalTrack> tracks;     // 本地航迹列表
};

Sensor

传感器信息,包含多个工作模式。

161:167:situation/include/situation_types.h 复制代码
// 传感器信息
struct Sensor {
    QString name;                       // 传感器实例名称
    QString type;                       // 传感器类型
    bool isTurnedOn = false;            // 传感器是否开机
    qint64 current_mode_index = -1;    // 当前激活的模式索引(-1表示无模式激活,0表示第一个模式)
    QVector<SensorMode> modes;          // 模式列表(所有模式的能力信息)
};

SensorMode

传感器工作模式,包含多个波束配置。

146:158:situation/include/situation_types.h 复制代码
// 模式能力信息
struct SensorMode {
    QString name;                       // 模式名称
    double frame_time = 0.0;            // 帧时间(秒)
    double revisit_time = 0.0;          // 重访时间(秒)
    double dwell_time = 0.0;            // 驻留时间(秒)
    qint64 max_request_count = 0;       // 最大并发请求数(跟踪/搜索请求)
    bool can_search_while_track = false; // 是否可以在跟踪时继续搜索
    bool disables_search = false;       // 是否禁用搜索
    double required_pd = 0.0;           // 所需检测概率(0.0~1.0)
    double establish_track_probability = 0.0;  // 建立航迹概率
    double maintain_track_probability = 0.0;   // 维持航迹概率
    QVector<Beam> beams;                // 波束列表
};

Beam

波束信息,包含探测范围和视场角(FOV)。

133:143:situation/include/situation_types.h 复制代码
// 波束信息
struct Beam {
    qint64 beam_index = 0;             // 波束索引
    bool can_transmit = false;         // 是否可以发射
    bool can_receive = false;          // 是否可以接收
    Antenna transmitter;                // 发射器天线
    Antenna receiver;                   // 接收器天线
    bool transmitter_valid = false;     // 发射器天线信息是否有效
    bool receiver_valid = false;        // 接收器天线信息是否有效
    double max_range = 0.0;            // 该波束的最大探测范围(米,1平方米目标)
    double min_range = 0.0;            // 该波束的最小探测范围(米)
};

Antenna

天线信息,定义视场角范围。

125:130:situation/include/situation_types.h 复制代码
// 天线信息
struct Antenna {
    double azimuth_fov_min = 0.0;      // 方位FOV最小值(度)
    double azimuth_fov_max = 0.0;      // 方位FOV最大值(度)
    double elevation_fov_min = 0.0;   // 俯仰FOV最小值(度)
    double elevation_fov_max = 0.0;   // 俯仰FOV最大值(度)
};

实现原理

1. 初始化

在构造函数中,设置默认参数和图形项属性:

8:22:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
GraphicsSensorCoverageItem::GraphicsSensorCoverageItem(QGraphicsItem *parent)
    : QGraphicsObject(parent)
    , m_pixelsPerKm(0.0)
    , m_color(0, 255, 0, 120) // 默认半透明绿色
{
    m_coveragePath.setFillRule(Qt::WindingFill);
    setZValue(-2);                // 位于目标图标及轨迹线下方
    setVisible(false);            // 默认不显示雷达探测范围,由右键菜单控制

    // 仅作为视觉效果,不参与任何鼠标事件命中
    setAcceptedMouseButtons(Qt::NoButton);
    setAcceptHoverEvents(false);
    setFlag(QGraphicsItem::ItemIsSelectable, false);
    setFlag(QGraphicsItem::ItemIsFocusable, false);
}

关键点:

  • setZValue(-2): 确保探测范围显示在目标图标和轨迹线下方
  • setVisible(false): 默认隐藏,需要通过外部控制显示
  • 禁用所有鼠标事件,避免遮挡其他元素的交互

2. 更新探测范围

updateSensorCoverage 方法遍历所有传感器和波束,生成覆盖路径:

42:111:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
void GraphicsSensorCoverageItem::updateSensorCoverage(const diting::EntityData &target)
{
    prepareGeometryChange();
    m_coveragePath = QPainterPath();
    m_coveragePath.setFillRule(Qt::WindingFill);

    if (m_pixelsPerKm <= 0.0) {
        // 比例尺无效时不绘制任何内容,但不主动修改可见性,交由外部控制
        update();
        return;
    }

    // 平台航向(罗经:0°北、顺时针),用于将波束 FOV 从平台坐标转换到场景坐标
    const double headingDeg = target.orient.heading; // 0~360,0=北,90=东

    // 遍历所有传感器
    for (const diting::Sensor &sensor : target.sensors) {
        if (!sensor.isTurnedOn) {
            continue;
        }

        // 当前激活模式
        int modeIndex = static_cast<int>(sensor.current_mode_index);
        if (modeIndex < 0 || modeIndex >= sensor.modes.size()) {
            // 若未指定当前模式,可按需要扩展为遍历所有模式
            continue;
        }

        const diting::SensorMode &mode = sensor.modes.at(modeIndex);

        // 同一模式下多个 beam 的扇形区域做叠加
        for (const diting::Beam &beam : mode.beams) {
            if (!beam.can_receive || !beam.receiver_valid) {
                continue;
            }
            if (beam.max_range <= 0.0) {
                continue;
            }

            const double maxRangeKm = beam.max_range * 0.001; // 米 -> 千米

            // FOV 角度(度),以平台坐标系为参考,假定 azimuth_fov_* 为相对平台正前方的罗经角偏移(顺时针为正)
            double azMinCompass = beam.receiver.azimuth_fov_min + headingDeg; // 0°北、顺时针
            double azMaxCompass = beam.receiver.azimuth_fov_max + headingDeg;
            if (qFuzzyCompare(azMinCompass, azMaxCompass)) {
                continue;
            }

            // 将罗经角(0°北、顺时针)转换为 Qt 角度(0°在+X轴向右,逆时针为正)。
            auto compassToQt = [](double compassDeg) -> double {
                // 0°北 -> 90°(向上),90°东 -> 0°(向右)
                double qt = 90.0 - compassDeg;
                return qt;
            };

            double azMinQt = compassToQt(azMinCompass);
            double azMaxQt = compassToQt(azMaxCompass);

            QPainterPath sectorPath = createBeamSectorPath(maxRangeKm, azMinQt, azMaxQt);
            if (!sectorPath.isEmpty()) {
                // 使用 addPath 做简单几何叠加,而不是 united 的布尔并集
                m_coveragePath.addPath(sectorPath);
            }
        }
    }

    // 不在此处修改可见性,交由外部(例如右键菜单)控制;
    // 当路径为空时,即使可见也不会绘制任何内容。
    update();
}

处理流程:

  1. 检查比例尺有效性
  2. 获取平台航向角
  3. 遍历所有传感器,只处理已开机的传感器
  4. 获取当前激活模式
  5. 遍历模式下的所有波束,只处理有效的接收波束
  6. 将波束的方位角从平台坐标系转换为场景坐标系
  7. 创建扇形路径并叠加到总路径中

3. 坐标转换

罗经角到Qt角度的转换:

  • 罗经角系统:0°指向北,顺时针为正(0°=北,90°=东,180°=南,270°=西)
  • Qt角度系统:0°指向+X轴(右),逆时针为正(0°=右,90°=上,180°=左,270°=下)

转换公式:Qt角度 = 90° - 罗经角

90:95:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
            // 将罗经角(0°北、顺时针)转换为 Qt 角度(0°在+X轴向右,逆时针为正)。
            auto compassToQt = [](double compassDeg) -> double {
                // 0°北 -> 90°(向上),90°东 -> 0°(向右)
                double qt = 90.0 - compassDeg;
                return qt;
            };

4. 扇形路径创建

createBeamSectorPath 方法根据最大探测范围和方位角范围创建扇形路径:

158:192:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
QPainterPath GraphicsSensorCoverageItem::createBeamSectorPath(double maxRangeKm,
                                                              double azimuthMinDeg,
                                                              double azimuthMaxDeg) const
{
    QPainterPath path;

    if (maxRangeKm <= 0.0 || m_pixelsPerKm <= 0.0) {
        return path;
    }

    const qreal radius = static_cast<qreal>(maxRangeKm) * m_pixelsPerKm;
    if (radius <= 0.0) {
        return path;
    }

    // 角度可能出现 azMin > azMax(跨越 0 度)的情况,这里简单保证 span 为 [-360, 360]
    double startDeg = azimuthMinDeg;
    double endDeg   = azimuthMaxDeg;
    double spanDeg  = endDeg - startDeg;

    QRectF rect(-radius, -radius, radius * 2.0, radius * 2.0);

    // 如果是完整圆(接近 360°),直接画圆,不从中心连一条半径,避免中间那根"接缝线"
    if (qFabs(qAbs(spanDeg) - 360.0) < 0.1 || qFuzzyIsNull(spanDeg)) {
        path.addEllipse(rect);
        return path;
    }

    // 普通扇形:从中心到弧边再闭合
    path.moveTo(0.0, 0.0);
    path.arcTo(rect, startDeg, spanDeg);
    path.closeSubpath();

    return path;
}

关键逻辑:

  • 计算扇形半径:radius = maxRangeKm × pixelsPerKm
  • 处理完整圆形(360°):直接绘制椭圆,避免接缝线
  • 普通扇形:从中心点开始,绘制圆弧,然后闭合路径

5. 绘制

paint 方法负责实际的图形绘制:

130:156:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
void GraphicsSensorCoverageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    if (m_coveragePath.isEmpty()) {
        return;
    }

    painter->save();
    painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, true);

    QColor fillColor = m_color;
    // 填充稍微更透明一些
    QColor strokeColor = m_color;
    strokeColor.setAlpha(qMin(200, m_color.alpha()));
    fillColor.setAlpha(qMax(40, m_color.alpha() / 3));

    QPen pen(strokeColor, 1.5);
    pen.setStyle(Qt::DashLine);
    painter->setPen(pen);
    painter->setBrush(fillColor);

    painter->drawPath(m_coveragePath);

    painter->restore();
}

绘制特点:

  • 启用抗锯齿,保证平滑显示
  • 填充色比边框色更透明
  • 使用虚线边框
  • 填充和边框使用相同的颜色(不同透明度)

关键方法解析

setScaleFactor

设置比例尺因子,用于将实际距离(千米)转换为像素距离。

28:34:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
void GraphicsSensorCoverageItem::setScaleFactor(qreal pixelsPerKm)
{
    if (qFuzzyCompare(m_pixelsPerKm, pixelsPerKm)) {
        return;
    }
    m_pixelsPerKm = pixelsPerKm;
}

使用场景: 当地图缩放级别改变时,需要更新比例尺以保持探测范围的正确显示。

setColor

设置显示颜色,自动调整透明度。

36:40:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
void GraphicsSensorCoverageItem::setColor(const QColor &color)
{
    m_color = color;
    m_color.setAlpha(50);
}

注意: 颜色透明度会被设置为50,确保半透明显示。

boundingRect

返回覆盖路径的边界矩形,用于Qt Graphics Framework的裁剪和更新区域计算。

113:116:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
QRectF GraphicsSensorCoverageItem::boundingRect() const
{
    return m_coveragePath.boundingRect();
}

shape

返回用于命中测试的形状。由于探测范围不参与交互,返回空路径。

118:122:radarmap/source/graphicsitems/graphicssensorcoverageitem.cpp 复制代码
QPainterPath GraphicsSensorCoverageItem::shape() const
{
    // 不参与命中测试,避免遮挡内部目标的鼠标事件
    return QPainterPath();
}

使用示例

基本使用

GraphicsGroupManager 中的使用示例:

124:131:radarmap/source/graphicsitems/graphicsgroupmanager.cpp 复制代码
    // 更新雷达探测范围显示(基于传感器 beams 信息)
    if (m_sensorCoverageItem) {
        m_sensorCoverageItem->setColor(m_targetItem->itemColor());
        // 使用当前比例尺(像素/千米)
        qreal pixelsPerKm = AppInstance::getInstance()->getPixelsPerKm();
        m_sensorCoverageItem->setScaleFactor(pixelsPerKm);
        m_sensorCoverageItem->updateSensorCoverage(m_tTargetInfo);
    }

使用步骤:

  1. 创建 GraphicsSensorCoverageItem 实例
  2. 设置颜色(通常与目标颜色一致)
  3. 设置比例尺因子
  4. 调用 updateSensorCoverage 更新探测范围
  5. 通过 setVisible(true) 显示探测范围

完整示例代码

cpp 复制代码
// 创建探测范围显示项
GraphicsSensorCoverageItem *coverageItem = new GraphicsSensorCoverageItem(parentItem);

// 设置颜色(例如:半透明绿色)
coverageItem->setColor(QColor(0, 255, 0, 120));

// 设置比例尺(假设1千米=100像素)
coverageItem->setScaleFactor(100.0);

// 更新探测范围
coverageItem->updateSensorCoverage(entityData);

// 显示探测范围
coverageItem->setVisible(true);

注意事项

1. 坐标系转换

  • 平台坐标系:波束的方位角是相对于平台正前方的
  • 场景坐标系:需要加上平台航向角,转换为场景绝对角度
  • Qt坐标系:需要从罗经角转换为Qt角度系统

2. 角度处理

  • 方位角范围可能跨越0°/360°边界,需要特殊处理
  • 完整圆形(360°)使用椭圆绘制,避免接缝线
  • 角度比较使用 qFuzzyCompareqFuzzyIsNull 处理浮点误差

3. 性能优化

  • 比例尺未变化时,setScaleFactor 会提前返回,避免不必要的更新
  • 使用 prepareGeometryChange() 通知Qt框架几何变化
  • 禁用鼠标事件,减少事件处理开销

4. 显示控制

  • 默认不显示,需要通过外部(如右键菜单)控制可见性
  • 即使路径为空,也不会主动修改可见性,交由外部控制
  • 使用 setZValue(-2) 确保显示在目标图标下方

5. 多波束叠加

  • 多个波束的扇形区域使用 addPath 叠加,而不是几何并集
  • 这样可以显示重叠区域,便于分析多波束覆盖情况

6. 数据有效性检查

  • 检查传感器是否开机(isTurnedOn
  • 检查波束是否可以接收(can_receive
  • 检查接收器信息是否有效(receiver_valid
  • 检查最大探测范围是否大于0

7. 比例尺依赖

  • 比例尺无效(pixelsPerKm <= 0)时,不绘制任何内容
  • 需要在地图缩放时及时更新比例尺

总结

雷达探测范围显示功能通过以下步骤实现:

  1. 数据获取 :从 EntityData 中提取传感器和波束信息
  2. 坐标转换:将平台坐标系的角度转换为场景坐标系
  3. 路径生成:为每个有效波束创建扇形路径
  4. 路径叠加:将所有波束路径叠加到总路径中
  5. 图形绘制:使用半透明颜色绘制覆盖区域

该实现具有良好的可扩展性和性能,支持多传感器、多波束的复杂场景显示。

Group

1062801117

相关推荐
初阳78512 小时前
【Qt】系统相关(2)——文件
开发语言·qt
尘中远1 天前
【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
qt·数据可视化·qcustomplot·qwt·工业软件·科学绘图
sycmancia1 天前
Qt——多线程间的互斥
开发语言·qt
尘中远1 天前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图
尘中远1 天前
【Qwt 7.0 系列】交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
qt·数据可视化·绘图·qcustomplot·qwt·科学绘图
sycmancia1 天前
Qt——进程与线程的概念
qt
郝学胜-神的一滴1 天前
Qt 高级编程 034:深耕QWidget底层内核—彻底吃透无边框窗口设计核心原理
开发语言·c++·qt·程序人生·软件开发·用户界面
尘中远1 天前
【Qwt 7.0 系列】3D 数据可视化 —— OpenGL 高性能三维绘图
qt·3d·qcustomplot·qwt·科学绘图·高性能绘图
满天星83035771 天前
【Qt】控件(二) (geometry及与frameGeometry的区别)
开发语言·qt
大气的小蜜蜂1 天前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·qt·sqlite