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