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