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