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