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