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