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