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