雷达具有广泛的应用领域,可以在军事、气象、航空、海洋、交通和环境等方面发挥重要作用。雷达扫描图,在影视作品中见到较多,比如飞机雷达、舰艇雷达,有一个扫描线转圈代表雷达一周旋转或一个批次的收发,发现目标就在表盘上标记位置。和仪表盘类似,仪表盘有底盘背景图、同圆、刻度、刻度值、旋转的指针。在仪表盘的基础上略作修改,比如指针换成带有余辉的扫描扇面,就能完成一个雷达扫描图。
一、简述
本示例使用QT实现的雷达模拟仿真自定义控件是一个用于模拟雷达工作原理的图形界面控件。它可以显示雷达的工作区域,并在该区域内模拟目标的位置和移动。用户可以通过控件的接口来设置雷达的参数,如扫描角度、扫描速度等。
二、 设计思路
使用QT框架实现一个雷达模拟仿真的自定义控件的设计思路:
-
创建一个自定义控件类Radar,继承自QWidget。
-
在Radar类中,定义需要的属性和方法,比如雷达的半径、角度的范围、雷达目标的位置等。
-
在Radar类中重写绘图事件函数paintEvent(QPaintEvent *event),用于绘制雷达的外观和雷达目标的位置。
-
在paintEvent函数中,使用QPainter类来进行绘图操作。可以使用一些基本的绘图函数如drawEllipse、drawLine等来绘制雷达的外圈和刻度。
-
在雷达的中心点位置上绘制一个表示雷达目标的小圆点或者其他形状。
-
在Radar类中提供一个公有的接口或者信号槽,用于更新雷达目标的位置信息。
-
在Radar类中使用定时器来定时更新雷达目标的位置信息,并触发更新绘图。
三、效果
四、核心代码
1、头文件
#ifndef RADAR_H
#define RADAR_H
#include <QWidget>
#include <QPainter>
#include <QTime>
#include <QTimer>
#include <QPoint>
#include <QtMath>
#include <QMouseEvent>
#include <QLabel>
#include <QToolTip>
#include <cmath>
#include <QWheelEvent>
#include <string>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <QColor>
//扫描方式
enum ScanModeEnum{
// 圆周扫描
CircleScan = 0,
// 扇形扫描
SectorScan
};
//扫描方向
enum ScanDirectionEnum{
ClockWize = 0, //顺时针扫描
AntiClockWize //逆时针扫描
};
//目标信息
struct TargetInfo
{
std::string sTargetId; //批号
std::string sTOA ; //到达时间
double dAzimuth ; //方位
double dPitch ; //俯仰
double dDistance; //距离
double dFrequency ; //射频
double dPulseWidth ; //脉宽
double dPulseAmplitude ; //脉幅(0.5-4。5)
double dPRI ; //脉冲重复周期
double dRCS ; //目标反射面积
int nAttr; //目标属性,0:我方;1:敌方;2:中立;3:不明
};
class Radar : public QWidget
{
Q_OBJECT
public:
Radar(QWidget *parent = nullptr);
~Radar();
//----------------------------------------------------
// 函数名称:Start
// 作用:开启雷达扫描定时器
// 参数:参数一:int类型,表示定时器间隔,默认为500ms
//-----------------------------------------------------
void Start(int nScanPeriod = 500);
//----------------------------------------------------
// 函数名称:Stop
// 作用:关闭雷达扫描定时器
// 参数:无参数
//-----------------------------------------------------
void Stop();
//----------------------------------------------------
// 函数名称:Init
// 作用:初始化雷达扫描基本数据
// 参数:参数一:int类型,表示最大探测距离,即雷达扫描图半径,默认300
// 参数二:int类型,表示等分数,即雷达扫描图圈数,默认5圈
// 参数三:int类型,扫描周期,默认500毫秒更新一次
// 参数四:int类型,扫描波束宽度,默认为10度
// 参数五:bool类型,是否显示轨迹,true表示显示轨迹
//-----------------------------------------------------
void Init(int nMaxDetectionDistance = 300,int nEqualParts = 5,int nScanPeriod = 500,
int nBeamWidth = 10,bool bShowTrack = false);
//----------------------------------------------------
// 函数名称:SetShowTrack
// 作用:设置是否显示航迹(一个目标的各个坐标点之间的连线成为航迹)
// 参数:参数一:bool类型,是否显示轨迹,true表示显示轨迹
//-----------------------------------------------------
void SetShowTrack(bool bShowTrack = false);
//----------------------------------------------------
// 函数名称:SetScanPeriod
// 作用:设置扫描周期,停止-设置扫描周期-开始
// 参数:参数一:int类型,默认为500ms
//-----------------------------------------------------
void SetScanPeriod(int nScanPeriod = 500);
//----------------------------------------------------
// 函数名称:SetScanParams
// 作用:设置扫描参数 停止-设置扫描参数-开始
// 参数:参数一:ScanDirectionEnum类型,扫描方式
// 参数二:ScanModeEnum类型,扫描方向
// 参数三:int类型,扫描波束宽度,默认为10度
// 参数四:int类型,起始角,默认从0度开始
// 参数五:int类型,结束角,默认360度结束
//-----------------------------------------------------
void SetScanParams(ScanDirectionEnum scanDirection = ClockWize,ScanModeEnum scanMode = CircleScan,
int nBeamWidth = 10,int nStartAngle = 0,int nEndAngle = 360);
//----------------------------------------------------
// 函数名称:SetData
// 作用:设置单点坐标数据
// 参数:参数一:TargetInfo类型
//-----------------------------------------------------
void SetData(TargetInfo ti);
//----------------------------------------------------
// 作用:设置多点坐标数据
// 参数:参数一:std::vector<TargetInfo>类型
//-----------------------------------------------------
void SetData(std::vector<TargetInfo>& ti);
//----------------------------------------------------
// 作用:设置航迹线颜色
// 参数:颜色的rgb数值
//-----------------------------------------------------
void SetColor(int r, int g, int b);
public slots:
private slots:
void timerTimeOut();
void mousePressEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event) override;
private:
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event);
//----------------------------------------------------
// 函数名称:drawAzimuth
// 作用:绘制雷达扫描图方位
// 参数:参数一:QPainter对象,类型:QPainter
// 参数二、三:在指定坐标(x, y)上绘制方位文本,类型:int
// 参数四:绘制的方位数据,类型:int
//-----------------------------------------------------
void drawAzimuth(QPainter *painter, int x, int y, int azimuth);
//----------------------------------------------------
// 函数名称:drawRange
// 作用:绘制雷达扫描图量程
// 参数:参数一:QPainter对象,类型:QPainter
// 参数二、三:在指定坐标(x, y)上绘制方位文本,类型:int
// 参数四:绘制的量程数据,类型:int
//-----------------------------------------------------
void drawRange(QPainter *painter, int x, int y, int range, int range1);
//----------------------------------------------------
// 函数名称:drawPoints
// 作用:绘制目标
// 参数:参数一:QPainter对象,类型:QPainter
// 参数二:目标的坐标
//-----------------------------------------------------
void drawPoints(QPainter* painter, QVector<QPoint> &Record);
//----------------------------------------------------
// 函数名称:drawTarget
// 作用:绘制目标
// 参数:参数一:QPainter对象,类型:QPainter
// 参数二:目标的信息
//-----------------------------------------------------
void drawTarget(QPainter* painter, TargetInfo ti);
private:
// 圆心坐标
int CoorX;
int CoorY;
// 实际半径
int pRadius;
// 圈数
int pNumTurns;
// 扫描周期
int pScanPeriod;
// 扫描速率
int ScanRate;
// 扫描波束宽度
int pBeamWidth;
// 显示轨迹
bool pShowTrack;
// 缩放因子
int ScaleFactor;
// 最小边长
int minSide;
// 窗口的半径
int MeasureRadius;
// 单点坐标数据
TargetInfo pTargetInfo;
// 多点坐标数据
std::vector<TargetInfo> pTargetInfos;
// 定时器对象
QTimer *timer;
// 扫描线的初始角度
int pStartAngle;
// 扫描线的结束角度
int pEndAngle;
// 绘制目标的直径大小
double cir_size;
// 记录目标的轨迹点
std::unordered_map<std::string, QVector<QPoint>>Record;
// 扫描方式
ScanModeEnum pScanMode;
// 扫描方向
ScanDirectionEnum pScanDirection;
int _start;
int _end;
bool flag;
// 航迹线颜色r、g、b
int r;
int g;
int b;
};
#endif // RADAR_H
2、实现代码
#include "Radar.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QRectF>
Radar::Radar(QWidget *parent)
: QWidget(parent)
{
timer = new QTimer();
// 目标点默认半径为5
cir_size = 5;
flag = false;
// 监听窗口大小变化的事件
installEventFilter(this);
// 默认扫描波束宽度为圆周,即360度
pBeamWidth = 360;
ScaleFactor = 1;
ScanRate = 1;
// 默认目标点颜色为蓝色
r = 0;
g = 0;
b = 255;
//强制设置背景颜色为黑色
setPalette(Qt::black); setAutoFillBackground(true);
//用槽函数控制雷达扫描效果。
connect(timer,&QTimer::timeout,this,&Radar::timerTimeOut);
Init();
SetShowTrack(true);
SetScanParams();
}
Radar::~Radar()
{
}
void Radar::Start(int nScanPeriod)
{
timer->start(nScanPeriod);
}
void Radar::Stop()
{
flag = false;
timer->stop();
}
void Radar::Init(int nMaxDetectionDistance, int nEqualParts, int nScanPeriod, int nBeamWidth, bool bShowTrack)
{
this->pRadius = nMaxDetectionDistance;
this->pNumTurns = nEqualParts;
this->pScanPeriod = nScanPeriod;
this->pBeamWidth = nBeamWidth;
this->pShowTrack = bShowTrack;
}
void Radar::SetShowTrack(bool bShowTrack)
{
this->pShowTrack = bShowTrack;
}
void Radar::SetScanPeriod(int nScanPeriod)
{
timer->stop();
this->pScanPeriod = nScanPeriod;
timer->start(nScanPeriod);
}
void Radar::SetScanParams(ScanDirectionEnum scanDirection, ScanModeEnum scanMode,
int nBeamWidth, int nStartAngle, int nEndAngle)
{
timer->stop();
this->pScanDirection = scanDirection;
this->pScanMode = scanMode;
this->pBeamWidth = nBeamWidth;
this->pStartAngle = nStartAngle;
this->pEndAngle = nEndAngle;
_start = pStartAngle;
_end = pEndAngle - pBeamWidth;
timer->start(pScanPeriod);
}
void Radar::SetData(TargetInfo ti)
{
this->pTargetInfo = ti;
}
void Radar::SetData(std::vector<TargetInfo> &ti)
{
this->pTargetInfos = ti;
}
void Radar::SetColor(int r, int g, int b)
{
this->r = r;
this->g = g;
this->b = b;
}
void Radar::timerTimeOut()
{
if(pScanMode == CircleScan)
{
if(pScanDirection == ClockWize)
{
if(!flag)
{
pStartAngle = 90;
flag = true;
}
pBeamWidth = 360;
//每次旋转ScanRate度
pStartAngle -= ScanRate;
if(pStartAngle <= -360)
{
pStartAngle = 0;
}
}
else
{
if(!flag)
{
pStartAngle = 90;
flag = true;
}
pBeamWidth = 360;
//每次旋转ScanRate度
pStartAngle += ScanRate;
if(pStartAngle >= 360)
{
pStartAngle = 0;
}
}
}
else if(pScanMode == SectorScan)
{
//每次旋转ScanRate度
pStartAngle -= ScanRate;
if(pStartAngle < _start || pStartAngle > _end)
{
ScanRate *= -1;
}
}
update();
}
void Radar::paintEvent(QPaintEvent *event)//此函数自动执行,实现扫描动画效果
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(qRgba(128, 255, 0,250)));
// 圆心
CoorX = this->width() / 2;
CoorY = this->height() / 2;
// 获取窗口的宽度和高度
int width = this->width();
int height = this->height();
// 计算最小边长
minSide = qMin(width, height) - 50;
// 求量程
int range = pRadius / pNumTurns;
MeasureRadius = minSide / 2;
int range1 = MeasureRadius / pNumTurns;
// 从外到内画圆
QPoint center(CoorX,CoorY);
for(int i = 0;i < pNumTurns;i++)
{
painter.drawEllipse(center, ScaleFactor * (MeasureRadius - range1 * i),
ScaleFactor * (MeasureRadius - range1 * i));
}
// 画量程
drawRange(&painter, CoorX, CoorY, range, range1);
/** 画外圈刻度值,将坐标系移动到圆心 **/
painter.translate(CoorX, CoorY);
painter.setPen(QPen(qRgba(255, 255, 0,250)));
painter.setFont(QFont("Calibri",10));
for(int i=1;i<=360;i++){
painter.rotate(1);
painter.drawLine(0, ScaleFactor * (MeasureRadius * -1), 0, ScaleFactor * (MeasureRadius * -1 + 5));
}
for(int i=1;i<=72;i++){
painter.rotate(5);
painter.drawLine(0, ScaleFactor * (MeasureRadius * -1), 0, ScaleFactor * (MeasureRadius * -1 + 8));
}
for(int i=1;i<=36;i++){
painter.setRenderHint(QPainter::Antialiasing);
painter.rotate(10);
painter.drawLine(0, ScaleFactor * (MeasureRadius * -1),0, ScaleFactor * (MeasureRadius * -1 + 12));
}
for(int i = 1;i <= 4;i++){
// 外圈刻度值
painter.drawLine(0, 0, 0, ScaleFactor * (MeasureRadius * -1));
// 绘制方位
drawAzimuth(&painter, -10, ScaleFactor * (MeasureRadius * -1 - 10), (i - 1) * 90);
// 顺时针旋转90度
painter.rotate(90);
}
painter.translate(-CoorX,-CoorY);//恢复坐标系
painter.setPen(QPen(Qt::NoPen));//去掉外框线
// 圆周扫描
if(pScanMode == CircleScan)
{
/** 绘制扫描效果 **/
if(pScanDirection == ClockWize)
{
QConicalGradient gradient(CoorX, CoorY, pStartAngle);
gradient.setColorAt(0.1, QColor(128, 255, 0, 150));
gradient.setColorAt(0.9, QColor(0, 255, 0, 0));//尾部
painter.setBrush(gradient);
}
else if(pScanDirection == AntiClockWize)
{
QConicalGradient gradient(CoorX, CoorY, pStartAngle);
gradient.setColorAt(0.9, QColor(128, 255, 0, 150));
gradient.setColorAt(0.1, QColor(0, 255, 0, 0));//尾部
painter.setBrush(gradient);
}
painter.drawPie(CoorX-ScaleFactor * MeasureRadius, CoorY-ScaleFactor * MeasureRadius,
ScaleFactor * MeasureRadius * 2, ScaleFactor * MeasureRadius * 2,
-pStartAngle * 16, pBeamWidth * 16);
}// 扇形扫描
else if(pScanMode == SectorScan)
{
painter.setBrush(QBrush(QColor(128, 255, 0, 150)));
painter.save();
QTransform transform;
transform.translate(CoorX, CoorY); // 平移原点位置
painter.setTransform(transform); // 应用变换矩阵
painter.rotate(-90);
painter.drawPie(-ScaleFactor * MeasureRadius, -ScaleFactor * MeasureRadius,
ScaleFactor * MeasureRadius * 2, ScaleFactor * MeasureRadius * 2,
-pStartAngle * 16, -pBeamWidth * 16);
painter.restore();
}
//模拟画出可疑点
painter.translate(CoorX, CoorY);
painter.setBrush(QBrush(QColor(50, 255, 200, 200)));
if(pTargetInfo.sTargetId.size() != 0)
{
drawTarget(&painter, pTargetInfo);
}
// 绘制多点坐标数据
for(auto target : pTargetInfos)
{
drawTarget(&painter, target);
}
}
void Radar::resizeEvent(QResizeEvent *event)
{
// 窗口大小改变时重新绘制
Q_UNUSED(event);
update();
}
void Radar::wheelEvent(QWheelEvent *event)
{
int angleDelta = event->angleDelta().y();
qreal scaleFactorDelta = 1;
if (angleDelta > 0) {
// 滚轮向前滚动,放大图形
ScaleFactor += scaleFactorDelta;
} else {
// 滚轮向后滚动,缩小图形
ScaleFactor -= scaleFactorDelta;
}
// 限制放大缩小比例的范围
if (ScaleFactor < 1)
{
ScaleFactor = 1;
}
else if (ScaleFactor > 5)
{
ScaleFactor = 5;
}
// 更新小部件进行重绘
update();
}
void Radar::drawAzimuth(QPainter *painter, int x, int y, int azimuth)
{
if(azimuth == 180)
{
painter->save();
painter->rotate(180);
QFont font = QFont("Arial",16);
QString str = QString::number(azimuth);
painter->setFont(font);
painter->drawText(x - 10, (y - 10) * -1, str);
painter->restore();
}
else
{
QFont font = QFont("Arial",16);
QString str = QString::number(azimuth);
painter->setFont(font);
painter->drawText(x, y, str);
}
}
// 绘制量程
void Radar::drawRange(QPainter* painter, int x, int y, int range, int range1)
{
for(int i = 1;i <= pNumTurns;i++)
{
painter->drawText(x, y + ScaleFactor * range1 * i - 3, QString::number(range * i));
painter->drawText(x, y - ScaleFactor * range1 * i + 13, QString::number(range * i));
painter->drawText(x + ScaleFactor * range1 * i - 20,y, QString::number(range * i));
painter->drawText(x - ScaleFactor * range1 * i, y, QString::number(range * i));
}
}
void Radar::drawPoints(QPainter* painter, QVector<QPoint> &Record)
{
// 获取窗口的宽度和高度
int width = this->width();
int height = this->height();
// 计算最小边长
minSide = qMin(width, height) - 50;
MeasureRadius = minSide / 2;
QVector<QPoint>p;
for(auto point : Record)
{
int x = point.x() * MeasureRadius / pRadius * ScaleFactor;
int y = point.y() * MeasureRadius / pRadius * ScaleFactor;
QPoint z(x, y);
p.push_back(z);
}
painter->drawPoints(p.data(), p.size());
}
void Radar::drawTarget(QPainter* painter, TargetInfo ti)
{
if(ti.dDistance > pRadius)
{
return;
}
QColor color(r, g, b);
painter->setPen(QPen(color));
double angle = qDegreesToRadians(-ti.dAzimuth + 90); // 将逆时针转换为顺时针,并调整方向,使y轴正方向为0度,x轴正方向为90度
double x1 = CoorX + ti.dDistance * cos(angle);
double y1 = CoorY - ti.dDistance * sin(angle);
int x2 = x1 - CoorX;
int y2 = -1 * (y1 - CoorY);
int x = x2 * MeasureRadius / pRadius;
int y = y2 * MeasureRadius / pRadius;
QPoint p(x2, -1 * y2);
if(Record.find(ti.sTargetId) != Record.end())
{
Record[ti.sTargetId].append(p);
}
else
{
QVector<QPoint> temp;
temp.append(p);
Record[ti.sTargetId] = temp;
}
painter->drawEllipse(x * ScaleFactor,-1 * y * ScaleFactor, cir_size, cir_size);
if(pShowTrack)
drawPoints(painter, Record[ti.sTargetId]);
// 画出批号
QFont font = QFont("Arial",11);
painter->setFont(font);
painter->setPen(QPen(Qt::red));
painter->drawText(x * ScaleFactor,-1 * y * ScaleFactor, QString::fromStdString(ti.sTargetId));
}
void Radar::mousePressEvent(QMouseEvent *event)
{
//如果鼠标左键按下
if (event->type() == QEvent::MouseButtonPress) {
int x = event->x();
int y = event->y();
// 计算最小边长
minSide = qMin(width(), height()) - 50;
MeasureRadius = minSide / 2;
// 计算距离
// 圆心坐标(CoorX, CoorY)
int distance = sqrt(pow(x - CoorX, 2) + pow(y - CoorY, 2))
* pRadius / MeasureRadius;
// 计算方位
double azimuth = atan2(y - CoorY, x - CoorX) * 180 / M_PI;
//坐标转换
QPoint point = this->mapToGlobal(event->pos());
point.setX(point.x() - 20);
point.setY(point.y() - 50);
if(static_cast<int>(azimuth) < -90 && static_cast<int>(azimuth) > -180)
{
QToolTip::showText(point, QString("距离: %1\n方位: %2").arg(distance / ScaleFactor).arg(azimuth + 450));
}
else
{
QToolTip::showText(point, QString("距离: %1\n方位: %2").arg(distance / ScaleFactor).arg(azimuth + 90));
}
}
}
以上是雷达模拟仿真的自定义控件实现代码,在实际使用中,具体实现可能会根据实际需求略有差异。在开发过程中,可根据需求对控件进行进一步的优化和扩展。
五、使用示例
以下是一个简单的示例代码,演示了如何在Qt中使用此控件:
#include "Radar.h"
#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QTimer>
#include <cmath>
#include <random>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow window;
window.resize(500,500);
// 可疑点的方位距离测试
std::vector<TargetInfo> InitTargetInfo(20);
// 生成20个100~300不重复的距离
std::mt19937 generator(std::time(0));
std::uniform_int_distribution<int> distribution(100, 300);
for (int i = 0; i < 20; ++i) {
InitTargetInfo[i].dDistance = distribution(generator);
}
//------------------------------------------------------------
// 生成20个0~360不重复的方位
std::mt19937 generator1(std::time(0));
std::uniform_int_distribution<int> distribution1(0, 360);
for (int i = 0; i < 20; ++i) {
InitTargetInfo[i].dAzimuth = distribution1(generator1);
}
// 批号
for (int i = 0; i < 20; ++i) {
InitTargetInfo[i].sTargetId = 'A' + i;
}
Radar *radar = new Radar(&window);
// 添加目标数据,程序根据目标的距离和方位自动计算并绘制坐标
radar->SetData(InitTargetInfo);
radar->Start(100);
// 该定时器模拟目标移动
QTimer timer;
timer.setInterval(100);
QObject::connect(&timer, &QTimer::timeout, [&]() {
// 所有的距离+1,即目标距离圆心+1,直到超出雷达范围
for (auto it = InitTargetInfo.begin(); it != InitTargetInfo.end();) {
if(it->dDistance == 0)
{
it = InitTargetInfo.erase(it);
}
else
{
it->dDistance += 1;
++it;
}
}
radar->SetData(InitTargetInfo);
});
timer.start();
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(radar);
QWidget *centralWidget = new QWidget(&window);
centralWidget->setLayout(layout);
window.setCentralWidget(centralWidget);
window.show();
return a.exec();
}
以上示例在控件的实现中,主要涉及到以下几个方面的功能:
- 绘制雷达区域:控件使用QT的绘图功能来绘制雷达的工作区域,并设置合适的样式和颜色来显示雷达的外观。
- 模拟目标移动:控件实现目标的移动效果,随机生成目标的初始位置。
谢谢您的关注和阅读!希望可以帮助读者更直观地了解雷达的工作原理,并且可以根据需要进行自定义的调整和操作。如有任何问题或需要帮助,请随时与我联系。希望您能继续支持并享受更多精彩的内容。祝您生活愉快!