OpenCPN Partial API docs
Loading...
Searching...
No Matches
ais_decoder.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2010 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 ***************************************************************************/
17
24// For compilers that support precompilation, includes "wx.h".
25
26#include <algorithm>
27#include <cstdio>
28#include <fstream>
29
30#ifdef __MINGW32__
31#undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
32#include <windows.h>
33#endif
34
35#include <wx/wxprec.h>
36#ifndef WX_PRECOMP
37#include <wx/wx.h>
38#endif
39
40#include <wx/datetime.h>
41#include <wx/event.h>
42#include <wx/log.h>
43#include <wx/string.h>
44#include <wx/textfile.h>
45#include <wx/timer.h>
46#include <wx/tokenzr.h>
47#include <wx/filename.h>
48
49// Be sure to include these before ais_decoder.h
50// to avoid a conflict with rapidjson/fwd.h
51#include "rapidjson/document.h"
52#include "rapidjson/writer.h"
53#include "rapidjson/stringbuffer.h"
54
55#include "model/ais_decoder.h"
57#include "model/meteo_points.h"
60#include "model/config_vars.h"
61#include "model/geodesic.h"
62#include "model/georef.h"
63#include "model/idents.h"
64#include "model/multiplexer.h"
65#include "model/navutil_base.h"
66#include "model/own_ship.h"
67#include "model/route_point.h"
68#include "model/select.h"
69#include "model/track.h"
70#include "N2KParser.h"
71
72#if !defined(NAN)
73static const long long lNaN = 0xfff8000000000000;
74#define NAN (*(double *)&lNaN)
75#endif
76
77wxEvtHandler *g_pais_alert_dialog_active;
78
81wxString GetShipNameFromFile(int);
83
85
86wxDEFINE_EVENT(EVT_N0183_VDO, ObservedEvt);
87wxDEFINE_EVENT(EVT_N0183_VDM, ObservedEvt);
88wxDEFINE_EVENT(EVT_N0183_FRPOS, ObservedEvt);
89wxDEFINE_EVENT(EVT_N0183_CDDSC, ObservedEvt);
90wxDEFINE_EVENT(EVT_N0183_CDDSE, ObservedEvt);
91wxDEFINE_EVENT(EVT_N0183_TLL, ObservedEvt);
92wxDEFINE_EVENT(EVT_N0183_TTM, ObservedEvt);
93wxDEFINE_EVENT(EVT_N0183_OSD, ObservedEvt);
94wxDEFINE_EVENT(EVT_N0183_WPL, ObservedEvt);
95wxDEFINE_EVENT(EVT_SIGNALK, ObservedEvt);
96wxDEFINE_EVENT(EVT_N2K_129038, ObservedEvt);
97wxDEFINE_EVENT(EVT_N2K_129039, ObservedEvt);
98wxDEFINE_EVENT(EVT_N2K_129041, ObservedEvt);
99wxDEFINE_EVENT(EVT_N2K_129794, ObservedEvt);
100wxDEFINE_EVENT(EVT_N2K_129809, ObservedEvt);
101wxDEFINE_EVENT(EVT_N2K_129810, ObservedEvt);
102wxDEFINE_EVENT(EVT_N2K_129793, ObservedEvt);
103
104BEGIN_EVENT_TABLE(AisDecoder, wxEvtHandler)
105EVT_TIMER(TIMER_AIS1, AisDecoder::OnTimerAIS)
106EVT_TIMER(TIMER_DSC, AisDecoder::OnTimerDSC)
107END_EVENT_TABLE()
108
109static constexpr double ms_to_knot_factor = 1.9438444924406;
110
111static int n_msgs;
112static int n_msg1;
113static int n_msg5;
114static int n_msg24;
115static bool b_firstrx;
116static int first_rx_ticks;
117static int rx_ticks;
118static double arpa_ref_hdg = NAN;
119
120static inline double GeodesicRadToDeg(double rads) {
121 return rads * 180.0 / M_PI;
122}
123
124static inline double MS2KNOTS(double ms) { return ms * 1.9438444924406; }
125
126static bool isBuoyMmsi(unsigned msi) {
127 // IMO standard is not yet(?) implemented for (net)buoys
128 // This adaption, based on real-world outcomes, is used instead
129 // Consider any not valid MMSI number for a class B target (message 18 or 19)
130 // to be a "net buoy"
131 int mid = msi / 1000000;
132 if ((mid > 200 && mid < 880) || mid >= 970) {
133 // MID is stated to be >200 <800. Handheld AIS starts with 8+mid thus 880
134 return false;
135 } else {
136 return true;
137 }
138 return false;
139}
140
141int AisMeteoNewMmsi(int, int, int, int, int);
142int origin_mmsi = 0;
143
144void AISshipNameCache(AisTargetData *pTargetData,
145 AIS_Target_Name_Hash *AISTargetNamesC,
146 AIS_Target_Name_Hash *AISTargetNamesNC, long mmsi);
147static void BuildERIShipTypeHash() {
148 make_hash_ERI(8000, _("Vessel, type unknown"));
149 make_hash_ERI(8150, _("Freightbarge"));
150 make_hash_ERI(8160, _("Tankbarge"));
151 make_hash_ERI(8163, _("Tankbarge, dry cargo as if liquid (e.g. cement)"));
152 make_hash_ERI(8450, _("Service vessel, police patrol, port service"));
153 make_hash_ERI(8430, _("Pushboat, single"));
154 make_hash_ERI(8510, _("Object, not otherwise specified"));
155 make_hash_ERI(8470, _("Object, towed, not otherwise specified"));
156 make_hash_ERI(8490, _("Bunkership"));
157 make_hash_ERI(8010, _("Motor freighter"));
158 make_hash_ERI(8020, _("Motor tanker"));
159 make_hash_ERI(8021, _("Motor tanker, liquid cargo, type N"));
160 make_hash_ERI(8022, _("Motor tanker, liquid cargo, type C"));
161 make_hash_ERI(8023, _("Motor tanker, dry cargo as if liquid (e.g. cement)"));
162 make_hash_ERI(8030, _("Container vessel"));
163 make_hash_ERI(8040, _("Gas tanker"));
164 make_hash_ERI(8050, _("Motor freighter, tug"));
165 make_hash_ERI(8060, _("Motor tanker, tug"));
166 make_hash_ERI(8070, _("Motor freighter with one or more ships alongside"));
167 make_hash_ERI(8080, _("Motor freighter with tanker"));
168 make_hash_ERI(8090, _("Motor freighter pushing one or more freighters"));
169 make_hash_ERI(8100, _("Motor freighter pushing at least one tank-ship"));
170 make_hash_ERI(8110, _("Tug, freighter"));
171 make_hash_ERI(8120, _("Tug, tanker"));
172 make_hash_ERI(8130, _("Tug freighter, coupled"));
173 make_hash_ERI(8140, _("Tug, freighter/tanker, coupled"));
174 make_hash_ERI(8161, _("Tankbarge, liquid cargo, type N"));
175 make_hash_ERI(8162, _("Tankbarge, liquid cargo, type C"));
176 make_hash_ERI(8170, _("Freightbarge with containers"));
177 make_hash_ERI(8180, _("Tankbarge, gas"));
178 make_hash_ERI(8210, _("Pushtow, one cargo barge"));
179 make_hash_ERI(8220, _("Pushtow, two cargo barges"));
180 make_hash_ERI(8230, _("Pushtow, three cargo barges"));
181 make_hash_ERI(8240, _("Pushtow, four cargo barges"));
182 make_hash_ERI(8250, _("Pushtow, five cargo barges"));
183 make_hash_ERI(8260, _("Pushtow, six cargo barges"));
184 make_hash_ERI(8270, _("Pushtow, seven cargo barges"));
185 make_hash_ERI(8280, _("Pushtow, eight cargo barges"));
186 make_hash_ERI(8290, _("Pushtow, nine or more barges"));
187 make_hash_ERI(8310, _("Pushtow, one tank/gas barge"));
188 make_hash_ERI(8320,
189 _("Pushtow, two barges at least one tanker or gas barge"));
190 make_hash_ERI(8330,
191 _("Pushtow, three barges at least one tanker or gas barge"));
192 make_hash_ERI(8340,
193 _("Pushtow, four barges at least one tanker or gas barge"));
194 make_hash_ERI(8350,
195 _("Pushtow, five barges at least one tanker or gas barge"));
196 make_hash_ERI(8360,
197 _("Pushtow, six barges at least one tanker or gas barge"));
198 make_hash_ERI(8370,
199 _("Pushtow, seven barges at least one tanker or gas barge"));
200 make_hash_ERI(8380,
201 _("Pushtow, eight barges at least one tanker or gas barge"));
202 make_hash_ERI(
203 8390, _("Pushtow, nine or more barges at least one tanker or gas barge"));
204 make_hash_ERI(8400, _("Tug, single"));
205 make_hash_ERI(8410, _("Tug, one or more tows"));
206 make_hash_ERI(8420, _("Tug, assisting a vessel or linked combination"));
207 make_hash_ERI(8430, _("Pushboat, single"));
208 make_hash_ERI(8440, _("Passenger ship, ferry, cruise ship, red cross ship"));
209 make_hash_ERI(8441, _("Ferry"));
210 make_hash_ERI(8442, _("Red cross ship"));
211 make_hash_ERI(8443, _("Cruise ship"));
212 make_hash_ERI(8444, _("Passenger ship without accommodation"));
213 make_hash_ERI(8460, _("Vessel, work maintenance craft, floating derrick, "
214 "cable-ship, buoy-ship, dredge"));
215 make_hash_ERI(8480, _("Fishing boat"));
216 make_hash_ERI(8500, _("Barge, tanker, chemical"));
217 make_hash_ERI(1500, _("General cargo Vessel maritime"));
218 make_hash_ERI(1510, _("Unit carrier maritime"));
219 make_hash_ERI(1520, _("Bulk carrier maritime"));
220 make_hash_ERI(1530, _("Tanker"));
221 make_hash_ERI(1540, _("Liquified gas tanker"));
222 make_hash_ERI(1850, _("Pleasure craft, longer than 20 metres"));
223 make_hash_ERI(1900, _("Fast ship"));
224 make_hash_ERI(1910, _("Hydrofoil"));
225}
226
227// DSE Expansion characters, decode table from ITU-R M.825
228static wxString DecodeDSEExpansionCharacters(const wxString &dseData) {
229 wxString result;
230 char lookupTable[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ',
231 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
232 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
233 'W', 'X', 'Y', 'Z', '.', ',', '-', '/', ' '};
234
235 for (size_t i = 0; i < dseData.length(); i += 2) {
236 result.append(1,
237 lookupTable[strtol(dseData.Mid(i, 2).data(), nullptr, 10)]);
238 }
239 return result;
240}
241
242static void getMmsiProperties(std::shared_ptr<AisTargetData> &pTargetData) {
243 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
244 if (pTargetData->MMSI == g_MMSI_Props_Array[i]->MMSI) {
246 pTargetData->b_isFollower = props->m_bFollower;
247 pTargetData->b_mPropPersistTrack = props->m_bPersistentTrack;
248 if (TRACKTYPE_NEVER == props->TrackType) {
249 pTargetData->b_show_track = false;
250 } else if (TRACKTYPE_ALWAYS == props->TrackType) {
251 pTargetData->b_show_track = true;
252 }
253 break;
254 }
255 }
256}
257
258//----------------------------------------------------------------------------
259// Parse a NMEA VDM/VDO Bitstring
260//----------------------------------------------------------------------------
261static bool Parse_VDXBitstring(AisBitstring *bstr,
262 const std::shared_ptr<AisTargetData> &ptd) {
263 bool parse_result = false;
264 bool b_posn_report = false;
265
266 wxDateTime now = wxDateTime::Now();
267 now.MakeGMT();
268 int message_ID = bstr->GetInt(1, 6); // Parse on message ID
269 ptd->MID = message_ID;
270
271 // Save for Ais8_001_31 and ais8_367_33 (class AIS_METEO)
272 int met_mmsi = ptd->MMSI;
273
274 // MMSI is always in the same spot in the bitstream
275 ptd->MMSI = bstr->GetInt(9, 30);
276
277 switch (message_ID) {
278 case 1: // Position Report
279 case 2:
280 case 3: {
281 n_msg1++;
282
283 ptd->NavStatus = bstr->GetInt(39, 4);
284 ptd->SOG = 0.1 * (bstr->GetInt(51, 10));
285
286 int lon = bstr->GetInt(62, 28);
287 if (lon & 0x08000000) // negative?
288 lon |= 0xf0000000;
289 double lon_tentative = lon / 600000.;
290
291 int lat = bstr->GetInt(90, 27);
292 if (lat & 0x04000000) // negative?
293 lat |= 0xf8000000;
294 double lat_tentative = lat / 600000.;
295
296 if ((lon_tentative <= 180.) &&
297 (lat_tentative <=
298 90.)) // Ship does not report Lat or Lon "unavailable"
299 {
300 ptd->Lon = lon_tentative;
301 ptd->Lat = lat_tentative;
302 ptd->b_positionDoubtful = false;
303 ptd->b_positionOnceValid = true; // Got the position at least once
304 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
305 ptd->PositionReportTicks = now.GetTicks();
306 } else
307 ptd->b_positionDoubtful = true;
308
309 // decode balance of message....
310 ptd->COG = 0.1 * (bstr->GetInt(117, 12));
311 ptd->HDG = 1.0 * (bstr->GetInt(129, 9));
312
313 ptd->ROTAIS = bstr->GetInt(43, 8);
314 double rot_dir = 1.0;
315
316 if (ptd->ROTAIS == 128)
317 ptd->ROTAIS = -128; // not available codes as -128
318 else if ((ptd->ROTAIS & 0x80) == 0x80) {
319 ptd->ROTAIS = ptd->ROTAIS - 256; // convert to twos complement
320 rot_dir = -1.0;
321 }
322
323 ptd->ROTIND = wxRound(rot_dir * pow((((double)ptd->ROTAIS) / 4.733),
324 2)); // Convert to indicated ROT
325
326 ptd->m_utc_sec = bstr->GetInt(138, 6);
327
328 if ((1 == message_ID) ||
329 (2 == message_ID)) // decode SOTDMA per 7.6.7.2.2
330 {
331 ptd->SyncState = bstr->GetInt(151, 2);
332 ptd->SlotTO = bstr->GetInt(153, 2);
333 if ((ptd->SlotTO == 1) && (ptd->SyncState == 0)) // UTCDirect follows
334 {
335 ptd->m_utc_hour = bstr->GetInt(155, 5);
336
337 ptd->m_utc_min = bstr->GetInt(160, 7);
338
339 if ((ptd->m_utc_hour < 24) && (ptd->m_utc_min < 60) &&
340 (ptd->m_utc_sec < 60)) {
341 wxDateTime rx_time(ptd->m_utc_hour, ptd->m_utc_min, ptd->m_utc_sec);
342 rx_ticks = rx_time.GetTicks();
343 if (!b_firstrx) {
344 first_rx_ticks = rx_ticks;
345 b_firstrx = true;
346 }
347 }
348 }
349 }
350
351 // Capture Euro Inland special passing arrangement signal ("stbd-stbd")
352 ptd->blue_paddle = bstr->GetInt(144, 2);
353 ptd->b_blue_paddle = (ptd->blue_paddle == 2); // paddle is set
354
355 if (!ptd->b_isDSCtarget) ptd->Class = AIS_CLASS_A;
356
357 // Check for SART and friends by looking at first two digits of MMSI
358 int mmsi_start = ptd->MMSI / 10000000;
359
360 if (mmsi_start == 97) {
361 ptd->Class = AIS_SART;
362 ptd->StaticReportTicks =
363 now.GetTicks(); // won't get a static report, so fake it here
364
365 // On receipt of Msg 3, force any existing SART target out of
366 // acknowledge mode by adjusting its ack_time to yesterday This will
367 // cause any previously "Acknowledged" SART to re-alert.
368
369 // On reflection, re-alerting seems a little excessive in real life
370 // use. After all, the target is on-screen, and in the AIS target
371 // list. So lets just honor the programmed ACK timout value for SART
372 // targets as well
373 // ptd->m_ack_time = wxDateTime::Now() - wxTimeSpan::Day();
374 }
375
376 parse_result = true; // so far so good
377 b_posn_report = true;
378 ptd->b_VDM_secure_Pos = true;
379
380 break;
381 }
382
383 case 18: {
384 ptd->NavStatus =
385 UNDEFINED; // Class B targets have no status. Enforce this...
386
387 ptd->SOG = 0.1 * (bstr->GetInt(47, 10));
388
389 int lon = bstr->GetInt(58, 28);
390 if (lon & 0x08000000) // negative?
391 lon |= 0xf0000000;
392 double lon_tentative = lon / 600000.;
393
394 int lat = bstr->GetInt(86, 27);
395 if (lat & 0x04000000) // negative?
396 lat |= 0xf8000000;
397 double lat_tentative = lat / 600000.;
398
399 if ((lon_tentative <= 180.) &&
400 (lat_tentative <=
401 90.)) // Ship does not report Lat or Lon "unavailable"
402 {
403 ptd->Lon = lon_tentative;
404 ptd->Lat = lat_tentative;
405 ptd->b_positionDoubtful = false;
406 ptd->b_positionOnceValid = true; // Got the position at least once
407 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
408 ptd->PositionReportTicks = now.GetTicks();
409 } else
410 ptd->b_positionDoubtful = true;
411
412 ptd->COG = 0.1 * (bstr->GetInt(113, 12));
413 ptd->HDG = 1.0 * (bstr->GetInt(125, 9));
414
415 ptd->m_utc_sec = bstr->GetInt(134, 6);
416
417 if (!ptd->b_isDSCtarget) {
418 if (!isBuoyMmsi(ptd->MMSI))
419 ptd->Class = AIS_CLASS_B;
420 else
421 ptd->Class = AIS_BUOY;
422 }
423 parse_result = true; // so far so good
424 b_posn_report = true;
425 ptd->b_VDM_secure_Pos = true;
426
427 break;
428 }
429
430 case 19: { // Class B mes_ID 19 Is same as mes_ID 18 until bit 139
431 ptd->NavStatus =
432 UNDEFINED; // Class B targets have no status. Enforce this...
433 ptd->SOG = 0.1 * (bstr->GetInt(47, 10));
434 int lon = bstr->GetInt(58, 28);
435 if (lon & 0x08000000) // negative?
436 lon |= 0xf0000000;
437 double lon_tentative = lon / 600000.;
438
439 int lat = bstr->GetInt(86, 27);
440 if (lat & 0x04000000) // negative?
441 lat |= 0xf8000000;
442 double lat_tentative = lat / 600000.;
443
444 if ((lon_tentative <= 180.) &&
445 (lat_tentative <=
446 90.)) // Ship does not report Lat or Lon "unavailable"
447 {
448 ptd->Lon = lon_tentative;
449 ptd->Lat = lat_tentative;
450 ptd->b_positionDoubtful = false;
451 ptd->b_positionOnceValid = true; // Got the position at least once
452 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
453 ptd->PositionReportTicks = now.GetTicks();
454 } else
455 ptd->b_positionDoubtful = true;
456
457 ptd->COG = 0.1 * (bstr->GetInt(113, 12));
458 ptd->HDG = 1.0 * (bstr->GetInt(125, 9));
459 ptd->m_utc_sec = bstr->GetInt(134, 6);
460 // From bit 140 and forward data as of mes 5
461 bstr->GetStr(144, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
462 ptd->b_nameValid = true;
463 if (!ptd->b_isDSCtarget) {
464 ptd->ShipType = (unsigned char)bstr->GetInt(264, 8);
465 }
466 ptd->DimA = bstr->GetInt(272, 9);
467 ptd->DimB = bstr->GetInt(281, 9);
468 ptd->DimC = bstr->GetInt(290, 6);
469 ptd->DimD = bstr->GetInt(296, 6);
470
471 if (!ptd->b_isDSCtarget) {
472 // Although outdated, message 19 is used by many "ATON" for net buoys
473 if (!isBuoyMmsi(ptd->MMSI))
474 ptd->Class = AIS_CLASS_B;
475 else
476 ptd->Class = AIS_BUOY;
477 }
478 parse_result = true; // so far so good
479 b_posn_report = true;
480 ptd->b_VDM_secure_Pos = true;
481
482 break;
483 }
484
485 case 27: {
486 // Long-range automatic identification system broadcast message
487 // This message is used for long-range detection of AIS Class A and Class
488 // B vessels (typically by satellite).
489
490 // Message 27 can be receiveed for a target that has already been decoded
491 // by class A message 1/2/3 or class B message 18/19. If so reject this.
492 if (ptd->b_VDM_secure_Pos) break;
493
494 // Define the constant to do the covertion from the internal encoded
495 // position in message 27. The position is less accuate : 1/10 minute
496 // position resolution.
497 int bitCorrection = 10;
498 int resolution = 10;
499
500 // Default aout of bounce values.
501 double lon_tentative = 181.;
502 double lat_tentative = 91.;
503
504#ifdef AIS_DEBUG
505 printf("AIS Message 27 - received:\r\n");
506 printf("MMSI : %i\r\n", ptd->MMSI);
507#endif
508
509 // It can be both a CLASS A and a CLASS B vessel - We have decided for
510 // CLASS A (Default if not changed by other messages.)
511 ptd->NavStatus =
512 ptd->Class == AIS_CLASS_A ? bstr->GetInt(41, 4) : UNDEFINED;
513
514 int lon = bstr->GetInt(45, 18);
515 int lat = bstr->GetInt(63, 17);
516
517 lat_tentative = lat;
518 lon_tentative = lon;
519
520 // Negative latitude?
521 if (lat >= (0x4000000 >> bitCorrection)) {
522 lat_tentative = (0x8000000 >> bitCorrection) - lat;
523 lat_tentative *= -1;
524 }
525
526 // Negative longitude?
527 if (lon >= (0x8000000 >> bitCorrection)) {
528 lon_tentative = (0x10000000 >> bitCorrection) - lon;
529 lon_tentative *= -1;
530 }
531
532 // Decode the internal position format.
533 lat_tentative = lat_tentative / resolution / 60.0;
534 lon_tentative = lon_tentative / resolution / 60.0;
535
536#ifdef AIS_DEBUG
537 printf("Latitude : %f\r\n", lat_tentative);
538 printf("Longitude : %f\r\n", lon_tentative);
539#endif
540
541 // Get the latency of the position report.
542 int positionLatency = bstr->GetInt(95, 1);
543
544 if ((lon_tentative <= 180.) &&
545 (lat_tentative <=
546 90.)) // Ship does not report Lat or Lon "unavailable"
547 {
548 ptd->Lon = lon_tentative;
549 ptd->Lat = lat_tentative;
550 ptd->b_positionDoubtful = false;
551 ptd->b_positionOnceValid = true; // Got the position at least once
552 if (positionLatency == 0) {
553// The position is less than 5 seconds old.
554#ifdef AIS_DEBUG
555 printf("Low latency position report.\r\n");
556#endif
557 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
558 ptd->PositionReportTicks = now.GetTicks();
559 }
560 } else
561 ptd->b_positionDoubtful = true;
562
563 ptd->SOG = 1.0 * (bstr->GetInt(80, 6));
564 ptd->COG = 1.0 * (bstr->GetInt(85, 9));
565
566 b_posn_report = true;
567 parse_result = true;
568 break;
569 }
570
571 case 5: {
572 if (bstr->GetBitCount() >= 424) { // Dismiss a malformed message
573 n_msg5++;
574 if (!ptd->b_isDSCtarget) ptd->Class = AIS_CLASS_A;
575
576 // Get the AIS Version indicator
577 // 0 = station compliant with Recommendation ITU-R M.1371-1
578 // 1 = station compliant with Recommendation ITU-R M.1371-3
579 // 2-3 = station compliant with future editions
580 int AIS_version_indicator = bstr->GetInt(39, 2);
581 if (AIS_version_indicator < 4) {
582 ptd->IMO = bstr->GetInt(41, 30);
583
584 bstr->GetStr(71, 42, &ptd->CallSign[0], 7);
585 bstr->GetStr(113, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
586 ptd->b_nameValid = true;
587 if (!ptd->b_isDSCtarget) {
588 ptd->ShipType = (unsigned char)bstr->GetInt(233, 8);
589 }
590
591 ptd->DimA = bstr->GetInt(241, 9);
592 ptd->DimB = bstr->GetInt(250, 9);
593 ptd->DimC = bstr->GetInt(259, 6);
594 ptd->DimD = bstr->GetInt(265, 6);
595
596 ptd->ETA_Mo = bstr->GetInt(275, 4);
597 ptd->ETA_Day = bstr->GetInt(279, 5);
598 ptd->ETA_Hr = bstr->GetInt(284, 5);
599 ptd->ETA_Min = bstr->GetInt(289, 6);
600
601 ptd->Draft = (double)(bstr->GetInt(295, 8)) / 10.0;
602
603 bstr->GetStr(303, 120, &ptd->Destination[0], DESTINATION_LEN - 1);
604
605 ptd->StaticReportTicks = now.GetTicks();
606
607 parse_result = true;
608 }
609 }
610 break;
611 }
612
613 case 24: { // Static data report
614 // First, check that we have received the position and thus
615 // a main message that has given the target a relevant class
616 if (!ptd->b_positionOnceValid) break;
617
618 int part_number = bstr->GetInt(39, 2);
619 if (0 == part_number) {
620 bstr->GetStr(41, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
621 ptd->b_nameValid = true;
622 parse_result = true;
623 n_msg24++;
624 } else if (1 == part_number) {
625 if (!ptd->b_isDSCtarget) {
626 ptd->ShipType = (unsigned char)bstr->GetInt(41, 8);
627 }
628 bstr->GetStr(91, 42, &ptd->CallSign[0], 7);
629
630 ptd->DimA = bstr->GetInt(133, 9);
631 ptd->DimB = bstr->GetInt(142, 9);
632 ptd->DimC = bstr->GetInt(151, 6);
633 ptd->DimD = bstr->GetInt(157, 6);
634 parse_result = true;
635 }
636 break;
637 }
638 case 4: // base station
639 {
640 ptd->Class = AIS_BASE;
641
642 ptd->m_utc_hour = bstr->GetInt(62, 5);
643 ptd->m_utc_min = bstr->GetInt(67, 6);
644 ptd->m_utc_sec = bstr->GetInt(73, 6);
645 // (79, 1);
646 int lon = bstr->GetInt(80, 28);
647 if (lon & 0x08000000) // negative?
648 lon |= 0xf0000000;
649 double lon_tentative = lon / 600000.;
650
651 int lat = bstr->GetInt(108, 27);
652 if (lat & 0x04000000) // negative?
653 lat |= 0xf8000000;
654 double lat_tentative = lat / 600000.;
655
656 if ((lon_tentative <= 180.) &&
657 (lat_tentative <=
658 90.)) // Ship does not report Lat or Lon "unavailable"
659 {
660 ptd->Lon = lon_tentative;
661 ptd->Lat = lat_tentative;
662 ptd->b_positionDoubtful = false;
663 ptd->b_positionOnceValid = true; // Got the position at least once
664 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
665 ptd->PositionReportTicks = now.GetTicks();
666 } else
667 ptd->b_positionDoubtful = true;
668
669 ptd->COG = -1.;
670 ptd->HDG = 511;
671 ptd->SOG = -1.;
672
673 parse_result = true;
674 b_posn_report = true;
675
676 break;
677 }
678 case 9: // Special Position Report (Standard SAR Aircraft Position Report)
679 {
680 ptd->SOG = bstr->GetInt(51, 10);
681
682 int lon = bstr->GetInt(62, 28);
683 if (lon & 0x08000000) // negative?
684 lon |= 0xf0000000;
685 double lon_tentative = lon / 600000.;
686
687 int lat = bstr->GetInt(90, 27);
688 if (lat & 0x04000000) // negative?
689 lat |= 0xf8000000;
690 double lat_tentative = lat / 600000.;
691
692 if ((lon_tentative <= 180.) &&
693 (lat_tentative <=
694 90.)) // Ship does not report Lat or Lon "unavailable"
695 {
696 ptd->Lon = lon_tentative;
697 ptd->Lat = lat_tentative;
698 ptd->b_positionDoubtful = false;
699 ptd->b_positionOnceValid = true; // Got the position at least once
700 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
701 ptd->PositionReportTicks = now.GetTicks();
702 } else
703 ptd->b_positionDoubtful = true;
704
705 // decode balance of message....
706 ptd->COG = 0.1 * (bstr->GetInt(117, 12));
707
708 int alt_tent = bstr->GetInt(39, 12);
709 ptd->altitude = alt_tent;
710
711 ptd->b_SarAircraftPosnReport = true;
712
713 parse_result = true;
714 b_posn_report = true;
715
716 break;
717 }
718 case 21: // Test Message (Aid to Navigation)
719 {
720 ptd->ShipType = (unsigned char)bstr->GetInt(39, 5);
721 ptd->IMO = 0;
722 ptd->SOG = 0;
723 ptd->HDG = 0;
724 ptd->COG = 0;
725 ptd->ROTAIS = -128; // i.e. not available
726 ptd->DimA = bstr->GetInt(220, 9);
727 ptd->DimB = bstr->GetInt(229, 9);
728 ptd->DimC = bstr->GetInt(238, 6);
729 ptd->DimD = bstr->GetInt(244, 6);
730 ptd->Draft = 0;
731
732 ptd->m_utc_sec = bstr->GetInt(254, 6);
733
734 int offpos = bstr->GetInt(260, 1); // off position flag
735 int virt = bstr->GetInt(270, 1); // virtual flag
736
737 if (virt)
738 ptd->NavStatus = ATON_VIRTUAL;
739 else
740 ptd->NavStatus = ATON_REAL;
741 if (ptd->m_utc_sec <= 59 /*&& !virt*/) {
742 ptd->NavStatus += 1;
743 if (offpos) ptd->NavStatus += 1;
744 }
745
746 bstr->GetStr(44, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
747 // short name only, extension wont fit in Ship structure
748
749 if (bstr->GetBitCount() > 276) {
750 int nx = ((bstr->GetBitCount() - 272) / 6) * 6;
751 bstr->GetStr(273, nx, &ptd->ShipNameExtension[0], 14);
752 ptd->ShipNameExtension[14] = 0;
753 } else {
754 ptd->ShipNameExtension[0] = 0;
755 }
756
757 ptd->b_nameValid = true;
758
759 parse_result = true; // so far so good
760
761 ptd->Class = AIS_ATON;
762
763 int lon = bstr->GetInt(165, 28);
764
765 if (lon & 0x08000000) // negative?
766 lon |= 0xf0000000;
767 double lon_tentative = lon / 600000.;
768
769 int lat = bstr->GetInt(193, 27);
770
771 if (lat & 0x04000000) // negative?
772 lat |= 0xf8000000;
773 double lat_tentative = lat / 600000.;
774
775 if ((lon_tentative <= 180.) &&
776 (lat_tentative <=
777 90.)) // Ship does not report Lat or Lon "unavailable"
778 {
779 ptd->Lon = lon_tentative;
780 ptd->Lat = lat_tentative;
781 ptd->b_positionDoubtful = false;
782 ptd->b_positionOnceValid = true; // Got the position at least once
783 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
784 ptd->PositionReportTicks = now.GetTicks();
785 } else
786 ptd->b_positionDoubtful = true;
787
788 b_posn_report = true;
789 break;
790 }
791 case 8: // Binary Broadcast
792 {
793 int dac = bstr->GetInt(41, 10);
794 int fi = bstr->GetInt(51, 6);
795
796 if (dac == 200) // European inland
797 {
798 if (fi == 10) // "Inland ship static and voyage related data"
799 {
800 ptd->b_isEuroInland = true;
801
802 bstr->GetStr(57, 48, &ptd->Euro_VIN[0], 8);
803 ptd->Euro_Length = ((double)bstr->GetInt(105, 13)) / 10.0;
804 ptd->Euro_Beam = ((double)bstr->GetInt(118, 10)) / 10.0;
805 ptd->UN_shiptype = bstr->GetInt(128, 14);
806 ptd->Euro_Draft = ((double)bstr->GetInt(145, 11)) / 100.0;
807 parse_result = true;
808 }
809 }
810 if (dac == 1 || dac == 366) // IMO or US
811 {
812 if (fi == 22) // Area Notice
813 {
814 if (bstr->GetBitCount() >= 111) {
815 Ais8_001_22 an;
816 an.link_id = bstr->GetInt(57, 10);
817 an.notice_type = bstr->GetInt(67, 7);
818 an.month = bstr->GetInt(74, 4);
819 an.day = bstr->GetInt(78, 5);
820 an.hour = bstr->GetInt(83, 5);
821 an.minute = bstr->GetInt(88, 6);
822 an.duration_minutes = bstr->GetInt(94, 18);
823
824 wxDateTime now_ = wxDateTime::Now();
825 now_.MakeGMT();
826
827 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
828 now_.GetYear(), an.hour, an.minute);
829
830 // msg is not supposed to be transmitted more than a day before it
831 // comes into effect, so a start_time less than a day or two away
832 // might indicate a month rollover
833 if (an.start_time > now_ + wxTimeSpan::Hours(48))
834 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
835 now_.GetYear() - 1, an.hour, an.minute);
836
837 an.expiry_time =
838 an.start_time + wxTimeSpan::Minutes(an.duration_minutes);
839
840 // msg is not supposed to be transmitted beyond expiration, so
841 // taking into account a fudge factor for clock issues, assume an
842 // expiry date in the past indicates incorrect year
843 if (an.expiry_time < now_ - wxTimeSpan::Hours(24)) {
844 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
845 now_.GetYear() + 1, an.hour, an.minute);
846 an.expiry_time =
847 an.start_time + wxTimeSpan::Minutes(an.duration_minutes);
848 }
849
850 // Default case, the IMO format
851 // https://www.e-navigation.nl/content/area-notice-0
852 int subarea_len = 87;
853 int lon_len = 25;
854 int lat_len = 24;
855 float pos_scale = 60000.0;
856 int prec_size = 3;
857 if (dac ==
858 366) { // Deprecated US format
859 // https://www.e-navigation.nl/content/area-notice-1
860 subarea_len = 90;
861 lon_len = 28;
862 lat_len = 27;
863 pos_scale = 600000.0;
864 prec_size = 0; // Not present in the in US format between
865 // coordinates and radius for some shapes
866 }
867
868 int subarea_count = (bstr->GetBitCount() - 111) / subarea_len;
869 for (int i = 0; i < subarea_count; ++i) {
870 int base = 111 + i * subarea_len;
872 sa.shape = bstr->GetInt(base + 1, 3);
873 int scale_factor = 1;
874 if (sa.shape == AIS8_001_22_SHAPE_TEXT) {
875 char t[15];
876 t[14] = 0;
877 bstr->GetStr(base + 4, subarea_len - 3, t, 14);
878 sa.text = wxString(t, wxConvUTF8);
879 } else {
880 int scale_multipliers[4] = {1, 10, 100, 1000};
881 scale_factor = scale_multipliers[bstr->GetInt(base + 4, 2)];
882 switch (sa.shape) {
883 case AIS8_001_22_SHAPE_SECTOR:
884 sa.left_bound_deg = bstr->GetInt(
885 base + 6 + lon_len + lat_len + prec_size + 12, 9);
886 sa.right_bound_deg = bstr->GetInt(
887 base + 6 + lon_len + lat_len + prec_size + 12 + 9, 9);
888 case AIS8_001_22_SHAPE_CIRCLE:
889 sa.radius_m =
890 bstr->GetInt(base + 6 + lon_len + lat_len + 3, 12) *
891 scale_factor;
892 // FALL THROUGH
893 case AIS8_001_22_SHAPE_RECT:
894 sa.longitude =
895 bstr->GetInt(base + 6, lon_len, true) / pos_scale;
896 sa.latitude =
897 bstr->GetInt(base + 6 + lon_len, lat_len, true) /
898 pos_scale;
899 sa.e_dim_m =
900 bstr->GetInt(base + 6 + lon_len + lat_len + prec_size,
901 8) *
902 scale_factor;
903 sa.n_dim_m =
904 bstr->GetInt(
905 base + 6 + lon_len + lat_len + prec_size + 8, 8) *
906 scale_factor;
907 sa.orient_deg = bstr->GetInt(
908 base + 6 + lon_len + lat_len + prec_size + 8 + 8, 9);
909 break;
910 case AIS8_001_22_SHAPE_POLYLINE:
911 case AIS8_001_22_SHAPE_POLYGON:
912 for (int j = 0; j < 4; ++j) {
913 sa.angles[j] = bstr->GetInt(base + 6 + j * 20, 10) * 0.5;
914 sa.dists_m[j] =
915 bstr->GetInt(base + 16 + j * 20, 10) * scale_factor;
916 }
917 }
918 }
919 an.sub_areas.push_back(sa);
920 }
921 ptd->area_notices[an.link_id] = an;
922 parse_result = true;
923 }
924 }
925
926 // Meteorological and Hydrographic data ref: IMO SN.1/Circ.289
927 if (fi == 31) {
928 if (bstr->GetBitCount() >= 360) {
929 // Ais8_001_31 mmsi can have been changed.
930 if (met_mmsi != 666) ptd->MMSI = met_mmsi;
931
932 // Default out of bounce values.
933 double lon_tentative = 181.;
934 double lat_tentative = 91.;
935
936 int lon = bstr->GetInt(57, 25);
937 int lat = bstr->GetInt(82, 24);
938
939 if (lon & 0x01000000) // negative?
940 lon |= 0xFE000000;
941 lon_tentative = lon / 60000.;
942
943 if (lat & 0x00800000) // negative?
944 lat |= 0xFF000000;
945 lat_tentative = lat / 60000.;
946
947 ptd->Lon = lon_tentative;
948 ptd->Lat = lat_tentative;
949
950 // Try to make unique name for each station based on position
951 wxString x = ptd->ShipName;
952 if (x.Find("METEO") == wxNOT_FOUND) {
953 double id1, id2;
954 wxString slat = wxString::Format("%0.3f", lat_tentative);
955 wxString slon = wxString::Format("%0.3f", lon_tentative);
956 slat.ToDouble(&id1);
957 slon.ToDouble(&id2);
958 wxString nameID = "METEO ";
959 nameID << wxString::Format("%0.3f", abs(id1) + abs(id2)).Right(3);
960 strncpy(ptd->ShipName, nameID, SHIP_NAME_LEN - 1);
961 }
962
963 ptd->met_data.pos_acc = bstr->GetInt(106, 1);
964 ptd->met_data.day = bstr->GetInt(107, 5);
965 ptd->met_data.hour = bstr->GetInt(112, 5);
966 ptd->met_data.minute = bstr->GetInt(117, 6);
967 ptd->met_data.wind_kn = bstr->GetInt(123, 7);
968 ptd->met_data.wind_gust_kn = bstr->GetInt(130, 7);
969 ptd->met_data.wind_dir = bstr->GetInt(137, 9);
970 ptd->met_data.wind_gust_dir = bstr->GetInt(146, 9);
971
972 int tmp = bstr->GetInt(155, 11);
973 if (tmp & 0x00000400) // negative?
974 tmp |= 0xFFFFF800;
975 ptd->met_data.air_temp = tmp / 10.;
976 ptd->met_data.rel_humid = bstr->GetInt(166, 7);
977 int dew = bstr->GetInt(173, 10);
978 if (dew & 0x00000200) // negative? (bit 9 = 1)
979 dew |= 0xFFFFFC00;
980 ptd->met_data.dew_point = dew / 10.;
981
982 /*Air pressure, defined as pressure reduced to sea level,
983 in 1 hPa steps.0 = pressure 799 hPa or less
984 1 - 401 = 800 - 1200 hPa*/
985 ptd->met_data.airpress = bstr->GetInt(183, 9) + 799;
986 ptd->met_data.airpress_tend = bstr->GetInt(192, 2);
987
988 int horVis = bstr->GetInt(194, 8);
989 if (horVis & 0x80u) { // if MSB = 1
990 horVis &= 0x7F; // We print >x.x
991 ptd->met_data.hor_vis_GT = true;
992 } else
993 ptd->met_data.hor_vis_GT = false;
994
995 ptd->met_data.hor_vis = horVis / 10.0;
996
997 ptd->met_data.water_lev_dev = (bstr->GetInt(202, 12) / 100.) - 10.;
998 ptd->met_data.water_lev_trend = bstr->GetInt(214, 2);
999 ptd->met_data.current = bstr->GetInt(216, 8) / 10.;
1000 ptd->met_data.curr_dir = bstr->GetInt(224, 9);
1001 ptd->met_data.wave_height = bstr->GetInt(277, 8) / 10.;
1002 ptd->met_data.wave_period = bstr->GetInt(285, 6);
1003 ptd->met_data.wave_dir = bstr->GetInt(291, 9);
1004 ptd->met_data.swell_height = bstr->GetInt(300, 8) / 10;
1005 ptd->met_data.swell_per = bstr->GetInt(308, 6);
1006 ptd->met_data.swell_dir = bstr->GetInt(314, 9);
1007 ptd->met_data.seastate = bstr->GetInt(323, 4);
1008
1009 int wt = bstr->GetInt(327, 10);
1010 if (wt & 0x00000200) // negative? (bit 9 = 1)
1011 wt |= 0xFFFFFC00;
1012 ptd->met_data.water_temp = wt / 10.;
1013
1014 ptd->met_data.precipitation = bstr->GetInt(337, 3);
1015 ptd->met_data.salinity = bstr->GetInt(340, 9) / 10.;
1016 ptd->met_data.ice = bstr->GetInt(349, 2);
1017
1018 ptd->Class = AIS_METEO;
1019 ptd->COG = -1.;
1020 ptd->HDG = 511;
1021 ptd->SOG = -1.;
1022 ptd->b_NoTrack = true;
1023 ptd->b_show_track = false;
1024 ptd->b_positionDoubtful = false;
1025 ptd->b_positionOnceValid = true;
1026 b_posn_report = true;
1027 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
1028 ptd->PositionReportTicks = now.GetTicks();
1029 ptd->b_nameValid = true;
1030
1031 parse_result = true;
1032 }
1033 }
1034 break;
1035 }
1036
1037 if (dac == 367 && fi == 33) { // ais8_367_33
1038 // US data acording to DAC367_FI33_em_version_release_3-23mar15_0
1039 // We use only the same kind of data as of ais8_001_31 from these
1040 // reports.
1041 const int size = bstr->GetBitCount();
1042 if (size >= 168) {
1043 // Change to meteo mmsi-ID
1044 if (met_mmsi != 666) ptd->MMSI = met_mmsi;
1045 const int startbits = 56;
1046 const int slotsize = 112;
1047 const int slots_count = (size - startbits) / slotsize;
1048 int slotbit;
1049 for (int slot = 0; slot < slots_count; slot++) {
1050 slotbit = slot * slotsize;
1051 int type = bstr->GetInt(slotbit + 57, 4);
1052 ptd->met_data.hour = bstr->GetInt(slotbit + 66, 5);
1053 ptd->met_data.minute = bstr->GetInt(slotbit + 71, 6);
1054 int Site_ID = bstr->GetInt(slotbit + 77, 7);
1055
1056 // Name the station acc to site ID until message type 1
1057 if (!ptd->b_nameValid) {
1058 wxString nameID = "METEO Site: ";
1059 nameID << Site_ID;
1060 strncpy(ptd->ShipName, nameID, SHIP_NAME_LEN - 1);
1061 ptd->b_nameValid = true;
1062 }
1063
1064 if (type == 0) { // Location
1065 int lon = bstr->GetInt(slotbit + 90, 28);
1066 if (lon & 0x08000000) // negative?
1067 lon |= 0xf0000000;
1068 ptd->Lon = lon / 600000.;
1069
1070 int lat = bstr->GetInt(slotbit + 118, 27);
1071 if (lat & 0x04000000) // negative?
1072 lat |= 0xf8000000;
1073 ptd->Lat = lat / 600000.;
1074 ptd->b_positionOnceValid = true;
1075
1076 } else if (type == 1) { // Name
1077 bstr->GetStr(slotbit + 84, 84, &ptd->ShipName[0], SHIP_NAME_LEN);
1078 ptd->b_nameValid = true;
1079
1080 } else if (type == 2) { // Wind
1081 // Description 1 and 2 are real time values.
1082 int descr = bstr->GetInt(slotbit + 116, 3);
1083 if (descr == 1 || descr == 2) {
1084 ptd->met_data.wind_kn = bstr->GetInt(slotbit + 84, 7);
1085 ptd->met_data.wind_gust_kn = bstr->GetInt(slotbit + 91, 7);
1086 ptd->met_data.wind_dir = bstr->GetInt(slotbit + 98, 9);
1087 ptd->met_data.wind_gust_dir = bstr->GetInt(slotbit + 107, 9);
1088 }
1089
1090 } else if (type == 3) { // Water level
1091 // Description 1 and 2 are real time values.
1092 int descr = bstr->GetInt(slotbit + 108, 3);
1093 if (descr == 1 || descr == 2) {
1094 int wltype = bstr->GetInt(slotbit + 84, 1);
1095 int wl = bstr->GetInt(slotbit + 85, 16); // cm
1096 if (wl & 0x00004000) // negative?
1097 wl |= 0xffff0000;
1098
1099 if (wltype == 1) // 0 = deviation from datum; 1 = water depth
1100 ptd->met_data.water_level = wl / 100.; // m
1101 else
1102 ptd->met_data.water_lev_dev = wl / 100.; // m
1103 }
1104 ptd->met_data.water_lev_trend = bstr->GetInt(slotbit + 101, 2);
1105 ptd->met_data.vertical_ref = bstr->GetInt(slotbit + 103, 5);
1106
1107 } else if (type == 6) { // Horizontal Current Profil
1108 ptd->met_data.current = bstr->GetInt(slotbit + 102, 8) / 10.0;
1109 ptd->met_data.curr_dir = bstr->GetInt(slotbit + 110, 9);
1110 } else if (type == 7) { // Sea state
1111 int swell_descr =
1112 bstr->GetInt(slotbit + 111, 3); // Use 1 || 2 real data
1113 if (swell_descr == 1 || swell_descr == 2) {
1114 ptd->met_data.swell_height =
1115 bstr->GetInt(slotbit + 84, 8) / 10.0;
1116 ptd->met_data.swell_per = bstr->GetInt(slotbit + 92, 6);
1117 ptd->met_data.swell_dir = bstr->GetInt(slotbit + 98, 9);
1118 }
1119 ptd->met_data.seastate = bstr->GetInt(slotbit + 107, 4); // Bf
1120 int wt_descr = bstr->GetInt(slotbit + 131, 3);
1121 if (wt_descr == 1 || wt_descr == 2)
1122 ptd->met_data.water_temp =
1123 bstr->GetInt(slotbit + 114, 10) / 10. - 10.;
1124
1125 int wawe_descr = bstr->GetInt(slotbit + 157, 3);
1126 if (wawe_descr == 1 || wawe_descr == 2) { // Only real data
1127 ptd->met_data.wave_height =
1128 bstr->GetInt(slotbit + 134, 8) / 10.0;
1129 ptd->met_data.wave_period = bstr->GetInt(slotbit + 142, 6);
1130 ptd->met_data.wave_dir = bstr->GetInt(slotbit + 148, 9);
1131 }
1132 ptd->met_data.salinity = bstr->GetInt(slotbit + 160, 9 / 10.0);
1133
1134 } else if (type == 8) { // Salinity
1135 ptd->met_data.water_temp =
1136 bstr->GetInt(slotbit + 84, 10) / 10.0 - 10.0;
1137 ptd->met_data.salinity = bstr->GetInt(slotbit + 120, 9) / 10.0;
1138
1139 } else if (type == 9) { // Weather
1140 int tmp = bstr->GetInt(slotbit + 84, 11);
1141 if (tmp & 0x00000400) // negative?
1142 tmp |= 0xFFFFF800;
1143 ptd->met_data.air_temp = tmp / 10.;
1144 int pp, precip = bstr->GetInt(slotbit + 98, 2);
1145 switch (precip) { // Adapt to IMO precipitation
1146 case 0:
1147 pp = 1;
1148 case 1:
1149 pp = 5;
1150 case 2:
1151 pp = 4;
1152 case 3:
1153 pp = 7;
1154 }
1155 ptd->met_data.precipitation = pp;
1156 ptd->met_data.hor_vis = bstr->GetInt(slotbit + 100, 8) / 10.0;
1157 ptd->met_data.dew_point =
1158 bstr->GetInt(slotbit + 108, 10) / 10.0 - 20.0;
1159 ptd->met_data.airpress = bstr->GetInt(slotbit + 121, 9) + 799;
1160 ptd->met_data.airpress_tend = bstr->GetInt(slotbit + 130, 2);
1161 ptd->met_data.salinity = bstr->GetInt(slotbit + 135, 9) / 10.0;
1162
1163 } else if (type == 11) { // Wind V2
1164 // Description 1 and 2 are real time values.
1165 int descr = bstr->GetInt(slotbit + 113, 3);
1166 if (descr == 1 || descr == 2) {
1167 ptd->met_data.wind_kn = bstr->GetInt(slotbit + 84, 7);
1168 ptd->met_data.wind_gust_kn = bstr->GetInt(slotbit + 91, 7);
1169 ptd->met_data.wind_dir = bstr->GetInt(slotbit + 98, 9);
1170 }
1171 }
1172 }
1173
1174 if (ptd->b_positionOnceValid) {
1175 ptd->Class = AIS_METEO;
1176 ptd->COG = -1.;
1177 ptd->HDG = 511;
1178 ptd->SOG = -1.;
1179 ptd->b_NoTrack = true;
1180 ptd->b_show_track = false;
1181 ptd->b_positionDoubtful = false;
1182 b_posn_report = true;
1183 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
1184 ptd->PositionReportTicks = now.GetTicks();
1185 ptd->b_nameValid = true;
1186
1187 parse_result = true;
1188 }
1189 }
1190 break;
1191 }
1192 break;
1193 }
1194 case 14: // Safety Related Broadcast
1195 {
1196 // Always capture the MSG_14 text
1197 char msg_14_text[968];
1198 if (bstr->GetBitCount() > 40) {
1199 int nx = ((bstr->GetBitCount() - 40) / 6) * 6;
1200 int nd = bstr->GetStr(41, nx, msg_14_text, 968);
1201 nd = wxMax(0, nd);
1202 nd = wxMin(nd, 967);
1203 msg_14_text[nd] = 0;
1204 ptd->MSG_14_text = wxString(msg_14_text, wxConvUTF8);
1205 }
1206 parse_result = true; // so far so good
1207
1208 break;
1209 }
1210
1211 case 6: // Addressed Binary Message
1212 [[fallthrough]];
1213 case 7: // Binary Ack
1214 [[fallthrough]];
1215 default:
1216 break;
1217 }
1218
1219 if (b_posn_report) ptd->b_lost = false;
1220
1221 if (true == parse_result) {
1222 // Revalidate the target under some conditions
1223 if (!ptd->b_active && !ptd->b_positionDoubtful && b_posn_report)
1224 ptd->b_active = true;
1225 }
1226
1227 return parse_result;
1228}
1229
1230AisDecoder::AisDecoder(const AisDecoderCallbacks &callbacks)
1231 : m_signalk_selfid(""), m_callbacks(callbacks) {
1232 // Load cached AIS target names from a file
1233 AISTargetNamesC = new AIS_Target_Name_Hash;
1234 AISTargetNamesNC = new AIS_Target_Name_Hash;
1235
1236 if (g_benableAISNameCache) {
1237 if (wxFileName::FileExists(AISTargetNameFileName)) {
1238 wxTextFile infile;
1239 if (infile.Open(AISTargetNameFileName)) {
1240 AIS_Target_Name_Hash *HashFile = AISTargetNamesNC;
1241 wxString line = infile.GetFirstLine();
1242 while (!infile.Eof()) {
1243 if (line.IsSameAs("+++==Confirmed Entry's==+++"))
1244 HashFile = AISTargetNamesC;
1245 else {
1246 if (line.IsSameAs("+++==Non Confirmed Entry's==+++"))
1247 HashFile = AISTargetNamesNC;
1248 else {
1249 wxStringTokenizer tokenizer(line, ",");
1250 int mmsi = wxAtoi(tokenizer.GetNextToken());
1251 wxString name = tokenizer.GetNextToken().Trim();
1252 (*HashFile)[mmsi] = name;
1253 }
1254 }
1255 line = infile.GetNextLine();
1256 }
1257 }
1258 infile.Close();
1259 }
1260 }
1261
1262 BuildERIShipTypeHash();
1263
1264 g_pais_alert_dialog_active = nullptr;
1265 m_bAIS_Audio_Alert_On = false;
1266
1267 m_n_targets = 0;
1268
1269 m_bAIS_AlertPlaying = false;
1270
1271 TimerAIS.SetOwner(this, TIMER_AIS1);
1272 TimerAIS.Start(TIMER_AIS_MSEC, wxTIMER_CONTINUOUS);
1273
1274 m_ptentative_dsctarget = nullptr;
1275 m_dsc_timer.SetOwner(this, TIMER_DSC);
1276
1277 // Create/connect a dynamic event handler slot for wxEVT_OCPN_DATASTREAM(s)
1278 // FIXME delete Connect(wxEVT_OCPN_DATASTREAM,
1279 // (wxObjectEventFunction)(wxEventFunction)&AisDecoder::OnEvtAIS);
1280 // Connect(EVT_OCPN_SIGNALKSTREAM,
1281 // (wxObjectEventFunction)(wxEventFunction)&AisDecoder::OnEvtSignalK);
1282 InitCommListeners();
1283}
1284
1285AisDecoder::~AisDecoder() {
1286 // for (const auto &it : GetTargetList()) {
1287 // AisTargetData *td = it.second;
1288 //
1289 // delete td;
1290 // }
1291
1292 // Write mmsi-shipsname to file in a safe way
1293 wxTempFile outfile;
1294 if (outfile.Open(AISTargetNameFileName)) {
1295 wxString content = "+++==Confirmed Entry's==+++";
1296 AIS_Target_Name_Hash::iterator it;
1297 for (it = AISTargetNamesC->begin(); it != AISTargetNamesC->end(); ++it) {
1298 content.append("\r\n");
1299 content.append(wxString::Format("%i", it->first));
1300 content.append(",").append(it->second);
1301 }
1302 content.append("\r\n");
1303 content.append("+++==Non Confirmed Entry's==+++");
1304 for (it = AISTargetNamesNC->begin(); it != AISTargetNamesNC->end(); ++it) {
1305 content.append("\r\n");
1306 content.append(wxString::Format("%i", it->first));
1307 content.append(",").append(it->second);
1308 }
1309 outfile.Write(content);
1310 outfile.Commit();
1311 }
1312
1313 AISTargetNamesC->clear();
1314 delete AISTargetNamesC;
1315 AISTargetNamesNC->clear();
1316 delete AISTargetNamesNC;
1317
1318 clear_hash_ERI();
1319
1320 m_dsc_timer.Stop();
1321 m_AIS_Audio_Alert_Timer.Stop();
1322 TimerAIS.Stop();
1323
1324#ifdef AIS_DEBUG
1325 printf(
1326 "First message[1, 2] ticks: %d Last Message [1,2]ticks %d Difference: "
1327 "%d\n",
1328 first_rx_ticks, rx_ticks, rx_ticks - first_rx_ticks);
1329#endif
1330}
1331
1332bool IsTargetOnTheIgnoreList(const int &mmsi) {
1333 // Check the MMSI-Prop list if the target shall be ignored
1334 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
1335 if (mmsi == g_MMSI_Props_Array[i]->MMSI) {
1337 if (props->m_bignore) {
1338 return true;
1339 }
1340 }
1341 }
1342 return false;
1343}
1344
1345void AisDecoder::InitCommListeners() {
1346 // Initialize the comm listeners
1347
1348 // NMEA0183
1349 // VDM
1350 Nmea0183Msg n0183_msg_VDM("VDM");
1351 listener_N0183_VDM.Listen(n0183_msg_VDM, this, EVT_N0183_VDM);
1352 Bind(EVT_N0183_VDM, [&](const ObservedEvt &ev) {
1353 auto ptr = ev.GetSharedPtr();
1354 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1355 HandleN0183_AIS(n0183_msg);
1356 });
1357
1358 // FRPOS
1359 Nmea0183Msg n0183_msg_FRPOS("FRPOS");
1360 listener_N0183_FRPOS.Listen(n0183_msg_FRPOS, this, EVT_N0183_FRPOS);
1361
1362 Bind(EVT_N0183_FRPOS, [&](const ObservedEvt &ev) {
1363 auto ptr = ev.GetSharedPtr();
1364 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1365 HandleN0183_AIS(n0183_msg);
1366 });
1367
1368 // CDDSC
1369 Nmea0183Msg n0183_msg_CDDSC("CDDSC");
1370 listener_N0183_CDDSC.Listen(n0183_msg_CDDSC, this, EVT_N0183_CDDSC);
1371 Bind(EVT_N0183_CDDSC, [&](const ObservedEvt &ev) {
1372 auto ptr = ev.GetSharedPtr();
1373 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1374 HandleN0183_AIS(n0183_msg);
1375 });
1376
1377 // CDDSE
1378 Nmea0183Msg n0183_msg_CDDSE("CDDSE");
1379 listener_N0183_CDDSE.Listen(n0183_msg_CDDSE, this, EVT_N0183_CDDSE);
1380 Bind(EVT_N0183_CDDSE, [&](const ObservedEvt &ev) {
1381 auto ptr = ev.GetSharedPtr();
1382 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1383 HandleN0183_AIS(n0183_msg);
1384 });
1385
1386 // TLL
1387 Nmea0183Msg n0183_msg_TLL("TLL");
1388 listener_N0183_TLL.Listen(n0183_msg_TLL, this, EVT_N0183_TLL);
1389
1390 Bind(EVT_N0183_TLL, [&](const ObservedEvt &ev) {
1391 auto ptr = ev.GetSharedPtr();
1392 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1393 HandleN0183_AIS(n0183_msg);
1394 });
1395
1396 // TTM
1397 Nmea0183Msg n0183_msg_ttm("TTM");
1398 listener_N0183_TTM.Listen(n0183_msg_ttm, this, EVT_N0183_TTM);
1399 Bind(EVT_N0183_TTM, [&](const ObservedEvt &ev) {
1400 auto ptr = ev.GetSharedPtr();
1401 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1402 HandleN0183_AIS(n0183_msg);
1403 });
1404
1405 // OSD
1406 Nmea0183Msg n0183_msg_OSD("OSD");
1407 listener_N0183_OSD.Listen(n0183_msg_OSD, this, EVT_N0183_OSD);
1408 Bind(EVT_N0183_OSD, [&](const ObservedEvt &ev) {
1409 auto ptr = ev.GetSharedPtr();
1410 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1411 HandleN0183_AIS(n0183_msg);
1412 });
1413
1414 // WPL
1415 Nmea0183Msg n0183_msg_WPL("WPL");
1416 listener_N0183_WPL.Listen(n0183_msg_WPL, this, EVT_N0183_WPL);
1417 Bind(EVT_N0183_WPL, [&](const ObservedEvt &ev) {
1418 auto ptr = ev.GetSharedPtr();
1419 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1420 HandleN0183_AIS(n0183_msg);
1421 });
1422
1423 // SignalK
1424 SignalkMsg sk_msg;
1425 listener_SignalK.Listen(sk_msg, this, EVT_SIGNALK);
1426 Bind(EVT_SIGNALK, [&](const ObservedEvt &ev) {
1427 HandleSignalK(obs::UnpackEvtPointer<SignalkMsg>(ev));
1428 });
1429
1430 // AIS Class A PGN 129038
1431 //-----------------------------
1432 Nmea2000Msg n2k_msg_129038(static_cast<uint64_t>(129038));
1433 listener_N2K_129038.Listen(n2k_msg_129038, this, EVT_N2K_129038);
1434 Bind(EVT_N2K_129038, [&](const ObservedEvt &ev) {
1435 HandleN2K_129038(obs::UnpackEvtPointer<Nmea2000Msg>(ev));
1436 });
1437
1438 // AIS Class B PGN 129039
1439 //-----------------------------
1440 Nmea2000Msg n2k_msg_129039(static_cast<uint64_t>(129039));
1441 listener_N2K_129039.Listen(n2k_msg_129039, this, EVT_N2K_129039);
1442 Bind(EVT_N2K_129039, [&](const ObservedEvt &ev) {
1443 HandleN2K_129039(obs::UnpackEvtPointer<Nmea2000Msg>(ev));
1444 });
1445
1446 // AIS ATON PGN 129041
1447 //-----------------------------
1448 Nmea2000Msg n2k_msg_129041(static_cast<uint64_t>(129041));
1449 listener_N2K_129041.Listen(n2k_msg_129041, this, EVT_N2K_129041);
1450 Bind(EVT_N2K_129041, [&](const ObservedEvt &ev) {
1451 HandleN2K_129041(obs::UnpackEvtPointer<Nmea2000Msg>(ev));
1452 });
1453
1454 // AIS static data class A PGN 129794
1455 //-----------------------------
1456 Nmea2000Msg n2k_msg_129794(static_cast<uint64_t>(129794));
1457 listener_N2K_129794.Listen(n2k_msg_129794, this, EVT_N2K_129794);
1458 Bind(EVT_N2K_129794, [&](const ObservedEvt &ev) {
1459 HandleN2K_129794(obs::UnpackEvtPointer<Nmea2000Msg>(ev));
1460 });
1461
1462 // AIS static data class B part A PGN 129809
1463 //-----------------------------
1464 Nmea2000Msg n2k_msg_129809(static_cast<uint64_t>(129809));
1465 listener_N2K_129809.Listen(n2k_msg_129809, this, EVT_N2K_129809);
1466 Bind(EVT_N2K_129809, [&](const ObservedEvt &ev) {
1467 HandleN2K_129809(obs::UnpackEvtPointer<Nmea2000Msg>(ev));
1468 });
1469
1470 // AIS static data class B part B PGN 129810
1471 //-----------------------------
1472 Nmea2000Msg n2k_msg_129810(static_cast<uint64_t>(129810));
1473 listener_N2K_129810.Listen(n2k_msg_129810, this, EVT_N2K_129810);
1474 Bind(EVT_N2K_129810, [&](const ObservedEvt &ev) {
1475 HandleN2K_129810(obs::UnpackEvtPointer<Nmea2000Msg>(ev));
1476 });
1477
1478 // AIS Base Station report PGN 129793
1479 //-----------------------------
1480 Nmea2000Msg n2k_msg_129793(static_cast<uint64_t>(129793));
1481 listener_N2K_129793.Listen(n2k_msg_129793, this, EVT_N2K_129793);
1482 Bind(EVT_N2K_129793, [&](const ObservedEvt &ev) {
1483 HandleN2K_129793(obs::UnpackEvtPointer<Nmea2000Msg>(ev));
1484 });
1485}
1486
1487bool AisDecoder::HandleN0183_AIS(const N0183MsgPtr &n0183_msg) {
1488 std::string str = n0183_msg->payload;
1489 wxString sentence(str.c_str());
1490 DecodeN0183(sentence);
1492 return true;
1493}
1494
1495bool AisDecoder::HandleN2K_129038(const N2000MsgPtr &n2k_msg) {
1496 std::vector<unsigned char> v = n2k_msg->payload;
1497
1498 uint8_t MessageID;
1499 tN2kAISRepeat Repeat;
1500 uint32_t UserID;
1501 double Latitude;
1502 double Longitude;
1503 bool Accuracy;
1504 bool RAIM;
1505 uint8_t Seconds;
1506 double COG;
1507 double SOG;
1508 double Heading;
1509 double ROT;
1510 tN2kAISNavStatus NavStat = N2kaisns_Under_Way_Motoring;
1511 tN2kAISTransceiverInformation AISTransceiverInformation;
1512
1513 if (ParseN2kPGN129038(v, MessageID, Repeat, UserID, Latitude, Longitude,
1514 Accuracy, RAIM, Seconds, COG, SOG, Heading, ROT,
1515 NavStat, AISTransceiverInformation)) {
1516 unsigned mmsi = UserID;
1517 // Stop here if the target shall be ignored
1518 if (mmsi == g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi)) return false;
1519
1520 // Is this target already in the global target list?
1521 // Search the current AISTargetList for an MMSI match
1522 std::shared_ptr<AisTargetData> pTargetData = nullptr;
1523 bool bnewtarget = false;
1524
1525 auto it = AISTargetList.find(mmsi);
1526 if (it == AISTargetList.end()) // not found
1527 {
1528 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1529 bnewtarget = true;
1530 m_n_targets++;
1531 } else {
1532 pTargetData = it->second; // find current entry
1533 }
1534
1535 wxDateTime now = wxDateTime::Now();
1536 now.MakeUTC();
1537
1538 // Populate the target_data
1539 pTargetData->MMSI = mmsi;
1540 pTargetData->MID = MessageID;
1541 pTargetData->MMSI = mmsi;
1542 pTargetData->Class = AIS_CLASS_A;
1543 // Check for SART and friends by looking at first two digits of MMSI
1544 if (97 == pTargetData->MMSI / 10000000) {
1545 pTargetData->Class = AIS_SART;
1546 // won't get a static report, so fake it here
1547 pTargetData->StaticReportTicks = now.GetTicks();
1548 }
1549 pTargetData->NavStatus = static_cast<ais_nav_status>(NavStat);
1550 if (!N2kIsNA(SOG)) pTargetData->SOG = MS2KNOTS(SOG);
1551 if (!N2kIsNA(COG)) pTargetData->COG = GeodesicRadToDeg(COG);
1552 if (!N2kIsNA(Heading)) pTargetData->HDG = GeodesicRadToDeg(Heading);
1553 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
1554 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
1555
1556 pTargetData->ROTAIS = ROT;
1557
1558 // FIXME (dave)
1559 // if (ROT == 128)
1560 // pTargetData->ROTAIS = -128; // not available codes as -128
1561 // else if ((ROT & 0x80) == 0x80) {
1562 // pTargetData->ROTAIS = ROT - 256; // convert to twos complement
1563 // rot_dir = -1.0;
1564 // }
1565 //
1566 // pTargetData->ROTIND = round(rot_dir * pow((ROT / 4.733), 2));
1567
1568 pTargetData->b_OwnShip =
1569 AISTransceiverInformation ==
1570 tN2kAISTransceiverInformation::N2kaisown_information_not_broadcast;
1571 pTargetData->b_active = true;
1572 pTargetData->b_lost = false;
1573 pTargetData->b_positionOnceValid = true;
1574 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1575 pTargetData->PositionReportTicks = now.GetTicks();
1576
1577 pSelectAIS->DeleteSelectablePoint((void *)(long)mmsi, SELTYPE_AISTARGET);
1578 CommitAISTarget(pTargetData, "", true, bnewtarget);
1579
1581 return true;
1582 } else
1583 return false;
1584}
1585
1586// AIS position reports for Class B
1587bool AisDecoder::HandleN2K_129039(const N2000MsgPtr &n2k_msg) {
1588 std::vector<unsigned char> v = n2k_msg->payload;
1589
1590 // Input:
1591 // - N2kMsg NMEA2000 message to decode
1592 // bool ParseN2kPGN129039(std::vector<unsigned char> &v, uint8_t &MessageID,
1593 // tN2kAISRepeat &Repeat, uint32_t &UserID,
1594 // double &Latitude, double &Longitude, bool
1595 // &Accuracy, bool &RAIM, uint8_t &Seconds, double
1596 // &COG, double &SOG, tN2kAISTransceiverInformation
1597 // &AISTransceiverInformation, double &Heading,
1598 // tN2kAISUnit &Unit, bool &Display, bool &DSC, bool
1599 // &Band, bool &Msg22, tN2kAISMode &Mode, bool
1600 // &State);
1601
1602 uint8_t MessageID;
1603 tN2kAISRepeat Repeat;
1604 uint32_t UserID;
1605 double Latitude;
1606 double Longitude;
1607 bool Accuracy;
1608 bool RAIM;
1609 uint8_t Seconds;
1610 double COG;
1611 double SOG;
1612 double Heading;
1613 tN2kAISTransceiverInformation AISTransceiverInformation;
1614 tN2kAISUnit Unit;
1615 bool DSC, Band, Msg22, State, Display;
1616 tN2kAISMode Mode;
1617
1618 if (ParseN2kPGN129039(v, MessageID, Repeat, UserID, Latitude, Longitude,
1619 Accuracy, RAIM, Seconds, COG, SOG,
1620 AISTransceiverInformation, Heading, Unit, Display, DSC,
1621 Band, Msg22, Mode, State)) {
1622 uint32_t mmsi = UserID;
1623 // Stop here if the target shall be ignored
1624 if (mmsi == g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi)) return false;
1625
1626 // Is this target already in the global target list?
1627 // Search the current AISTargetList for an MMSI match
1628 std::shared_ptr<AisTargetData> pTargetData = nullptr;
1629 bool bnewtarget = false;
1630
1631 auto it = AISTargetList.find(mmsi);
1632 if (it == AISTargetList.end()) // not found
1633 {
1634 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1635 bnewtarget = true;
1636 m_n_targets++;
1637 } else {
1638 pTargetData = it->second; // find current entry
1639 }
1640
1641 wxDateTime now = wxDateTime::Now();
1642 now.MakeUTC();
1643
1644 // Populate the target_data
1645 pTargetData->MMSI = mmsi;
1646 pTargetData->MID = MessageID;
1647 if (!isBuoyMmsi(mmsi))
1648 pTargetData->Class = AIS_CLASS_B;
1649 else
1650 pTargetData->Class = AIS_BUOY;
1651
1652 pTargetData->NavStatus = UNDEFINED; // Class B targets have no status.
1653 if (!N2kIsNA(SOG)) pTargetData->SOG = MS2KNOTS(SOG);
1654 if (!N2kIsNA(COG)) pTargetData->COG = GeodesicRadToDeg(COG);
1655 if (!N2kIsNA(Heading)) pTargetData->HDG = GeodesicRadToDeg(Heading);
1656 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
1657 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
1658
1659 pTargetData->b_positionOnceValid = true;
1660 pTargetData->b_active = true;
1661 pTargetData->b_lost = false;
1662 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1663 pTargetData->PositionReportTicks = now.GetTicks();
1664 pTargetData->b_OwnShip =
1665 AISTransceiverInformation ==
1666 tN2kAISTransceiverInformation::N2kaisown_information_not_broadcast;
1667
1668 pSelectAIS->DeleteSelectablePoint((void *)(long)mmsi, SELTYPE_AISTARGET);
1669 CommitAISTarget(pTargetData, "", true, bnewtarget);
1670
1672 return true;
1673 } else
1674 return false;
1675
1676 return true;
1677}
1678
1679bool AisDecoder::HandleN2K_129041(const N2000MsgPtr &n2k_msg) {
1680 std::vector<unsigned char> v = n2k_msg->payload;
1681
1682 tN2kAISAtoNReportData data;
1683
1684#if 0
1685 struct tN2kAISAtoNReportData {
1686 uint8_t MessageID;
1687 tN2kAISRepeat Repeat;
1688 uint32_t UserID;
1689 double Longitude;
1690 double Latitude;
1691 bool Accuracy;
1692 bool RAIM;
1693 uint8_t Seconds;
1694 double Length;
1695 double Beam;
1696 double PositionReferenceStarboard ;
1697 double PositionReferenceTrueNorth;
1698 tN2kAISAtoNType AtoNType;
1699 bool OffPositionIndicator;
1700 bool VirtualAtoNFlag;
1701 bool AssignedModeFlag;
1702 tN2kGNSStype GNSSType;
1703 uint8_t AtoNStatus;
1704 tN2kAISTransceiverInformation AISTransceiverInformation;
1705 char AtoNName[34 + 1];
1706#endif
1707
1708 if (ParseN2kPGN129041(v, data)) {
1709 uint32_t mmsi = data.UserID;
1710 // Stop here if the target shall be ignored
1711 if (mmsi == g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi)) return false;
1712
1713 // Is this target already in the global target list?
1714 // Search the current AISTargetList for an MMSI match
1715 std::shared_ptr<AisTargetData> pTargetData = nullptr;
1716 bool bnewtarget = false;
1717
1718 auto it = AISTargetList.find(mmsi);
1719 if (it == AISTargetList.end()) // not found
1720 {
1721 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1722 bnewtarget = true;
1723 m_n_targets++;
1724 } else {
1725 pTargetData = it->second; // find current entry
1726 }
1727
1728 // Populate the target_data
1729 pTargetData->MMSI = mmsi;
1730
1731 wxDateTime now = wxDateTime::Now();
1732 now.MakeUTC();
1733
1734 int offpos = data.OffPositionIndicator; // off position flag
1735 int virt = data.VirtualAtoNFlag; // virtual flag
1736
1737 if (virt)
1738 pTargetData->NavStatus = ATON_VIRTUAL;
1739 else
1740 pTargetData->NavStatus = ATON_REAL;
1741
1742 pTargetData->m_utc_sec = data.Seconds;
1743
1744 if (pTargetData->m_utc_sec <= 59) {
1745 pTargetData->NavStatus += 1;
1746 if (offpos) pTargetData->NavStatus += 1;
1747 }
1748
1749 data.AtoNName[34] = 0;
1750 strncpy(pTargetData->ShipName, data.AtoNName, SHIP_NAME_LEN - 1);
1751 pTargetData->ShipName[sizeof(pTargetData->ShipName) - 1] = '\0';
1752 pTargetData->b_nameValid = true;
1753 pTargetData->MID = 124; // Indicates a name from n2k
1754
1755 pTargetData->ShipType = data.AtoNType;
1756 pTargetData->Class = AIS_ATON;
1757
1758 if (!N2kIsNA(data.Longitude)) pTargetData->Lon = data.Longitude;
1759 if (!N2kIsNA(data.Latitude)) pTargetData->Lat = data.Latitude;
1760 pTargetData->b_positionDoubtful = false;
1761 pTargetData->b_positionOnceValid = true; // Got the position at least once
1762 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1763 pTargetData->PositionReportTicks = now.GetTicks();
1764
1765 // FIXME (dave) Populate more fiddly static data
1766
1767 pSelectAIS->DeleteSelectablePoint((void *)(long)mmsi, SELTYPE_AISTARGET);
1768 CommitAISTarget(pTargetData, "", true, bnewtarget);
1769
1770 touch_state.Notify();
1771 return true;
1772 } else
1773 return false;
1774}
1775
1776// AIS static data class A
1777bool AisDecoder::HandleN2K_129794(const N2000MsgPtr &n2k_msg) {
1778 std::vector<unsigned char> v = n2k_msg->payload;
1779
1780 uint8_t MessageID;
1781 tN2kAISRepeat Repeat;
1782 uint32_t UserID;
1783 uint32_t IMOnumber;
1784 char Callsign[21];
1785 char Name[SHIP_NAME_LEN];
1786 uint8_t VesselType;
1787 double Length;
1788 double Beam;
1789 double PosRefStbd;
1790 double PosRefBow;
1791 uint16_t ETAdate;
1792 double ETAtime;
1793 double Draught;
1794 char Destination[DESTINATION_LEN];
1795 tN2kAISVersion AISversion;
1796 tN2kGNSStype GNSStype;
1797 tN2kAISDTE DTE;
1798 tN2kAISTranceiverInfo AISinfo;
1799
1800 if (ParseN2kPGN129794(v, MessageID, Repeat, UserID, IMOnumber, Callsign, Name,
1801 VesselType, Length, Beam, PosRefStbd, PosRefBow,
1802 ETAdate, ETAtime, Draught, Destination, AISversion,
1803 GNSStype, DTE, AISinfo)) {
1804 uint32_t mmsi = UserID;
1805 // Stop here if the target shall be ignored
1806 if (mmsi == g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi)) return false;
1807
1808 // Is this target already in the global target list?
1809 // Search the current AISTargetList for an MMSI match
1810 std::shared_ptr<AisTargetData> pTargetData = nullptr;
1811 bool bnewtarget = false;
1812
1813 auto it = AISTargetList.find(mmsi);
1814 if (it == AISTargetList.end()) // not found
1815 {
1816 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1817 bnewtarget = true;
1818 m_n_targets++;
1819 } else {
1820 pTargetData = it->second; // find current entry
1821 }
1822
1823 // Populate the target_data
1824 pTargetData->MMSI = mmsi;
1825 strncpy(pTargetData->ShipName, Name, SHIP_NAME_LEN - 1);
1826 pTargetData->ShipName[sizeof(pTargetData->ShipName) - 1] = '\0';
1827 Name[sizeof(Name) - 1] = 0;
1828 pTargetData->b_nameValid = true;
1829 pTargetData->MID = 124; // Indicates a name from n2k
1830
1831 pTargetData->b_OwnShip =
1832 AISinfo ==
1833 tN2kAISTranceiverInfo::N2kaisti_Own_information_not_broadcast;
1834
1835 pTargetData->DimA = PosRefBow;
1836 pTargetData->DimB = Length - PosRefBow;
1837 pTargetData->DimC = Beam - PosRefStbd;
1838 pTargetData->DimD = PosRefStbd;
1839 pTargetData->Draft = Draught;
1840 pTargetData->IMO = IMOnumber;
1841 strncpy(pTargetData->CallSign, Callsign, CALL_SIGN_LEN - 1);
1842 pTargetData->CallSign[sizeof(pTargetData->CallSign) - 1] = '\0';
1843 pTargetData->ShipType = (unsigned char)VesselType;
1844 strncpy(pTargetData->Destination, Destination, DESTINATION_LEN - 1);
1845 pTargetData->Destination[sizeof(pTargetData->Destination) - 1] = '\0';
1846 Destination[sizeof(Destination) - 1] = 0;
1847
1848 if (!N2kIsNA(ETAdate) && !N2kIsNA(ETAtime)) {
1849 long secs = (ETAdate * 24 * 3600) + wxRound(ETAtime);
1850 wxDateTime t((time_t)secs);
1851 if (t.IsValid()) {
1852 wxDateTime tz = t.ToUTC();
1853 pTargetData->ETA_Mo = tz.GetMonth() + 1;
1854 pTargetData->ETA_Day = tz.GetDay();
1855 pTargetData->ETA_Hr = tz.GetHour();
1856 pTargetData->ETA_Min = tz.GetMinute();
1857 }
1858 }
1859
1860 pSelectAIS->DeleteSelectablePoint((void *)(long)mmsi, SELTYPE_AISTARGET);
1861 CommitAISTarget(pTargetData, "", true, bnewtarget);
1862
1864 return true;
1865 } else
1866 return false;
1867}
1868// AIS static data class B part A
1869bool AisDecoder::HandleN2K_129809(const N2000MsgPtr &n2k_msg) {
1870 std::vector<unsigned char> v = n2k_msg->payload;
1871
1872 uint8_t MessageID;
1873 tN2kAISRepeat Repeat;
1874 uint32_t UserID;
1875 char Name[21];
1876
1877 if (ParseN2kPGN129809(v, MessageID, Repeat, UserID, Name)) {
1878 unsigned mmsi = UserID;
1879 // Stop here if the target shall be ignored
1880 if (mmsi == g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi)) return false;
1881
1882 // Is this target already in the global target list?
1883 // Search the current AISTargetList for an MMSI match
1884 std::shared_ptr<AisTargetData> pTargetData = nullptr;
1885 bool bnewtarget = false;
1886
1887 auto it = AISTargetList.find(mmsi);
1888 if (it == AISTargetList.end()) // not found
1889 {
1890 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1891 bnewtarget = true;
1892 m_n_targets++;
1893 } else {
1894 pTargetData = it->second; // find current entry
1895 }
1896
1897 // Populate the target_data
1898 pTargetData->MMSI = mmsi;
1899 Name[sizeof(Name) - 1] = 0;
1900 strncpy(pTargetData->ShipName, Name, SHIP_NAME_LEN - 1);
1901 pTargetData->b_nameValid = true;
1902 pTargetData->MID = 124; // Indicates a name from n2k
1903
1904 pSelectAIS->DeleteSelectablePoint((void *)(long)mmsi, SELTYPE_AISTARGET);
1905 CommitAISTarget(pTargetData, "", true, bnewtarget);
1906
1908 return true;
1909
1910 } else
1911 return false;
1912}
1913
1914// AIS static data class B part B
1915bool AisDecoder::HandleN2K_129810(const N2000MsgPtr &n2k_msg) {
1916 std::vector<unsigned char> v = n2k_msg->payload;
1917
1918 uint8_t MessageID;
1919 tN2kAISRepeat Repeat;
1920 uint32_t UserID;
1921 uint8_t VesselType;
1922 char Vendor[20];
1923 char Callsign[20];
1924 double Length;
1925 double Beam;
1926 double PosRefStbd;
1927 double PosRefBow;
1928 uint32_t MothershipID;
1929
1930 if (ParseN2kPGN129810(v, MessageID, Repeat, UserID, VesselType, Vendor,
1931 Callsign, Length, Beam, PosRefStbd, PosRefBow,
1932 MothershipID)) {
1933 unsigned mmsi = UserID;
1934 // Stop here if the target shall be ignored
1935 if (mmsi == g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi)) return false;
1936
1937 // Is this target already in the global target list?
1938 // Search the current AISTargetList for an MMSI match
1939 std::shared_ptr<AisTargetData> pTargetData = nullptr;
1940 bool bnewtarget = false;
1941
1942 auto it = AISTargetList.find(mmsi);
1943 if (it == AISTargetList.end()) // not found
1944 {
1945 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1946 bnewtarget = true;
1947 m_n_targets++;
1948 } else {
1949 pTargetData = it->second; // find current entry
1950 }
1951
1952 // Populate the target_data
1953 pTargetData->MMSI = mmsi;
1954 pTargetData->DimA = PosRefBow;
1955 pTargetData->DimB = Length - PosRefBow;
1956 pTargetData->DimC = Beam - PosRefStbd;
1957 pTargetData->DimD = PosRefStbd;
1958 strncpy(pTargetData->CallSign, Callsign, CALL_SIGN_LEN - 1);
1959 pTargetData->CallSign[sizeof(pTargetData->CallSign) - 1] = '\0';
1960 pTargetData->ShipType = (unsigned char)VesselType;
1961
1962 pSelectAIS->DeleteSelectablePoint((void *)(long)mmsi, SELTYPE_AISTARGET);
1963 CommitAISTarget(pTargetData, "", true, bnewtarget);
1964
1966 return true;
1967 } else
1968 return false;
1969}
1970
1971// AIS Base Station Report
1972bool AisDecoder::HandleN2K_129793(const N2000MsgPtr &n2k_msg) {
1973 std::vector<unsigned char> v = n2k_msg->payload;
1974
1975 uint8_t MessageID;
1976 tN2kAISRepeat Repeat;
1977 uint32_t UserID;
1978 double Longitude;
1979 double Latitude;
1980 unsigned int SecondsSinceMidnight;
1981 unsigned int DaysSinceEpoch;
1982
1983 if (ParseN2kPGN129793(v, MessageID, Repeat, UserID, Longitude, Latitude,
1984 SecondsSinceMidnight, DaysSinceEpoch)) {
1985 wxDateTime now = wxDateTime::Now();
1986 now.MakeUTC();
1987
1988 // Is this target already in the global target list?
1989 // Search the current AISTargetList for an MMSI match
1990 int mmsi = UserID;
1991 std::shared_ptr<AisTargetData> pTargetData = nullptr;
1992 bool bnewtarget = false;
1993
1994 auto it = AISTargetList.find(mmsi);
1995 if (it == AISTargetList.end()) // not found
1996 {
1997 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1998 bnewtarget = true;
1999 m_n_targets++;
2000 } else {
2001 pTargetData = it->second; // find current entry
2002 }
2003
2004 // Populate the target_data
2005 pTargetData->MMSI = mmsi;
2006 pTargetData->Class = AIS_BASE;
2007
2008 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
2009 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
2010 pTargetData->b_positionDoubtful = false;
2011 pTargetData->b_positionOnceValid = true; // Got the position at least once
2012 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
2013 pTargetData->PositionReportTicks = now.GetTicks();
2014
2015 // FIXME (dave) Populate more fiddly static data
2016
2017 pSelectAIS->DeleteSelectablePoint((void *)(long)mmsi, SELTYPE_AISTARGET);
2018 CommitAISTarget(pTargetData, "", true, bnewtarget);
2019
2021 return true;
2022 } else
2023 return false;
2024}
2025
2026// void AisDecoder::HandleSignalK(std::shared_ptr<const SignalkMsg> sK_msg){
2027// std::string msgTerminated = sK_msg->raw_message;;
2028// wxString sentence(msgTerminated.c_str());
2029// Decode(sentence);
2030// touch_state.notify();
2031// return true;
2032// }
2033
2034//----------------------------------------------------------------------------------
2035// Handle events from SignalK
2036//----------------------------------------------------------------------------------
2037void AisDecoder::HandleSignalK(const SignalKMsgPtr &sK_msg) {
2038 rapidjson::Document root;
2039
2040 root.Parse(sK_msg->raw_message);
2041
2042 if (root.HasParseError()) return;
2043
2044 if (root.HasMember("self")) {
2045 // m_signalk_selfid = "vessels." + (root["self"].AsString());
2046 m_signalk_selfid =
2047 (root["self"]
2048 .GetString()); // Verified for OpenPlotter node.js server 1.20
2049 }
2050 if (m_signalk_selfid.IsEmpty()) {
2051 return; // Don't handle any messages (with out self) until we know how we
2052 // are
2053 }
2054 long mmsi = 0;
2055 int meteo_SiteID = 0;
2056 if (root.HasMember("context") && root["context"].IsString()) {
2057 wxString context = root["context"].GetString();
2058 if (context == m_signalk_selfid) {
2059#if 0
2060 wxLogMessage("** Ignore context own ship..");
2061#endif
2062 return;
2063 }
2064 wxString mmsi_string;
2065 if (context.StartsWith("vessels.urn:mrn:imo:mmsi:", &mmsi_string) ||
2066 context.StartsWith("atons.urn:mrn:imo:mmsi:", &mmsi_string) ||
2067 context.StartsWith("aircraft.urn:mrn:imo:mmsi:", &mmsi_string)) {
2068 // wxLogMessage(wxString::Format("Context: %s, %s",
2069 // context.c_str(), mmsi_string));
2070 if (mmsi_string.ToLong(&mmsi)) {
2071 // wxLogMessage("Got MMSI from context.");
2072 } else {
2073 mmsi = 0;
2074 }
2075 } else if (context.StartsWith("meteo.urn:mrn:imo:mmsi:", &mmsi_string)) {
2076 // mmsi_string for a Meteo is like: 002655619:672707
2077 origin_mmsi = wxAtoi(wxString(mmsi_string).BeforeFirst(':'));
2078 meteo_SiteID = wxAtoi('1' + wxString(mmsi_string).AfterFirst(':'));
2079 // Preface "1" to distinguish e.g. "012345" from "12345"
2080 // Get a meteo mmsi_ID
2081 int meteo_mmsi = AisMeteoNewMmsi(origin_mmsi, 0, 0, 999, meteo_SiteID);
2082 if (meteo_mmsi)
2083 mmsi = meteo_mmsi;
2084 else
2085 mmsi = 0;
2086 }
2087 }
2088 if (mmsi == 0) {
2089 return; // Only handle ships with MMSI for now
2090 }
2091
2092 if (g_pMUX && g_pMUX->IsLogActive()) {
2093 wxString logmsg;
2094 logmsg.Printf("AIS :MMSI: %ld", mmsi);
2095 g_pMUX->LogInputMessage(sK_msg, false, false);
2096 }
2097
2098 // Stop here if the target shall be ignored
2099 if (IsTargetOnTheIgnoreList(mmsi)) return;
2100 // If self data is not set on the SK server, own ship mmsi
2101 // could fall through here but being set in options Own ship.
2102 if (mmsi == g_OwnShipmmsi) return;
2103
2104#if 0
2105 wxLogDebug( "AisDecoder::OnEvtSignalK: %s", root.dump().c_str(); );
2106#endif
2107 std::shared_ptr<AisTargetData> pTargetData = nullptr;
2108 std::shared_ptr<AisTargetData> pStaleTarget = nullptr;
2109 bool bnewtarget = false;
2110 int last_report_ticks;
2111 wxDateTime now;
2112 getAISTarget(mmsi, pTargetData, pStaleTarget, bnewtarget, last_report_ticks,
2113 now);
2114 if (pTargetData) {
2115 pTargetData->MMSI = mmsi;
2116 getMmsiProperties(pTargetData);
2117 if (root.HasMember("updates") && root["updates"].IsArray()) {
2118 for (rapidjson::Value::ConstValueIterator itr = root["updates"].Begin();
2119 itr != root["updates"].End(); ++itr) {
2120 handleUpdate(pTargetData, bnewtarget, *itr);
2121 }
2122 }
2123
2124 // A SART can send wo any values first transmits. Detect class already here.
2125 if (97 == mmsi / 10000000) {
2126 pTargetData->Class = AIS_SART;
2127 } else if (1994 == mmsi / 100000) {
2128 // SignalK meteo data
2129 pTargetData->Class = AIS_METEO;
2130 pTargetData->met_data.original_mmsi = origin_mmsi;
2131 pTargetData->met_data.stationID = meteo_SiteID;
2132 /* Make a unique "shipname" for each station
2133 based on position inherited from meteo_SiteID */
2134 wxString met_name = pTargetData->ShipName;
2135 if (met_name.Find("METEO") == wxNOT_FOUND) {
2136 wxString s_id;
2137 int id1, id2;
2138 s_id << meteo_SiteID;
2139 id1 = wxAtoi(s_id.Mid(1, 3));
2140 id2 = wxAtoi(s_id.Mid(4, 3));
2141 met_name = "METEO ";
2142 met_name << wxString::Format("%03d", (id1 + id2)).Right(3);
2143 strncpy(pTargetData->ShipName, met_name, SHIP_NAME_LEN - 1);
2144 }
2145 pTargetData->b_nameValid = true;
2146 pTargetData->MID = 123; // Indicates a name from SignalK
2147 pTargetData->COG = -1.;
2148 pTargetData->HDG = 511;
2149 pTargetData->SOG = -1.;
2150 pTargetData->b_NoTrack = true;
2151 pTargetData->b_show_track = false;
2152 }
2153 pTargetData->b_OwnShip = false;
2154 AISTargetList[pTargetData->MMSI] = pTargetData;
2155 }
2156}
2157
2158void AisDecoder::handleUpdate(const std::shared_ptr<AisTargetData> &pTargetData,
2159 bool bnewtarget, const rapidjson::Value &update) {
2160 wxString sfixtime = "";
2161
2162 if (update.HasMember("timestamp")) {
2163 sfixtime = update["timestamp"].GetString();
2164 }
2165 if (update.HasMember("values") && update["values"].IsArray()) {
2166 for (rapidjson::Value::ConstValueIterator itr = update["values"].Begin();
2167 itr != update["values"].End(); ++itr) {
2168 updateItem(pTargetData, bnewtarget, *itr, sfixtime);
2169 }
2170 }
2171 wxDateTime now = wxDateTime::Now();
2172 pTargetData->m_utc_hour = now.ToUTC().GetHour();
2173 pTargetData->m_utc_min = now.ToUTC().GetMinute();
2174 pTargetData->m_utc_sec = now.ToUTC().GetSecond();
2175 // pTargetData->NavStatus = 15; // undefined
2176 pTargetData->b_active = true;
2177 pTargetData->b_lost = false;
2178
2179 if (pTargetData->b_positionOnceValid) {
2180 long mmsi_long = pTargetData->MMSI;
2181 SelectItem *pSel =
2182 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
2183 (void *)mmsi_long, SELTYPE_AISTARGET);
2184 pSel->SetUserData(pTargetData->MMSI);
2185 }
2186 UpdateOneCPA(pTargetData.get());
2187 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
2188}
2189
2190void AisDecoder::updateItem(const std::shared_ptr<AisTargetData> &pTargetData,
2191 bool bnewtarget, const rapidjson::Value &item,
2192 wxString &sfixtime) const {
2193 if (item.HasMember("path") && item.HasMember("value")) {
2194 const wxString &update_path = item["path"].GetString();
2195 if (update_path == "navigation.position") {
2196 if (item["value"].HasMember("latitude") &&
2197 item["value"].HasMember("longitude")) {
2198 wxDateTime now = wxDateTime::Now();
2199 now.MakeUTC();
2200 double lat = item["value"]["latitude"].GetDouble();
2201 double lon = item["value"]["longitude"].GetDouble();
2202 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
2203 pTargetData->PositionReportTicks = now.GetTicks();
2204 pTargetData->StaticReportTicks = now.GetTicks();
2205 pTargetData->Lat = lat;
2206 pTargetData->Lon = lon;
2207 pTargetData->b_positionOnceValid = true;
2208 pTargetData->b_positionDoubtful = false;
2209 }
2210
2211 /* Not implemented in SK server (2024-01)
2212 if (item["value"].HasMember("altitude")) {
2213 pTargetData->altitude = item["value"]["altitude "].GetInt();
2214 }*/
2215 } else if (update_path == "navigation.speedOverGround" &&
2216 item["value"].IsNumber()) {
2217 pTargetData->SOG = item["value"].GetDouble() * ms_to_knot_factor;
2218 } else if (update_path == "navigation.courseOverGroundTrue" &&
2219 item["value"].IsNumber()) {
2220 pTargetData->COG = GEODESIC_RAD2DEG(item["value"].GetDouble());
2221 } else if (update_path == "navigation.headingTrue" &&
2222 item["value"].IsNumber()) {
2223 pTargetData->HDG = GEODESIC_RAD2DEG(item["value"].GetDouble());
2224 } else if (update_path == "navigation.rateOfTurn" &&
2225 item["value"].IsNumber()) {
2226 pTargetData->ROTAIS = 4.733 * sqrt(item["value"].GetDouble());
2227 } else if (update_path == "design.aisShipType") {
2228 if (item["value"].HasMember("id")) {
2229 if (!pTargetData->b_isDSCtarget) {
2230 pTargetData->ShipType = item["value"]["id"].GetUint();
2231 }
2232 }
2233 } else if (update_path == "atonType") {
2234 if (item["value"].HasMember("id")) {
2235 pTargetData->ShipType = item["value"]["id"].GetUint();
2236 }
2237 } else if (update_path == "virtual") {
2238 if (item["value"].GetBool()) {
2239 pTargetData->NavStatus = ATON_VIRTUAL;
2240 } else {
2241 pTargetData->NavStatus = ATON_REAL;
2242 }
2243 } else if (update_path == "offPosition") {
2244 if (item["value"].GetBool()) {
2245 if (ATON_REAL == pTargetData->NavStatus) {
2246 pTargetData->NavStatus = ATON_REAL_OFFPOSITION;
2247 } else if (ATON_VIRTUAL == pTargetData->NavStatus) {
2248 pTargetData->NavStatus = ATON_VIRTUAL_OFFPOSITION;
2249 }
2250 }
2251 } else if (update_path == "design.draft") {
2252 if (item["value"].HasMember("maximum") &&
2253 item["value"]["maximum"].IsNumber()) {
2254 pTargetData->Draft = item["value"]["maximum"].GetDouble();
2255 pTargetData->Euro_Draft = item["value"]["maximum"].GetDouble();
2256 }
2257 if (item["value"].HasMember("current") &&
2258 item["value"]["current"].IsNumber()) {
2259 double draft = item["value"]["current"].GetDouble();
2260 if (draft > 0) {
2261 pTargetData->Draft = draft;
2262 pTargetData->Euro_Draft = draft;
2263 }
2264 }
2265 } else if (update_path == "design.length") {
2266 if (pTargetData->DimB == 0) {
2267 if (item["value"].HasMember("overall")) {
2268 if (item["value"]["overall"].IsNumber()) {
2269 pTargetData->Euro_Length = item["value"]["overall"].GetDouble();
2270 pTargetData->DimA = item["value"]["overall"].GetDouble();
2271 }
2272 pTargetData->DimB = 0;
2273 }
2274 }
2275 } else if (update_path == "sensors.ais.class") {
2276 wxString aisclass = item["value"].GetString();
2277 if (aisclass == "A") {
2278 if (!pTargetData->b_isDSCtarget) pTargetData->Class = AIS_CLASS_A;
2279 } else if (aisclass == "B") {
2280 if (!pTargetData->b_isDSCtarget) {
2281 if (!isBuoyMmsi(pTargetData->MMSI))
2282 pTargetData->Class = AIS_CLASS_B;
2283 else
2284 pTargetData->Class = AIS_BUOY;
2285
2286 // Class B targets have no status. Enforce this...
2287 pTargetData->NavStatus = UNDEFINED;
2288 }
2289 } else if (aisclass == "BASE") {
2290 pTargetData->Class = AIS_BASE;
2291 } else if (aisclass == "ATON") {
2292 pTargetData->Class = AIS_ATON;
2293 }
2294 } else if (update_path == "sensors.ais.fromBow") {
2295 if (pTargetData->DimB == 0 && pTargetData->DimA != 0) {
2296 int length = pTargetData->DimA;
2297 if (item["value"].IsNumber()) {
2298 pTargetData->DimA = item["value"].GetDouble();
2299 pTargetData->DimB = length - item["value"].GetDouble();
2300 }
2301 }
2302 } else if (update_path == "design.beam") {
2303 if (pTargetData->DimD == 0) {
2304 if (item["value"].IsNumber()) {
2305 pTargetData->Euro_Beam = item["value"].GetDouble();
2306 pTargetData->DimC = item["value"].GetDouble();
2307 }
2308 pTargetData->DimD = 0;
2309 }
2310 } else if (update_path == "sensors.ais.fromCenter") {
2311 if (pTargetData->DimD == 0 && pTargetData->DimC != 0) {
2312 int beam = pTargetData->DimC;
2313 int center = beam / 2;
2314 if (item["value"].IsNumber()) {
2315 // FIXME (nohal): Dim* are int, but we have seen data streams with
2316 // doubles in them...
2317 pTargetData->DimC = center + item["value"].GetDouble();
2318 pTargetData->DimD = beam - pTargetData->DimC;
2319 }
2320 }
2321 } else if (update_path == "navigation.state") {
2322 wxString state = item["value"].GetString();
2323 if (state == "motoring") {
2324 pTargetData->NavStatus = UNDERWAY_USING_ENGINE;
2325 } else if (state == "anchored") {
2326 pTargetData->NavStatus = AT_ANCHOR;
2327 } else if (state == "not under command") {
2328 pTargetData->NavStatus = NOT_UNDER_COMMAND;
2329 } else if (state == "restricted manouverability") {
2330 pTargetData->NavStatus = RESTRICTED_MANOEUVRABILITY;
2331 } else if (state == "constrained by draft") {
2332 pTargetData->NavStatus = CONSTRAINED_BY_DRAFT;
2333 } else if (state == "moored") {
2334 pTargetData->NavStatus = MOORED;
2335 } else if (state == "aground") {
2336 pTargetData->NavStatus = AGROUND;
2337 } else if (state == "fishing") {
2338 pTargetData->NavStatus = FISHING;
2339 } else if (state == "sailing") {
2340 pTargetData->NavStatus = UNDERWAY_SAILING;
2341 } else if (state == "hazardous material high speed") {
2342 pTargetData->NavStatus = HSC;
2343 } else if (state == "hazardous material wing in ground") {
2344 pTargetData->NavStatus = WIG;
2345 } else if (state == "ais-sart") {
2346 pTargetData->NavStatus = RESERVED_14;
2347 } else {
2348 pTargetData->NavStatus = UNDEFINED;
2349 }
2350 } else if (update_path == "navigation.destination.commonName") {
2351 const wxString &destination = item["value"].GetString();
2352 pTargetData->Destination[0] = '\0';
2353 strncpy(pTargetData->Destination, destination.c_str(),
2354 DESTINATION_LEN - 1);
2355 } else if (update_path == "navigation.destination.eta") {
2356 const wxString &eta = item["value"].GetString();
2357 if (eta.Len()) {
2358 // Parse ISO 8601 date/time
2359 wxDateTime tz;
2360 ParseGPXDateTime(tz, eta);
2361 pTargetData->ETA_Mo = tz.GetMonth() + 1;
2362 pTargetData->ETA_Day = tz.GetDay();
2363 pTargetData->ETA_Hr = tz.GetHour();
2364 pTargetData->ETA_Min = tz.GetMinute();
2365 }
2366 } else if (update_path == "navigation.specialManeuver") {
2367 if (strcmp("not available", item["value"].GetString()) != 0 &&
2368 pTargetData->IMO < 1) {
2369 const wxString &bluesign = item["value"].GetString();
2370 if ("not engaged" == bluesign) {
2371 pTargetData->blue_paddle = 1;
2372 }
2373 if ("engaged" == bluesign) {
2374 pTargetData->blue_paddle = 2;
2375 }
2376 pTargetData->b_blue_paddle =
2377 pTargetData->blue_paddle == 2 ? true : false;
2378 }
2379 } else if (update_path == "sensors.ais.designatedAreaCode") {
2380 if (item["value"].GetInt() == 200) { // European inland
2381 pTargetData->b_hasInlandDac = true;
2382 }
2383 } else if (update_path == "sensors.ais.functionalId") {
2384 if (item["value"].GetInt() == 10 && pTargetData->b_hasInlandDac) {
2385 // "Inland ship static and voyage related data"
2386 pTargetData->b_isEuroInland = true;
2387 }
2388
2389 // METEO Data
2390 } else if (update_path == "environment.date") {
2391 const wxString &issued = item["value"].GetString();
2392 if (issued.Len()) {
2393 // Parse ISO 8601 date/time
2394 wxDateTime tz;
2395 ParseGPXDateTime(tz, issued);
2396 pTargetData->met_data.day = tz.GetDay();
2397 pTargetData->met_data.hour = tz.GetHour();
2398 pTargetData->met_data.minute = tz.GetMinute();
2399 }
2400 } else if (update_path == "environment.wind.averageSpeed" &&
2401 item["value"].IsNumber()) {
2402 pTargetData->met_data.wind_kn = MS2KNOTS(item["value"].GetDouble());
2403 } else if (update_path == "environment.wind.gust" &&
2404 item["value"].IsNumber()) {
2405 pTargetData->met_data.wind_gust_kn = MS2KNOTS(item["value"].GetDouble());
2406 } else if (update_path == "environment.wind.directionTrue" &&
2407 item["value"].IsNumber()) {
2408 pTargetData->met_data.wind_dir =
2409 GEODESIC_RAD2DEG(item["value"].GetDouble());
2410 } else if (update_path == "environment.wind.gustDirectionTrue" &&
2411 item["value"].IsNumber()) {
2412 pTargetData->met_data.wind_gust_dir =
2413 GEODESIC_RAD2DEG(item["value"].GetDouble());
2414 } else if (update_path == "environment.outside.temperature" &&
2415 item["value"].IsNumber()) {
2416 pTargetData->met_data.air_temp = KelvinToC(item["value"].GetDouble());
2417 } else if (update_path == "environment.outside.relativeHumidity" &&
2418 item["value"].IsNumber()) {
2419 pTargetData->met_data.rel_humid = item["value"].GetDouble();
2420 } else if (update_path == "environment.outside.dewPointTemperature" &&
2421 item["value"].IsNumber()) {
2422 pTargetData->met_data.dew_point = KelvinToC(item["value"].GetDouble());
2423 } else if (update_path == "environment.outside.pressure" &&
2424 item["value"].IsNumber()) {
2425 pTargetData->met_data.airpress =
2426 static_cast<int>(item["value"].GetDouble() / 100);
2427 } else if (update_path == "environment.water.level" &&
2428 item["value"].IsNumber()) {
2429 pTargetData->met_data.water_lev_dev = item["value"].GetDouble();
2430 } else if (update_path == "environment.water.current.drift" &&
2431 item["value"].IsNumber()) { // surfcurrspd
2432 pTargetData->met_data.current = MS2KNOTS(item["value"].GetDouble());
2433 } else if (update_path == "environment.water.current.set" &&
2434 item["value"].IsNumber()) { // surfcurrdir
2435 pTargetData->met_data.curr_dir =
2436 GEODESIC_RAD2DEG(item["value"].GetDouble());
2437 } else if (update_path == "environment.water.levelTendencyValue" &&
2438 item["value"].IsNumber()) {
2439 pTargetData->met_data.water_lev_trend =
2440 static_cast<int>(item["value"].GetDouble());
2441 } else if (update_path == "environment.water.levelTendency") {
2442 // Don't use this text we parse it ourself.
2443 } else if (update_path == "environment.water.waves.significantHeight" &&
2444 item["value"].IsNumber()) {
2445 pTargetData->met_data.wave_height = item["value"].GetDouble();
2446 } else if (update_path == "environment.water.waves.period" &&
2447 item["value"].IsNumber()) {
2448 pTargetData->met_data.wave_period =
2449 static_cast<int>(item["value"].GetDouble());
2450 } else if (update_path == "environment.water.waves.directionTrue" &&
2451 item["value"].IsNumber()) {
2452 pTargetData->met_data.wave_dir =
2453 GEODESIC_RAD2DEG(item["value"].GetDouble());
2454 } else if (update_path == "environment.water.swell.height" &&
2455 item["value"].IsNumber()) {
2456 pTargetData->met_data.swell_height = item["value"].GetDouble();
2457 } else if (update_path == "environment.water.swell.period" &&
2458 item["value"].IsNumber()) {
2459 pTargetData->met_data.swell_per =
2460 static_cast<int>(item["value"].GetDouble());
2461 } else if (update_path == "environment.water.swell.directionTrue" &&
2462 item["value"].IsNumber()) {
2463 pTargetData->met_data.swell_dir =
2464 GEODESIC_RAD2DEG(item["value"].GetDouble());
2465 } else if (update_path == "environment.water.temperature" &&
2466 item["value"].IsNumber()) {
2467 pTargetData->met_data.water_temp = KelvinToC(item["value"].GetDouble());
2468 } else if (update_path == "environment.water.salinity" &&
2469 item["value"].IsNumber()) {
2470 pTargetData->met_data.salinity = item["value"].GetDouble();
2471 } else if (update_path == "environment.water.ice") {
2472 // Don't use. We parse it ourself
2473 } else if (update_path == "environment.water.iceValue" &&
2474 item["value"].IsNumber()) {
2475 pTargetData->met_data.ice = static_cast<int>(item["value"].GetDouble());
2476 } else if (update_path == "environment.water.seaStateValue" &&
2477 item["value"].IsNumber()) {
2478 pTargetData->met_data.seastate =
2479 static_cast<int>(item["value"].GetDouble());
2480 } else if (update_path == "environment.water.seaState") {
2481 // This is the parsed (air!) Bf-scale. Don't use
2482 } else if (update_path == "environment.outside.precipitation") {
2483 // Don't use. We parse it ourself
2484 } else if (update_path == "environment.outside.precipitationValue" &&
2485 item["value"].IsNumber()) {
2486 pTargetData->met_data.precipitation =
2487 static_cast<int>(item["value"].GetDouble());
2488 } else if (update_path == "environment.outside.pressureTendencyValue" &&
2489 item["value"].IsNumber()) {
2490 pTargetData->met_data.airpress_tend =
2491 static_cast<int>(item["value"].GetDouble());
2492 } else if (update_path == "environment.outside.pressureTendency") {
2493 // Parsed value, don't use, we do it ourself
2494 } else if (update_path == "environment.outside.horizontalVisibility" &&
2495 item["value"].IsNumber()) {
2496 pTargetData->met_data.hor_vis =
2497 GEODESIC_METERS2NM(item["value"].GetDouble());
2498 } else if (update_path ==
2499 "environment.outside.horizontalVisibility.overRange") {
2500 pTargetData->met_data.hor_vis_GT = item["value"].GetBool();
2501 } else if (update_path.empty()) {
2502 if (item["value"].HasMember("name")) {
2503 const wxString &name = item["value"]["name"].GetString();
2504 strncpy(pTargetData->ShipName, name.c_str(), SHIP_NAME_LEN - 1);
2505 pTargetData->b_nameValid = true;
2506 pTargetData->MID = 123; // Indicates a name from SignalK
2507 } else if (item["value"].HasMember("registrations")) {
2508 const wxString &imo = item["value"]["registrations"]["imo"].GetString();
2509 pTargetData->IMO = wxAtoi(imo.Right(7));
2510 } else if (item["value"].HasMember("communication")) {
2511 const wxString &callsign =
2512 item["value"]["communication"]["callsignVhf"].GetString();
2513 strncpy(pTargetData->CallSign, callsign.c_str(), 7);
2514 }
2515 if (item["value"].HasMember("mmsi") &&
2516 1994 != (pTargetData->MMSI) / 100000) { // Meteo
2517 long mmsi;
2518 wxString tmp = item["value"]["mmsi"].GetString();
2519 if (tmp.ToLong(&mmsi)) {
2520 pTargetData->MMSI = mmsi;
2521
2522 if (97 == mmsi / 10000000) {
2523 pTargetData->Class = AIS_SART;
2524 }
2525 if (111 == mmsi / 1000000) {
2526 pTargetData->b_SarAircraftPosnReport = true;
2527 }
2528
2529 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
2530 mmsi);
2531 }
2532 }
2533 } else {
2534 wxLogMessage(wxString::Format(
2535 "** AisDecoder::updateItem: unhandled path %s", update_path));
2536#if 1
2537 rapidjson::StringBuffer buffer;
2538 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2539 item.Accept(writer);
2540 wxString msg("update: ");
2541 msg.append(buffer.GetString());
2542 wxLogMessage(msg);
2543#endif
2544 }
2545 }
2546}
2547
2548//----------------------------------------------------------------------------------
2549// Decode a single AIVDO sentence to a Generic Position Report
2550//----------------------------------------------------------------------------------
2551AisError AisDecoder::DecodeSingleVDO(const wxString &str, GenericPosDatEx *pos,
2552 wxString *accumulator) {
2553 // Make some simple tests for validity
2554 if (str.Len() > 128) return AIS_NMEAVDX_TOO_LONG;
2555
2556 if (!NMEACheckSumOK(str)) return AIS_NMEAVDX_CHECKSUM_BAD;
2557
2558 if (!pos) return AIS_GENERIC_ERROR;
2559
2560 if (!accumulator) return AIS_GENERIC_ERROR;
2561
2562 // We only process AIVDO messages
2563 if (!str.Mid(1, 5).IsSameAs("AIVDO")) return AIS_GENERIC_ERROR;
2564
2565 // Use a tokenizer to pull out the first 4 fields
2566 wxStringTokenizer tkz(str, ",");
2567
2568 wxString token;
2569 token = tkz.GetNextToken(); // !xxVDx
2570
2571 token = tkz.GetNextToken();
2572 int nsentences = atoi(token.mb_str());
2573
2574 token = tkz.GetNextToken();
2575 int isentence = atoi(token.mb_str());
2576
2577 token = tkz.GetNextToken(); // skip 2 fields
2578 token = tkz.GetNextToken();
2579
2580 wxString string_to_parse;
2581 string_to_parse.Clear();
2582
2583 // Fill the output structure with all NANs
2584 pos->kLat = NAN;
2585 pos->kLon = NAN;
2586 pos->kCog = NAN;
2587 pos->kSog = NAN;
2588 pos->kHdt = NAN;
2589 pos->kVar = NAN;
2590 pos->kHdm = NAN;
2591
2592 // Simple case first
2593 // First and only part of a one-part sentence
2594 if ((1 == nsentences) && (1 == isentence)) {
2595 string_to_parse = tkz.GetNextToken(); // the encapsulated data
2596 }
2597
2598 else if (nsentences > 1) {
2599 if (1 == isentence) {
2600 *accumulator = tkz.GetNextToken(); // the encapsulated data
2601 }
2602
2603 else {
2604 accumulator->Append(tkz.GetNextToken());
2605 }
2606
2607 if (isentence == nsentences) { // ready to parse
2608 string_to_parse = *accumulator;
2609 }
2610 }
2611
2612 if (string_to_parse.IsEmpty() &&
2613 (nsentences > 1)) { // not ready, so return with NAN
2614 return AIS_INCOMPLETE_MULTIPART; // and non-zero return
2615 }
2616
2617 // Create the bit accessible string
2618 AisBitstring strbit(string_to_parse.mb_str());
2619
2620 // auto TargetData = std::make_unique<AisTargetData>(
2621 // *AisTargetDataMaker::GetInstance().GetTargetData());
2622
2623 auto TargetData = AisTargetDataMaker::GetInstance().GetTargetData();
2624
2625 bool bdecode_result = Parse_VDXBitstring(&strbit, TargetData);
2626
2627 if (bdecode_result) {
2628 switch (TargetData->MID) {
2629 case 1:
2630 case 2:
2631 case 3:
2632 case 18: {
2633 if (!TargetData->b_positionDoubtful) {
2634 pos->kLat = TargetData->Lat;
2635 pos->kLon = TargetData->Lon;
2636 } else {
2637 pos->kLat = NAN;
2638 pos->kLon = NAN;
2639 }
2640
2641 if (TargetData->COG == 360.0)
2642 pos->kCog = NAN;
2643 else
2644 pos->kCog = TargetData->COG;
2645
2646 if (TargetData->SOG > 102.2)
2647 pos->kSog = NAN;
2648 else
2649 pos->kSog = TargetData->SOG;
2650
2651 if ((int)TargetData->HDG == 511)
2652 pos->kHdt = NAN;
2653 else
2654 pos->kHdt = TargetData->HDG;
2655
2656 // VDO messages do not contain variation or magnetic heading
2657 pos->kVar = NAN;
2658 pos->kHdm = NAN;
2659 break;
2660 }
2661 default:
2662 return AIS_GENERIC_ERROR; // unrecognised sentence
2663 }
2664
2665 return AIS_NoError;
2666 } else
2667 return AIS_GENERIC_ERROR;
2668}
2669
2670//----------------------------------------------------------------------------------------
2671// Decode NMEA VDM/VDO/FRPOS/DSCDSE/TTM/TLL/OSD/RSD/TLB/WPL sentence to AIS
2672// Target(s)
2673//----------------------------------------------------------------------------------------
2674
2675AisError AisDecoder::DecodeN0183(const wxString &str) {
2676 AisError ret = AIS_GENERIC_ERROR;
2677 wxString string_to_parse;
2678
2679 double gpsg_lat, gpsg_lon, gpsg_mins, gpsg_degs;
2680 double gpsg_cog, gpsg_sog, gpsg_utc_time;
2681 int gpsg_utc_hour = 0;
2682 int gpsg_utc_min = 0;
2683 int gpsg_utc_sec = 0;
2684 char gpsg_name_str[21];
2685 wxString gpsg_date;
2686
2687 bool bdecode_result = false;
2688
2689 int gpsg_mmsi = 0;
2690 int arpa_mmsi = 0;
2691 int aprs_mmsi = 0;
2692 unsigned mmsi = 0;
2693
2694 long arpa_tgt_num = 0;
2695 double arpa_sog = 0.;
2696 double arpa_cog = 0.;
2697 double arpa_lat = 0.;
2698 double arpa_lon = 0.;
2699 double arpa_dist = 0.;
2700 double arpa_brg = 0.;
2701 wxString arpa_brgunit;
2702 wxString arpa_status;
2703 wxString arpa_distunit;
2704 wxString arpa_cogunit;
2705 double arpa_mins, arpa_degs;
2706 double arpa_utc_time;
2707 int arpa_utc_hour = 0;
2708 int arpa_utc_min = 0;
2709 int arpa_utc_sec = 0;
2710 char arpa_name_str[21];
2711 bool arpa_lost = true;
2712 bool arpa_nottracked = false;
2713
2714 double aprs_lat = 0.;
2715 double aprs_lon = 0.;
2716 char aprs_name_str[21];
2717 double aprs_mins, aprs_degs;
2718
2719 std::shared_ptr<AisTargetData> pTargetData = nullptr;
2720 std::shared_ptr<AisTargetData> pStaleTarget = nullptr;
2721 bool bnewtarget = false;
2722 int last_report_ticks;
2723
2724 // Make some simple tests for validity
2725
2726 if (str.Len() > 128) return AIS_NMEAVDX_TOO_LONG;
2727
2728 if (!NMEACheckSumOK(str)) {
2729 return AIS_NMEAVDX_CHECKSUM_BAD;
2730 }
2731 if (str.Mid(1, 2).IsSameAs("CD")) {
2732 ProcessDSx(str);
2733 return AIS_NoError;
2734 } else if (str.Mid(3, 3).IsSameAs("TTM")) {
2735 //$--TTM,xx,x.x,x.x,a,x.x,x.x,a,x.x,x.x,a,c--c,a,a*hh <CR><LF>
2736 // or
2737 //$--TTM,xx,x.x,x.x,a,x.x,x.x,a,x.x,x.x,a,c--c,a,a,hhmmss.ss,a*hh<CR><LF>
2738 wxStringTokenizer tkz(str, ",*");
2739
2740 wxString token;
2741 token = tkz.GetNextToken(); // Sentence (xxTTM)
2742 token = tkz.GetNextToken(); // 1) Target Number
2743 token.ToLong(&arpa_tgt_num);
2744 token = tkz.GetNextToken(); // 2)Target Distance
2745 token.ToDouble(&arpa_dist);
2746 token = tkz.GetNextToken(); // 3) Bearing from own ship
2747 token.ToDouble(&arpa_brg);
2748 arpa_brgunit = tkz.GetNextToken(); // 4) Bearing Units
2749 if (arpa_brgunit == "R") {
2750 if (std::isnan(arpa_ref_hdg)) {
2751 if (!std::isnan(gHdt))
2752 arpa_brg += gHdt;
2753 else
2754 arpa_brg += gCog;
2755 } else
2756 arpa_brg += arpa_ref_hdg;
2757 if (arpa_brg >= 360.) arpa_brg -= 360.;
2758 }
2759 token = tkz.GetNextToken(); // 5) Target speed
2760 token.ToDouble(&arpa_sog);
2761 token = tkz.GetNextToken(); // 6) Target Course
2762 token.ToDouble(&arpa_cog);
2763 arpa_cogunit = tkz.GetNextToken(); // 7) Course Units
2764 if (arpa_cogunit == "R") {
2765 if (std::isnan(arpa_ref_hdg)) {
2766 if (!std::isnan(gHdt))
2767 arpa_cog += gHdt;
2768 else
2769 arpa_cog += gCog;
2770 } else
2771 arpa_cog += arpa_ref_hdg;
2772 if (arpa_cog >= 360.) arpa_cog -= 360.;
2773 }
2774 token = tkz.GetNextToken(); // 8) Distance of closest-point-of-approach
2775 token = tkz.GetNextToken(); // 9) Time until closest-point-of-approach "-"
2776 // means increasing
2777 arpa_distunit = tkz.GetNextToken(); // 10)Speed/ dist unit
2778 token = tkz.GetNextToken(); // 11) Target name
2779 if (token == "") token = wxString::Format("ARPA %ld", arpa_tgt_num);
2780 int len = wxMin(token.Length(), 20);
2781 strncpy(arpa_name_str, token.mb_str(), len);
2782 arpa_name_str[len] = 0;
2783 arpa_status = tkz.GetNextToken(); // 12) Target Status
2784 if (arpa_status != "L") {
2785 arpa_lost = false;
2786 } else if (arpa_status != "")
2787 arpa_nottracked = true;
2788 wxString arpa_reftarget = tkz.GetNextToken(); // 13) Reference Target
2789 if (tkz.HasMoreTokens()) {
2790 token = tkz.GetNextToken();
2791 token.ToDouble(&arpa_utc_time);
2792 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2793 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2794 arpa_utc_sec =
2795 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2796 } else {
2797 arpa_utc_hour = wxDateTime::Now().ToUTC().GetHour();
2798 arpa_utc_min = wxDateTime::Now().ToUTC().GetMinute();
2799 arpa_utc_sec = wxDateTime::Now().ToUTC().GetSecond();
2800 }
2801
2802 if (arpa_distunit == "K") {
2803 arpa_dist = fromUsrDistance(arpa_dist, DISTANCE_KM, g_iDistanceFormat);
2804 arpa_sog = fromUsrSpeed(arpa_sog, SPEED_KMH, g_iSpeedFormat);
2805 } else if (arpa_distunit == "S") {
2806 arpa_dist = fromUsrDistance(arpa_dist, DISTANCE_MI, g_iDistanceFormat);
2807 arpa_sog = fromUsrSpeed(arpa_sog, SPEED_MPH, g_iSpeedFormat);
2808 }
2809
2810 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2811 // 199 is INMARSAT-A MID, should not occur ever in AIS
2812 // stream + we make sure we are out of the hashes for
2813 // GPSGate buddies by being above 1992*
2814 } else if (str.Mid(3, 3).IsSameAs("TLL")) {
2815 //$--TLL,xx,llll.lll,a,yyyyy.yyy,a,c--c,hhmmss.ss,a,a*hh<CR><LF>
2816 //"$RATLL,01,5603.370,N,01859.976,E,ALPHA,015200.36,T,*75\r\n"
2817 wxStringTokenizer tkz(str, ",*");
2818
2819 wxString token;
2820 wxString aprs_tll_str = tkz.GetNextToken(); // Sentence (xxTLL)
2821 token = tkz.GetNextToken(); // 1) Target number 00 - 99
2822 token.ToLong(&arpa_tgt_num);
2823 token = tkz.GetNextToken(); // 2) Latitude, N/S
2824 token.ToDouble(&arpa_lat);
2825 arpa_degs = (int)(arpa_lat / 100.0);
2826 arpa_mins = arpa_lat - arpa_degs * 100.0;
2827 arpa_lat = arpa_degs + arpa_mins / 60.0;
2828 token = tkz.GetNextToken(); // hemisphere N or S
2829 if (token.Mid(0, 1).Contains("S") == true ||
2830 token.Mid(0, 1).Contains("s") == true)
2831 arpa_lat = 0. - arpa_lat;
2832 token = tkz.GetNextToken(); // 3) Longitude, E/W
2833 token.ToDouble(&arpa_lon);
2834 arpa_degs = (int)(arpa_lon / 100.0);
2835 arpa_mins = arpa_lon - arpa_degs * 100.0;
2836 arpa_lon = arpa_degs + arpa_mins / 60.0;
2837 token = tkz.GetNextToken(); // hemisphere E or W
2838 if (token.Mid(0, 1).Contains("W") == true ||
2839 token.Mid(0, 1).Contains("w") == true)
2840 arpa_lon = 0. - arpa_lon;
2841 token = tkz.GetNextToken(); // 4) Target name
2842 if (token == "") token = wxString::Format("ARPA %d", arpa_tgt_num);
2843 int len = wxMin(token.Length(), 20);
2844 strncpy(arpa_name_str, token.mb_str(), len);
2845 arpa_name_str[len] = 0;
2846 token = tkz.GetNextToken(); // 5) UTC of data
2847 token.ToDouble(&arpa_utc_time);
2848 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2849 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2850 arpa_utc_sec =
2851 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2852 arpa_status =
2853 tkz.GetNextToken(); // 6) Target status: L = lost,tracked
2854 // target has beenlost Q = query,target in
2855 // the process of acquisition T = tracking
2856 if (arpa_status != "L")
2857 arpa_lost = false;
2858 else if (arpa_status != "")
2859 arpa_nottracked = true;
2860 wxString arpa_reftarget = tkz.GetNextToken(); // 7) Reference target=R,null
2861 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2862 // 199 is INMARSAT-A MID, should not occur ever in AIS
2863 // stream + we make sure we are out of the hashes for
2864 // GPSGate buddies by being above 1992*
2865 } else if (str.Mid(3, 3).IsSameAs("OSD")) {
2866 //$--OSD,x.x,A,x.x,a,x.x,a,x.x,x.x,a*hh <CR><LF>
2867 wxStringTokenizer tkz(str, ",*");
2868
2869 wxString token;
2870 token = tkz.GetNextToken(); // Sentence (xxOSD)
2871 token = tkz.GetNextToken(); // 1) Heading (true)
2872 token.ToDouble(&arpa_ref_hdg);
2873 // 2) speed
2874 // 3) Vessel Course, degrees True
2875 // 4) Course Reference, B/M/W/R/P (see note)
2876 // 5) Vessel Speed
2877 // 6) Speed Reference, B/M/W/R/P (see note)
2878 // 7) Vessel Set, degrees True - Manually entered
2879 // 8) Vessel drift (speed) - Manually entered
2880 // 9) Speed Units K = km/h; N = Knots; S = statute miles/h
2881
2882 } else if (g_bWplUsePosition && str.Mid(3, 3).IsSameAs("WPL")) {
2883 //** $--WPL,llll.ll,a,yyyyy.yy,a,c--c*hh<CR><LF>
2884 wxStringTokenizer tkz(str, ",*");
2885
2886 wxString token;
2887 token = tkz.GetNextToken(); // Sentence (xxWPL)
2888 token = tkz.GetNextToken(); // 1) Latitude, N/S
2889 token.ToDouble(&aprs_lat);
2890 aprs_degs = (int)(aprs_lat / 100.0);
2891 aprs_mins = aprs_lat - aprs_degs * 100.0;
2892 aprs_lat = aprs_degs + aprs_mins / 60.0;
2893 token = tkz.GetNextToken(); // 2) hemisphere N or S
2894 if (token.Mid(0, 1).Contains("S") == true ||
2895 token.Mid(0, 1).Contains("s") == true)
2896 aprs_lat = 0. - aprs_lat;
2897 token = tkz.GetNextToken(); // 3) Longitude, E/W
2898 token.ToDouble(&aprs_lon);
2899 aprs_degs = (int)(aprs_lon / 100.0);
2900 aprs_mins = aprs_lon - aprs_degs * 100.0;
2901 aprs_lon = aprs_degs + aprs_mins / 60.0;
2902 token = tkz.GetNextToken(); // 4) hemisphere E or W
2903 if (token.Mid(0, 1).Contains("W") == true ||
2904 token.Mid(0, 1).Contains("w") == true)
2905 aprs_lon = 0. - aprs_lon;
2906 token = tkz.GetNextToken(); // 5) Target name
2907 int len = wxMin(token.Length(), 20);
2908 strncpy(aprs_name_str, token.mb_str(), len + 1);
2909 if (0 == g_WplAction) { // APRS position reports
2910 int i, hash = 0;
2911 aprs_name_str[len] = 0;
2912 for (i = 0; i < len; i++) {
2913 hash = hash * 10;
2914 hash += (int)(aprs_name_str[i]);
2915 while (hash >= 100000) hash = hash / 100000;
2916 }
2917 mmsi = aprs_mmsi = 199300000 + hash;
2918 // 199 is INMARSAT-A MID, should not occur ever in AIS stream +
2919 // we make sure we are out of the hashes for GPSGate buddies
2920 // and ARPA by being above 1993*
2921 } else if (1 == g_WplAction) { // Create mark
2922 auto *pWP =
2923 new RoutePoint(aprs_lat, aprs_lon, "", aprs_name_str, "", false);
2924 pWP->m_bIsolatedMark = true;
2925 InsertWpt(pWP, true);
2926 }
2927 } else if (str.Mid(1, 5).IsSameAs("FRPOS")) {
2928 // parse a GpsGate Position message $FRPOS,.....
2929
2930 // Use a tokenizer to pull out the first 9 fields
2931 wxStringTokenizer tkz(str, ",*");
2932
2933 wxString token;
2934 token = tkz.GetNextToken(); // !$FRPOS
2935
2936 token = tkz.GetNextToken(); // latitude DDMM.MMMM
2937 token.ToDouble(&gpsg_lat);
2938 gpsg_degs = (int)(gpsg_lat / 100.0);
2939 gpsg_mins = gpsg_lat - gpsg_degs * 100.0;
2940 gpsg_lat = gpsg_degs + gpsg_mins / 60.0;
2941
2942 token = tkz.GetNextToken(); // hemisphere N or S
2943 if (token.Mid(0, 1).Contains("S") == true ||
2944 token.Mid(0, 1).Contains("s") == true)
2945 gpsg_lat = 0. - gpsg_lat;
2946
2947 token = tkz.GetNextToken(); // longitude DDDMM.MMMM
2948 token.ToDouble(&gpsg_lon);
2949 gpsg_degs = (int)(gpsg_lon / 100.0);
2950 gpsg_mins = gpsg_lon - gpsg_degs * 100.0;
2951 gpsg_lon = gpsg_degs + gpsg_mins / 60.0;
2952
2953 token = tkz.GetNextToken(); // hemisphere E or W
2954 if (token.Mid(0, 1).Contains("W") == true ||
2955 token.Mid(0, 1).Contains("w") == true)
2956 gpsg_lon = 0. - gpsg_lon;
2957
2958 token = tkz.GetNextToken(); // altitude AA.a
2959 // token.toDouble(&gpsg_alt);
2960
2961 token = tkz.GetNextToken(); // speed over ground SSS.SS knots
2962 token.ToDouble(&gpsg_sog);
2963
2964 token = tkz.GetNextToken(); // heading over ground HHH.hh degrees
2965 token.ToDouble(&gpsg_cog);
2966
2967 token = tkz.GetNextToken(); // date DDMMYY
2968 gpsg_date = token;
2969
2970 token = tkz.GetNextToken(); // time UTC hhmmss.dd
2971 token.ToDouble(&gpsg_utc_time);
2972 gpsg_utc_hour = (int)(gpsg_utc_time / 10000.0);
2973 gpsg_utc_min = (int)(gpsg_utc_time / 100.0) - gpsg_utc_hour * 100;
2974 gpsg_utc_sec =
2975 (int)gpsg_utc_time - gpsg_utc_hour * 10000 - gpsg_utc_min * 100;
2976
2977 // now comes the name, followed by in * and NMEA checksum
2978
2979 token = tkz.GetNextToken();
2980 int i, len, hash = 0;
2981 len = wxMin(wxStrlen(token), 20);
2982 strncpy(gpsg_name_str, token.mb_str(), len);
2983 gpsg_name_str[len] = 0;
2984 for (i = 0; i < len; i++) {
2985 hash = hash * 10;
2986 hash += (int)(token[i]);
2987 while (hash >= 100000) hash = hash / 100000;
2988 }
2989 // 199 is INMARSAT-A MID, should not occur ever in AIS stream
2990 gpsg_mmsi = 199000000 + hash;
2991 mmsi = gpsg_mmsi;
2992 } else if (!str.Mid(3, 2).IsSameAs("VD")) {
2993 return AIS_NMEAVDX_BAD;
2994 }
2995
2996 // OK, looks like the sentence is OK
2997
2998 // Use a tokenizer to pull out the first 4 fields
2999 string_to_parse.Clear();
3000
3001 if (str.Mid(3, 2).IsSameAs("VD")) {
3002 wxStringTokenizer tkz(str, ",");
3003
3004 wxString token;
3005 token = tkz.GetNextToken(); // !xxVDx
3006
3007 token = tkz.GetNextToken();
3008 nsentences = atoi(token.mb_str());
3009
3010 token = tkz.GetNextToken();
3011 isentence = atoi(token.mb_str());
3012
3013 token = tkz.GetNextToken();
3014 long lsequence_id = 0;
3015 token.ToLong(&lsequence_id);
3016
3017 token = tkz.GetNextToken();
3018 long lchannel;
3019 token.ToLong(&lchannel);
3020 // Now, some decisions
3021
3022 // Simple case first
3023 // First and only part of a one-part sentence
3024 if ((1 == nsentences) && (1 == isentence)) {
3025 string_to_parse = tkz.GetNextToken(); // the encapsulated data
3026 }
3027
3028 else if (nsentences > 1) {
3029 if (1 == isentence) {
3030 sentence_accumulator = tkz.GetNextToken(); // the encapsulated data
3031 }
3032
3033 else {
3034 sentence_accumulator += tkz.GetNextToken();
3035 }
3036
3037 if (isentence == nsentences) {
3038 string_to_parse = sentence_accumulator;
3039 }
3040 }
3041 }
3042
3043 if (mmsi || (!string_to_parse.IsEmpty() &&
3044 (string_to_parse.Len() < AIS_MAX_MESSAGE_LEN))) {
3045 // Create the bit accessible string
3046 wxCharBuffer abuf = string_to_parse.ToUTF8();
3047 if (!abuf.data()) // badly formed sentence?
3048 return AIS_GENERIC_ERROR;
3049
3050 AisBitstring strbit(abuf.data());
3051
3052 // Extract the MMSI
3053 if (!mmsi) mmsi = strbit.GetInt(9, 30);
3054 long mmsi_long = mmsi;
3055
3056 // Ais8_001_31 || ais8_367_33 (class AIS_METEO) test for a new mmsi ID
3057 int origin_mmsi = 0;
3058 int messID = strbit.GetInt(1, 6);
3059 // No need for a message without a valid mess-ID
3060 if (messID < 1 || messID > 63) return AIS_GENERIC_ERROR;
3061
3062 int dac = strbit.GetInt(41, 10);
3063 int fi = strbit.GetInt(51, 6);
3064 if (messID == 8) {
3065 int met_lon, met_lat;
3066 if (dac == 001 && fi == 31) {
3067 origin_mmsi = mmsi;
3068 met_lon = strbit.GetInt(57, 25);
3069 met_lat = strbit.GetInt(82, 24);
3070 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 25, 0);
3071 mmsi_long = mmsi;
3072
3073 } else if (dac == 367 && fi == 33) { // ais8_367_33
3074 // Check for a valid message size before further handling
3075 const int size = strbit.GetBitCount();
3076 if (size < 168) return AIS_GENERIC_ERROR;
3077 const int startb = 56;
3078 const int slot_size = 112;
3079 const int extra_bits = (size - startb) % slot_size;
3080 if (extra_bits > 0) return AIS_GENERIC_ERROR;
3081
3082 int mes_type = strbit.GetInt(57, 4);
3083 int site_ID = strbit.GetInt(77, 7);
3084 if (mes_type == 0) { // Location
3085 origin_mmsi = mmsi;
3086 met_lon = strbit.GetInt(90, 28);
3087 met_lat = strbit.GetInt(118, 27);
3088 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 28, site_ID);
3089 mmsi_long = mmsi;
3090 } else { // Other messsage types without position.
3091 // We need a previously received type 0, position message
3092 // to get use of any sensor report.
3093 int x_mmsi = AisMeteoNewMmsi(mmsi, 91, 181, 0, site_ID);
3094 if (x_mmsi) {
3095 origin_mmsi = mmsi;
3096 mmsi = x_mmsi;
3097 mmsi_long = mmsi;
3098 } else // So far no use for this report.
3099 return AIS_GENERIC_ERROR;
3100 }
3101 }
3102 }
3103
3104 // Check for own ship mmsi. It's not a valid AIS target.
3105 if (mmsi == g_OwnShipmmsi) return AIS_GENERIC_ERROR;
3106
3107 // Search the current AISTargetList for an MMSI match
3108 auto it = AISTargetList.find(mmsi);
3109 if (it == AISTargetList.end()) // not found
3110 {
3111 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3112 bnewtarget = true;
3113 m_n_targets++;
3114
3115 if (origin_mmsi) { // New mmsi allocated for a Meteo station
3116 pTargetData->MMSI = mmsi;
3117 pTargetData->met_data.original_mmsi = origin_mmsi;
3118 }
3119 } else {
3120 pTargetData = it->second; // find current entry
3121
3122 if (!bnewtarget)
3123 pStaleTarget = pTargetData; // save a pointer to stale data
3124 if (origin_mmsi) { // Meteo point
3125 pTargetData->MMSI = mmsi;
3126 pTargetData->met_data.original_mmsi = origin_mmsi;
3127 }
3128 }
3129 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
3131 if (mmsi == static_cast<unsigned>(props->MMSI)) {
3132 // Check if this target has a dedicated tracktype
3133 if (TRACKTYPE_NEVER == props->TrackType) {
3134 pTargetData->b_show_track = false;
3135 } else if (TRACKTYPE_ALWAYS == props->TrackType) {
3136 pTargetData->b_show_track = true;
3137 }
3138
3139 // Check to see if this MMSI has been configured to be ignored
3140 // completely...
3141 if (props->m_bignore) return AIS_NoError;
3142 // Check to see if this MMSI wants VDM translated to VDO or whether we
3143 // want to persist it's track...
3144 else if (props->m_bVDM) {
3145 // Only single line VDM messages to be translated
3146 if (str.Mid(3, 9).IsSameAs("VDM,1,1,,")) {
3147 int message_ID = strbit.GetInt(1, 6); // Parse on message ID
3148 // Only translate the dynamic positionreport messages (1, 2, 3 or
3149 // 18)
3150 if ((message_ID <= 3) || (message_ID == 18)) {
3151 // set OwnShip to prevent target from being drawn
3152 pTargetData->b_OwnShip = true;
3153 // Rename nmea sentence to AIVDO and calc a new checksum
3154 wxString aivdostr = str;
3155 aivdostr.replace(1, 5, "AIVDO");
3156 unsigned char calculated_checksum = 0;
3157 wxString::iterator j;
3158 for (j = aivdostr.begin() + 1; j != aivdostr.end() && *j != '*';
3159 ++j)
3160 calculated_checksum ^= static_cast<unsigned char>(*j);
3161 // if i is not at least 3 positons befoere end, there is no
3162 // checksum added so also no need to add one now.
3163 if (j <= aivdostr.end() - 3)
3164 aivdostr.replace(
3165 j + 1, j + 3,
3166 wxString::Format(_("%02X"), calculated_checksum));
3167
3168 gps_watchdog_timeout_ticks =
3169 60; // increase watchdog time up to 1 minute
3170 // add the changed sentence into nmea message system
3171 std::string full_sentence = aivdostr.ToStdString();
3172 auto address = std::make_shared<NavAddr0183>("virtual");
3173 // We notify based on full message, including the Talker ID
3174 // Notify message listener
3175 auto msg = std::make_shared<const Nmea0183Msg>(
3176 "AIVDO", full_sentence, address);
3177 NavMsgBus::GetInstance().Notify(std::move(msg));
3178 }
3179 }
3180 return AIS_NoError;
3181 } else
3182 break;
3183 }
3184 }
3185
3186 // Grab the stale targets's last report time
3187 wxDateTime now = wxDateTime::Now();
3188 now.MakeGMT();
3189
3190 if (pStaleTarget)
3191 last_report_ticks = pStaleTarget->PositionReportTicks;
3192 else
3193 last_report_ticks = now.GetTicks();
3194
3195 // Delete the stale AIS Target selectable point
3196 if (pStaleTarget)
3197 pSelectAIS->DeleteSelectablePoint((void *)mmsi_long, SELTYPE_AISTARGET);
3198
3199 if (pTargetData) {
3200 if (gpsg_mmsi) {
3201 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3202 pTargetData->PositionReportTicks = now.GetTicks();
3203 pTargetData->StaticReportTicks = now.GetTicks();
3204 pTargetData->m_utc_hour = gpsg_utc_hour;
3205 pTargetData->m_utc_min = gpsg_utc_min;
3206 pTargetData->m_utc_sec = gpsg_utc_sec;
3207 pTargetData->m_date_string = gpsg_date;
3208 pTargetData->MMSI = gpsg_mmsi;
3209 pTargetData->NavStatus = 0; // underway
3210 pTargetData->Lat = gpsg_lat;
3211 pTargetData->Lon = gpsg_lon;
3212 pTargetData->b_positionOnceValid = true;
3213 pTargetData->COG = gpsg_cog;
3214 pTargetData->SOG = gpsg_sog;
3215 pTargetData->ShipType = 52; // buddy
3216 pTargetData->Class = AIS_GPSG_BUDDY;
3217 memcpy(pTargetData->ShipName, gpsg_name_str, sizeof(gpsg_name_str));
3218 pTargetData->b_nameValid = true;
3219 pTargetData->b_active = true;
3220 pTargetData->b_lost = false;
3221
3222 bdecode_result = true;
3223 } else if (arpa_mmsi) {
3224 pTargetData->m_utc_hour = arpa_utc_hour;
3225 pTargetData->m_utc_min = arpa_utc_min;
3226 pTargetData->m_utc_sec = arpa_utc_sec;
3227 pTargetData->MMSI = arpa_mmsi;
3228 pTargetData->NavStatus = 15; // undefined
3229 if (str.Mid(3, 3).IsSameAs("TLL")) {
3230 if (!bnewtarget) {
3231 int age_of_last =
3232 (now.GetTicks() - pTargetData->PositionReportTicks);
3233 if (age_of_last > 0) {
3234 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, arpa_lat,
3235 arpa_lon, &pTargetData->COG, &pTargetData->SOG);
3236 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3237 }
3238 }
3239 pTargetData->Lat = arpa_lat;
3240 pTargetData->Lon = arpa_lon;
3241 } else if (str.Mid(3, 3).IsSameAs("TTM")) {
3242 if (arpa_dist != 0.) // Not a new or turned off target
3243 ll_gc_ll(gLat, gLon, arpa_brg, arpa_dist, &pTargetData->Lat,
3244 &pTargetData->Lon);
3245 else
3246 arpa_lost = true;
3247 pTargetData->COG = arpa_cog;
3248 pTargetData->SOG = arpa_sog;
3249 }
3250 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3251 pTargetData->PositionReportTicks = now.GetTicks();
3252 pTargetData->StaticReportTicks = now.GetTicks();
3253 pTargetData->b_positionOnceValid = true;
3254 pTargetData->ShipType = 55; // arpa
3255 pTargetData->Class = AIS_ARPA;
3256
3257 memcpy(pTargetData->ShipName, arpa_name_str, sizeof(arpa_name_str));
3258 if (arpa_status != "Q")
3259 pTargetData->b_nameValid = true;
3260 else
3261 pTargetData->b_nameValid = false;
3262 pTargetData->b_active = !arpa_lost;
3263 pTargetData->b_lost = arpa_nottracked;
3264
3265 bdecode_result = true;
3266 } else if (aprs_mmsi) {
3267 pTargetData->m_utc_hour = now.GetHour();
3268 pTargetData->m_utc_min = now.GetMinute();
3269 pTargetData->m_utc_sec = now.GetSecond();
3270 pTargetData->MMSI = aprs_mmsi;
3271 pTargetData->NavStatus = 15; // undefined
3272 if (!bnewtarget) {
3273 int age_of_last = (now.GetTicks() - pTargetData->PositionReportTicks);
3274 if (age_of_last > 0) {
3275 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, aprs_lat,
3276 aprs_lon, &pTargetData->COG, &pTargetData->SOG);
3277 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3278 }
3279 }
3280 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3281 pTargetData->PositionReportTicks = now.GetTicks();
3282 pTargetData->StaticReportTicks = now.GetTicks();
3283 pTargetData->Lat = aprs_lat;
3284 pTargetData->Lon = aprs_lon;
3285 pTargetData->b_positionOnceValid = true;
3286 pTargetData->ShipType = 56; // aprs
3287 pTargetData->Class = AIS_APRS;
3288 memcpy(pTargetData->ShipName, aprs_name_str, sizeof(aprs_name_str));
3289 pTargetData->b_nameValid = true;
3290 pTargetData->b_active = true;
3291 pTargetData->b_lost = false;
3292
3293 bdecode_result = true;
3294 } else {
3295 // The normal Plain-Old AIS target code path....
3296 bdecode_result =
3297 Parse_VDXBitstring(&strbit, pTargetData); // Parse the new data
3298 }
3299
3300 // Catch mmsi properties like track, persistent track, follower.
3301 getMmsiProperties(pTargetData);
3302
3303 // Update the most recent report period
3304 pTargetData->RecentPeriod =
3305 pTargetData->PositionReportTicks - last_report_ticks;
3306 }
3307 ret = AIS_NoError;
3308 } else {
3309 ret = AIS_Partial; // accumulating parts of a multi-sentence message
3310 pTargetData = nullptr;
3311 }
3312
3313 if (pTargetData) {
3314 // pTargetData is valid, either new or existing. Commit to GUI
3315 CommitAISTarget(pTargetData, str, bdecode_result, bnewtarget);
3316 }
3317
3318 n_msgs++;
3319#ifdef AIS_DEBUG
3320 if ((n_msgs % 10000) == 0)
3321 printf("n_msgs %10d m_n_targets: %6d n_msg1: %10d n_msg5+24: %10d \n",
3322 n_msgs, m_n_targets, n_msg1, n_msg5 + n_msg24);
3323#endif
3324
3325 return ret;
3326}
3327
3328void AisDecoder::CommitAISTarget(
3329 const std::shared_ptr<AisTargetData> &pTargetData, const wxString &str,
3330 bool message_valid, bool new_target) {
3331 m_pLatestTargetData = pTargetData;
3332
3333 if (!str.IsEmpty()) { // NMEA0183 message
3334 if (str.Mid(3, 3).IsSameAs("VDO"))
3335 pTargetData->b_OwnShip = true;
3336 else
3337 pTargetData->b_OwnShip = false;
3338 }
3339
3340 if (!pTargetData->b_OwnShip) {
3341 // set mmsi-props to default values
3342 if (0 == m_persistent_tracks.count(pTargetData->MMSI)) {
3343 // Normal target
3344 pTargetData->b_PersistTrack = false;
3345 // Or first decode for this target
3346 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
3347 if (pTargetData->MMSI == g_MMSI_Props_Array[i]->MMSI) {
3349 pTargetData->b_mPropPersistTrack = props->m_bPersistentTrack;
3350 break;
3351 }
3352 }
3353 } else {
3354 // The track persistency enabled in the query window or mmsi-props
3355 }
3356 pTargetData->b_NoTrack = false;
3357 }
3358
3359 // If the message was decoded correctly
3360 // Update the AIS Target information
3361 if (message_valid) {
3362 // Print to name cache only if not mmsi = 0
3363 if (pTargetData->MMSI) {
3364 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
3365 pTargetData->MMSI);
3366 }
3367 AISTargetList[pTargetData->MMSI] =
3368 pTargetData; // update the hash table entry
3369
3370 if (!pTargetData->area_notices.empty()) {
3371 auto it = AIS_AreaNotice_Sources.find(pTargetData->MMSI);
3372 if (it == AIS_AreaNotice_Sources.end())
3373 AIS_AreaNotice_Sources[pTargetData->MMSI] = pTargetData;
3374 }
3375
3376 // If this is not an ownship message, update the AIS Target in the
3377 // Selectable list, and update the CPA info
3378 if (!pTargetData->b_OwnShip) {
3379 if (pTargetData->b_positionOnceValid) {
3380 long mmsi_long = pTargetData->MMSI;
3381 SelectItem *pSel = pSelectAIS->AddSelectablePoint(
3382 pTargetData->Lat, pTargetData->Lon, (void *)mmsi_long,
3383 SELTYPE_AISTARGET);
3384 pSel->SetUserData(pTargetData->MMSI);
3385 }
3386
3387 // Calculate CPA info for this target immediately
3388 UpdateOneCPA(pTargetData.get());
3389
3390 // Update this target's track
3391 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3392 }
3393 // TODO add ais message call
3394 plugin_msg_evt.Notify(std::make_shared<AisTargetData>(*pTargetData), "");
3395 } else {
3396 // printf("Unrecognised AIS message ID: %d\n",
3397 // pTargetData->MID);
3398 if (new_target) {
3399 // delete pTargetData; // this target is not going to be used
3400 m_n_targets--;
3401 } else {
3402 // If this is not an ownship message, update the AIS Target in the
3403 // Selectable list even if the message type was not recognized
3404 if (!pTargetData->b_OwnShip) {
3405 if (pTargetData->b_positionOnceValid) {
3406 long mmsi_long = pTargetData->MMSI;
3407 SelectItem *pSel = pSelectAIS->AddSelectablePoint(
3408 pTargetData->Lat, pTargetData->Lon, (void *)mmsi_long,
3409 SELTYPE_AISTARGET);
3410 pSel->SetUserData(pTargetData->MMSI);
3411 }
3412 }
3413 }
3414 }
3415}
3416
3417void AisDecoder::getAISTarget(long mmsi,
3418 std::shared_ptr<AisTargetData> &pTargetData,
3419 std::shared_ptr<AisTargetData> &pStaleTarget,
3420 bool &bnewtarget, int &last_report_ticks,
3421 wxDateTime &now) {
3422 now = wxDateTime::Now();
3423 auto it = AISTargetList.find(mmsi);
3424 if (it == AISTargetList.end()) // not found
3425 {
3426 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3427 bnewtarget = true;
3428 m_n_targets++;
3429 } else {
3430 pTargetData = it->second; // find current entry
3431 pStaleTarget = pTargetData; // save a pointer to stale data
3432 }
3433
3434 // Grab the stale targets's last report time
3435 now.MakeGMT();
3436
3437 if (pStaleTarget)
3438 last_report_ticks = pStaleTarget->PositionReportTicks;
3439 else
3440 last_report_ticks = now.GetTicks();
3441
3442 // Delete the stale AIS Target selectable point
3443 if (pStaleTarget)
3444 pSelectAIS->DeleteSelectablePoint((void *)mmsi, SELTYPE_AISTARGET);
3445}
3446
3447std::shared_ptr<AisTargetData> AisDecoder::ProcessDSx(const wxString &str,
3448 bool b_take_dsc) {
3449 double dsc_lat = 0.;
3450 double dsc_lon = 0.;
3451 double dsc_mins, dsc_degs, dsc_tmp, dsc_addr;
3452 double dse_tmp;
3453 double dse_lat = 0.;
3454 double dse_lon = 0.;
3455 long dsc_fmt, dsc_quadrant, dsc_cat, dsc_nature;
3456
3457 int dsc_mmsi = 0;
3458 int dsc_tx_mmsi = 0;
3459 int dse_mmsi = 0;
3460 double dse_cog = 0.;
3461 double dse_sog = 0.;
3462 wxString dse_shipName = "";
3463 wxString dseSymbol;
3464
3465 unsigned mmsi = 0;
3466
3467 std::shared_ptr<AisTargetData> pTargetData = nullptr;
3468
3469 // parse a DSC Position message $CDDSx,.....
3470 // Use a tokenizer to pull out the first 9 fields
3471 wxStringTokenizer tkz(str, ",*");
3472
3473 wxString token;
3474 token = tkz.GetNextToken(); // !$CDDS
3475
3476 if (str.Mid(3, 3).IsSameAs("DSC")) {
3477 m_dsc_last_string = str;
3478
3479 token = tkz.GetNextToken(); // format specifier
3480 token.ToLong(
3481 &dsc_fmt); // (02-area,12-distress,16-allships,20-individual,...)
3482
3483 token = tkz.GetNextToken(); // address i.e. mmsi*10 for received msg, or
3484 // area spec or sender mmsi for (12) and (16)
3485 if (dsc_fmt == 12 || dsc_fmt == 16) {
3486 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3487 } else {
3488 token.ToDouble(&dsc_addr);
3489 dsc_mmsi = 0 - (int)(dsc_addr / 10); // as per NMEA 0183 3.01
3490 }
3491
3492 token = tkz.GetNextToken(); // category
3493 token.ToLong(&dsc_cat); // 12 - Distress (relayed)
3494
3495 token = tkz.GetNextToken(); // nature of distress or telecommand1
3496 if (!token.IsSameAs("")) { // 00-12 = nature of distress
3497 token.ToLong(&dsc_nature);
3498 } else
3499 dsc_nature = 99;
3500
3501 token = tkz.GetNextToken(); // comm type or telecommand2
3502
3503 token = tkz.GetNextToken(); // position or channel/freq
3504 token.ToDouble(&dsc_tmp);
3505
3506 token = tkz.GetNextToken(); // time or tel. no.
3507 token = tkz.GetNextToken(); // mmsi of ship in distress, relay
3508 if (dsc_fmt == 16 && dsc_cat == 12 && !token.IsSameAs("")) {
3509 // wxString dmmsi = token.Mid(0,9);
3510 dsc_tx_mmsi = dsc_mmsi; // mmsi of relay issuer
3511 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3512 }
3513 token = tkz.GetNextToken(); // nature of distress, relay
3514 if (dsc_fmt == 16 && dsc_cat == 12) {
3515 if (!token.IsSameAs("")) { // 00-12 = nature of distress
3516 token.ToLong(&dsc_nature);
3517 } else
3518 dsc_nature = 99;
3519 }
3520 token = tkz.GetNextToken(); // acknowledgement
3521 token = tkz.GetNextToken(); // expansion indicator
3522
3523 dsc_quadrant = (int)(dsc_tmp / 1000000000.0);
3524
3525 if (dsc_quadrant > 3) // Position is "Unspecified", or 9999999999
3526 return nullptr;
3527
3528 dsc_lat = (int)(dsc_tmp / 100000.0);
3529 dsc_lon = dsc_tmp - dsc_lat * 100000.0;
3530 dsc_lat = dsc_lat - dsc_quadrant * 10000;
3531 dsc_degs = (int)(dsc_lat / 100.0);
3532 dsc_mins = dsc_lat - dsc_degs * 100.0;
3533 dsc_lat = dsc_degs + dsc_mins / 60.0;
3534
3535 dsc_degs = (int)(dsc_lon / 100.0);
3536 dsc_mins = dsc_lon - dsc_degs * 100.0;
3537 dsc_lon = dsc_degs + dsc_mins / 60.0;
3538 switch (dsc_quadrant) {
3539 case 0:
3540 break; // NE
3541 case 1:
3542 dsc_lon = -dsc_lon;
3543 break; // NW
3544 case 2:
3545 dsc_lat = -dsc_lat;
3546 break; // SE
3547 case 3:
3548 dsc_lon = -dsc_lon;
3549 dsc_lat = -dsc_lat;
3550 break; // SW
3551 default:
3552 break;
3553 }
3554 if (dsc_fmt != 02) mmsi = (int)dsc_mmsi;
3555
3556 } else if (str.Mid(3, 3).IsSameAs("DSE")) {
3557 token = tkz.GetNextToken(); // total number of sentences
3558 token = tkz.GetNextToken(); // sentence number
3559 token = tkz.GetNextToken(); // query/rely flag
3560 token = tkz.GetNextToken(); // vessel MMSI
3561 dse_mmsi = wxAtoi(token.Mid(
3562 0, 9)); // ITU-R M.493-10 �5.2
3563 // token.ToDouble(&dse_addr);
3564 // 0 - (int)(dse_addr / 10); // as per NMEA 0183 3.01
3565
3566#if 0
3567 token = tkz.GetNextToken(); // code field
3568 token =
3569 tkz.GetNextToken(); // data field - position - 2*4 digits latlon .mins
3570 token.ToDouble(&dse_tmp);
3571 dse_lat = (int)(dse_tmp / 10000.0);
3572 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3573 dse_lat = dse_lat / 600000.0;
3574 dse_lon = dse_lon / 600000.0;
3575#endif
3576 // DSE Sentence may contain multiple dse expansion data items
3577 while (tkz.HasMoreTokens()) {
3578 dseSymbol = tkz.GetNextToken(); // dse expansion data symbol
3579 token = tkz.GetNextToken(); // dse expansion data
3580 if (dseSymbol.IsSameAs("00")) { // Position
3581 token.ToDouble(&dse_tmp);
3582 dse_lat = (int)(dse_tmp / 10000.0);
3583 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3584 dse_lat = dse_lat / 600000.0;
3585 dse_lon = dse_lon / 600000.0;
3586 } else if (dseSymbol.IsSameAs("01")) { // Source & Datum
3587 } else if (dseSymbol.IsSameAs("02")) { // SOG
3588 token.ToDouble(&dse_tmp);
3589 dse_sog = dse_tmp / 10.0;
3590 } else if (dseSymbol.IsSameAs("03")) { // COG
3591 token.ToDouble(&dse_tmp);
3592 dse_cog = dse_tmp / 10.0;
3593 } else if (dseSymbol.IsSameAs("04")) { // Station Information
3594 dse_shipName = DecodeDSEExpansionCharacters(token);
3595 } else if (dseSymbol.IsSameAs("05")) { // Geographic Information
3596 } else if (dseSymbol.IsSameAs("06")) { // Persons On Board
3597 }
3598 }
3599 mmsi = abs((int)dse_mmsi);
3600 }
3601
3602 // Get the last report time for this target, if it exists
3603 wxDateTime now = wxDateTime::Now();
3604 now.MakeGMT();
3605 int last_report_ticks = now.GetTicks();
3606
3607 // Search the current AISTargetList for an MMSI match
3608 auto it = AISTargetList.find(mmsi);
3609 std::shared_ptr<AisTargetData> pStaleTarget = nullptr;
3610 if (it == AISTargetList.end()) { // not found
3611 } else {
3612 pStaleTarget = it->second; // find current entry
3613 last_report_ticks = pStaleTarget->PositionReportTicks;
3614 }
3615
3616 if (dsc_mmsi) {
3617 // Create a tentative target, but do not post it pending receipt of
3618 // extended data
3619 m_ptentative_dsctarget = AisTargetDataMaker::GetInstance().GetTargetData();
3620
3621 m_ptentative_dsctarget->PositionReportTicks = now.GetTicks();
3622 m_ptentative_dsctarget->StaticReportTicks = now.GetTicks();
3623
3624 m_ptentative_dsctarget->MMSI = mmsi;
3625 m_ptentative_dsctarget->NavStatus =
3626 99; // Undefind. "-" in the AIS target list
3627 m_ptentative_dsctarget->Lat = dsc_lat;
3628 m_ptentative_dsctarget->Lon = dsc_lon;
3629 m_ptentative_dsctarget->b_positionOnceValid = true;
3630 m_ptentative_dsctarget->COG = 0;
3631 m_ptentative_dsctarget->SOG = 0;
3632 m_ptentative_dsctarget->ShipType = dsc_fmt;
3633 m_ptentative_dsctarget->Class = AIS_DSC;
3634 m_ptentative_dsctarget->b_isDSCtarget = true;
3635 m_ptentative_dsctarget->b_nameValid = true;
3636 if (dsc_fmt == 12 || (dsc_fmt == 16 && dsc_cat == 12)) {
3637 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN, "DISTRESS %u",
3638 mmsi);
3639 m_ptentative_dsctarget->m_dscNature = int(dsc_nature);
3640 m_ptentative_dsctarget->m_dscTXmmsi = dsc_tx_mmsi;
3641 } else {
3642 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN, "POSITION %u",
3643 mmsi);
3644 }
3645
3646 m_ptentative_dsctarget->b_active = true;
3647 m_ptentative_dsctarget->b_lost = false;
3648 m_ptentative_dsctarget->RecentPeriod =
3649 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3650
3651 // Start a timer, looking for an expected DSE extension message
3652 if (!b_take_dsc) m_dsc_timer.Start(1000, wxTIMER_ONE_SHOT);
3653 }
3654
3655 // Got an extension message, or the timer expired and no extension is
3656 // expected
3657 if (dse_mmsi || b_take_dsc) {
3658 if (m_ptentative_dsctarget) {
3659 // stop the timer for sure
3660 m_dsc_timer.Stop();
3661
3662 // Update the extended information
3663 if (dse_mmsi) {
3664 m_ptentative_dsctarget->Lat =
3665 m_ptentative_dsctarget->Lat +
3666 ((m_ptentative_dsctarget->Lat) >= 0 ? dse_lat : -dse_lat);
3667 m_ptentative_dsctarget->Lon =
3668 m_ptentative_dsctarget->Lon +
3669 ((m_ptentative_dsctarget->Lon) >= 0 ? dse_lon : -dse_lon);
3670 if (!dse_shipName.empty()) {
3671 memset(m_ptentative_dsctarget->ShipName, '\0', SHIP_NAME_LEN);
3672 snprintf(m_ptentative_dsctarget->ShipName, dse_shipName.length(),
3673 "%s", dse_shipName.ToAscii().data());
3674 }
3675 m_ptentative_dsctarget->COG = dse_cog;
3676 m_ptentative_dsctarget->SOG = dse_sog;
3677 }
3678
3679 // Update the most recent report period
3680 m_ptentative_dsctarget->RecentPeriod =
3681 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3682
3683 // And post the target
3684
3685 // Search the current AISTargetList for an MMSI match
3686 auto found = AISTargetList.find(mmsi);
3687 if (found == AISTargetList.end()) { // not found
3688 pTargetData = m_ptentative_dsctarget;
3689 } else {
3690 pTargetData = found->second; // find current entry
3691 std::vector<AISTargetTrackPoint> ptrack =
3692 std::move(pTargetData->m_ptrack);
3693 pTargetData->CloneFrom(
3694 m_ptentative_dsctarget
3695 .get()); // this will make an empty track list
3696
3697 pTargetData->m_ptrack =
3698 std::move(ptrack); // and substitute the old track list
3699
3700 // delete m_ptentative_dsctarget;
3701 }
3702
3703 // Reset for next time
3704 m_ptentative_dsctarget = nullptr;
3705
3706 m_pLatestTargetData = pTargetData;
3707
3708 AISTargetList[pTargetData->MMSI] =
3709 pTargetData; // update the hash table entry
3710
3711 long mmsi_long = pTargetData->MMSI;
3712
3713 // Delete any stale Target selectable point
3714 if (pStaleTarget)
3715 pSelectAIS->DeleteSelectablePoint((void *)mmsi_long, SELTYPE_AISTARGET);
3716 // And add the updated target
3717 SelectItem *pSel =
3718 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
3719 (void *)mmsi_long, SELTYPE_AISTARGET);
3720 pSel->SetUserData(pTargetData->MMSI);
3721
3722 // Calculate CPA info for this target immediately
3723 UpdateOneCPA(pTargetData.get());
3724
3725 // Update this target's track
3726 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3727 }
3728 }
3729
3730 return pTargetData;
3731}
3732
3733bool AisDecoder::NMEACheckSumOK(const wxString &str_in) {
3734 unsigned char checksum_value = 0;
3735 int sentence_hex_sum;
3736
3737 wxCharBuffer buf = str_in.ToUTF8();
3738 if (!buf.data()) return false; // cannot decode string
3739
3740 char str_ascii[AIS_MAX_MESSAGE_LEN + 1];
3741 strncpy(str_ascii, buf.data(), AIS_MAX_MESSAGE_LEN);
3742 str_ascii[AIS_MAX_MESSAGE_LEN] = '\0';
3743
3744 int string_length = strlen(str_ascii);
3745
3746 int payload_length = 0;
3747 while ((payload_length < string_length) &&
3748 (str_ascii[payload_length] != '*')) // look for '*'
3749 payload_length++;
3750
3751 if (payload_length == string_length)
3752 return false; // '*' not found at all, no checksum
3753
3754 int index = 1; // Skip over the $ at the begining of the sentence
3755
3756 while (index < payload_length) {
3757 checksum_value ^= str_ascii[index];
3758 index++;
3759 }
3760
3761 if (string_length > 4) {
3762 char scanstr[3];
3763 scanstr[0] = str_ascii[payload_length + 1];
3764 scanstr[1] = str_ascii[payload_length + 2];
3765 scanstr[2] = 0;
3766 sscanf(scanstr, "%2x", &sentence_hex_sum);
3767
3768 if (sentence_hex_sum == checksum_value) return true;
3769 }
3770
3771 return false;
3772}
3773
3774void AisDecoder::UpdateAllCPA() {
3775 // Iterate thru all the targets
3776 for (const auto &it : GetTargetList()) {
3777 std::shared_ptr<AisTargetData> td = it.second;
3778
3779 if (nullptr != td) UpdateOneCPA(td.get());
3780 }
3781}
3782
3783void AisDecoder::UpdateAllTracks() {
3784 for (const auto &it : GetTargetList()) {
3785 std::shared_ptr<AisTargetData> td = it.second;
3786 if (td) UpdateOneTrack(td.get());
3787 }
3788}
3789
3790int gdup;
3791void AisDecoder::UpdateOneTrack(AisTargetData *ptarget) {
3792 if (!ptarget->b_positionOnceValid) return;
3793 // Reject for unbelievable jumps (corrupted/bad data)
3794 if (!ptarget->m_ptrack.empty()) {
3795 const AISTargetTrackPoint &LastTrackpoint = ptarget->m_ptrack.back();
3796 if (fabs(LastTrackpoint.m_lat - ptarget->Lat) > .1 ||
3797 fabs(LastTrackpoint.m_lon - ptarget->Lon) > .1) {
3798 // after an unlikely jump in pos, the last trackpoint might also be wrong
3799 // just to be sure we do delete this one as well.
3800 ptarget->m_ptrack.pop_back();
3801 ptarget->b_positionDoubtful = true;
3802 return;
3803 }
3804 }
3805
3806 // Avoid duplicate track points
3807 // Do not add track point if time since last point is < 2 seconds.
3808 if ((ptarget->PositionReportTicks - ptarget->LastPositionReportTicks) > 2) {
3809 // Create the newest point
3810 AISTargetTrackPoint ptrackpoint;
3811 ptrackpoint.m_lat = ptarget->Lat;
3812 ptrackpoint.m_lon = ptarget->Lon;
3813 ptrackpoint.m_time = wxDateTime::Now().GetTicks();
3814
3815 ptarget->m_ptrack.push_back(ptrackpoint);
3816
3817 if (ptarget->b_PersistTrack || ptarget->b_mPropPersistTrack) {
3818 Track *t;
3819 if (0 == m_persistent_tracks.count(ptarget->MMSI)) {
3820 t = new Track();
3821 t->SetName(wxString::Format(
3822 "AIS %s (%u) %s %s", ptarget->GetFullName().c_str(), ptarget->MMSI,
3823 wxDateTime::Now().FormatISODate().c_str(),
3824 wxDateTime::Now().FormatISOTime().c_str()));
3825 g_TrackList.push_back(t);
3826 new_track.Notify(t);
3827 m_persistent_tracks[ptarget->MMSI] = t;
3828 } else {
3829 t = m_persistent_tracks[ptarget->MMSI];
3830 }
3831 TrackPoint *tp = t->GetLastPoint();
3832 vector2D point(ptrackpoint.m_lon, ptrackpoint.m_lat);
3833 TrackPoint *tp1 =
3834 t->AddNewPoint(point, wxDateTime(ptrackpoint.m_time).ToUTC());
3835
3836 if (tp)
3837 pSelect->AddSelectableTrackSegment(tp->m_lat, tp->m_lon, tp1->m_lat,
3838 tp1->m_lon, tp, tp1, t);
3839
3840 // We do not want dependency on the GUI here, do we?
3841 // if( pRouteManagerDialog && pRouteManagerDialog->IsShown() )
3842 // pRouteManagerDialog->UpdateTrkListCtrl();
3843
3844 } else {
3845 // Walk the list, removing any track points that are older than the
3846 // stipulated time
3847 time_t test_time =
3848 wxDateTime::Now().GetTicks() - (time_t)(g_AISShowTracks_Mins * 60);
3849
3850 ptarget->m_ptrack.erase(
3851 std::remove_if(ptarget->m_ptrack.begin(), ptarget->m_ptrack.end(),
3852 [=](const AISTargetTrackPoint &track) {
3853 return track.m_time < test_time;
3854 }),
3855 ptarget->m_ptrack.end());
3856 }
3857 }
3858}
3859
3860void AisDecoder::DeletePersistentTrack(const Track *track) {
3861 for (auto it = m_persistent_tracks.begin(); it != m_persistent_tracks.end();
3862 it++) {
3863 if (it->second == track) {
3864 unsigned mmsi = it->first;
3865 m_persistent_tracks.erase(it);
3866 // Last tracks for this target?
3867 if (0 == m_persistent_tracks.count(mmsi)) {
3868 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
3869 if (mmsi == static_cast<unsigned>(g_MMSI_Props_Array[i]->MMSI)) {
3871 if (props->m_bPersistentTrack) {
3872 // Ask if mmsi props should be changed.
3873 // Avoid creation of a new track while messaging
3874
3875 std::shared_ptr<AisTargetData> td =
3876 Get_Target_Data_From_MMSI(mmsi);
3877 if (td) {
3878 props->m_bPersistentTrack = false;
3879 td->b_mPropPersistTrack = false;
3880 }
3881 if (!m_callbacks.confirm_stop_track()) {
3882 props->m_bPersistentTrack = true;
3883 }
3884 }
3885 break;
3886 }
3887 }
3888 }
3889 break;
3890 }
3891 }
3892}
3893
3894void AisDecoder::UpdateAllAlarms() {
3895 m_bGeneralAlert = false; // no alerts yet
3896
3897 // Iterate thru all the targets
3898 for (const auto &it : GetTargetList()) {
3899 std::shared_ptr<AisTargetData> td = it.second;
3900
3901 if (td) {
3902 // Maintain General Alert
3903 if (!m_bGeneralAlert) {
3904 // Quick check on basic condition
3905 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3906 (td->Class != AIS_ATON) && (td->Class != AIS_BASE))
3907 m_bGeneralAlert = true;
3908
3909 // Some options can suppress general alerts
3910 if (g_bAIS_CPA_Alert_Suppress_Moored && (td->SOG <= g_ShowMoored_Kts))
3911 m_bGeneralAlert = false;
3912
3913 // Skip distant targets if requested
3914 if ((g_bCPAMax) && (td->Range_NM > g_CPAMax_NM))
3915 m_bGeneralAlert = false;
3916
3917 // Skip if TCPA is too long
3918 if ((g_bTCPA_Max) && (td->TCPA > g_TCPA_Max)) m_bGeneralAlert = false;
3919
3920 // SART targets always alert if "Active"
3921 if (td->Class == AIS_SART && td->NavStatus == 14)
3922 m_bGeneralAlert = true;
3923
3924 // DSC Distress targets always alert
3925 if ((td->Class == AIS_DSC) &&
3926 ((td->ShipType == 12) || (td->ShipType == 16)))
3927 m_bGeneralAlert = true;
3928 }
3929
3930 ais_alert_type this_alarm = AIS_NO_ALERT;
3931
3932 // SART targets always alert if "Active"
3933 if (td->Class == AIS_SART && td->NavStatus == 14)
3934 this_alarm = AIS_ALERT_SET;
3935
3936 // DSC Distress targets always alert
3937 if ((td->Class == AIS_DSC) &&
3938 ((td->ShipType == 12) || (td->ShipType == 16)))
3939 this_alarm = AIS_ALERT_SET;
3940
3941 if (g_bCPAWarn && td->b_active && td->b_positionOnceValid &&
3942 (td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
3943 // Skip anchored/moored(interpreted as low speed) targets if
3944 // requested
3945 if ((g_bHideMoored) && (td->SOG <= g_ShowMoored_Kts)) { // dsr
3946 td->n_alert_state = AIS_NO_ALERT;
3947 continue;
3948 }
3949
3950 // No Alert on moored(interpreted as low speed) targets if so
3951 // requested
3952 if (g_bAIS_CPA_Alert_Suppress_Moored &&
3953 (td->SOG <= g_ShowMoored_Kts)) { // dsr
3954 td->n_alert_state = AIS_NO_ALERT;
3955 continue;
3956 }
3957
3958 // Skip distant targets if requested
3959 if (g_bCPAMax) {
3960 if (td->Range_NM > g_CPAMax_NM) {
3961 td->n_alert_state = AIS_NO_ALERT;
3962 continue;
3963 }
3964 }
3965
3966 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3967 (td->Class != AIS_ATON) && (td->Class != AIS_BASE) &&
3968 (td->Class != AIS_METEO)) {
3969 if (g_bTCPA_Max) {
3970 if (td->TCPA < g_TCPA_Max) {
3971 if (td->b_isFollower)
3972 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3973 else
3974 this_alarm = AIS_ALERT_SET;
3975 }
3976 } else {
3977 if (td->b_isFollower)
3978 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3979 else
3980 this_alarm = AIS_ALERT_SET;
3981 }
3982 }
3983 }
3984
3985 // Maintain the timer for in_ack flag
3986 // SART and DSC targets always maintain ack timeout
3987 if (td->Class == AIS_SART) {
3988 if (td->b_in_ack_timeout) {
3989 wxTimeSpan delta = wxDateTime::Now() - td->m_ack_time;
3990 // SART Alert obeys fixed 10 minute ACK-TO.
3991 if (delta.GetMinutes() >= 10) td->b_in_ack_timeout = false;
3992 }
3993 } else if (g_bAIS_ACK_Timeout ||
3994 ((td->Class == AIS_DSC) &&
3995 ((td->ShipType == 12) || (td->ShipType == 16)))) {
3996 if (td->b_in_ack_timeout) {
3997 wxTimeSpan delta = wxDateTime::Now() - td->m_ack_time;
3998 if (delta.GetMinutes() > g_AckTimeout_Mins)
3999 td->b_in_ack_timeout = false;
4000 }
4001 } else {
4002 // Not using ack timeouts.
4003 // If a target has been acknowledged, leave it ack'ed until it goes out
4004 // of AIS_ALARM_SET state
4005 if (td->b_in_ack_timeout) {
4006 if (this_alarm == AIS_NO_ALERT) td->b_in_ack_timeout = false;
4007 }
4008 }
4009
4010 td->n_alert_state = this_alarm;
4011 }
4012 }
4013}
4014
4015void AisDecoder::UpdateOneCPA(AisTargetData *ptarget) {
4016 ptarget->Range_NM = -1.; // Defaults
4017 ptarget->Brg = -1.;
4018
4019 // Compute the current Range/Brg to the target
4020 // This should always be possible even if GPS data is not valid
4021 // because O must always have a position for own-ship. Plugins need
4022 // AIS target range and bearing from own-ship position even if GPS is not
4023 // valid.
4024 double brg, dist;
4025 DistanceBearingMercator(ptarget->Lat, ptarget->Lon, gLat, gLon, &brg, &dist);
4026 ptarget->Range_NM = dist;
4027 ptarget->Brg = brg;
4028
4029 if (dist <= 1e-5) ptarget->Brg = -1.0; // Brg is undefined if Range == 0.
4030
4031 if (!ptarget->b_positionOnceValid || !bGPSValid) {
4032 ptarget->bCPA_Valid = false;
4033 return;
4034 }
4035 // Ais Meteo is not a hard target in danger for collision
4036 if (ptarget->Class == AIS_METEO) {
4037 ptarget->bCPA_Valid = false;
4038 return;
4039 }
4040
4041 // There can be no collision between ownship and itself....
4042 // This can happen if AIVDO messages are received, and there is another
4043 // source of ownship position, like NMEA GLL The two positions are always
4044 // temporally out of sync, and one will always be exactly in front of the
4045 // other one.
4046 if (ptarget->b_OwnShip) {
4047 ptarget->CPA = 100;
4048 ptarget->TCPA = -100;
4049 ptarget->bCPA_Valid = false;
4050 return;
4051 }
4052
4053 double cpa_calc_ownship_cog = gCog;
4054 double cpa_calc_target_cog = ptarget->COG;
4055
4056 // Ownship is not reporting valid SOG, so no way to calculate CPA
4057 if (std::isnan(gSog) || (gSog > 102.2)) {
4058 ptarget->bCPA_Valid = false;
4059 return;
4060 }
4061
4062 // Ownship is maybe anchored and not reporting COG
4063 if (std::isnan(gCog) || gCog == 360.0) {
4064 if (gSog < .01)
4065 cpa_calc_ownship_cog =
4066 0.; // substitute value
4067 // for the case where SOG ~= 0, and COG is unknown.
4068 else {
4069 ptarget->bCPA_Valid = false;
4070 return;
4071 }
4072 }
4073
4074 // Target is maybe anchored and not reporting COG
4075 if (ptarget->COG == 360.0) {
4076 if (ptarget->SOG > 102.2) {
4077 ptarget->bCPA_Valid = false;
4078 return;
4079 } else if (ptarget->SOG < .01) {
4080 cpa_calc_target_cog = 0.; // substitute value
4081 // for the case where SOG ~= 0, and COG is unknown.
4082 } else {
4083 ptarget->bCPA_Valid = false;
4084 return;
4085 }
4086 }
4087
4088 // Express the SOGs as meters per hour
4089 double v0 = gSog * 1852.;
4090 double v1 = ptarget->SOG * 1852.;
4091
4092 if ((v0 < 1e-6) && (v1 < 1e-6)) {
4093 ptarget->TCPA = 0.;
4094 ptarget->CPA = 0.;
4095
4096 ptarget->bCPA_Valid = false;
4097 } else {
4098 // Calculate the TCPA first
4099
4100 // Working on a Reduced Lat/Lon orthogonal plotting sheet....
4101 // Get easting/northing to target, in meters
4102
4103 double east1 = (ptarget->Lon - gLon) * 60 * 1852;
4104 double north1 = (ptarget->Lat - gLat) * 60 * 1852;
4105
4106 double east = east1 * (cos(gLat * PI / 180.));
4107
4108 double north = north1;
4109
4110 // Convert COGs trigonometry to standard unit circle
4111 double cosa = cos((90. - cpa_calc_ownship_cog) * PI / 180.);
4112 double sina = sin((90. - cpa_calc_ownship_cog) * PI / 180.);
4113 double cosb = cos((90. - cpa_calc_target_cog) * PI / 180.);
4114 double sinb = sin((90. - cpa_calc_target_cog) * PI / 180.);
4115
4116 // These will be useful
4117 double fc = (v0 * cosa) - (v1 * cosb);
4118 double fs = (v0 * sina) - (v1 * sinb);
4119
4120 double d = (fc * fc) + (fs * fs);
4121 double tcpa;
4122
4123 // the tracks are almost parallel
4124 if (fabs(d) < 1e-6)
4125 tcpa = 0.;
4126 else
4127 // Here is the equation for t, which will be in hours
4128 tcpa = ((fc * east) + (fs * north)) / d;
4129
4130 // Convert to minutes
4131 ptarget->TCPA = tcpa * 60.;
4132
4133 // Calculate CPA
4134 // Using TCPA, predict ownship and target positions
4135
4136 double OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA, TargetLonCPA;
4137
4138 ll_gc_ll(gLat, gLon, cpa_calc_ownship_cog, gSog * tcpa, &OwnshipLatCPA,
4139 &OwnshipLonCPA);
4140 ll_gc_ll(ptarget->Lat, ptarget->Lon, cpa_calc_target_cog,
4141 ptarget->SOG * tcpa, &TargetLatCPA, &TargetLonCPA);
4142
4143 // And compute the distance
4144 ptarget->CPA = DistGreatCircle(OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA,
4145 TargetLonCPA);
4146
4147 ptarget->bCPA_Valid = true;
4148
4149 if (ptarget->TCPA < 0) ptarget->bCPA_Valid = false;
4150 }
4151}
4152
4153void AisDecoder::OnTimerDSC(wxTimerEvent &event) {
4154 // Timer expired, no CDDSE message was received, so accept the latest CDDSC
4155 // message
4156 if (m_ptentative_dsctarget) {
4157 ProcessDSx(m_dsc_last_string, true);
4158 }
4159}
4160
4161void AisDecoder::OnTimerAIS(wxTimerEvent &event) {
4162 TimerAIS.Stop();
4163 // Scrub the target hash list
4164 // removing any targets older than stipulated age
4165
4166 wxDateTime now = wxDateTime::Now();
4167 now.MakeGMT();
4168
4169 std::unordered_map<int, std::shared_ptr<AisTargetData>> &current_targets =
4170 GetTargetList();
4171
4172 auto it = current_targets.begin();
4173 std::vector<int> remove_array; // collector for MMSI of targets to be removed
4174
4175 while (it != current_targets.end()) {
4176 if (it->second ==
4177 nullptr) // This should never happen, but I saw it once....
4178 {
4179 current_targets.erase(it);
4180 break; // leave the loop
4181 }
4182 // std::shared_ptr<AisTargetData>
4183 // xtd(std::make_shared<AisTargetData>(*it->second));
4184 std::shared_ptr<AisTargetData> xtd = it->second;
4185
4186 int target_posn_age = now.GetTicks() - xtd->PositionReportTicks;
4187 int target_static_age = now.GetTicks() - xtd->StaticReportTicks;
4188
4189 // Global variables controlling lost target handling
4190 // g_bMarkLost
4191 // g_MarkLost_Mins // Minutes until black "cross out
4192 // g_bRemoveLost
4193 // g_RemoveLost_Mins); // minutes until target is removed from screen and
4194 // internal lists
4195
4196 // g_bInlandEcdis
4197
4198 // Mark lost targets if specified
4199 double removelost_Mins = fmax(g_RemoveLost_Mins, g_MarkLost_Mins);
4200
4201 if (g_bInlandEcdis && (xtd->Class != AIS_ARPA)) {
4202 double iECD_LostTimeOut = 0.0;
4203 // special rules apply for europe inland ecdis timeout settings. overrule
4204 // option settings Won't apply for ARPA targets where the radar has all
4205 // control
4206 if (xtd->Class == AIS_CLASS_A) {
4207 if ((xtd->NavStatus == MOORED) || (xtd->NavStatus == AT_ANCHOR)) {
4208 if (xtd->SOG < 3.)
4209 iECD_LostTimeOut = 18 * 60;
4210 else
4211 iECD_LostTimeOut = 60;
4212 } else
4213 iECD_LostTimeOut = 60;
4214 } else if (xtd->Class == AIS_CLASS_B) {
4215 if (xtd->SOG < 2.)
4216 iECD_LostTimeOut = 18 * 60;
4217 else
4218 iECD_LostTimeOut = 180;
4219 } else if (xtd->Class == AIS_ATON || xtd->Class == AIS_BASE)
4220 iECD_LostTimeOut = 18 * 60; // Like a not moving Class A target
4221
4222 if ((target_posn_age > iECD_LostTimeOut) &&
4223 (xtd->Class != AIS_GPSG_BUDDY))
4224 xtd->b_active = false;
4225
4226 removelost_Mins = (2 * iECD_LostTimeOut) / 60.;
4227
4228 } else if (g_bMarkLost) {
4229 if ((target_posn_age > g_MarkLost_Mins * 60) &&
4230 (xtd->Class != AIS_GPSG_BUDDY) && (xtd->Class != AIS_SART))
4231 xtd->b_active = false;
4232 }
4233
4234 if (xtd->Class == AIS_SART || xtd->Class == AIS_METEO)
4235 removelost_Mins = 18.0;
4236
4237 // Remove lost targets if specified
4238
4239 if (g_bRemoveLost || g_bInlandEcdis) {
4240 bool b_arpalost =
4241 (xtd->Class == AIS_ARPA &&
4242 xtd->b_lost); // A lost ARPA target would be deleted at once
4243 if (((target_posn_age > removelost_Mins * 60) &&
4244 (xtd->Class != AIS_GPSG_BUDDY)) ||
4245 b_arpalost) {
4246 // So mark the target as lost, with unknown position, and make it
4247 // not selectable
4248 xtd->b_lost = true;
4249 xtd->b_positionOnceValid = false;
4250 xtd->COG = 360.0;
4251 xtd->SOG = 103.0;
4252 xtd->HDG = 511.0;
4253 xtd->ROTAIS = -128;
4254
4255 plugin_msg_evt.Notify(xtd, "");
4256
4257 long mmsi_long = xtd->MMSI;
4258 pSelectAIS->DeleteSelectablePoint((void *)mmsi_long, SELTYPE_AISTARGET);
4259
4260 // If we have not seen a static report in 3 times the removal spec,
4261 // then remove the target from all lists
4262 // or a lost ARPA target.
4263 if (target_static_age > removelost_Mins * 60 * 3 || b_arpalost) {
4264 xtd->b_removed = true;
4265 plugin_msg_evt.Notify(xtd, "");
4266 remove_array.push_back(xtd->MMSI); // Add this target to removal list
4267 }
4268 }
4269 }
4270
4271 // Remove any targets specified as to be "ignored", so that they won't
4272 // trigger phantom alerts (e.g. SARTs)
4273 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
4275 if (xtd->MMSI == props->MMSI) {
4276 if (props->m_bignore) {
4277 remove_array.push_back(xtd->MMSI); // Add this target to removal list
4278 xtd->b_removed = true;
4279 plugin_msg_evt.Notify(xtd, "");
4280 }
4281 break;
4282 }
4283 }
4284
4285 // Check if the target has recently been set as own MMSI
4286 if (static_cast<unsigned>(xtd->MMSI) == g_OwnShipmmsi) {
4287 remove_array.push_back(xtd->MMSI); // Add this target to removal list
4288 xtd->b_removed = true;
4289 plugin_msg_evt.Notify(xtd, "");
4290 }
4291
4292 ++it;
4293 }
4294
4295 // Remove all the targets collected in remove_array in one pass
4296 for (unsigned int i = 0; i < remove_array.size(); i++) {
4297 auto itd = current_targets.find(remove_array[i]);
4298 if (itd != current_targets.end()) {
4299 std::shared_ptr<AisTargetData> td = itd->second;
4300 current_targets.erase(itd);
4301 // delete td;
4302 }
4303 }
4304
4305 UpdateAllCPA();
4306 UpdateAllAlarms();
4307
4308 // Update the general suppression flag
4309 m_bSuppressed = false;
4310 if (g_bAIS_CPA_Alert_Suppress_Moored || g_bHideMoored ||
4311 (g_bShowScaled && g_bAllowShowScaled))
4312 m_bSuppressed = true;
4313
4314 m_bAIS_Audio_Alert_On = false; // default, may be set on
4315
4316 // Process any Alarms
4317
4318 // If the AIS Alert Dialog is not currently shown....
4319
4320 // Scan all targets, looking for SART, DSC Distress, and CPA incursions
4321 // In the case of multiple targets of the same type, select the shortest
4322 // range or shortest TCPA
4323 std::shared_ptr<AisTargetData> palert_target = nullptr;
4324
4325 if (!g_pais_alert_dialog_active) {
4326 pAISMOBRoute = nullptr; // Reset the AISMOB auto route.
4327 double tcpa_min = 1e6; // really long
4328 double sart_range = 1e6;
4329 std::shared_ptr<AisTargetData> palert_target_cpa = nullptr;
4330 std::shared_ptr<AisTargetData> palert_target_sart = nullptr;
4331 std::shared_ptr<AisTargetData> palert_target_dsc = nullptr;
4332
4333 for (it = current_targets.begin(); it != current_targets.end(); ++it) {
4334 std::shared_ptr<AisTargetData> td = it->second;
4335 if (td) {
4336 if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
4337 if (g_bAIS_CPA_Alert && td->b_active) {
4338 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4339 if (td->TCPA < tcpa_min) {
4340 tcpa_min = td->TCPA;
4341 palert_target_cpa = td;
4342 }
4343 }
4344 }
4345 } else if ((td->Class == AIS_DSC) &&
4346 ((td->ShipType == 12) || (td->ShipType == 16))) {
4347 if (td->b_active) {
4348 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4349 palert_target_dsc = td;
4350 } else { // Reset DCS flag to open for a real AIS for the same
4351 // target
4352 td->b_isDSCtarget = false;
4353 }
4354 }
4355 }
4356
4357 else if (td->Class == AIS_SART) {
4358 if (td->b_active) {
4359 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4360 if (td->Range_NM < sart_range) {
4361 tcpa_min = sart_range;
4362 palert_target_sart = td;
4363 }
4364 }
4365 }
4366 }
4367 }
4368 }
4369
4370 // Which of multiple targets?
4371 // Give priority to SART targets, then DSC Distress, then CPA incursion
4372 palert_target = palert_target_cpa;
4373
4374 if (palert_target_sart) {
4375 palert_target = palert_target_sart;
4376 }
4377
4378 if (palert_target_dsc) {
4379 palert_target = palert_target_dsc;
4380 }
4381 } else {
4382 // Alert is currently shown, get target from from knowable GUI
4383 palert_target = Get_Target_Data_From_MMSI(m_callbacks.get_target_mmsi());
4384 }
4385 // Show or update the alert
4386 if (palert_target) info_update.Notify(palert_target, "");
4387
4388 TimerAIS.Start(TIMER_AIS_MSEC, wxTIMER_CONTINUOUS);
4389}
4390
4391std::shared_ptr<AisTargetData> AisDecoder::Get_Target_Data_From_MMSI(
4392 unsigned mmsi) {
4393 if (AISTargetList.find(mmsi) == AISTargetList.end()) return nullptr;
4394 return AISTargetList[mmsi];
4395}
4396
4397ArrayOfMmsiProperties g_MMSI_Props_Array;
4398
4399// MmsiProperties Implementation
4400
4401MmsiProperties::MmsiProperties(wxString &spec) {
4402 Init();
4403 wxStringTokenizer tkz(spec, ";");
4404 wxString s;
4405
4406 s = tkz.GetNextToken();
4407 long mmsil;
4408 s.ToLong(&mmsil);
4409 MMSI = (int)mmsil;
4410
4411 s = tkz.GetNextToken();
4412 if (s.Len()) {
4413 if (s.Upper() == "ALWAYS")
4414 TrackType = TRACKTYPE_ALWAYS;
4415 else if (s.Upper() == "NEVER")
4416 TrackType = TRACKTYPE_NEVER;
4417 }
4418
4419 s = tkz.GetNextToken();
4420 if (s.Len()) {
4421 if (s.Upper() == "IGNORE") m_bignore = true;
4422 }
4423
4424 s = tkz.GetNextToken();
4425 if (s.Len()) {
4426 if (s.Upper() == "MOB") m_bMOB = true;
4427 }
4428
4429 s = tkz.GetNextToken();
4430 if (s.Len()) {
4431 if (s.Upper() == "VDM") m_bVDM = true;
4432 }
4433
4434 s = tkz.GetNextToken();
4435 if (s.Len()) {
4436 if (s.Upper() == "FOLLOWER") m_bFollower = true;
4437 }
4438
4439 s = tkz.GetNextToken();
4440 if (s.Len()) {
4441 if (s.Upper() == "PERSIST") m_bPersistentTrack = true;
4442 }
4443
4444 s = tkz.GetNextToken();
4445 if (s.Len()) {
4446 m_ShipName = s.Upper();
4447 }
4448}
4449
4450MmsiProperties::~MmsiProperties() = default;
4451
4452void MmsiProperties::Init() {
4453 MMSI = -1;
4454 TrackType = TRACKTYPE_DEFAULT;
4455 m_bignore = false;
4456 m_bMOB = false;
4457 m_bVDM = false;
4458 m_bFollower = false;
4459 m_bPersistentTrack = false;
4460 m_ShipName = "";
4461}
4462
4463wxString MmsiProperties::Serialize() {
4464 wxString sMMSI;
4465 wxString s;
4466
4467 sMMSI.Printf("%d", MMSI);
4468 sMMSI << ";";
4469
4470 if (TrackType) {
4471 if (TRACKTYPE_ALWAYS == TrackType)
4472 sMMSI << "always";
4473 else if (TRACKTYPE_NEVER == TrackType)
4474 sMMSI << "never";
4475 }
4476 sMMSI << ";";
4477
4478 if (m_bignore) {
4479 sMMSI << "ignore";
4480 }
4481 sMMSI << ";";
4482
4483 if (m_bMOB) {
4484 sMMSI << "MOB";
4485 }
4486 sMMSI << ";";
4487
4488 if (m_bVDM) {
4489 sMMSI << "VDM";
4490 }
4491 sMMSI << ";";
4492
4493 if (m_bFollower) {
4494 sMMSI << "Follower";
4495 }
4496 sMMSI << ";";
4497
4498 if (m_bPersistentTrack) {
4499 sMMSI << "PERSIST";
4500 }
4501 sMMSI << ";";
4502
4503 if (m_ShipName == "") {
4504 m_ShipName = GetShipNameFromFile(MMSI);
4505 }
4506 sMMSI << m_ShipName;
4507 return sMMSI;
4508}
4509
4510void AISshipNameCache(AisTargetData *pTargetData,
4511 AIS_Target_Name_Hash *AISTargetNamesC,
4512 AIS_Target_Name_Hash *AISTargetNamesNC, long mmsi) {
4513 if (g_benableAISNameCache) {
4514 wxString ship_name = "";
4515
4516 // Check for valid name data
4517 if (!pTargetData->b_nameValid) {
4518 AIS_Target_Name_Hash::iterator it = AISTargetNamesC->find(mmsi);
4519 if (it != AISTargetNamesC->end()) {
4520 ship_name = (*AISTargetNamesC)[mmsi].Left(20);
4521 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4522 ship_name.length() + 1);
4523 pTargetData->b_nameValid = true;
4524 pTargetData->b_nameFromCache = true;
4525 } else if (!g_bUseOnlyConfirmedAISName) {
4526 it = AISTargetNamesNC->find(mmsi);
4527 if (it != AISTargetNamesNC->end()) {
4528 ship_name = (*AISTargetNamesNC)[mmsi].Left(20);
4529 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4530 ship_name.length() + 1);
4531 pTargetData->b_nameValid = true;
4532 pTargetData->b_nameFromCache = true;
4533 }
4534 }
4535 }
4536 // else there IS a valid name, lets check if it is in one of the hash lists.
4537 else if ((pTargetData->MID == 5) || (pTargetData->MID == 24) ||
4538 (pTargetData->MID == 19) ||
4539 (pTargetData->MID == 123) || // 123: Has got a name from SignalK
4540 (pTargetData->MID == 124)) { // 124: Has got a name from n2k
4541 // This message contains ship static data, so has a name field
4542 pTargetData->b_nameFromCache = false;
4543 ship_name = trimAISField(pTargetData->ShipName);
4544 AIS_Target_Name_Hash::iterator itC = AISTargetNamesC->find(mmsi);
4545 AIS_Target_Name_Hash::iterator itNC = AISTargetNamesNC->find(mmsi);
4546 if (itC !=
4547 AISTargetNamesC->end()) { // There is a confirmed entry for this mmsi
4548 if ((*AISTargetNamesC)[mmsi] ==
4549 ship_name) { // Received name is same as confirmed name
4550 if (itNC != AISTargetNamesNC->end()) { // there is also an entry in
4551 // the NC list, delete it
4552 AISTargetNamesNC->erase(itNC);
4553 }
4554 } else { // There is a confirmed name but that one is different
4555 if (itNC != AISTargetNamesNC->end()) { // there is an entry in the NC
4556 // list check if name is same
4557 if ((*AISTargetNamesNC)[mmsi] ==
4558 ship_name) { // Same name is already in NC list so promote till
4559 // confirmed list
4560 (*AISTargetNamesC)[mmsi] = ship_name;
4561 // And delete from NC list
4562 AISTargetNamesNC->erase(itNC);
4563 } else { // A different name is in the NC list, update with
4564 // received one
4565 (*AISTargetNamesNC)[mmsi] = ship_name;
4566 }
4567 if (g_bUseOnlyConfirmedAISName)
4568 strncpy(pTargetData->ShipName, (*AISTargetNamesC)[mmsi].mb_str(),
4569 (*AISTargetNamesC)[mmsi].Left(20).Length() + 1);
4570 } else { // The C list name is different but no NC entry. Add it.
4571 (*AISTargetNamesNC)[mmsi] = ship_name;
4572 }
4573 }
4574 } else { // No confirmed entry available
4575 if (itNC !=
4576 AISTargetNamesNC->end()) { // there is an entry in the NC list,
4577 if ((*AISTargetNamesNC)[mmsi] ==
4578 ship_name) { // Received name same as already in NC list, promote
4579 // to confirmen
4580 (*AISTargetNamesC)[mmsi] = ship_name;
4581 // And delete from NC list
4582 AISTargetNamesNC->erase(itNC);
4583 } else { // entry in NC list is not same as received one
4584 (*AISTargetNamesNC)[mmsi] = ship_name;
4585 }
4586 } else { // No entry in NC list so add it
4587 (*AISTargetNamesNC)[mmsi] = ship_name;
4588 }
4589 if (g_bUseOnlyConfirmedAISName) { // copy back previous name
4590 pTargetData->ShipName[SHIP_NAME_LEN - 1] = '\0';
4591 strncpy(pTargetData->ShipName, "Unknown ",
4592 SHIP_NAME_LEN - 1);
4593 }
4594 }
4595 }
4596 }
4597}
4598
4599wxString GetShipNameFromFile(int nmmsi) {
4600 wxString name = "";
4601 if (g_benableAISNameCache) {
4602 std::ifstream infile(AISTargetNameFileName.mb_str());
4603 if (infile) {
4604 std::string line;
4605 while (getline(infile, line)) {
4606 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()), ",");
4607 if (nmmsi == wxAtoi(tokenizer.GetNextToken())) {
4608 name = tokenizer.GetNextToken().Trim();
4609 break;
4610 } else
4611 tokenizer.GetNextToken();
4612 }
4613 }
4614 infile.close();
4615 }
4616 return name;
4617}
4618
4619void AisDecoder::UpdateMMSItoNameFile(const wxString &mmsi,
4620 const wxString &name) {
4621 // Path to the mmsitoname.csv file is already in AISTargetNameFileName
4622
4623 // Create a map to hold the current contents of the file
4624 std::map<wxString, wxString> mmsi_name_map;
4625
4626 // Read the existing file
4627 std::ifstream infile(AISTargetNameFileName.mb_str());
4628 if (infile) {
4629 std::string line;
4630 while (getline(infile, line)) {
4631 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()), ",");
4632 wxString file_mmsi = tokenizer.GetNextToken();
4633 wxString file_name = tokenizer.GetNextToken().Trim();
4634 mmsi_name_map[file_mmsi] = file_name;
4635 }
4636 infile.close();
4637 }
4638
4639 // Update or add the new entry.
4640 mmsi_name_map[mmsi] = name.Upper();
4641
4642 // Write the updated map back to the file
4643 std::ofstream outfile(AISTargetNameFileName.mb_str());
4644 if (outfile) {
4645 for (const auto &pair : mmsi_name_map) {
4646 std::string line = std::string(pair.first.mb_str()) + "," +
4647 std::string(pair.second.mb_str()) + "\n";
4648 outfile << line;
4649 }
4650 outfile.close();
4651 }
4652}
4653
4654wxString AisDecoder::GetMMSItoNameEntry(const wxString &mmsi) {
4655 return GetShipNameFromFile(wxAtoi(mmsi));
4656}
4657
4658// Assign a unique meteo mmsi related to position
4659int AisMeteoNewMmsi(int orig_mmsi, int m_lat, int m_lon, int lon_bits = 0,
4660 int siteID = 0) {
4661 bool found = false;
4662 int new_mmsi = 0;
4663 if ((!lon_bits || lon_bits == 999) && siteID) {
4664 // Check if a ais8_367_33 data report belongs to a present site
4665 // Or SignalK data (lon_bits == 999)
4666 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4667 if (points.size()) {
4668 for (const auto &point : points) {
4669 // Does this station ID exist
4670 if (siteID == point.siteID && orig_mmsi == point.orig_mmsi) {
4671 // Created before. Continue
4672 new_mmsi = point.mmsi;
4673 found = true;
4674 break;
4675 }
4676 }
4677 }
4678 if (!found && !lon_bits) {
4679 // ais8_367_33
4680 return 0;
4681 }
4682 }
4683 double lon_tentative = 181.;
4684 double lat_tentative = 91.;
4685
4686 if (lon_bits == 25) {
4687 if (m_lon & 0x01000000) // negative?
4688 m_lon |= 0xFE000000;
4689 lon_tentative = m_lon / 60000.;
4690
4691 if (m_lat & 0x00800000) // negative?
4692 m_lat |= 0xFF000000;
4693 lat_tentative = m_lat / 60000.;
4694
4695 } else if (lon_bits == 28) {
4696 if (m_lon & 0x08000000) // negative?
4697 m_lon |= 0xf0000000;
4698 lon_tentative = m_lon / 600000.;
4699
4700 if (m_lat & 0x04000000) // negative?
4701 m_lat |= 0xf8000000;
4702 lat_tentative = m_lat / 600000.;
4703 }
4704
4705 // Since buoys can move we use position precision not better
4706 // than 50 m to be able to compare previous messages
4707 wxString slon = wxString::Format("%0.3f", lon_tentative);
4708 wxString slat = wxString::Format("%0.3f", lat_tentative);
4709
4710 // Change mmsi_ID number
4711 // Some countries use one equal mmsi for all meteo stations.
4712 // Others use the same mmsi for a meteo station and a nearby AtoN
4713 // So we create our own fake mmsi to separate them.
4714 // 199 is INMARSAT-A MID, should not occur ever in AIS stream.
4715 // 1992 to 1993 are already used so here we use 1994+
4716 static int nextMeteommsi = 199400000;
4717 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4718
4719 if (lon_bits != 999 && !points.empty()) { // 999 comes from SignalK
4720 wxString t_lat, t_lon;
4721 for (const auto &point : points) {
4722 // Does this station position exist
4723 if (slat.IsSameAs(point.lat) && slon.IsSameAs(point.lon)) {
4724 // Created before. Continue
4725 new_mmsi = point.mmsi;
4726 found = true;
4727 break;
4728 }
4729 }
4730 }
4731 if (!found) {
4732 // Create a new post
4733 nextMeteommsi++;
4734 points.emplace_back(
4735 AisMeteoPoint(nextMeteommsi, slat, slon, siteID, orig_mmsi));
4736 new_mmsi = nextMeteommsi;
4737 }
4738 return new_mmsi;
4739}
Select * pSelectAIS
Global instance.
wxString AISTargetNameFileName
Global instance.
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
unsigned g_OwnShipmmsi
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Select * pSelectAIS
Global instance.
wxString AISTargetNameFileName
Global instance.
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
unsigned g_OwnShipmmsi
Global instance.
Global state for AIS decoder.
AIS target definitions.
int GetInt(int sp, int len, bool signed_flag=false)
sp is starting bit, 1-based
obs::EventVar info_update
Notified when AIS user dialogs should update.
obs::EventVar plugin_msg_evt
A JSON message should be sent.
obs::EventVar touch_state
Notified when gFrame->TouchAISActive() should be invoked.
obs::EventVar new_track
Notified on new track creation.
Add a new point to the list of Meteo stations.
Process incoming AIS messages.
Definition ais_decoder.h:76
void LogInputMessage(const std::shared_ptr< const NavMsg > &msg, bool is_filtered, bool is_error, const wxString &error_msg="") const
Logs an input message with context information.
void Notify(std::shared_ptr< const NavMsg > message)
Accept message received by driver, make it available for upper layers.
A regular Nmea0183 message.
See: https://github.com/OpenCPN/OpenCPN/issues/2729#issuecomment-1179506343.
void Listen(const std::string &key, wxEvtHandler *listener, wxEventType evt)
Set object to send wxEventType ev to listeners on changes in key.
Custom event class for OpenCPN's notification system.
std::shared_ptr< const void > GetSharedPtr() const
Gets the event's payload data.
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
A parsed SignalK message over ipv4.
Represents a single point in a track.
Definition track.h:59
Represents a track, which is a series of connected track points.
Definition track.h:117
void Notify() override
Notify all listeners, no data supplied.
Definition evtvar.h:83
Raw messages layer, supports sending and recieving navmsg messages.
int g_iSpeedFormat
User-selected speed unit format for display and input.
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
Global variables stored in configuration file.
OpenCPN Georef utility.
GUI constant definitions.
Meteo points are Meteorological and Hydrographic data received by NMEA0183 (AIS) VDM message 8 dac:00...
Multiplexer class and helpers.
const wxChar * ParseGPXDateTime(wxDateTime &dt, const wxChar *datetime)
This function parses a string containing a GPX time representation and returns a wxDateTime containin...
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
Navigation Utility Functions without GUI dependencies.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Waypoint or mark abstraction.
Route * pAISMOBRoute
Global instance.
Definition routeman.cpp:61
Select * pSelect
Global instance.
Definition select.cpp:36
Selected route, segment, waypoint, etc.
A generic position and navigation data structure.
Definition ocpn_types.h:68
double kCog
Course over ground in degrees.
Definition ocpn_types.h:86
double kHdt
True heading in degrees.
Definition ocpn_types.h:111
double kHdm
Magnetic heading in degrees.
Definition ocpn_types.h:104
double kLat
Latitude in decimal degrees.
Definition ocpn_types.h:75
double kSog
Speed over ground in knots.
Definition ocpn_types.h:92
double kVar
Magnetic variation in degrees.
Definition ocpn_types.h:98
double kLon
Longitude in decimal degrees.
Definition ocpn_types.h:83
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:97
Recorded track abstraction.