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