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