文章目录
  1. 1. 利用GDI绘制雷达P显的解决途径及关键代码示例
    1. 1.1. 引言
    2. 1.2. 功能概述
    3. 1.3. 存在问题
    4. 1.4. 功能完善
    5. 1.5. 演示示例

利用GDI绘制雷达P显的解决途径及关键代码示例

引言

雷达P显包含了实时的波束扫描、探测点迹等信息,对于指挥员了解态势非常有帮助。在Visual Studio平台上,借助于GDI+来绘制雷达P显,是一个非常有挑战的工作。本博文利用双缓存机制,结合特定区域刷新技术,实现了一个较为完整的雷达P显版本,写出关键源码,旨在抛砖引玉,和大家一起沟通交流。

功能概述

最近研究使用GDI+绘制雷达P显,参考了开源代码,实现了一个初步版本,实现的主要功能如下:

  1. 绘制P显底图,含距离环、距离标注、方位射线(即方位角)等;
  2. 实时显示方位扫描波位,并添加余辉效果(即渐变颜色);
  3. 依据给定的检测概率,随机生成一系列的探测点迹并显示在P显底图上;
  4. 实现了特定区域刷新效果——即方位扫描波位覆盖的区域才进行重绘,其它区域保持不变;
  5. 在界面上添加了其它的显示信息(很多尚未添加内核模型,仅为示意)。

存在问题

通过前期编程努力,在Visual Studio 2013上完成了代码编写与测试,运行效果参加本帖最后的GIF图像。通过运行发现,系统距离预期的显示效果有一定的区别,主要表现为:

  • 虽然使用了InvalidRgn()函数,仅对方位扫描波位覆盖的屏幕区域才进行重绘,但是通过实际运行发现,屏幕上遗留了较多的余辉残留(图中的绿色部分),具体原因尚未发现;
  • 通过长时间运行发现,程序的CPU占用较为平稳(在笔者的电脑上维持在3%左右),但是它的内存占用会缓慢地增加,表明程序中存在者内存泄露,但通过仔细检查代码,未发现问题代码。

功能完善

同时,我还想补充添加两个功能,具体为:

  1. 目前采用的是随机生成一系列的探测点迹并显示在P显底图上的方式,仅为示意。如果我想在P显上显示一个稳定的目标轨迹(即连续多次扫描都能发现这个目标,将每次的扫描探测结果用折线连接,显示在底图上),这会牵扯到较大范围的屏幕区域重绘,目前的程序结构无法完成,不知该如何修改;
  2. 想实现在屏幕上通过鼠标点击选择特定点迹的功能,相应的右键菜单已经做好,但不知道如何才能实现鼠标点击选择。粗略地考虑,应该把当前屏幕上所有点位置均记录下来,然后通过获取当前鼠标点击位置,借助于“最近邻”准则来判决,这样感觉有些繁琐,不知道有没有更好的解决方式?

注:在OnPaint()函数中引用了网上的开源代码,对作者表示感谢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
//相关成员变量定义(在.h文件中,摘抄如下)
double m_dCurSimTime; //当前扫描时间,s
double m_dCurAzimuth; //当前波束扫描方位角,rad
int m_nCurScanFrame; //当前扫描处理帧周期
int m_nCenterX; //P显中心X坐标,屏幕上
int m_nCenterY; //P显中心Y坐标,屏幕上
int m_nRadius; //P显半径,屏幕上
//其它公共变量定义
const double PI = 3.14159265358979; //圆周率
const double d2r = PI/180.0; //从°至rad的转换
const UINT TimeIntervalInMS = 100; //以ms形式表示的方位扫描时间间隔
const double BeamWidth = 6.0*d2r; //方位扫描波束宽度,rad
const double ScanPeriod = 6.0; //一个完整的方位扫描周期,s
const double ScanRate = 360.0/ScanPeriod*d2r; //方位波束扫描速率,rad/s
const double MaxDetectRange = 500.0e3; //距离环代表的最大探测距离,m
const int RANGERINGNUM = 10; //P显底图上的距离环数目
//用于实时显示状态的矩形区域(特定刷新)
RECT rectTime,rectAzScan,rectFrame;
RECT rectRate,rectDotNum,rectEchoNum,rectTgtNum,rectSysTime;
RECT rectR,rectAz,rectID;
//生成给定区间内的均匀分布随机数
double Rand(double dblStart, double dblFinish)
{
double minVal = min(dblStart, dblFinish);
double maxVal = max(dblStart, dblFinish);
return (maxVal - minVal) * (double)rand() / (RAND_MAX + 1) + minVal;
}
BOOL CPPIDispDemoDlg::OnInitDialog()
{
……
SetTimer(0, TimeIntervalInMS, NULL);
}
void CPPIDispDemoDlg::OnPaint()
{
if (IsIconic())
{
……
}
else
{
CPaintDC dc(this);
CRect rect;
This->GetClientRect(&rect);
int nMargin = 1; //边距
int nHeight = rect.Height() - 2*nMargin;
int nWidth = rect.Width() - 2*nMargin;
m_nCenterX = nWidth/2;
m_nCenterY = nHeight/2;
m_nRadius = min(nWidth,nHeight)/2;
CDC xDC;
CBitmap xBMP;
xDC.CreateCompatibleDC(&dc);
xBMP.CreateCompatibleBitmap(&dc,nWidth,nHeight);
xDC.SelectObject(xBMP);
//这是显示的核心函数!
OnDraw(&xDC);
CDC yDC;
CBitmap yBMP;
yDC.CreateCompatibleDC(&dc);
yBMP.CreateCompatibleBitmap(&dc,nWidth,nHeight);
yDC.SelectObject(&yBMP);
yDC.FillSolidRect(rect,GetSysColor(COLOR_3DFACE));
//采用双缓存机制,防止背景闪烁
yDC.BitBlt(nMargin,nMargin,nWidth,nHeight,&xDC,0,0,SRCCOPY);
dc.BitBlt(0,0,nWidth,nHeight,&yDC,0,0,SRCCOPY);
xBMP.DeleteObject();
xDC.DeleteDC();
yBMP.DeleteObject();
yDC.DeleteDC();
}
}
void CPPIDispDemoDlg::OnDraw(CDC *pDC)
{
//绘制方位波束扫描余辉效果(可以进行更为精细的控制)
double deltaT = BeamWidth/255;
for(double dt = 0.0; dt < 1.0*BeamWidth; dt += deltaT)
{
long x = m_nCenterX + m_nRadius*cos(dt + m_dCurAzimuth-BeamWidth);
long y = m_nCenterY + m_nRadius*sin(dt + m_dCurAzimuth-BeamWidth);
pDC->MoveTo(m_nCenterX,m_nCenterY);
xPen.DeleteObject();
xPen.CreatePen(0,4,RGB(0,j++,0));
pDC->SelectObject(&xPen);
pDC->LineTo(x,y);
}
xPen.DeleteObject();
//绘制当前方位扫描波束中心线
xPen.CreatePen(0,4,RGB(0,255,0));
pDC->SelectObject(&xPen);
pDC->MoveTo(m_nCenterX, m_nCenterY);
pDC->LineTo(m_nCenterX + m_nRadius*cos(m_dCurAzimuth),
m_nCenterY + m_nRadius*sin(m_dCurAzimuth));
xPen.DeleteObject();
//在当前方位扫描波束覆盖的区域里生成距离、方位均随机分布的目标
int Rmt = int(Rand(10, m_nRadius));
//注意:这里显示的实际上是上一个方位扫描波束的目标,模拟实际系统的处理延迟
double angleAZ = Rand(m_dCurAzimuth - 2*BeamWidth, m_dCurAzimuth - BeamWidth);
//以给定的概率发现目标,并在P显上绘制出
double Threshold = 0.65;
if (Rand(0.0,1.0) >= 1 - Threshold)
{
CPoint dot;
dot.x = m_nCenterX + Rmt*cos(angleAZ);
dot.y = m_nCenterY + Rmt*sin(angleAZ);
pDC->SetPixel(dot,RGB(255,0,0));
pDC->FillSolidRect(dot.x, dot.y, 4, 4, RGB(255,255,0));
}
CFont font;
font.CreateFont(12,6,0,0,600,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_ROMAN,"宋体");
CString str("");
//绘制距离环
for (i = 0; i < RANGERINGNUM-1; i++)
{
xPen.CreatePen(PS_SOLID,1,RGB(128,118,105));
pDC->SelectObject(&xPen);
pDC->Arc(m_nCenterX - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,0,0,0,0);
xPen.DeleteObject();
//显示距离标注
if (i % 2 == 1)
{
pDC->SelectObject(&font);
pDC->SetTextColor(RGB(160,160,164));
pDC->SetBkMode(TRANSPARENT);
str.Format("%d",int((RANGERINGNUM-i)*MaxDetectRange/1.0e3/RANGERINGNUM));
pDC->TextOut(m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY, str);
}
}
xPen.CreatePen(PS_SOLID,1,RGB(128,118,105));
pDC->SelectObject(&xPen);
//绘制水平线
pDC->MoveTo(m_nCenterX - m_nRadius, m_nCenterY);
pDC->LineTo(m_nCenterX + m_nRadius, m_nCenterY);
//绘制垂直线
pDC->MoveTo(m_nCenterX, m_nCenterY - m_nRadius);
pDC->LineTo(m_nCenterX, m_nCenterY + m_nRadius);
//绘制30°方位射线
double dTheta = 30.0*d2r;
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
//绘制60°方位射线
dTheta = 60.0*d2r;
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
xPen.DeleteObject();
//对中心距离环线换用红色进行突出显示
xPen.CreatePen(PS_SOLID,2,RGB(255,0,0));
pDC->SelectObject(&xPen);
i = RANGERINGNUM-1;
pDC->Arc(m_nCenterX - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,0,0,0,0);
xPen.DeleteObject();
//显示距离标注
pDC->SelectObject(&font);
pDC->SetTextColor(RGB(160,160,164));
pDC->SetBkMode(TRANSPARENT);
str.Format("%d",int((RANGERINGNUM-i)*MaxDetectRange/1.0e3/RANGERINGNUM)); /单位km
pDC->TextOut(m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM, m_nCenterY, str);
//显示当前仿真时间(注意:不是系统时间)
str.Format("当前时间(s):%6.3f", m_dCurSimTime);
rectTime.left = 5;
rectTime.top = 5;
rectTime.right = rectTime.left + strlen(str) * 7;
rectTime.bottom = rectTime.top + 12;
//pDC->FillSolidRect(&rectTime,RGB(255,255,0));
pDC->DrawText(str,&rectTime,DT_LEFT);
//还有其它信息显示,重复度较大,不再赘述
……
font.DeleteObject();
//换回原来的画笔
pDC->SelectObject(oPen);
}
void CPPIDispDemoDlg::OnTimer(UINT_PTR nIDEvent)
{
//更新当前方位扫描角,以及当前仿真时刻
m_dCurAzimuth += BeamWidth;
m_dCurSimTime += 1.0e-3*TimeIntervalInMS;
//将方位扫描角限定在[0,360°]之间,并且进行方位扫描帧周期的判断
if(m_dCurAzimuth > 2*PI)
{
m_dCurAzimuth -= 2*PI;
m_nCurScanFrame++;
}
//刷新特定区域(显示各种实时状态信息)
InvalidateRect(&rectTime);
InvalidateRect(&rectAzScan);
InvalidateRect(&rectFrame);
InvalidateRect(&rectDotNum);
InvalidateRect(&rectEchoNum);
InvalidateRect(&rectTgtNum);
InvalidateRect(&rectSysTime);
InvalidateRect(&rectRate);
InvalidateRect(&rectID);
InvalidateRect(&rectR);
InvalidateRect(&rectAz);
CDC dc;
dc.CreateCompatibleDC(&dc);
dc.SetViewportOrg(m_nCenterX,m_nCenterY);
CPoint topleft,bottomright;
topleft.x = -m_nRadius ;
topleft.y = -m_nRadius;
bottomright.x = m_nRadius ;
bottomright.y = m_nRadius ;
CRgn rgn;
CRect rect(topleft,bottomright);
//当前方位扫描波束的起点、终点
CPoint pt1, pt2;
pt1.x = m_nRadius*cos(m_dCurAzimuth);
pt1.y = m_nRadius*sin(m_dCurAzimuth);
pt2.x = m_nRadius*cos(m_dCurAzimuth - 2*BeamWidth);
pt2.y = m_nRadius*sin(m_dCurAzimuth - 2*BeamWidth);
//指定当前方位扫描波束覆盖的P显区域,并进行重绘
dc.BeginPath();
dc.MoveTo(CPoint(0,0));
dc.LineTo(pt1);
dc.ArcTo(rect,pt1,pt2);
dc.LineTo(CPoint(0,0));
dc.EndPath();
rgn.CreateFromPath(&dc);
InvalidateRgn(&rgn,TRUE);
rgn.DeleteObject();
dc.DeleteDC();
CDialog::OnTimer(nIDEvent);
}
void CPPIDispDemoDlg::OnClose()
{
KillTimer(0);
……
}

演示示例

演示见下图最主要的缺陷就是雷达扫描余辉无法全部清除,在图中的左下角区域(第三象限),有很多的绿色残留,而且在最中心的圆形局域内尤其严重,目前尚未找到问题症结。本来怀疑是指定的刷新区域坐标有误,但从图中可看出,右上角的区域很好地实现了特定区域刷新效果,最中心的区域内也较少有余辉残留,所以排除了这个原因。初步判断是不是因为在程序中,将sin()和cos()等函数计算得出的double类型数据转换为屏幕上的int型坐标而导致的?

RadarPPIDispDemo

文章目录
  1. 1. 利用GDI绘制雷达P显的解决途径及关键代码示例
    1. 1.1. 引言
    2. 1.2. 功能概述
    3. 1.3. 存在问题
    4. 1.4. 功能完善
    5. 1.5. 演示示例