OpenCPN Partial API docs
Loading...
Searching...
No Matches
ais.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: AIS Decoder Object
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2010 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 ***************************************************************************
25 */
26
27#include <stdlib.h>
28#include <math.h>
29#include <time.h>
30
31#ifdef __MINGW32__
32#undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
33#include <windows.h>
34#endif
35
36#include <wx/wx.h>
37#include <wx/tokenzr.h>
38#include <wx/datetime.h>
39#include <wx/wfstream.h>
40#include <wx/imaglist.h>
41
42#include "model/ais_decoder.h"
44#include "model/ais_target_data.h"
45#include "model/cutil.h"
46#include "model/georef.h"
47#include "model/own_ship.h"
48#include "model/select.h"
49#include "model/wx28compat.h"
50
51#include "ais.h"
52#include "AISTargetAlertDialog.h"
53#include "AISTargetQueryDialog.h"
54#include "chcanv.h"
55#include "FontMgr.h"
56#include "line_clip.h"
57#include "navutil.h" // for Select
58#include "ocpn_frame.h"
59#include "OCPNPlatform.h"
60#include "ocpn_plugin.h"
61#include "styles.h"
62
63extern MyFrame *gFrame;
64extern OCPNPlatform *g_Platform;
65
66int g_ais_cog_predictor_width;
67extern AISTargetQueryDialog *g_pais_query_dialog_active;
68
69int ImportanceSwitchPoint = 100;
70
71extern ArrayOfMmsiProperties g_MMSI_Props_Array;
72extern bool g_bopengl;
73
74extern float g_ShipScaleFactorExp;
75
76float AISImportanceSwitchPoint = 0.0;
77
78#if !defined(NAN)
79static const long long lNaN = 0xfff8000000000000;
80#define NAN (*(double *)&lNaN)
81#endif
82
83wxString ais8_001_22_notice_names[] = {
84 // 128] = {
85 _("Caution Area: Marine mammals habitat (implies whales NOT "
86 "observed)"), // 0 - WARNING: extra text by Kurt
87 _("Caution Area: Marine mammals in area - reduce speed"), // 1
88 _("Caution Area: Marine mammals in area - stay clear"), // 2
89 _("Caution Area: Marine mammals in area - report sightings"), // 3
90 _("Caution Area: Protected habitat - reduce speed"), // 4
91 _("Caution Area: Protected habitat - stay clear"), // 5
92 _("Caution Area: Protected habitat - no fishing or anchoring"), // 6
93 _("Caution Area: Derelicts (drifting objects)"), // 7
94 _("Caution Area: Traffic congestion"), // 8
95 _("Caution Area: Marine event"), // 9
96 _("Caution Area: Divers down"), // 10
97 _("Caution Area: Swim area"), // 11
98 _("Caution Area: Dredge operations"), // 12
99 _("Caution Area: Survey operations"), // 13
100 _("Caution Area: Underwater operation"), // 14
101 _("Caution Area: Seaplane operations"), // 15
102 _("Caution Area: Fishery - nets in water"), // 16
103 _("Caution Area: Cluster of fishing vessels"), // 17
104 _("Caution Area: Fairway closed"), // 18
105 _("Caution Area: Harbour closed"), // 19
106 _("Caution Area: Risk (define in Associated text field)"), // 20
107 _("Caution Area: Underwater vehicle operation"), // 21
108 _("(reserved for future use)"), // 22
109 _("Environmental Caution Area: Storm front (line squall)"), // 23
110 _("Environmental Caution Area: Hazardous sea ice"), // 24
111 _("Environmental Caution Area: Storm warning (storm cell or line "
112 "of storms)"), // 25
113 _("Environmental Caution Area: High wind"), // 26
114 _("Environmental Caution Area: High waves"), // 27
115 _("Environmental Caution Area: Restricted visibility (fog, rain, "
116 "etc.)"), // 28
117 _("Environmental Caution Area: Strong currents"), // 29
118 _("Environmental Caution Area: Heavy icing"), // 30
119 _("(reserved for future use)"), // 31
120 _("Restricted Area: Fishing prohibited"), // 32
121 _("Restricted Area: No anchoring."), // 33
122 _("Restricted Area: Entry approval required prior to transit"), // 34
123 _("Restricted Area: Entry prohibited"), // 35
124 _("Restricted Area: Active military OPAREA"), // 36
125 _("Restricted Area: Firing - danger area."), // 37
126 _("Restricted Area: Drifting Mines"), // 38
127 _("(reserved for future use)"), // 39
128 _("Anchorage Area: Anchorage open"), // 40
129 _("Anchorage Area: Anchorage closed"), // 41
130 _("Anchorage Area: Anchoring prohibited"), // 42
131 _("Anchorage Area: Deep draft anchorage"), // 43
132 _("Anchorage Area: Shallow draft anchorage"), // 44
133 _("Anchorage Area: Vessel transfer operations"), // 45
134 _("(reserved for future use)"), // 46
135 _("(reserved for future use)"), // 47
136 _("(reserved for future use)"), // 48
137 _("(reserved for future use)"), // 49
138 _("(reserved for future use)"), // 50
139 _("(reserved for future use)"), // 51
140 _("(reserved for future use)"), // 52
141 _("(reserved for future use)"), // 53
142 _("(reserved for future use)"), // 54
143 _("(reserved for future use)"), // 55
144 _("Security Alert - Level 1"), // 56
145 _("Security Alert - Level 2"), // 57
146 _("Security Alert - Level 3"), // 58
147 _("(reserved for future use)"), // 59
148 _("(reserved for future use)"), // 60
149 _("(reserved for future use)"), // 61
150 _("(reserved for future use)"), // 62
151 _("(reserved for future use)"), // 63
152 _("Distress Area: Vessel disabled and adrift"), // 64
153 _("Distress Area: Vessel sinking"), // 65
154 _("Distress Area: Vessel abandoning ship"), // 66
155 _("Distress Area: Vessel requests medical assistance"), // 67
156 _("Distress Area: Vessel flooding"), // 68
157 _("Distress Area: Vessel fire/explosion"), // 69
158 _("Distress Area: Vessel grounding"), // 70
159 _("Distress Area: Vessel collision"), // 71
160 _("Distress Area: Vessel listing/capsizing"), // 72
161 _("Distress Area: Vessel under assault"), // 73
162 _("Distress Area: Person overboard"), // 74
163 _("Distress Area: SAR area"), // 75
164 _("Distress Area: Pollution response area"), // 76
165 _("(reserved for future use)"), // 77
166 _("(reserved for future use)"), // 78
167 _("(reserved for future use)"), // 79
168 _("Instruction: Contact VTS at this point/juncture"), // 80
169 _("Instruction: Contact Port Administration at this "
170 "point/juncture"), // 81
171 _("Instruction: Do not proceed beyond this point/juncture"), // 82
172 _("Instruction: Await instructions prior to proceeding beyond this "
173 "point/juncture"), // 83
174 _("Proceed to this location - await instructions"), // 84
175 _("Clearance granted - proceed to berth"), // 85
176 _("(reserved for future use)"), // 86
177 _("(reserved for future use)"), // 87
178 _("Information: Pilot boarding position"), // 88
179 _("Information: Icebreaker waiting area"), // 89
180 _("Information: Places of refuge"), // 90
181 _("Information: Position of icebreakers"), // 91
182 _("Information: Location of response units"), // 92
183 _("VTS active target"), // 93
184 _("Rogue or suspicious vessel"), // 94
185 _("Vessel requesting non-distress assistance"), // 95
186 _("Chart Feature: Sunken vessel"), // 96
187 _("Chart Feature: Submerged object"), // 97
188 _("Chart Feature: Semi-submerged object"), // 98
189 _("Chart Feature: Shoal area"), // 99
190 _("Chart Feature: Shoal area due north"), // 100
191 _("Chart Feature: Shoal area due east"), // 101
192 _("Chart Feature: Shoal area due south"), // 102
193 _("Chart Feature: Shoal area due west"), // 103
194 _("Chart Feature: Channel obstruction"), // 104
195 _("Chart Feature: Reduced vertical clearance"), // 105
196 _("Chart Feature: Bridge closed"), // 106
197 _("Chart Feature: Bridge partially open"), // 107
198 _("Chart Feature: Bridge fully open"), // 108
199 _("(reserved for future use)"), // 109
200 _("(reserved for future use)"), // 110
201 _("(reserved for future use)"), // 111
202 _("Report from ship: Icing info"), // 112
203 _("(reserved for future use)"), // 113
204 _("Report from ship: Miscellaneous information - define in "
205 "Associated text field"), // 114
206 _("(reserved for future use)"), // 115
207 _("(reserved for future use)"), // 116
208 _("(reserved for future use)"), // 117
209 _("(reserved for future use)"), // 118
210 _("(reserved for future use)"), // 119
211 _("Route: Recommended route"), // 120
212 _("Route: Alternative route"), // 121
213 _("Route: Recommended route through ice"), // 122
214 _("(reserved for future use)"), // 123
215 _("(reserved for future use)"), // 124
216 _("Other - Define in associated text field"), // 125
217 _("Cancellation - cancel area as identified by Message Linkage "
218 "ID"), // 126
219 _("Undefined (default)") //, // 127
220};
221
222static bool GetCanvasPointPix(ViewPort &vp, ChartCanvas *cp, double rlat,
223 double rlon, wxPoint *r) {
224 if (cp != NULL) {
225 return cp->GetCanvasPointPix(rlat, rlon, r);
226 }
227 *r = vp.GetPixFromLL(rlat, rlon);
228 return true;
229}
230
231static wxPoint transrot(wxPoint pt, float sin_theta, float cos_theta,
232 wxPoint offset = wxPoint(0, 0)) {
233 wxPoint ret;
234 float px = (float)(pt.x * sin_theta) + (float)(pt.y * cos_theta);
235 float py = (float)(pt.y * sin_theta) - (float)(pt.x * cos_theta);
236 ret.x = (int)wxRound(px);
237 ret.y = (int)wxRound(py);
238 ret.x += offset.x;
239 ret.y += offset.y;
240
241 return ret;
242}
243
244static void transrot_pts(int n, wxPoint *pt, float sin_theta, float cos_theta,
245 wxPoint offset = wxPoint(0, 0)) {
246 for (int i = 0; i < n; i++)
247 pt[i] = transrot(pt[i], sin_theta, cos_theta, offset);
248}
249
250void AISDrawAreaNotices(ocpnDC &dc, ViewPort &vp, ChartCanvas *cp) {
251 if (cp == NULL) return;
252 if (!g_pAIS || !cp->GetShowAIS() || !g_bShowAreaNotices) return;
253
254 wxDateTime now = wxDateTime::Now();
255 now.MakeGMT();
256
257 bool b_pens_set = false;
258 wxPen pen_save;
259 wxBrush brush_save;
260 wxColour yellow;
261 wxColour green;
262 wxPen pen;
263 wxBrush *yellow_brush = wxTheBrushList->FindOrCreateBrush(
264 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
265 wxBrush *green_brush = wxTheBrushList->FindOrCreateBrush(
266 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
267 ;
268 wxBrush *brush;
269
270 float vp_scale = vp.view_scale_ppm;
271
272 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
273 auto target_data = target.second;
274 if (!target_data->area_notices.empty()) {
275 if (!b_pens_set) {
276 pen_save = dc.GetPen();
277 brush_save = dc.GetBrush();
278
279 yellow = GetGlobalColor(_T ( "YELO1" ));
280 yellow.Set(yellow.Red(), yellow.Green(), yellow.Blue(), 64);
281
282 green = GetGlobalColor(_T ( "GREEN4" ));
283 green.Set(green.Red(), green.Green(), green.Blue(), 64);
284
285 pen.SetColour(yellow);
286 pen.SetWidth(2);
287
288 yellow_brush = wxTheBrushList->FindOrCreateBrush(
289 yellow, wxBRUSHSTYLE_CROSSDIAG_HATCH);
290 green_brush =
291 wxTheBrushList->FindOrCreateBrush(green, wxBRUSHSTYLE_TRANSPARENT);
292 brush = yellow_brush;
293
294 b_pens_set = true;
295 }
296
297 for (auto &ani : target_data->area_notices) {
298 Ais8_001_22 &area_notice = ani.second;
299
300 if (area_notice.expiry_time > now) {
301 std::vector<wxPoint> points;
302 bool draw_polygon = false;
303
304 switch (area_notice.notice_type) {
305 case 0:
306 pen.SetColour(green);
307 brush = green_brush;
308 break;
309 case 1:
310 pen.SetColour(yellow);
311 brush = yellow_brush;
312 break;
313 default:
314 pen.SetColour(yellow);
315 brush = yellow_brush;
316 }
317 dc.SetPen(pen);
318 dc.SetBrush(*brush);
319
320 for (Ais8_001_22_SubAreaList::iterator sa =
321 area_notice.sub_areas.begin();
322 sa != area_notice.sub_areas.end(); ++sa) {
323 switch (sa->shape) {
324 case AIS8_001_22_SHAPE_CIRCLE: {
325 wxPoint target_point;
326 GetCanvasPointPix(vp, cp, sa->latitude, sa->longitude,
327 &target_point);
328 points.push_back(target_point);
329 if (sa->radius_m > 0.0)
330 dc.DrawCircle(target_point, sa->radius_m * vp_scale);
331 break;
332 }
333 case AIS8_001_22_SHAPE_RECT: {
334 wxPoint target_point;
335 double lat = sa->latitude;
336 double lon = sa->longitude;
337 int orient_east = 90 + sa->orient_deg;
338 if (orient_east > 360) orient_east -= 360;
339 GetCanvasPointPix(vp, cp, lat, lon, &target_point);
340 points.push_back(target_point);
341 ll_gc_ll(lat, lon, orient_east, sa->e_dim_m / 1852.0, &lat,
342 &lon);
343 GetCanvasPointPix(vp, cp, lat, lon, &target_point);
344 points.push_back(target_point);
345 ll_gc_ll(lat, lon, sa->orient_deg, sa->n_dim_m / 1852.0, &lat,
346 &lon);
347 GetCanvasPointPix(vp, cp, lat, lon, &target_point);
348 points.push_back(target_point);
349 ll_gc_ll(sa->latitude, sa->longitude, sa->orient_deg,
350 sa->n_dim_m / 1852.0, &lat, &lon);
351 GetCanvasPointPix(vp, cp, lat, lon, &target_point);
352 points.push_back(target_point);
353 draw_polygon = true;
354 break;
355 }
356 case AIS8_001_22_SHAPE_SECTOR: {
357 wxPoint target_point;
358 double lat, lon;
359 double lat1 = sa->latitude;
360 double lon1 = sa->longitude;
361 GetCanvasPointPix(vp, cp, lat1, lon1, &target_point);
362 points.push_back(target_point);
363
364 for (int i = 0; i < 18; ++i) {
365 ll_gc_ll(
366 lat1, lon1,
367 sa->left_bound_deg +
368 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
369 sa->radius_m / 1852.0, &lat, &lon);
370 GetCanvasPointPix(vp, cp, lat, lon, &target_point);
371 points.push_back(target_point);
372 }
373 // Last angle explicitly to avoid any rounding errors
374 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
375 &lat, &lon);
376 GetCanvasPointPix(vp, cp, lat, lon, &target_point);
377 points.push_back(target_point);
378
379 draw_polygon = true;
380 break;
381 }
382 case AIS8_001_22_SHAPE_POLYGON:
383 draw_polygon = true;
384 // FALL THROUGH
385 case AIS8_001_22_SHAPE_POLYLINE: {
386 double lat = sa->latitude;
387 double lon = sa->longitude;
388 for (int i = 0; i < 4; ++i) {
389 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
390 &lat, &lon);
391 wxPoint target_point;
392 GetCanvasPointPix(vp, cp, lat, lon, &target_point);
393 points.push_back(target_point);
394 }
395 dc.DrawLines(points.size(), &points.front());
396 }
397 }
398 }
399 if (draw_polygon) dc.DrawPolygon(points.size(), &points.front());
400 }
401 }
402 }
403 }
404
405 if (b_pens_set) {
406 dc.SetPen(pen_save);
407 dc.SetBrush(brush_save);
408 }
409}
410
411static void TargetFrame(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
412 // Constants?
413 int gap2 = 2 * radius / 6;
414
415 wxPen pen_save = dc.GetPen();
416
417 dc.SetPen(pen);
418
419 dc.DrawLine(x - radius, y + gap2, x - radius, y + radius);
420 dc.DrawLine(x - radius, y + radius, x - gap2, y + radius);
421 dc.DrawLine(x + gap2, y + radius, x + radius, y + radius);
422 dc.DrawLine(x + radius, y + radius, x + radius, y + gap2);
423 dc.DrawLine(x + radius, y - gap2, x + radius, y - radius);
424 dc.DrawLine(x + radius, y - radius, x + gap2, y - radius);
425 dc.DrawLine(x - gap2, y - radius, x - radius, y - radius);
426 dc.DrawLine(x - radius, y - radius, x - radius, y - gap2);
427
428 dc.SetPen(pen_save);
429}
430
431static void AtoN_Diamond(ocpnDC &dc, wxPen pen, int x, int y, int radius,
432 AisTargetData *td) {
433 // Constants?
434 wxPen pen_save = dc.GetPen();
435
436 wxPen aton_DrawPen;
437 wxPen aton_WhiteBorderPen;
438 wxBrush aton_Brush;
439
440 int rad1a = radius / 2; // size off topmarks of AtoN
441 int rad2a = radius / 4;
442 int rad3a =
443 rad1a -
444 1; // slightly smaller size off topmarks to look better for the eye
445
446 // Set the Pen for what is needed
447 if ((td->NavStatus == ATON_VIRTUAL_OFFPOSITION) ||
448 (td->NavStatus == ATON_REAL_OFFPOSITION))
449 aton_DrawPen = wxPen(GetGlobalColor(_T ( "URED" )), pen.GetWidth());
450 else
451 aton_DrawPen = wxPen(GetGlobalColor(_T ( "UBLCK" )), pen.GetWidth());
452
453 bool b_virt = (td->NavStatus == ATON_VIRTUAL) |
454 (td->NavStatus == ATON_VIRTUAL_ONPOSITION) |
455 (td->NavStatus == ATON_VIRTUAL_OFFPOSITION);
456
457 if (b_virt) aton_DrawPen.SetStyle(wxPENSTYLE_SHORT_DASH);
458
459 aton_WhiteBorderPen =
460 wxPen(GetGlobalColor(_T ( "CHWHT" )), aton_DrawPen.GetWidth() + 2);
461
462 // Draw Base Diamond. First with Thick White pen then custom pen io to get a
463 // white border around the line.
464 wxPoint diamond[5];
465 diamond[0] = wxPoint(radius, 0);
466 diamond[1] = wxPoint(0, -radius);
467 diamond[2] = wxPoint(-radius, 0);
468 diamond[3] = wxPoint(0, radius);
469 diamond[4] = wxPoint(radius, 0);
470 dc.SetPen(aton_WhiteBorderPen);
471 dc.DrawLines(5, diamond, x, y);
472 dc.SetPen(aton_DrawPen);
473 dc.DrawLines(5, diamond, x, y);
474
475 aton_DrawPen =
476 wxPen(GetGlobalColor(_T ( "UBLCK" )),
477 pen.GetWidth()); // Change drawing pen to Solid and width 1
478 aton_WhiteBorderPen =
479 wxPen(GetGlobalColor(_T ( "CHWHT" )), aton_DrawPen.GetWidth() + 2);
480
481 // draw cross inside
482 wxPoint cross[5];
483 cross[0] = wxPoint(-rad2a, 0);
484 cross[1] = wxPoint(rad2a, 0);
485 cross[2] = wxPoint(0, 0);
486 cross[3] = wxPoint(0, rad2a);
487 cross[4] = wxPoint(0, -rad2a);
488 dc.SetPen(aton_WhiteBorderPen);
489 dc.DrawLines(5, cross, x, y);
490 dc.SetPen(aton_DrawPen);
491 dc.DrawLines(5, cross, x, y);
492
493 wxPoint TriPointUp[4]; // Declare triangles here for multiple use
494 TriPointUp[0] = wxPoint(-rad1a, 0);
495 TriPointUp[1] = wxPoint(rad1a, 0);
496 TriPointUp[2] = wxPoint(0, -rad1a);
497 TriPointUp[3] = wxPoint(-rad1a, 0);
498
499 wxPoint TriPointDown[4]; // Declare triangles here for multiple use
500 TriPointDown[0] = wxPoint(-rad1a, -rad1a);
501 TriPointDown[1] = wxPoint(rad1a, -rad1a);
502 TriPointDown[2] = wxPoint(0, 0);
503 TriPointDown[3] = wxPoint(-rad1a, -rad1a);
504
505 wxPoint CircleOpen[16]; // Workaround to draw transparent circles
506 CircleOpen[0] = wxPoint(-1, 5);
507 CircleOpen[1] = wxPoint(1, 5);
508 CircleOpen[2] = wxPoint(3, 4);
509 CircleOpen[3] = wxPoint(4, 3);
510 CircleOpen[4] = wxPoint(5, 1);
511 CircleOpen[5] = wxPoint(5, -1);
512 CircleOpen[6] = wxPoint(4, -3);
513 CircleOpen[7] = wxPoint(3, -4);
514 CircleOpen[8] = wxPoint(1, -5);
515 CircleOpen[9] = wxPoint(-1, -5);
516 CircleOpen[10] = wxPoint(-3, -4);
517 CircleOpen[11] = wxPoint(-4, -3);
518 CircleOpen[12] = wxPoint(-5, -1);
519 CircleOpen[13] = wxPoint(-4, 3);
520 CircleOpen[14] = wxPoint(-3, 4);
521 CircleOpen[15] = wxPoint(-1, 5);
522
523 switch (td->ShipType) {
524 case 9:
525 case 20: // Card. N
526 dc.SetPen(aton_WhiteBorderPen);
527 dc.DrawLines(4, TriPointUp, x, y - radius - 1);
528 dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
529 dc.SetPen(aton_DrawPen);
530 dc.DrawLines(4, TriPointUp, x, y - radius - 1);
531 dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
532 break;
533 case 10:
534 case 21: // Card E
535 dc.SetPen(aton_WhiteBorderPen);
536 dc.DrawLines(4, TriPointDown, x, y - radius - 1);
537 dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
538 dc.SetPen(aton_DrawPen);
539 dc.DrawLines(4, TriPointDown, x, y - radius - 1);
540 dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
541 break;
542 case 11:
543 case 22: // Card S
544 dc.SetPen(aton_WhiteBorderPen);
545 dc.DrawLines(4, TriPointDown, x, y - radius - 1);
546 dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
547 dc.SetPen(aton_DrawPen);
548 dc.DrawLines(4, TriPointDown, x, y - radius - 1);
549 dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
550 break;
551 case 12:
552 case 23: // Card W
553 dc.SetPen(aton_WhiteBorderPen);
554 dc.DrawLines(4, TriPointUp, x, y - radius - 1);
555 dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
556 dc.SetPen(aton_DrawPen);
557 dc.DrawLines(4, TriPointUp, x, y - radius - 1);
558 dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
559 break;
560 case 13: // PortHand Beacon IALA-A
561 case 24: { // StarboardHand Beacon IALA-B
562 wxPoint aRect[5]; // Square topmark
563 aRect[0] = wxPoint(-rad3a, 0);
564 aRect[1] = wxPoint(-rad3a, -rad3a - rad3a);
565 aRect[2] = wxPoint(rad3a, -rad3a - rad3a);
566 aRect[3] = wxPoint(rad3a, 0);
567 aRect[4] = wxPoint(-rad3a, 0);
568 dc.SetPen(aton_WhiteBorderPen);
569 dc.DrawLines(5, aRect, x, y - radius - 1);
570 dc.SetPen(aton_DrawPen);
571 dc.DrawLines(5, aRect, x, y - radius - 1);
572 } break;
573 case 14: // StarboardHand Beacon IALA-A
574 case 25: // PortHand Beacon IALA-B
575 dc.SetPen(aton_WhiteBorderPen);
576 dc.DrawLines(4, TriPointUp, x, y - radius);
577 dc.SetPen(aton_DrawPen);
578 dc.DrawLines(4, TriPointUp, x, y - radius);
579 break;
580 case 17:
581 case 28: // Isolated danger
582 dc.SetPen(aton_WhiteBorderPen);
583 dc.DrawLines(16, CircleOpen, x, y - radius - 5);
584 dc.SetPen(aton_DrawPen);
585 dc.DrawLines(16, CircleOpen, x, y - radius - 5);
586 dc.SetPen(aton_WhiteBorderPen);
587 dc.DrawLines(16, CircleOpen, x, y - radius - 16);
588 dc.SetPen(aton_DrawPen);
589 dc.DrawLines(16, CircleOpen, x, y - radius - 16);
590 break;
591 case 18:
592 case 29: // Safe water
593 dc.SetPen(aton_WhiteBorderPen);
594 dc.DrawLines(16, CircleOpen, x, y - radius - 5);
595 dc.SetPen(aton_DrawPen);
596 dc.DrawLines(16, CircleOpen, x, y - radius - 5);
597 break;
598 case 19:
599 case 30: { // Special Mark
600 cross[0] = wxPoint(-rad2a, -rad2a); // reuse of cross array
601 cross[1] = wxPoint(rad2a, rad2a);
602 cross[2] = wxPoint(0, 0);
603 cross[3] = wxPoint(-rad2a, rad2a);
604 cross[4] = wxPoint(rad2a, -rad2a);
605 dc.SetPen(aton_WhiteBorderPen);
606 dc.DrawLines(5, cross, x, y - radius - rad3a);
607 dc.SetPen(aton_DrawPen);
608 dc.DrawLines(5, cross, x, y - radius - rad3a);
609 } break;
610 default:
611 break;
612 }
613 dc.SetPen(pen_save);
614}
615
616static void Base_Square(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
617 // Constants?
618 int gap2 = 2 * radius / 6;
619 int pen_width = pen.GetWidth();
620
621 wxPen pen_save = dc.GetPen();
622
623 dc.SetPen(pen); // draw square
624
625 dc.DrawLine(x - radius, y - radius, x - radius, y + radius);
626 dc.DrawLine(x - radius, y + radius, x + radius, y + radius);
627 dc.DrawLine(x + radius, y + radius, x + radius, y - radius);
628 dc.DrawLine(x + radius, y - radius, x - radius, y - radius);
629
630 if (pen_width > 1) {
631 pen_width -= 1;
632 pen.SetWidth(pen_width);
633 } // draw cross inside
634
635 dc.DrawLine(x - gap2, y, x + gap2, y);
636 dc.DrawLine(x, y - gap2, x, y + gap2);
637
638 dc.SetPen(pen_save);
639}
640
641static void SART_Render(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
642 // Constants
643 int gap = (radius * 12) / 10;
644 int pen_width = pen.GetWidth();
645
646 wxPen pen_save = dc.GetPen();
647
648 dc.SetPen(pen);
649
650 wxBrush brush_save = dc.GetBrush();
651 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
652 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
653 dc.SetBrush(*ppBrush);
654
655 dc.DrawCircle(x, y, radius);
656
657 if (pen_width > 1) {
658 pen_width -= 1;
659 pen.SetWidth(pen_width);
660 } // draw cross inside
661
662 dc.DrawLine(x - gap, y - gap, x + gap, y + gap);
663 dc.DrawLine(x - gap, y + gap, x + gap, y - gap);
664
665 dc.SetBrush(brush_save);
666 dc.SetPen(pen_save);
667}
668
669// spherical coordinates is sufficient for visually plotting with relatively
670// small distances and about 6x faster than ll_gc_ll
671static void spherical_ll_gc_ll(float lat, float lon, float brg, float dist,
672 float *dlat, float *dlon) {
673 float angr = brg / 180 * M_PI;
674 float latr = lat * M_PI / 180;
675 float D = dist / 3443; // earth radius in nm
676 float sD = sinf(D), cD = cosf(D);
677 float sy = sinf(latr), cy = cosf(latr);
678 float sa = sinf(angr), ca = cosf(angr);
679
680 *dlon = lon + asinf(sa * sD / cy) * 180 / M_PI;
681 *dlat = asinf(sy * cD + cy * sD * ca) * 180 / M_PI;
682}
683
684// Global static AIS target rendering metrics
685float AIS_scale_factor;
686float AIS_nominal_target_size_mm;
687float AIS_nominal_icon_size_pixels;
688float AIS_pix_factor;
689float AIS_user_scale_factor;
690double AIS_nominal_line_width_pix;
691
692float AIS_width_interceptbar_base;
693float AIS_width_interceptbar_top;
694float AIS_intercept_bar_circle_diameter;
695float AIS_width_interceptline;
696float AIS_width_cogpredictor_base;
697float AIS_width_cogpredictor_line;
698float AIS_width_target_outline;
699float AIS_icon_diameter;
700wxFont *AIS_NameFont;
701
702static void AISSetMetrics() {
703 AIS_scale_factor = g_current_monitor_dip_px_ratio;
704 // Adapt for possible scaled display (Win)
705 double DPIscale = 1.0;
706 DPIscale = g_Platform->GetDisplayDIPMult(gFrame);
707
708 // Set the onscreen size of the symbol
709 // Compensate for various display resolutions
710 // Develop empirically, making a "diamond ATON" symbol about 4 mm square
711
712 // By experience, it is found that specifying target size in pixels, then
713 // bounding rendered size for high or lo resolution displays, gives the best
714 // compromise.
715
716 AIS_nominal_target_size_mm = 30.0 / g_Platform->GetDisplayDPmm();
717 // nominal_target_size_mm = gFrame->GetPrimaryCanvas()->GetDisplaySizeMM()
718 // / 60.0;
719
720 AIS_nominal_target_size_mm = wxMin(AIS_nominal_target_size_mm, 10.0);
721 AIS_nominal_target_size_mm = wxMax(AIS_nominal_target_size_mm, 5.0);
722
723 AIS_nominal_icon_size_pixels =
724 wxMax(4.0, g_Platform->GetDisplayDPmm() *
725 AIS_nominal_target_size_mm); // nominal size, but not
726 // less than 4 pixel
727 AIS_pix_factor = AIS_nominal_icon_size_pixels /
728 30.0; // generic A/B icons are 30 units in size
729
730 AIS_scale_factor *= AIS_pix_factor;
731
732 AIS_user_scale_factor = g_ShipScaleFactorExp;
733 if (g_ShipScaleFactorExp > 1.0)
734 AIS_user_scale_factor = (log(g_ShipScaleFactorExp) + 1.0) *
735 1.2; // soften the scale factor a bit
736
737 AIS_scale_factor *= AIS_user_scale_factor;
738
739 // Establish some graphic element line widths dependent on the platform
740 // display resolution
741 AIS_nominal_line_width_pix =
742 wxMax(2, g_Platform->GetDisplayDPmm() / (4.0 / DPIscale));
743 // 0.25 mm nominal, but not less than 2 pixels
744
745 AIS_width_interceptbar_base = 3 * AIS_nominal_line_width_pix;
746 AIS_width_interceptbar_top = 1.5 * AIS_nominal_line_width_pix;
747 AIS_intercept_bar_circle_diameter = 3.5 * AIS_nominal_line_width_pix;
748 AIS_width_interceptline = 2 * AIS_nominal_line_width_pix;
749 AIS_width_cogpredictor_base = 3 * AIS_nominal_line_width_pix;
750 AIS_width_cogpredictor_line = 1.3 * AIS_nominal_line_width_pix;
751 AIS_width_target_outline = 1.4 * AIS_nominal_line_width_pix;
752 AIS_icon_diameter = AIS_intercept_bar_circle_diameter * AIS_user_scale_factor;
753
754 wxFont *font = FontMgr::Get().GetFont(_("AIS Target Name"), 0);
755 double scaler = DPIscale;
756
757 AIS_NameFont = FindOrCreateFont_PlugIn(
758 font->GetPointSize() / scaler, font->GetFamily(), font->GetStyle(),
759 font->GetWeight(), false, font->GetFaceName());
760}
761
762static void AISDrawTarget(AisTargetData *td, ocpnDC &dc, ViewPort &vp,
763 ChartCanvas *cp) {
764 // Target data must be valid
765 if (NULL == td) return;
766
767 static bool firstTimeUse = true;
768 // First time AIS received
769 if (firstTimeUse) {
770 g_AisFirstTimeUse = true;
771 // Show Status Bar CPA warning status
772 cp->ToggleCPAWarn();
773 g_AisFirstTimeUse = false;
774 firstTimeUse = false;
775 }
776
777 // Target is lost due to position report time-out, but still in Target List
778 if (td->b_lost) return;
779
780 // Skip anchored/moored (interpreted as low speed) targets if requested
781 // unless the target is NUC or AtoN, in which case it is always
782 // displayed.
783 if ((g_bHideMoored) && (td->SOG <= g_ShowMoored_Kts) &&
784 (td->NavStatus != NOT_UNDER_COMMAND) &&
785 ((td->Class == AIS_CLASS_A) || (td->Class == AIS_CLASS_B)))
786 return;
787
788 // Target data position must have been valid once
789 if (!td->b_positionOnceValid) return;
790
791 // And we never draw ownship
792 if (td->b_OwnShip) return;
793
794 // If target's speed is unavailable, use zero for further calculations
795 float target_sog = td->SOG;
796 if ((td->SOG > 102.2) && !td->b_SarAircraftPosnReport) target_sog = 0.;
797
798 int drawit = 0;
799 wxPoint TargetPoint, PredPoint;
800
801 // Always draw alert targets, even if they are off the screen
802 if (td->n_alert_state == AIS_ALERT_SET) {
803 drawit++;
804 } else {
805 // Is target in Vpoint?
806 if (vp.GetBBox().Contains(td->Lat, td->Lon))
807 drawit++; // yep
808 else {
809 // If AIS tracks are shown, is the first point of the track on-screen?
810 if (1 /*g_bAISShowTracks*/ && td->b_show_track) {
811 if (td->m_ptrack.size() > 0) {
812 const AISTargetTrackPoint &ptrack_point = td->m_ptrack.front();
813 if (vp.GetBBox().Contains(ptrack_point.m_lat, ptrack_point.m_lon))
814 drawit++;
815 }
816 }
817 }
818 }
819
820 // Calculate AIS target Position Predictor, using global static variable
821 // for length of vector
822
823 float pred_lat, pred_lon;
824 spherical_ll_gc_ll(td->Lat, td->Lon, td->COG,
825 target_sog * g_ShowCOG_Mins / 60., &pred_lat, &pred_lon);
826
827 // Is predicted point in the VPoint?
828 if (vp.GetBBox().Contains(pred_lat, pred_lon))
829 drawit++; // yep
830 else {
831 LLBBox box;
832 box.SetFromSegment(td->Lat, td->Lon, pred_lat, pred_lon);
833 // And one more test to catch the case where target COG line crosses the
834 // screen, but the target itself and its pred point are both off-screen
835 if (!vp.GetBBox().IntersectOut(box)) drawit++;
836 }
837
838 // Do the draw if conditions indicate
839 if (!drawit) return;
840
841 GetCanvasPointPix(vp, cp, td->Lat, td->Lon, &TargetPoint);
842 GetCanvasPointPix(vp, cp, pred_lat, pred_lon, &PredPoint);
843
844 bool b_hdgValid = true;
845
846 float theta = (float)-PI / 2.;
847 // If the target reported a valid HDG, then use it for icon
848 if ((int)(td->HDG) != 511) {
849 theta = ((td->HDG - 90) * PI / 180.) + vp.rotation;
850 } else {
851 b_hdgValid = false; // tentative judgement
852
853 if (!g_bInlandEcdis) {
854 // question: why can we not compute similar to above using COG instead of
855 // HDG?
856 // Calculate the relative angle for this chart orientation
857 // Use a 100 pixel vector to calculate angle
858 float angle_distance_nm = (100. / vp.view_scale_ppm) / 1852.;
859 float angle_lat, angle_lon;
860 spherical_ll_gc_ll(td->Lat, td->Lon, td->COG, angle_distance_nm,
861 &angle_lat, &angle_lon);
862
863 wxPoint AnglePoint;
864 GetCanvasPointPix(vp, cp, angle_lat, angle_lon, &AnglePoint);
865
866 if (abs(AnglePoint.x - TargetPoint.x) > 0) {
867 if (target_sog > g_SOGminCOG_kts) {
868 theta = atan2f((double)(AnglePoint.y - TargetPoint.y),
869 (double)(AnglePoint.x - TargetPoint.x));
870 b_hdgValid = true;
871 } else
872 theta = (float)-PI / 2.;
873 } else {
874 if (AnglePoint.y > TargetPoint.y)
875 theta = (float)PI / 2.; // valid COG 180
876 else {
877 theta = (float)-PI /
878 2.; // valid COG 000 or speed is too low to resolve course
879 if (td->SOG >= g_SOGminCOG_kts) // valid COG 000 or speed is too
880 // low to resolve course
881 b_hdgValid = true;
882 }
883 }
884 }
885 }
886
887 // only need to compute this once;
888 float sin_theta = sinf(theta), cos_theta = cosf(theta);
889
890 wxDash dash_long[2];
891 dash_long[0] = (int)(1.0 * gFrame->GetPrimaryCanvas()
892 ->GetPixPerMM()); // Long dash <---------+
893 dash_long[1] =
894 (int)(0.5 * gFrame->GetPrimaryCanvas()->GetPixPerMM()); // Short gap |
895
896 int targetscale = 100;
897 int idxCC = 0;
898 if (cp != NULL) {
899 idxCC = cp->m_canvasIndex;
900
901 if (idxCC > AIS_TARGETDATA_MAX_CANVAS - 1)
902 return; // If more then n canvasses do not draw AIS anymore as we are
903 // running out of array index
904 if (cp->GetAttenAIS()) {
905 if (td->NavStatus <= 15) { // NavStatus > 15 is AtoN, and we don want
906 // AtoN being counted for attenuation
907 // with one tick per second targets can slink from 100 to 50 in abt 25
908 // seconds
909 if (td->importance < AISImportanceSwitchPoint)
910 targetscale = td->last_scale[idxCC] - 2;
911 // growing from 50 till 100% goes faster in 10 seconds
912 if (td->importance > AISImportanceSwitchPoint)
913 targetscale = td->last_scale[idxCC] + 5;
914 if (targetscale > 100) targetscale = 100;
915 if (targetscale < 50) targetscale = 50;
916 td->last_scale[idxCC] = targetscale;
917 }
918 }
919 }
920
921 // Draw the icon rotated to the COG
922 wxPoint ais_real_size[6];
923 bool bcan_draw_size = true;
924 if (g_bDrawAISSize) {
925 if (td->DimA + td->DimB == 0 || td->DimC + td->DimD == 0) {
926 bcan_draw_size = false;
927 } else {
928 double ref_lat, ref_lon;
929 ll_gc_ll(td->Lat, td->Lon, 0, 100. / 1852., &ref_lat, &ref_lon);
930 wxPoint2DDouble b_point = vp.GetDoublePixFromLL(td->Lat, td->Lon);
931 wxPoint2DDouble r_point = vp.GetDoublePixFromLL(ref_lat, ref_lon);
932 double ppm = r_point.GetDistance(b_point) / 100.;
933 double offwid = (td->DimC + td->DimD) * ppm * 0.25;
934 double offlen = (td->DimA + td->DimB) * ppm * 0.15;
935 ais_real_size[0].x = -td->DimD * ppm;
936 ais_real_size[0].y = -td->DimB * ppm;
937 ais_real_size[1].x = -td->DimD * ppm;
938 ais_real_size[1].y = td->DimA * ppm - offlen;
939 ais_real_size[2].x = -td->DimD * ppm + offwid;
940 ais_real_size[2].y = td->DimA * ppm;
941 ais_real_size[3].x = td->DimC * ppm - offwid;
942 ais_real_size[3].y = td->DimA * ppm;
943 ais_real_size[4].x = td->DimC * ppm;
944 ais_real_size[4].y = td->DimA * ppm - offlen;
945 ais_real_size[5].x = td->DimC * ppm;
946 ais_real_size[5].y = -td->DimB * ppm;
947
948 if (ais_real_size[4].x - ais_real_size[0].x < 16 ||
949 ais_real_size[2].y - ais_real_size[0].y < 30)
950 bcan_draw_size = false; // drawing too small does not make sense
951 else {
952 bcan_draw_size = true;
953 transrot_pts(6, ais_real_size, sin_theta, cos_theta);
954 }
955 }
956 }
957
958 wxPoint *iconPoints;
959 int nPoints;
960 wxPoint ais_quad_icon[4] = {wxPoint(-8, -6), wxPoint(0, 24), wxPoint(8, -6),
961 wxPoint(0, -6)};
962 wxPoint ais_octo_icon[8] = {wxPoint(4, 8), wxPoint(8, 4), wxPoint(8, -4),
963 wxPoint(4, -8), wxPoint(-4, -8), wxPoint(-8, -4),
964 wxPoint(-8, 4), wxPoint(-4, 8)};
965
966 if (!g_bInlandEcdis) {
967 // to speed up we only calculate scale when not max or minimal
968 if (targetscale == 50) {
969 ais_quad_icon[0] = wxPoint(-4, -3);
970 ais_quad_icon[1] = wxPoint(0, 12);
971 ais_quad_icon[2] = wxPoint(4, -3);
972 ais_quad_icon[3] = wxPoint(0, -3);
973 } else if (targetscale != 100) {
974 ais_quad_icon[0] =
975 wxPoint((int)-8 * targetscale / 100, (int)-6 * targetscale / 100);
976 ais_quad_icon[1] = wxPoint(0, (int)24 * targetscale / 100);
977 ais_quad_icon[2] =
978 wxPoint((int)8 * targetscale / 100, (int)-6 * targetscale / 100);
979 ais_quad_icon[3] = wxPoint(0, (int)-6 * targetscale / 100);
980 }
981
982 // If this is an AIS Class B target, so symbolize it differently
983 if (td->Class == AIS_CLASS_B) ais_quad_icon[3].y = 0;
984
985 if ((td->Class == AIS_GPSG_BUDDY) || (td->b_isFollower)) {
986 ais_quad_icon[0] = wxPoint(-5, -12);
987 ais_quad_icon[1] = wxPoint(-3, 12);
988 ais_quad_icon[2] = wxPoint(3, 12);
989 ais_quad_icon[3] = wxPoint(5, -12);
990 } else if (td->Class == AIS_DSC) {
991 ais_quad_icon[0].y = 0;
992 ais_quad_icon[1].y = 8;
993 ais_quad_icon[2].y = 0;
994 ais_quad_icon[3].y = -8;
995 } else if (td->Class == AIS_APRS) {
996 ais_quad_icon[0] = wxPoint(-8, -8);
997 ais_quad_icon[1] = wxPoint(-8, 8);
998 ais_quad_icon[2] = wxPoint(8, 8);
999 ais_quad_icon[3] = wxPoint(8, -8);
1000 }
1001
1002 transrot_pts(4, ais_quad_icon, sin_theta, cos_theta);
1003
1004 nPoints = 4;
1005 iconPoints = ais_quad_icon;
1006
1007 } else { // iENC
1008 if (b_hdgValid) {
1009 transrot_pts(4, ais_quad_icon, sin_theta, cos_theta);
1010 nPoints = 4;
1011 iconPoints = ais_quad_icon;
1012 } else {
1013 nPoints = 8;
1014 iconPoints = ais_octo_icon;
1015 }
1016 }
1017
1018 wxColour UBLCK = GetGlobalColor(_T ( "UBLCK" ));
1019 dc.SetPen(wxPen(UBLCK));
1020
1021 // Default color is green
1022 wxColour UINFG = GetGlobalColor(_T ( "UINFG" ));
1023 wxBrush target_brush = wxBrush(UINFG);
1024
1025 // Euro Inland targets render slightly differently, unless in InlandENC mode
1026 if (td->b_isEuroInland && !g_bInlandEcdis)
1027 target_brush = wxBrush(GetGlobalColor(_T ( "TEAL1" )));
1028
1029 // Target name comes from cache
1030 if (td->b_nameFromCache)
1031 target_brush = wxBrush(GetGlobalColor(_T ( "GREEN5" )));
1032
1033 // and....
1034 wxColour URED = GetGlobalColor(_T ( "URED" ));
1035 if (!td->b_nameValid) target_brush = wxBrush(GetGlobalColor(_T ( "CHYLW" )));
1036
1037 if ((td->Class == AIS_DSC) &&
1038 ((td->ShipType == 12) || (td->ShipType == 16))) // distress(relayed)
1039 target_brush = wxBrush(URED);
1040
1041 if (td->b_SarAircraftPosnReport) target_brush = wxBrush(UINFG);
1042
1043 if ((td->n_alert_state == AIS_ALERT_SET) && (td->bCPA_Valid))
1044 target_brush = wxBrush(URED);
1045
1046 if ((td->n_alert_state == AIS_ALERT_NO_DIALOG_SET) && (td->bCPA_Valid) &&
1047 (!td->b_isFollower))
1048 target_brush = wxBrush(URED);
1049
1050 if (td->b_positionDoubtful)
1051 target_brush = wxBrush(GetGlobalColor(_T ( "UINFF" )));
1052
1053 wxPen target_outline_pen(UBLCK, AIS_width_target_outline);
1054
1055 // Check for alarms here, maintained by AIS class timer tick
1056 if (((td->n_alert_state == AIS_ALERT_SET) && (td->bCPA_Valid)) ||
1057 (td->b_show_AIS_CPA && (td->bCPA_Valid))) {
1058 // Calculate the point of CPA for target
1059 double tcpa_lat, tcpa_lon;
1060 ll_gc_ll(td->Lat, td->Lon, td->COG, target_sog * td->TCPA / 60., &tcpa_lat,
1061 &tcpa_lon);
1062 wxPoint tCPAPoint;
1063 wxPoint TPoint = TargetPoint;
1064 GetCanvasPointPix(vp, cp, tcpa_lat, tcpa_lon, &tCPAPoint);
1065
1066 // Draw the intercept line from target
1067 ClipResult res = cohen_sutherland_line_clip_i(
1068 &TPoint.x, &TPoint.y, &tCPAPoint.x, &tCPAPoint.y, 0, vp.pix_width, 0,
1069 vp.pix_height);
1070
1071 if (res != Invisible) {
1072 wxPen ppPen2(URED, AIS_width_cogpredictor_line, wxPENSTYLE_USER_DASH);
1073 ppPen2.SetDashes(2, dash_long);
1074 dc.SetPen(ppPen2);
1075
1076 dc.StrokeLine(TPoint.x, TPoint.y, tCPAPoint.x, tCPAPoint.y);
1077 }
1078
1079 // Calculate the point of CPA for ownship
1080 double ocpa_lat, ocpa_lon;
1081
1082 // Detect and handle the case where ownship COG is undefined....
1083 if (std::isnan(gCog) || std::isnan(gSog)) {
1084 ocpa_lat = gLat;
1085 ocpa_lon = gLon;
1086 } else {
1087 ll_gc_ll(gLat, gLon, gCog, gSog * td->TCPA / 60., &ocpa_lat, &ocpa_lon);
1088 }
1089
1090 wxPoint oCPAPoint;
1091
1092 GetCanvasPointPix(vp, cp, ocpa_lat, ocpa_lon, &oCPAPoint);
1093 GetCanvasPointPix(vp, cp, tcpa_lat, tcpa_lon, &tCPAPoint);
1094
1095 // Save a copy of these unclipped points
1096 wxPoint oCPAPoint_unclipped = oCPAPoint;
1097 wxPoint tCPAPoint_unclipped = tCPAPoint;
1098
1099 // Draw a line from target CPA point to ownship CPA point
1100 ClipResult ores = cohen_sutherland_line_clip_i(
1101 &tCPAPoint.x, &tCPAPoint.y, &oCPAPoint.x, &oCPAPoint.y, 0, vp.pix_width,
1102 0, vp.pix_height);
1103
1104 if (ores != Invisible) {
1105 wxColour yellow = GetGlobalColor(_T ( "YELO1" ));
1106 dc.SetPen(wxPen(yellow, AIS_width_interceptbar_base));
1107 dc.StrokeLine(tCPAPoint.x, tCPAPoint.y, oCPAPoint.x, oCPAPoint.y);
1108
1109 wxPen ppPen2(URED, AIS_width_interceptbar_top, wxPENSTYLE_USER_DASH);
1110 ppPen2.SetDashes(2, dash_long);
1111 dc.SetPen(ppPen2);
1112 dc.StrokeLine(tCPAPoint.x, tCPAPoint.y, oCPAPoint.x, oCPAPoint.y);
1113
1114 // Draw little circles at the ends of the CPA alert line
1115 wxBrush br(GetGlobalColor(_T ( "BLUE3" )));
1116 dc.SetBrush(br);
1117 dc.SetPen(wxPen(UBLCK, AIS_width_target_outline));
1118
1119 // Using the true ends, not the clipped ends
1120 dc.StrokeCircle(
1121 tCPAPoint_unclipped.x, tCPAPoint_unclipped.y,
1122 AIS_intercept_bar_circle_diameter * AIS_user_scale_factor);
1123 dc.StrokeCircle(
1124 oCPAPoint_unclipped.x, oCPAPoint_unclipped.y,
1125 AIS_intercept_bar_circle_diameter * AIS_user_scale_factor);
1126 }
1127
1128 // Draw the intercept line from ownship
1129 wxPoint oShipPoint;
1130 GetCanvasPointPix(vp, cp, gLat, gLon, &oShipPoint);
1131 oCPAPoint = oCPAPoint_unclipped; // recover the unclipped point
1132
1133 ClipResult ownres = cohen_sutherland_line_clip_i(
1134 &oShipPoint.x, &oShipPoint.y, &oCPAPoint.x, &oCPAPoint.y, 0,
1135 vp.pix_width, 0, vp.pix_height);
1136
1137 if (ownres != Invisible) {
1138 wxPen ppPen2(URED, AIS_width_interceptline, wxPENSTYLE_USER_DASH);
1139 ppPen2.SetDashes(2, dash_long);
1140 dc.SetPen(ppPen2);
1141
1142 dc.StrokeLine(oShipPoint.x, oShipPoint.y, oCPAPoint.x, oCPAPoint.y);
1143 } // TR : till here
1144
1145 dc.SetPen(wxPen(UBLCK));
1146 dc.SetBrush(wxBrush(URED));
1147 }
1148
1149 // Highlight the AIS target symbol if an alert dialog is currently open for
1150 // it
1151 if (cp != NULL) {
1152 auto alert_dlg_active =
1153 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
1154 if (alert_dlg_active && alert_dlg_active->IsShown() && cp) {
1155 if (alert_dlg_active->Get_Dialog_MMSI() == td->MMSI)
1156 cp->JaggyCircle(dc, wxPen(URED, 2), TargetPoint.x, TargetPoint.y, 100);
1157 }
1158 }
1159
1160 // Highlight the AIS target symbol if a query dialog is currently open for it
1161 if (g_pais_query_dialog_active && g_pais_query_dialog_active->IsShown()) {
1162 if (g_pais_query_dialog_active->GetMMSI() == td->MMSI)
1163 TargetFrame(dc, wxPen(UBLCK, 2), TargetPoint.x, TargetPoint.y, 25);
1164 }
1165
1166 // Render the COG line if the speed is greater than moored speed defined
1167 // by ais options dialog
1168 if ((g_bShowCOG) && (target_sog > g_SOGminCOG_kts) && td->b_active) {
1169 int pixx = TargetPoint.x;
1170 int pixy = TargetPoint.y;
1171 int pixx1 = PredPoint.x;
1172 int pixy1 = PredPoint.y;
1173
1174 // Don't draw the COG line and predictor point if zoomed far out.... or if
1175 // target lost/inactive
1176 float l = sqrtf(powf((float)(PredPoint.x - TargetPoint.x), 2) +
1177 powf((float)(PredPoint.y - TargetPoint.y), 2));
1178
1179 if (l > 24) {
1180 ClipResult res = cohen_sutherland_line_clip_i(
1181 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
1182
1183 if (res != Invisible) {
1184 // Draw a wider coloured line
1185 if (targetscale >= 75) {
1186 wxPen wide_pen(target_brush.GetColour(), AIS_width_cogpredictor_base);
1187 dc.SetPen(wide_pen);
1188 dc.StrokeLine(pixx, pixy, pixx1, pixy1);
1189 }
1190
1191 if (AIS_width_cogpredictor_base > 1) {
1192 // Draw narrow black line
1193 wxPen narrow_pen(UBLCK, AIS_width_cogpredictor_line);
1194 if (targetscale < 75) {
1195 narrow_pen.SetWidth(1);
1196 narrow_pen.SetStyle(wxPENSTYLE_USER_DASH);
1197 wxDash dash_dot[2];
1198 dash_dot[0] = 2;
1199 dash_dot[1] = 2;
1200 narrow_pen.SetDashes(2, dash_dot);
1201 }
1202 dc.SetPen(narrow_pen);
1203 dc.StrokeLine(pixx, pixy, pixx1, pixy1);
1204 }
1205
1206 if (dc.GetDC()) {
1207 dc.SetBrush(target_brush);
1208 dc.StrokeCircle(PredPoint.x, PredPoint.y, 5 * targetscale / 100);
1209 } else {
1210#ifdef ocpnUSE_GL
1211
1212// #ifndef USE_ANDROID_GLES2
1213#if !defined(USE_ANDROID_GLES2) && !defined(ocpnUSE_GLSL)
1214
1215 glPushMatrix();
1216 glTranslated(PredPoint.x, PredPoint.y, 0);
1217 glScalef(AIS_scale_factor, AIS_scale_factor, AIS_scale_factor);
1218 // draw circle
1219 float points[] = {0.0f, 5.0f, 2.5f, 4.330127f, 4.330127f,
1220 2.5f, 5.0f, 0, 4.330127f, -2.5f,
1221 2.5f, -4.330127f, 0, -5.1f, -2.5f,
1222 -4.330127f, -4.330127f, -2.5f, -5.0f, 0,
1223 -4.330127f, 2.5f, -2.5f, 4.330127f, 0,
1224 5.0f};
1225 if (targetscale <= 75) {
1226 for (unsigned int i = 0; i < (sizeof points) / (sizeof *points);
1227 i++)
1228 points[i] = points[i] / 2;
1229 }
1230
1231 wxColour c = target_brush.GetColour();
1232 glColor3ub(c.Red(), c.Green(), c.Blue());
1233
1234 glBegin(GL_TRIANGLE_FAN);
1235 for (unsigned int i = 0; i < (sizeof points) / (sizeof *points);
1236 i += 2)
1237 glVertex2i(points[i], points[i + 1]);
1238 glEnd();
1239
1240 glColor3ub(0, 0, 0);
1241 glLineWidth(AIS_width_target_outline);
1242 glBegin(GL_LINE_LOOP);
1243 for (unsigned int i = 0; i < (sizeof points) / (sizeof *points);
1244 i += 2)
1245 glVertex2i(points[i], points[i + 1]);
1246 glEnd();
1247 glPopMatrix();
1248#else
1249
1250 dc.SetBrush(target_brush);
1251 dc.StrokeCircle(PredPoint.x, PredPoint.y,
1252 AIS_intercept_bar_circle_diameter *
1253 AIS_user_scale_factor * targetscale / 100);
1254#endif
1255#endif
1256 }
1257 }
1258
1259 // Draw RateOfTurn Vector
1260 if ((td->ROTAIS != 0) && (td->ROTAIS != -128) && (!g_bShowScaled)) {
1261 float cog_angle = td->COG * PI / 180.;
1262
1263 float theta2 = theta; // ownship drawn angle
1264 if (td->SOG >= g_SOGminCOG_kts)
1265 theta2 = cog_angle - (PI / 2); // actual cog angle
1266
1267 float nv = 10;
1268 if (td->ROTAIS > 0)
1269 theta2 += (float)PI / 2;
1270 else
1271 theta2 -= (float)PI / 2;
1272
1273 int xrot = (int)round(pixx1 + (nv * cosf(theta2)));
1274 int yrot = (int)round(pixy1 + (nv * sinf(theta2)));
1275 dc.StrokeLine(pixx1, pixy1, xrot, yrot);
1276 }
1277 }
1278 }
1279
1280 // Actually Draw the target
1281 if (td->Class == AIS_ARPA || td->Class == AIS_BUOY) {
1282 wxPen target_pen(UBLCK, 2);
1283
1284 dc.SetPen(target_pen);
1285 dc.SetBrush(target_brush);
1286 dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 1.8 * AIS_icon_diameter);
1287
1288 dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 1);
1289 // Draw the inactive cross-out line
1290 if (!td->b_active) {
1291 dc.SetPen(wxPen(UBLCK, 2));
1292 dc.StrokeLine(TargetPoint.x - 14, TargetPoint.y, TargetPoint.x + 14,
1293 TargetPoint.y);
1294 dc.SetPen(wxPen(UBLCK, 1));
1295 }
1296
1297 } else if (td->Class == AIS_METEO) { // Meteorologic
1298 wxPen met(UBLCK, (wxMax(target_outline_pen.GetWidth(), 2.5)));
1299 dc.SetPen(met);
1300 dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
1301 double met_radius = 1.8 * AIS_icon_diameter;
1302 dc.StrokeCircle(TargetPoint.x, TargetPoint.y, met_radius);
1303
1304 /* Inscribed "W" in the circle. */
1305 dc.SetPen(wxPen(wxMax(target_outline_pen.GetWidth(), 1)));
1306 // Left part
1307 dc.StrokeLine(TargetPoint.x, TargetPoint.y - met_radius / 4,
1308 TargetPoint.x - met_radius / 3,
1309 TargetPoint.y + met_radius / 2);
1310 dc.StrokeLine(
1311 TargetPoint.x - met_radius / 3, TargetPoint.y + met_radius / 2,
1312 TargetPoint.x - met_radius / 2, TargetPoint.y - met_radius / 2);
1313 // Right part
1314 dc.StrokeLine(TargetPoint.x, TargetPoint.y - met_radius / 4,
1315 TargetPoint.x + met_radius / 3,
1316 TargetPoint.y + met_radius / 2);
1317 dc.StrokeLine(
1318 TargetPoint.x + met_radius / 3, TargetPoint.y + met_radius / 2,
1319 TargetPoint.x + met_radius / 2, TargetPoint.y - met_radius / 2);
1320
1321 } else if (td->Class == AIS_ATON) { // Aid to Navigation
1322 AtoN_Diamond(dc, wxPen(UBLCK, AIS_width_target_outline), TargetPoint.x,
1323 TargetPoint.y, AIS_icon_diameter * 1.5, td);
1324 } else if (td->Class == AIS_BASE) { // Base Station
1325 Base_Square(dc, wxPen(UBLCK, AIS_width_target_outline), TargetPoint.x,
1326 TargetPoint.y, AIS_icon_diameter);
1327 } else if (td->Class == AIS_SART) { // SART Target
1328 if (td->NavStatus == 14) // active
1329 SART_Render(dc, wxPen(URED, AIS_width_target_outline), TargetPoint.x,
1330 TargetPoint.y, AIS_icon_diameter);
1331 else
1332 SART_Render(
1333 dc, wxPen(GetGlobalColor(_T ( "UGREN" )), AIS_width_target_outline),
1334 TargetPoint.x, TargetPoint.y, AIS_icon_diameter);
1335
1336 } else if (td->b_SarAircraftPosnReport) {
1337 int airtype = (td->MMSI % 1000) / 100; // xxxyyy5zz >> helicopter
1338 int ar = airtype == 5 ? 15 : 9; // array size
1339 wxPoint SarIcon[15];
1340 wxPoint SarRot[15];
1341 double scaleplus = 1.4;
1342 if (airtype == 5) {
1343 SarIcon[0] = wxPoint(0, 9) * AIS_scale_factor * scaleplus;
1344 SarIcon[1] = wxPoint(1, 1) * AIS_scale_factor * scaleplus;
1345 SarIcon[2] = wxPoint(2, 1) * AIS_scale_factor * scaleplus;
1346 SarIcon[3] = wxPoint(9, 8) * AIS_scale_factor * scaleplus;
1347 SarIcon[4] = wxPoint(9, 7) * AIS_scale_factor * scaleplus;
1348 SarIcon[5] = wxPoint(3, 0) * AIS_scale_factor * scaleplus;
1349 SarIcon[6] = wxPoint(3, -5) * AIS_scale_factor * scaleplus;
1350 SarIcon[7] = wxPoint(9, -12) * AIS_scale_factor * scaleplus;
1351 SarIcon[8] = wxPoint(9, -13) * AIS_scale_factor * scaleplus;
1352 SarIcon[9] = wxPoint(2, -5) * AIS_scale_factor * scaleplus;
1353 SarIcon[10] = wxPoint(1, -15) * AIS_scale_factor * scaleplus;
1354 SarIcon[11] = wxPoint(3, -16) * AIS_scale_factor * scaleplus;
1355 SarIcon[12] = wxPoint(4, -18) * AIS_scale_factor * scaleplus;
1356 SarIcon[13] = wxPoint(1, -18) * AIS_scale_factor * scaleplus;
1357 SarIcon[14] = wxPoint(0, -19) * AIS_scale_factor * scaleplus;
1358 } else {
1359 SarIcon[0] = wxPoint(0, 12) * AIS_scale_factor;
1360 SarIcon[1] = wxPoint(4, 2) * AIS_scale_factor;
1361 SarIcon[2] = wxPoint(16, -2) * AIS_scale_factor;
1362 SarIcon[3] = wxPoint(16, -8) * AIS_scale_factor;
1363 SarIcon[4] = wxPoint(4, -8) * AIS_scale_factor;
1364 SarIcon[5] = wxPoint(3, -16) * AIS_scale_factor;
1365 SarIcon[6] = wxPoint(10, -18) * AIS_scale_factor;
1366 SarIcon[7] = wxPoint(10, -22) * AIS_scale_factor;
1367 SarIcon[8] = wxPoint(0, -22) * AIS_scale_factor;
1368 }
1369
1370 if (airtype == 5) { // helicopter
1371 // Draw icon as two halves
1372 // First half
1373
1374 for (int i = 0; i < ar; i++) SarRot[i] = SarIcon[i];
1375 transrot_pts(ar, SarRot, sin_theta, cos_theta);
1376
1377 wxPen tri_pen(target_brush.GetColour(), 1);
1378 dc.SetPen(tri_pen);
1379 dc.SetBrush(target_brush);
1380
1381 // Manual tesselation
1382 int mappings[14][3] = {
1383 {0, 1, 10}, {0, 10, 14}, {1, 2, 9}, {1, 9, 10}, {10, 13, 14},
1384 {10, 11, 13}, {11, 12, 13}, {1, 14, 10}, {2, 5, 9}, {5, 6, 9},
1385 {2, 3, 5}, {3, 4, 5}, {6, 7, 8}, {6, 9, 8}};
1386
1387 int nmap = 14;
1388 for (int i = 0; i < nmap; i++) {
1389 wxPoint ais_tri_icon[3];
1390 for (int j = 0; j < 3; j++) ais_tri_icon[j] = SarRot[mappings[i][j]];
1391 dc.StrokePolygon(3, ais_tri_icon, TargetPoint.x, TargetPoint.y);
1392 }
1393
1394 dc.SetPen(target_outline_pen);
1395 dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
1396 dc.StrokePolygon(ar, SarRot, TargetPoint.x, TargetPoint.y);
1397
1398 // second half
1399
1400 for (int i = 0; i < ar; i++)
1401 SarRot[i] =
1402 wxPoint(-SarIcon[i].x, SarIcon[i].y); // mirror the icon (x -> -x)
1403
1404 transrot_pts(ar, SarRot, sin_theta, cos_theta);
1405
1406 dc.SetPen(tri_pen);
1407 dc.SetBrush(target_brush);
1408
1409 for (int i = 0; i < nmap; i++) {
1410 wxPoint ais_tri_icon[3];
1411 for (int j = 0; j < 3; j++) ais_tri_icon[j] = SarRot[mappings[i][j]];
1412 dc.StrokePolygon(3, ais_tri_icon, TargetPoint.x, TargetPoint.y);
1413 }
1414
1415 dc.SetPen(target_outline_pen);
1416 dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
1417 dc.StrokePolygon(ar, SarRot, TargetPoint.x, TargetPoint.y);
1418 } else {
1419 // Draw icon as two halves
1420 // First half
1421
1422 for (int i = 0; i < ar; i++) SarRot[i] = SarIcon[i];
1423 transrot_pts(ar, SarRot, sin_theta, cos_theta);
1424
1425 wxPen tri_pen(target_brush.GetColour(), 1);
1426 dc.SetPen(tri_pen);
1427 dc.SetBrush(target_brush);
1428
1429 // Manual tesselation
1430 int mappings[7][3] = {{0, 1, 4}, {1, 2, 3}, {1, 3, 4}, {0, 4, 5},
1431 {0, 5, 8}, {5, 6, 7}, {5, 7, 8}};
1432 for (int i = 0; i < 7; i++) {
1433 wxPoint ais_tri_icon[3];
1434 for (int j = 0; j < 3; j++) ais_tri_icon[j] = SarRot[mappings[i][j]];
1435 dc.StrokePolygon(3, ais_tri_icon, TargetPoint.x, TargetPoint.y);
1436 }
1437
1438 dc.SetPen(target_outline_pen);
1439 dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
1440 dc.StrokePolygon(ar, SarRot, TargetPoint.x, TargetPoint.y);
1441
1442 // second half
1443
1444 for (int i = 0; i < ar; i++)
1445 SarRot[i] =
1446 wxPoint(-SarIcon[i].x, SarIcon[i].y); // mirror the icon (x -> -x)
1447
1448 transrot_pts(ar, SarRot, sin_theta, cos_theta);
1449
1450 dc.SetPen(tri_pen);
1451 dc.SetBrush(target_brush);
1452
1453 for (int i = 0; i < 7; i++) {
1454 wxPoint ais_tri_icon[3];
1455 for (int j = 0; j < 3; j++) ais_tri_icon[j] = SarRot[mappings[i][j]];
1456 dc.StrokePolygon(3, ais_tri_icon, TargetPoint.x, TargetPoint.y);
1457 }
1458
1459 dc.SetPen(target_outline_pen);
1460 dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
1461 dc.StrokePolygon(ar, SarRot, TargetPoint.x, TargetPoint.y);
1462 }
1463
1464 // Draw the inactive cross-out line
1465 if (!td->b_active) {
1466 dc.SetPen(wxPen(UBLCK, 3));
1467 dc.StrokeLine(TargetPoint.x - 16, TargetPoint.y, TargetPoint.x + 16,
1468 TargetPoint.y);
1469 }
1470
1471 } else { // ship class A or B or a Buddy or DSC
1472 wxPen target_pen(UBLCK, 1);
1473 dc.SetPen(target_pen);
1474
1475 wxPoint Point = TargetPoint;
1476 if (g_bDrawAISRealtime &&
1477 (td->Class == AIS_CLASS_A || td->Class == AIS_CLASS_B) &&
1478 td->SOG > g_AIS_RealtPred_Kts && td->SOG < 102.2) {
1479 wxDateTime now = wxDateTime::Now();
1480 now.MakeGMT();
1481 int target_age = now.GetTicks() - td->PositionReportTicks;
1482
1483 float lat, lon;
1484 spherical_ll_gc_ll(td->Lat, td->Lon, td->COG,
1485 td->SOG * target_age / 3600.0, &lat, &lon);
1486
1487 GetCanvasPointPix(vp, cp, lat, lon, &Point);
1488
1489 wxBrush realtime_brush = wxBrush(GetGlobalColor("GREY1"));
1490 dc.SetBrush(realtime_brush);
1491 dc.StrokePolygon(nPoints, iconPoints, Point.x, Point.y, AIS_scale_factor);
1492 }
1493 dc.SetBrush(target_brush);
1494
1495 if (dc.GetDC()) {
1496 dc.StrokePolygon(nPoints, iconPoints, TargetPoint.x, TargetPoint.y,
1497 AIS_scale_factor);
1498 } else {
1499#ifdef ocpnUSE_GL
1500// #ifndef USE_ANDROID_GLES2
1501#if !defined(USE_ANDROID_GLES2) && !defined(ocpnUSE_GLSL)
1502
1503 wxColour c = target_brush.GetColour();
1504 glColor3ub(c.Red(), c.Green(), c.Blue());
1505
1506 glPushMatrix();
1507 glTranslated(TargetPoint.x, TargetPoint.y, 0);
1508 glScalef(AIS_scale_factor, AIS_scale_factor, AIS_scale_factor);
1509
1510 glBegin(GL_TRIANGLE_FAN);
1511
1512 if (nPoints == 4) {
1513 glVertex2i(ais_quad_icon[3].x, ais_quad_icon[3].y);
1514 glVertex2i(ais_quad_icon[0].x, ais_quad_icon[0].y);
1515 glVertex2i(ais_quad_icon[1].x, ais_quad_icon[1].y);
1516 glVertex2i(ais_quad_icon[2].x, ais_quad_icon[2].y);
1517 } else {
1518 for (int i = 0; i < 8; i++) {
1519 glVertex2i(iconPoints[i].x, iconPoints[i].y);
1520 }
1521 }
1522
1523 glEnd();
1524 glLineWidth(AIS_width_target_outline);
1525
1526 glColor3ub(UBLCK.Red(), UBLCK.Green(), UBLCK.Blue());
1527
1528 glBegin(GL_LINE_LOOP);
1529 for (int i = 0; i < nPoints; i++)
1530 glVertex2i(iconPoints[i].x, iconPoints[i].y);
1531 glEnd();
1532 glPopMatrix();
1533
1534#else
1535 dc.SetPen(target_outline_pen);
1536 dc.DrawPolygon(nPoints, iconPoints, TargetPoint.x, TargetPoint.y,
1537 AIS_scale_factor);
1538#endif
1539#endif
1540 }
1541 // Draw stroke "inverted v" for GPS Follower
1542 if (td->b_isFollower) {
1543 wxPoint ais_follow_stroke[3];
1544 ais_follow_stroke[0] = wxPoint(-3, -20) * AIS_scale_factor;
1545 ais_follow_stroke[1] = wxPoint(0, 0) * AIS_scale_factor;
1546 ais_follow_stroke[2] = wxPoint(3, -20) * AIS_scale_factor;
1547
1548 transrot_pts(3, ais_follow_stroke, sin_theta, cos_theta);
1549
1550 int penWidth = wxMax(target_outline_pen.GetWidth(), 2);
1551 dc.SetPen(wxPen(UBLCK, penWidth));
1552 dc.StrokeLine(ais_follow_stroke[0].x + TargetPoint.x,
1553 ais_follow_stroke[0].y + TargetPoint.y,
1554 ais_follow_stroke[1].x + TargetPoint.x,
1555 ais_follow_stroke[1].y + TargetPoint.y);
1556 dc.StrokeLine(ais_follow_stroke[1].x + TargetPoint.x,
1557 ais_follow_stroke[1].y + TargetPoint.y,
1558 ais_follow_stroke[2].x + TargetPoint.x,
1559 ais_follow_stroke[2].y + TargetPoint.y);
1560 }
1561
1562 if (g_bDrawAISSize && bcan_draw_size) {
1563 dc.SetPen(target_outline_pen);
1564 dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
1565 if (!g_bInlandEcdis) {
1566 dc.StrokePolygon(6, ais_real_size, TargetPoint.x, TargetPoint.y, 1.0);
1567 } else {
1568 if (b_hdgValid) {
1569 dc.StrokePolygon(6, ais_real_size, TargetPoint.x, TargetPoint.y, 1.0);
1570 }
1571 }
1572 }
1573
1574 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "SHIPS" ))));
1575 int navstatus = td->NavStatus;
1576
1577 // HSC usually have correct ShipType but navstatus == 0...
1578 // Class B can have (HSC)ShipType but never navstatus.
1579 if (((td->ShipType >= 40) && (td->ShipType < 50)) &&
1580 (navstatus == UNDERWAY_USING_ENGINE || td->Class == AIS_CLASS_B))
1581 navstatus = HSC;
1582
1583 if (targetscale > 90) {
1584 switch (navstatus) {
1585 case MOORED:
1586 case AT_ANCHOR: {
1587 dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4 * AIS_scale_factor);
1588 break;
1589 }
1590 case RESTRICTED_MANOEUVRABILITY: {
1591 wxPoint diamond[4];
1592 diamond[0] = wxPoint(4, 0) * AIS_scale_factor;
1593 diamond[1] = wxPoint(0, -6) * AIS_scale_factor;
1594 diamond[2] = wxPoint(-4, 0) * AIS_scale_factor;
1595 diamond[3] = wxPoint(0, 6) * AIS_scale_factor;
1596 dc.StrokePolygon(4, diamond, TargetPoint.x,
1597 TargetPoint.y - (11 * AIS_scale_factor));
1598 dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4 * AIS_scale_factor);
1599 dc.StrokeCircle(TargetPoint.x,
1600 TargetPoint.y - (22 * AIS_scale_factor),
1601 4 * AIS_scale_factor);
1602 break;
1603 break;
1604 }
1605 case CONSTRAINED_BY_DRAFT: {
1606 wxPoint can[4] = {wxPoint(-3, 0) * AIS_scale_factor,
1607 wxPoint(3, 0) * AIS_scale_factor,
1608 wxPoint(3, -16) * AIS_scale_factor,
1609 wxPoint(-3, -16) * AIS_scale_factor};
1610 dc.StrokePolygon(4, can, TargetPoint.x, TargetPoint.y);
1611 break;
1612 }
1613 case NOT_UNDER_COMMAND: {
1614 dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4 * AIS_scale_factor);
1615 dc.StrokeCircle(TargetPoint.x, TargetPoint.y - 9,
1616 4 * AIS_scale_factor);
1617 break;
1618 }
1619 case FISHING: {
1620 wxPoint tri[3];
1621 tri[0] = wxPoint(-4, 0) * AIS_scale_factor;
1622 tri[1] = wxPoint(4, 0) * AIS_scale_factor;
1623 tri[2] = wxPoint(0, -9) * AIS_scale_factor;
1624 dc.StrokePolygon(3, tri, TargetPoint.x, TargetPoint.y);
1625 tri[0] = wxPoint(0, -9) * AIS_scale_factor;
1626 tri[1] = wxPoint(4, -18) * AIS_scale_factor;
1627 tri[2] = wxPoint(-4, -18) * AIS_scale_factor;
1628 dc.StrokePolygon(3, tri, TargetPoint.x, TargetPoint.y);
1629 break;
1630 }
1631 case AGROUND: {
1632 dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4 * AIS_scale_factor);
1633 dc.StrokeCircle(TargetPoint.x, TargetPoint.y - 9,
1634 4 * AIS_scale_factor);
1635 dc.StrokeCircle(TargetPoint.x, TargetPoint.y - 18,
1636 4 * AIS_scale_factor);
1637 break;
1638 }
1639 case HSC:
1640 case WIG: {
1641 dc.SetBrush(target_brush);
1642
1643 wxPoint arrow1[3] = {wxPoint(-4, 20) * AIS_scale_factor,
1644 wxPoint(0, 27) * AIS_scale_factor,
1645 wxPoint(4, 20) * AIS_scale_factor};
1646 transrot_pts(3, arrow1, sin_theta, cos_theta, TargetPoint);
1647 dc.StrokePolygon(3, arrow1);
1648
1649 wxPoint arrow2[3] = {wxPoint(-4, 27) * AIS_scale_factor,
1650 wxPoint(0, 34) * AIS_scale_factor,
1651 wxPoint(4, 27) * AIS_scale_factor};
1652 transrot_pts(3, arrow2, sin_theta, cos_theta, TargetPoint);
1653 dc.StrokePolygon(3, arrow2);
1654 break;
1655 }
1656 }
1657 } // end if (targetscale > 75)
1658
1659 // Draw the inactive cross-out line
1660 if (!td->b_active) {
1661 wxPoint p1 = transrot(wxPoint((int)-14 * targetscale / 100, 0), sin_theta,
1662 cos_theta, TargetPoint);
1663 wxPoint p2 = transrot(wxPoint((int)14 * targetscale / 100, 0), sin_theta,
1664 cos_theta, TargetPoint);
1665
1666 dc.SetPen(wxPen(UBLCK, 2));
1667 dc.StrokeLine(p1.x, p1.y, p2.x, p2.y);
1668 }
1669
1670 // European Inland AIS define a "stbd-stbd" meeting sign, a blue paddle.
1671 // Symbolize it if set by most recent message
1672 // Blue paddel is used while "not engaged"(1) or "engaged"(2) (3 ==
1673 // "reserved")
1674 if (td->blue_paddle && td->blue_paddle < 3) {
1675 wxPoint ais_flag_icon[4];
1676 int penWidth = 2;
1677
1678 if (g_bInlandEcdis) {
1679 if (b_hdgValid) {
1680 ais_flag_icon[0] = wxPoint(-4, 4);
1681 ais_flag_icon[1] = wxPoint(-4, 11);
1682 ais_flag_icon[2] = wxPoint(-11, 11);
1683 ais_flag_icon[3] = wxPoint(-11, 4);
1684 transrot_pts(4, ais_flag_icon, sin_theta, cos_theta, TargetPoint);
1685 } else {
1686 ais_flag_icon[0] = wxPoint(TargetPoint.x - 4, TargetPoint.y + 4);
1687 ais_flag_icon[1] = wxPoint(TargetPoint.x - 4, TargetPoint.y - 3);
1688 ais_flag_icon[2] = wxPoint(TargetPoint.x + 3, TargetPoint.y - 3);
1689 ais_flag_icon[3] = wxPoint(TargetPoint.x + 3, TargetPoint.y + 4);
1690 }
1691
1692 dc.SetPen(wxPen(GetGlobalColor(_T ( "CHWHT" )), penWidth));
1693
1694 } else {
1695 ais_flag_icon[0] =
1696 wxPoint((int)-8 * targetscale / 100, (int)-6 * targetscale / 100);
1697 ais_flag_icon[1] =
1698 wxPoint((int)-2 * targetscale / 100, (int)18 * targetscale / 100);
1699 ais_flag_icon[2] = wxPoint((int)-2 * targetscale / 100, 0);
1700 ais_flag_icon[3] =
1701 wxPoint((int)-2 * targetscale / 100, (int)-6 * targetscale / 100);
1702 transrot_pts(4, ais_flag_icon, sin_theta, cos_theta, TargetPoint);
1703
1704 if (targetscale < 100) penWidth = 1;
1705 dc.SetPen(wxPen(GetGlobalColor(_T ( "CHWHT" )), penWidth));
1706 }
1707 if (td->blue_paddle == 1) {
1708 ais_flag_icon[1] = ais_flag_icon[0];
1709 ais_flag_icon[2] = ais_flag_icon[3];
1710 }
1711
1712 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UINFB" ))));
1713 dc.StrokePolygon(4, ais_flag_icon);
1714 }
1715 }
1716
1717 if ((g_bShowAISName) && (targetscale > 75)) {
1718 int true_scale_display = (int)(floor(vp.chart_scale / 100.) * 100);
1719 if (true_scale_display <
1720 g_Show_Target_Name_Scale) { // from which scale to display name
1721
1722 wxString tgt_name = td->GetFullName();
1723 tgt_name = tgt_name.substr(0, tgt_name.find(_T ( "Unknown" ), 0));
1724
1725 if (tgt_name != wxEmptyString) {
1726 dc.SetFont(*AIS_NameFont);
1727 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("AIS Target Name")));
1728
1729 int w, h;
1730 dc.GetTextExtent(_T("W"), &w, &h);
1731 h *= g_Platform->GetDisplayDIPMult(gFrame);
1732 w *= g_Platform->GetDisplayDIPMult(gFrame);
1733
1734 if ((td->COG > 90) && (td->COG < 180))
1735 dc.DrawText(tgt_name, TargetPoint.x + w, TargetPoint.y - h);
1736 else
1737 dc.DrawText(tgt_name, TargetPoint.x + w,
1738 TargetPoint.y /*+ (0.5 * h)*/);
1739
1740 } // If name do not empty
1741 } // if scale
1742 }
1743
1744 // Draw tracks if enabled
1745 // Check the Special MMSI Properties array
1746 bool b_noshow = false;
1747 bool b_forceshow = false;
1748 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
1749 if (td->MMSI == g_MMSI_Props_Array[i]->MMSI) {
1750 MmsiProperties *props = g_MMSI_Props_Array[i];
1751 if (TRACKTYPE_NEVER == props->TrackType) {
1752 b_noshow = true;
1753 break;
1754 } else if (TRACKTYPE_ALWAYS == props->TrackType) {
1755 b_forceshow = true;
1756 break;
1757 } else
1758 break;
1759 }
1760 }
1761
1762 int TrackLength = td->m_ptrack.size();
1763 if (((!b_noshow && td->b_show_track) || b_forceshow) && (TrackLength > 1)) {
1764 // create vector of x-y points
1765 int TrackPointCount;
1766 wxPoint *TrackPoints = 0;
1767 TrackPoints = new wxPoint[TrackLength];
1768 auto it = td->m_ptrack.begin();
1769 for (TrackPointCount = 0;
1770 it != td->m_ptrack.end() && (TrackPointCount < TrackLength);
1771 TrackPointCount++, ++it) {
1772 const AISTargetTrackPoint &ptrack_point = *it;
1773 GetCanvasPointPix(vp, cp, ptrack_point.m_lat, ptrack_point.m_lon,
1774 &TrackPoints[TrackPointCount]);
1775 }
1776
1777 wxColour c = GetGlobalColor(_T ( "CHMGD" ));
1778 dc.SetPen(wxPen(c, 1.5 * AIS_nominal_line_width_pix));
1779
1780 // Check for any persistently tracked target
1781 // Render persistently tracked targets slightly differently.
1782 std::map<int, Track *>::iterator itt;
1783 itt = g_pAIS->m_persistent_tracks.find(td->MMSI);
1784 if (itt != g_pAIS->m_persistent_tracks.end()) {
1785 auto *ptrack = itt->second;
1786 if (ptrack->m_Colour == wxEmptyString) {
1787 c = GetGlobalColor(_T ( "TEAL1" ));
1788 dc.SetPen(wxPen(c, 2.0 * AIS_nominal_line_width_pix));
1789 } else {
1790 for (unsigned int i = 0;
1791 i < sizeof(::GpxxColorNames) / sizeof(wxString); i++) {
1792 if (ptrack->m_Colour == ::GpxxColorNames[i]) {
1793 c = ::GpxxColors[i];
1794 dc.SetPen(wxPen(c, 2.0 * AIS_nominal_line_width_pix));
1795 break;
1796 }
1797 }
1798 }
1799 }
1800
1801#ifdef ocpnUSE_GL
1802#if !defined(USE_ANDROID_GLES2) && !defined(ocpnUSE_GLSL)
1803
1804 if (!dc.GetDC()) {
1805 glLineWidth(2);
1806 glColor3ub(c.Red(), c.Green(), c.Blue());
1807 glBegin(GL_LINE_STRIP);
1808
1809 for (TrackPointCount = 0; TrackPointCount < TrackLength;
1810 TrackPointCount++)
1811 glVertex2i(TrackPoints[TrackPointCount].x,
1812 TrackPoints[TrackPointCount].y);
1813
1814 glEnd();
1815 } else {
1816 dc.DrawLines(TrackPointCount, TrackPoints);
1817 }
1818#else
1819 dc.DrawLines(TrackPointCount, TrackPoints);
1820#endif
1821
1822#else
1823 if (dc.GetDC()) dc.StrokeLines(TrackPointCount, TrackPoints);
1824
1825#endif
1826
1827 delete[] TrackPoints;
1828
1829 } // Draw tracks
1830}
1831
1832void AISDraw(ocpnDC &dc, ViewPort &vp, ChartCanvas *cp) {
1833 if (!g_pAIS) return;
1834
1835 // Toggling AIS display on and off
1836 if (cp != NULL) {
1837 if (!cp->GetShowAIS()) return;
1838 }
1839
1840 AISSetMetrics();
1841
1842 const auto &current_targets = g_pAIS->GetTargetList();
1843
1844 // Iterate over the AIS Target Hashmap but only for the main chartcanvas.
1845 // For secundairy canvasses we use the same value for the AIS importance
1846 bool go = false;
1847
1848 if (cp == NULL) {
1849 go = true;
1850 } else if (cp->m_canvasIndex == 0) {
1851 go = true;
1852 }
1853
1854 if (go) {
1855 for (const auto &it : current_targets) {
1856 // calculate the importancefactor for each target
1857 auto td = it.second;
1858 double So, Cpa, Rang, Siz = 0.0;
1859 So = g_ScaledNumWeightSOG / 12 *
1860 td->SOG; // 0 - 12 knts gives 0 - g_ScaledNumWeightSOG weight
1861 if (So > g_ScaledNumWeightSOG) So = g_ScaledNumWeightSOG;
1862
1863 if (td->bCPA_Valid) {
1864 Cpa = g_ScaledNumWeightCPA - g_ScaledNumWeightCPA / 4 * td->CPA;
1865 // if TCPA is positief (target is coming closer), make weight of CPA
1866 // bigger
1867 if (td->TCPA > .0) Cpa = Cpa + Cpa * g_ScaledNumWeightTCPA / 100;
1868 if (Cpa < .0) Cpa = .0; // if CPA is > 4
1869 } else
1870 Cpa = .0;
1871
1872 Rang = g_ScaledNumWeightRange / 10 * td->Range_NM;
1873 if (Rang > g_ScaledNumWeightRange) Rang = g_ScaledNumWeightRange;
1874 Rang = g_ScaledNumWeightRange - Rang;
1875
1876 Siz = g_ScaledNumWeightSizeOfT / 30 * (td->DimA + td->DimB);
1877 if (Siz > g_ScaledNumWeightSizeOfT) Siz = g_ScaledNumWeightSizeOfT;
1878 td->importance = (float)So + Cpa + Rang + Siz;
1879 }
1880 }
1881
1882 // If needed iterate over all targets, check if they fit in the viewport and
1883 // if yes add the importancefactor to a sorted list
1884 AISImportanceSwitchPoint = 0.0;
1885
1886 float *Array = new float[g_ShowScaled_Num];
1887 for (int i = 0; i < g_ShowScaled_Num; i++) Array[i] = 0.0;
1888
1889 int LowestInd = 0;
1890 if (cp != NULL) {
1891 if (cp->GetAttenAIS()) {
1892 for (const auto &it : current_targets) {
1893 auto td = it.second;
1894 if (vp.GetBBox().Contains(td->Lat, td->Lon)) {
1895 if (td->importance > AISImportanceSwitchPoint) {
1896 Array[LowestInd] = td->importance;
1897
1898 AISImportanceSwitchPoint = Array[0];
1899 LowestInd = 0;
1900 for (int i = 1; i < g_ShowScaled_Num; i++) {
1901 if (Array[i] < AISImportanceSwitchPoint) {
1902 AISImportanceSwitchPoint = Array[i];
1903 LowestInd = i;
1904 }
1905 }
1906 }
1907 }
1908 }
1909 }
1910 }
1911 delete[] Array;
1912
1913 // Draw all targets in three pass loop, sorted on SOG, GPSGate & DSC on top
1914 // This way, fast targets are not obscured by slow/stationary targets
1915 for (const auto &it : current_targets) {
1916 auto td = it.second;
1917 if ((td->SOG < g_SOGminCOG_kts) &&
1918 !((td->Class == AIS_GPSG_BUDDY) || (td->Class == AIS_DSC))) {
1919 AISDrawTarget(td.get(), dc, vp, cp);
1920 }
1921 }
1922
1923 for (const auto &it : current_targets) {
1924 auto td = it.second;
1925 if ((td->SOG >= g_SOGminCOG_kts) &&
1926 !((td->Class == AIS_GPSG_BUDDY) || (td->Class == AIS_DSC))) {
1927 AISDrawTarget(td.get(), dc, vp, cp); // yes this is a doubling of code;(
1928 if (td->importance > 0) AISDrawTarget(td.get(), dc, vp, cp);
1929 }
1930 }
1931
1932 for (const auto &it : current_targets) {
1933 auto td = it.second;
1934 if ((td->Class == AIS_GPSG_BUDDY) || (td->Class == AIS_DSC))
1935 AISDrawTarget(td.get(), dc, vp, cp);
1936 }
1937}
1938
1939bool AnyAISTargetsOnscreen(ChartCanvas *cc, ViewPort &vp) {
1940 if (!g_pAIS) return false;
1941
1942 if (!cc->GetShowAIS()) return false; //
1943
1944 // Iterate over the AIS Target Hashmap
1945 for (const auto &it : g_pAIS->GetTargetList()) {
1946 auto td = it.second;
1947 if (vp.GetBBox().Contains(td->Lat, td->Lon)) return true; // yep
1948 }
1949
1950 return false;
1951}
Global state for AIS decoder.
Dialog for displaying AIS target alerts.
Dialog for querying detailed information about an AIS target.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Chart display canvas.
Definition chcanv.h:135
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4518
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Gets a font object for a UI element.
Definition FontMgr.cpp:186
Main application frame.
Definition ocpn_frame.h:136
Provides platform-specific support utilities for OpenCPN.
Represents the view port for chart display in OpenCPN.
Definition viewport.h:84
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:199
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:221
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:209
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:219
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:145
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:136
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:214
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:64
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:476
PlugIn Object Definition/API.
wxFont * FindOrCreateFont_PlugIn(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline, const wxString &facename, wxFontEncoding encoding)
Creates or finds a font in the font cache.