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 #include <fstream>
25 
26 #ifdef __MINGW32__
27 #undef IPV6STRICT    // mingw FTBS fix:  missing struct ip_mreq
28 #include <windows.h>
29 #endif
30 
31 #include "wx/tokenzr.h"
32 
33 #include "SoundFactory.h"
34 #include "AIS_Decoder.h"
35 #include "AIS_Target_Data.h"
36 #include "AISTargetAlertDialog.h"
37 #include "Select.h"
38 #include "georef.h"
39 #include "geodesic.h"
40 #include "OCPN_DataStreamEvent.h"
41 #include "OCPN_SignalKEvent.h"
42 #include "OCPNPlatform.h"
43 #include "pluginmanager.h"
44 #include "Track.h"
45 #include <multiplexer.h>
46 #include "config.h"
47 #include <cstdio>
48 
49 #if !defined(NAN)
50     static const long long lNaN = 0xfff8000000000000;
51     #define NAN (*(double*)&lNaN)
52 #endif
53 
54 extern AISTargetAlertDialog *g_pais_alert_dialog_active;
55 extern Select *pSelectAIS;
56 extern Select *pSelect;
57 extern MyFrame *gFrame;
58 extern bool bGPSValid;
59 extern bool     g_bCPAMax;
60 extern double   g_CPAMax_NM;
61 extern bool     g_bCPAWarn;
62 extern double   g_CPAWarn_NM;
63 extern bool     g_bTCPA_Max;
64 extern double   g_TCPA_Max;
65 extern bool     g_bMarkLost;
66 extern double   g_MarkLost_Mins;
67 extern bool     g_bRemoveLost;
68 extern double   g_RemoveLost_Mins;
69 extern double   g_AISShowTracks_Mins;
70 extern bool     g_bHideMoored;
71 extern double   g_ShowMoored_Kts;
72 extern wxString g_sAIS_Alert_Sound_File;
73 extern bool     g_bAIS_CPA_Alert_Suppress_Moored;
74 extern bool     g_bAIS_ACK_Timeout;
75 extern double   g_AckTimeout_Mins;
76 extern bool     g_bShowAreaNotices;
77 extern bool     g_bDrawAISSize;
78 extern bool     g_bDrawAISRealtime;
79 extern double   g_AIS_RealtPred_Kts;
80 extern bool     g_bShowAISName;
81 extern int      g_Show_Target_Name_Scale;
82 extern bool     g_bAllowShowScaled;
83 extern bool     g_bShowScaled;
84 extern bool     g_bInlandEcdis;
85 extern int      g_iSoundDeviceIndex;
86 extern bool     g_bWplIsAprsPosition;
87 extern double gLat;
88 extern double gLon;
89 extern double gCog;
90 extern double gSog;
91 extern double gHdt;
92 extern bool g_bAIS_CPA_Alert;
93 extern bool g_bAIS_CPA_Alert_Audio;
94 extern ArrayOfMMSIProperties   g_MMSI_Props_Array;
95 extern Route    *pAISMOBRoute;
96 extern wxString AISTargetNameFileName;
97 extern MyConfig *pConfig;
98 extern TrackList *pTrackList;
99 extern OCPNPlatform     *g_Platform;
100 extern PlugInManager             *g_pi_manager;
101 extern Multiplexer      *g_pMUX;
102 extern AIS_Decoder      *g_pAIS;
103 
104 extern wxString g_CmdSoundString;
105 
106 bool g_benableAISNameCache;
107 bool g_bUseOnlyConfirmedAISName;
108 wxString GetShipNameFromFile(int);
109 
110 wxDEFINE_EVENT(SOUND_PLAYED_EVTYPE, wxCommandEvent);
111 
112 BEGIN_EVENT_TABLE(AIS_Decoder, wxEvtHandler)
113     EVT_TIMER(TIMER_AIS1, AIS_Decoder::OnTimerAIS)
114     EVT_TIMER(TIMER_DSC, AIS_Decoder::OnTimerDSC)
115     EVT_COMMAND(wxID_ANY, SOUND_PLAYED_EVTYPE, AIS_Decoder::OnSoundFinishedAISAudio)
116 END_EVENT_TABLE()
117 
118 static int n_msgs;
119 static int n_msg1;
120 static int n_msg5;
121 static int n_msg24;
122 static bool b_firstrx;
123 static int first_rx_ticks;
124 static int rx_ticks;
125 static double arpa_ref_hdg = NAN;
126 
127 extern  const wxEventType wxEVT_OCPN_DATASTREAM;
128 extern int              gps_watchdog_timeout_ticks;
129 extern bool g_bquiting;
130 
onSoundFinished(void * ptr)131 static void onSoundFinished(void* ptr)
132 {
133     if (!g_bquiting) {
134         //auto aisDecoder = static_cast<AIS_Decoder*>(ptr);
135         wxCommandEvent ev(SOUND_PLAYED_EVTYPE);
136         wxPostEvent(g_pAIS, ev);
137     }
138 }
139 
140 void AISshipNameCache(AIS_Target_Data *pTargetData,
141              AIS_Target_Name_Hash *AISTargetNamesC,
142              AIS_Target_Name_Hash *AISTargetNamesNC,
143              long mmsi );
144 
AIS_Decoder(wxFrame * parent)145 AIS_Decoder::AIS_Decoder( wxFrame *parent )
146         : m_signalk_selfid(""),
147         AISTargetList(new AIS_Target_Hash())
148 {
149     AISTargetList = new AIS_Target_Hash;
150 
151     // Load cached AIS target names from a file
152     AISTargetNamesC = new AIS_Target_Name_Hash;
153     AISTargetNamesNC = new AIS_Target_Name_Hash;
154 
155     if(g_benableAISNameCache){
156         wxTextFile  infile;
157         if ( infile.Open(AISTargetNameFileName) ){
158             AIS_Target_Name_Hash *HashFile = AISTargetNamesNC;
159             wxString line = infile.GetFirstLine();
160             while ( !infile.Eof() ) {
161                 if( line.IsSameAs( wxT("+++==Confirmed Entry's==+++") ) )
162                     HashFile = AISTargetNamesC;
163                 else {
164                     if( line.IsSameAs( wxT("+++==Non Confirmed Entry's==+++") ) )
165                         HashFile = AISTargetNamesNC;
166                     else{
167                         wxStringTokenizer tokenizer( line, _T(",") );
168                         int mmsi = wxAtoi( tokenizer.GetNextToken() );
169                         wxString name = tokenizer.GetNextToken().Trim();
170                         ( *HashFile )[mmsi] = name;
171                     }
172                 }
173                 line = infile.GetNextLine();
174             }
175         }
176         infile.Close();
177     }
178 
179     AIS_AreaNotice_Sources = new AIS_Target_Hash;
180     BuildERIShipTypeHash();
181 
182     g_pais_alert_dialog_active = NULL;
183     m_bAIS_Audio_Alert_On = false;
184     m_AIS_Sound = 0;
185 
186     m_n_targets = 0;
187 
188     m_parent_frame = parent;
189 
190     m_bAIS_AlertPlaying = false;
191 
192     TimerAIS.SetOwner(this, TIMER_AIS1);
193     TimerAIS.Start(TIMER_AIS_MSEC,wxTIMER_CONTINUOUS);
194 
195     m_ptentative_dsctarget = NULL;
196     m_dsc_timer.SetOwner( this, TIMER_DSC );
197 
198 
199     //  Create/connect a dynamic event handler slot for wxEVT_OCPN_DATASTREAM(s)
200     Connect(wxEVT_OCPN_DATASTREAM, (wxObjectEventFunction)(wxEventFunction)&AIS_Decoder::OnEvtAIS);
201     Connect( EVT_OCPN_SIGNALKSTREAM, (wxObjectEventFunction) (wxEventFunction) &AIS_Decoder::OnEvtSignalK );
202 }
203 
~AIS_Decoder(void)204 AIS_Decoder::~AIS_Decoder( void )
205 {
206     AIS_Target_Hash::iterator it;
207     AIS_Target_Hash *current_targets = GetTargetList();
208 
209     for( it = ( *current_targets ).begin(); it != ( *current_targets ).end(); ++it ) {
210         AIS_Target_Data *td = it->second;
211 
212         delete td;
213     }
214 
215     delete AISTargetList;
216 
217     delete AIS_AreaNotice_Sources;
218 
219     //Write mmsi-shipsname to file in a safe way
220     wxTempFile outfile;
221     if ( outfile.Open(AISTargetNameFileName) )
222     {
223         wxString content;
224         content = wxT("+++==Confirmed Entry's==+++");
225         AIS_Target_Name_Hash::iterator it;
226         for ( it = AISTargetNamesC->begin(); it != AISTargetNamesC->end(); ++it )
227                 {
228                     content.append(_T("\r\n") );
229                     content.append( wxString::Format(wxT("%i"),it->first ) );
230                     content.append( _T(",") ).append(it->second );
231                 }
232             content.append( _T("\r\n"));
233             content.append( _T("+++==Non Confirmed Entry's==+++") );
234         for ( it = AISTargetNamesNC->begin(); it != AISTargetNamesNC->end(); ++it )
235                 {
236                     content.append(_T("\r\n") );
237                     content.append( wxString::Format(wxT("%i"),it->first ) );
238                     content.append( _T(",") ).append(it->second );
239                 }
240         outfile.Write( content );
241         outfile.Commit();
242     }
243 
244 
245     AISTargetNamesC->clear();
246     delete AISTargetNamesC;
247     AISTargetNamesNC->clear();
248     delete AISTargetNamesNC;
249 
250     clear_hash_ERI();
251 
252     m_dsc_timer.Stop();
253     m_AIS_Audio_Alert_Timer.Stop();
254     TimerAIS.Stop();
255 
256 #ifdef AIS_DEBUG
257     printf("First message[1, 2] ticks: %d  Last Message [1,2]ticks %d  Difference:  %d\n", first_rx_ticks, rx_ticks, rx_ticks - first_rx_ticks);
258 #endif
259 }
260 
BuildERIShipTypeHash(void)261 void AIS_Decoder::BuildERIShipTypeHash(void)
262 {
263       make_hash_ERI(8000, _("Vessel, type unknown"));
264       make_hash_ERI(8150, _("Freightbarge"));
265       make_hash_ERI(8160, _("Tankbarge"));
266       make_hash_ERI(8163, _("Tankbarge, dry cargo as if liquid (e.g. cement)"));
267       make_hash_ERI(8450, _("Service vessel, police patrol, port service"));
268       make_hash_ERI(8430, _("Pushboat, single"));
269       make_hash_ERI(8510, _("Object, not otherwise specified"));
270       make_hash_ERI(8470, _("Object, towed, not otherwise specified"));
271       make_hash_ERI(8490, _("Bunkership"));
272       make_hash_ERI(8010, _("Motor freighter"));
273       make_hash_ERI(8020, _("Motor tanker"));
274       make_hash_ERI(8021, _("Motor tanker, liquid cargo, type N"));
275       make_hash_ERI(8022, _("Motor tanker, liquid cargo, type C"));
276       make_hash_ERI(8023, _("Motor tanker, dry cargo as if liquid (e.g. cement)"));
277       make_hash_ERI(8030, _("Container vessel"));
278       make_hash_ERI(8040, _("Gas tanker"));
279       make_hash_ERI(8050, _("Motor freighter, tug"));
280       make_hash_ERI(8060, _("Motor tanker, tug"));
281       make_hash_ERI(8070, _("Motor freighter with one or more ships alongside"));
282       make_hash_ERI(8080, _("Motor freighter with tanker"));
283       make_hash_ERI(8090, _("Motor freighter pushing one or more freighters"));
284       make_hash_ERI(8100, _("Motor freighter pushing at least one tank-ship"));
285       make_hash_ERI(8110, _("Tug, freighter"));
286       make_hash_ERI(8120, _("Tug, tanker"));
287       make_hash_ERI(8130, _("Tug freighter, coupled"));
288       make_hash_ERI(8140, _("Tug, freighter/tanker, coupled"));
289       make_hash_ERI(8161, _("Tankbarge, liquid cargo, type N"));
290       make_hash_ERI(8162, _("Tankbarge, liquid cargo, type C"));
291       make_hash_ERI(8170, _("Freightbarge with containers"));
292       make_hash_ERI(8180, _("Tankbarge, gas"));
293       make_hash_ERI(8210, _("Pushtow, one cargo barge"));
294       make_hash_ERI(8220, _("Pushtow, two cargo barges"));
295       make_hash_ERI(8230, _("Pushtow, three cargo barges"));
296       make_hash_ERI(8240, _("Pushtow, four cargo barges"));
297       make_hash_ERI(8250, _("Pushtow, five cargo barges"));
298       make_hash_ERI(8260, _("Pushtow, six cargo barges"));
299       make_hash_ERI(8270, _("Pushtow, seven cargo barges"));
300       make_hash_ERI(8280, _("Pushtow, eight cargo barges"));
301       make_hash_ERI(8290, _("Pushtow, nine or more barges"));
302       make_hash_ERI(8310, _("Pushtow, one tank/gas barge"));
303       make_hash_ERI(8320, _("Pushtow, two barges at least one tanker or gas barge"));
304       make_hash_ERI(8330, _("Pushtow, three barges at least one tanker or gas barge"));
305       make_hash_ERI(8340, _("Pushtow, four barges at least one tanker or gas barge"));
306       make_hash_ERI(8350, _("Pushtow, five barges at least one tanker or gas barge"));
307       make_hash_ERI(8360, _("Pushtow, six barges at least one tanker or gas barge"));
308       make_hash_ERI(8370, _("Pushtow, seven barges at least one tanker or gas barge"));
309       make_hash_ERI(8380, _("Pushtow, eight barges at least one tanker or gas barge"));
310       make_hash_ERI(8390, _("Pushtow, nine or more barges at least one tanker or gas barge"));
311       make_hash_ERI(8400, _("Tug, single"));
312       make_hash_ERI(8410, _("Tug, one or more tows"));
313       make_hash_ERI(8420, _("Tug, assisting a vessel or linked combination"));
314       make_hash_ERI(8430, _("Pushboat, single"));
315       make_hash_ERI(8440, _("Passenger ship, ferry, cruise ship, red cross ship"));
316       make_hash_ERI(8441, _("Ferry"));
317       make_hash_ERI(8442, _("Red cross ship"));
318       make_hash_ERI(8443, _("Cruise ship"));
319       make_hash_ERI(8444, _("Passenger ship without accommodation"));
320       make_hash_ERI(8460, _("Vessel, work maintenance craft, floating derrick, cable-ship, buoy-ship, dredge"));
321       make_hash_ERI(8480, _("Fishing boat"));
322       make_hash_ERI(8500, _("Barge, tanker, chemical"));
323       make_hash_ERI(1500, _("General cargo Vessel maritime"));
324       make_hash_ERI(1510, _("Unit carrier maritime"));
325       make_hash_ERI(1520, _("Bulk carrier maritime"));
326       make_hash_ERI(1530, _("Tanker"));
327       make_hash_ERI(1540, _("Liquified gas tanker"));
328       make_hash_ERI(1850, _("Pleasure craft, longer than 20 metres"));
329       make_hash_ERI(1900, _("Fast ship"));
330       make_hash_ERI(1910, _("Hydrofoil"));
331 }
332 
333 //----------------------------------------------------------------------------------
334 //     Handle events from AIS DataStream
335 //----------------------------------------------------------------------------------
OnEvtAIS(OCPN_DataStreamEvent & event)336 void AIS_Decoder::OnEvtAIS( OCPN_DataStreamEvent& event )
337 {
338     wxString message = event.ProcessNMEA4Tags();
339 
340     int nr = 0;
341     if( !message.IsEmpty() )
342     {
343         if( message.Mid( 3, 3 ).IsSameAs( _T("VDM") ) ||
344             message.Mid( 3, 3 ).IsSameAs( _T("VDO") ) ||
345             message.Mid( 1, 5 ).IsSameAs( _T("FRPOS") ) ||
346             message.Mid( 1, 2 ).IsSameAs( _T("CD") ) ||
347             message.Mid( 3, 3 ).IsSameAs( _T("TLL") ) ||
348             message.Mid( 3, 3 ).IsSameAs( _T("TTM") ) ||
349             message.Mid( 3, 3 ).IsSameAs( _T("OSD") ) ||
350             ( g_bWplIsAprsPosition && message.Mid( 3, 3 ).IsSameAs( _T("WPL") ) ) )
351         {
352                 nr = Decode( message );
353                 if( nr == AIS_NoError ) {
354                     g_pi_manager->SendAISSentenceToAllPlugIns(message);
355                 }
356                 gFrame->TouchAISActive();
357         }
358     }
359 }
360 
361 //----------------------------------------------------------------------------------
362 //     Handle events from SignalK DataStream
363 //----------------------------------------------------------------------------------
OnEvtSignalK(OCPN_SignalKEvent & event)364 void AIS_Decoder::OnEvtSignalK(OCPN_SignalKEvent &event)
365 {
366     wxJSONReader jsonReader;
367     wxJSONValue root;
368 
369     std::string msgTerminated = event.GetString();
370     msgTerminated.append("\r\n");
371 
372     int errors = jsonReader.Parse(msgTerminated, &root);
373     if(errors > 0)
374         return;
375 
376     if(root.HasMember(_T("self"))) {
377         //m_signalk_selfid = _T("vessels.") + (root["self"].AsString());
378         m_signalk_selfid = (root["self"].AsString());           // Verified for OpenPlotter node.js server 1.20
379     }
380     if(m_signalk_selfid.IsEmpty()) {
381         return; // Don't handle any messages (with out self) until we know how we are
382     }
383     long mmsi = 0;
384     if(root.HasMember(_T("context"))
385        && root[_T("context")].IsString()) {
386         auto context = root[_T("context")].AsString();
387         if (context == m_signalk_selfid) {
388 #if 0
389             wxLogMessage(_T("** Ignore context own ship.."));
390 #endif
391             return;
392         }
393         wxString mmsi_string;
394         if(context.StartsWith(_T("vessels.urn:mrn:imo:mmsi:"), &mmsi_string) ||
395             context.StartsWith(_T("atons.urn:mrn:imo:mmsi:"), &mmsi_string) ||
396             context.StartsWith(_T("aircraft.urn:mrn:imo:mmsi:"), &mmsi_string) ) {
397         //    wxLogMessage(wxString::Format(_T("Context: %s, %s"), context.c_str(), mmsi_string));
398             if(mmsi_string.ToLong(&mmsi)) {
399                 //wxLogMessage(_T("Got MMSI from context."));
400             } else {
401                 mmsi = 0;
402             }
403         }
404     }
405     if(mmsi == 0) {
406         return; // Only handle ships with MMSI for now
407     }
408 #if 0
409     wxString dbg;
410     wxJSONWriter writer;
411     writer.Write(root, dbg);
412 
413     wxString msg( _T("AIS_Decoder::OnEvtSignalK: ") );
414     msg.append(dbg);
415     wxLogMessage(msg);
416 #endif
417     AIS_Target_Data *pTargetData = 0;
418     AIS_Target_Data *pStaleTarget = NULL;
419     bool bnewtarget = false;
420     int last_report_ticks;
421     wxDateTime now;
422     getAISTarget(mmsi, pTargetData, pStaleTarget, bnewtarget, last_report_ticks, now);
423     if(pTargetData) {
424         if (root.HasMember(_T("updates"))
425             && root[_T("updates")].IsArray()) {
426             wxJSONValue &updates = root[_T("updates")];
427             for (int i = 0; i < updates.Size(); ++i) {
428                 handleUpdate(pTargetData, bnewtarget, updates[i]);
429             }
430         }
431         pTargetData->MMSI = mmsi;
432         // A SART can send wo any values first transmits. Detect class already here.
433         if ( 97 == mmsi / 10000000 ) { pTargetData->Class = AIS_SART; }
434         pTargetData->b_OwnShip = false;
435         ( *AISTargetList )[pTargetData->MMSI] = pTargetData;
436     }
437 }
438 
handleUpdate(AIS_Target_Data * pTargetData,bool bnewtarget,wxJSONValue & update)439 void AIS_Decoder::handleUpdate(AIS_Target_Data *pTargetData,
440         bool bnewtarget,
441         wxJSONValue &update)
442 {
443     wxString sfixtime = "";
444 
445     if(update.HasMember(_T("timestamp"))) {
446         sfixtime = update[_T("timestamp")].AsString();
447     }
448     if(update.HasMember(_T("values"))
449        && update[_T("values")].IsArray())
450     {
451         for (int j = 0; j < update[_T("values")].Size(); ++j) {
452             wxJSONValue &item = update[_T("values")][j];
453             updateItem(pTargetData, bnewtarget, item, sfixtime);
454         }
455     }
456     wxDateTime now = wxDateTime::Now();
457     pTargetData->m_utc_hour = now.ToUTC().GetHour();
458     pTargetData->m_utc_min = now.ToUTC().GetMinute();
459     pTargetData->m_utc_sec = now.ToUTC().GetSecond();
460     // pTargetData->NavStatus = 15; // undefined
461     pTargetData->b_active = true;
462     pTargetData->b_lost = false;
463 
464     if( pTargetData->b_positionOnceValid ) {
465         long mmsi_long = pTargetData->MMSI;
466         SelectItem *pSel = pSelectAIS->AddSelectablePoint( pTargetData->Lat,
467                                                            pTargetData->Lon,
468                                                            (void *) mmsi_long,
469                                                            SELTYPE_AISTARGET );
470         pSel->SetUserData( pTargetData->MMSI );
471     }
472     UpdateOneCPA(pTargetData);
473     if( pTargetData->b_show_track )
474         UpdateOneTrack( pTargetData );
475 
476 }
477 
updateItem(AIS_Target_Data * pTargetData,bool bnewtarget,wxJSONValue & item,wxString & sfixtime) const478 void AIS_Decoder::updateItem(AIS_Target_Data *pTargetData,
479                              bool bnewtarget,
480                              wxJSONValue &item,
481                              wxString &sfixtime) const
482 {
483     if (item.HasMember(_T("path"))
484         && item.HasMember(_T("value"))) {
485 
486         const wxString &update_path = item[_T("path")].AsString();
487         wxJSONValue &value = item[_T("value")];
488         if (update_path == _T("navigation.position")) {
489             if (value.HasMember(_T("latitude"))
490                 && value.HasMember(_T("longitude"))) {
491                 wxDateTime now = wxDateTime::Now();
492                 now.MakeUTC();
493                 double lat = value[_T("latitude")].AsDouble();
494                 double lon = value[_T("longitude")].AsDouble();
495                 pTargetData->PositionReportTicks = now.GetTicks();
496                 pTargetData->StaticReportTicks = now.GetTicks();
497                 pTargetData->Lat = lat;
498                 pTargetData->Lon = lon;
499                 pTargetData->b_positionOnceValid = true;
500                 pTargetData->b_positionDoubtful = false;
501             }
502 
503             if ( value.HasMember(_T("altitude")) ) {
504                 pTargetData->altitude = value[_T("altitude ")].AsInt(); }
505         } else if (update_path == _T("navigation.speedOverGround")) {
506             pTargetData->SOG = value.AsDouble() * ms_to_knot_factor;
507         } else if (update_path == _T("navigation.courseOverGroundTrue")) {
508             pTargetData->COG = GEODESIC_RAD2DEG(value.AsDouble());
509         } else if (update_path == _T("navigation.headingTrue")) {
510             pTargetData->HDG = GEODESIC_RAD2DEG(value.AsDouble());
511         } else if (update_path == _T("navigation.rateOfTurn")) {
512             pTargetData->ROTAIS = 4.733*sqrt(value.AsDouble());
513         } else if (update_path == _T("design.aisShipType")) {
514             if (value.HasMember(_T("id"))) {
515                 pTargetData->ShipType = value[_T("id")].AsUInt();
516             }
517         } else if (update_path == _T("atonType")) {
518             if (value.HasMember(_T("id"))) {
519                 pTargetData->ShipType = value[_T("id")].AsUInt();
520             }
521         } else if (update_path == _T("virtual")) {
522             if (_T("true") == value.AsString()) {
523                 pTargetData->NavStatus = ATON_VIRTUAL; }
524             else { pTargetData->NavStatus = ATON_REAL; }
525         } else if (update_path == _T("offPosition")) {
526             if (_T("true") == value.AsString()) {
527                 if (ATON_REAL == pTargetData->NavStatus) {
528                     pTargetData->NavStatus = ATON_REAL_OFFPOSITION;
529                 }
530                 else if (ATON_VIRTUAL == pTargetData->NavStatus) {
531                     pTargetData->NavStatus = ATON_VIRTUAL_OFFPOSITION;
532                 }
533             }
534         } else if (update_path == _T("design.draft")) {
535             if (value.HasMember(_T("maximum"))) {
536                 pTargetData->Draft = value[_T("maximum")].AsDouble();
537                 pTargetData->Euro_Draft = value[_T("maximum")].AsDouble();
538             }
539             if (value.HasMember(_T("current"))) {
540                 double draft = value[_T("current")].AsDouble();
541                 if (draft > 0) {
542                     pTargetData->Draft = draft;
543                     pTargetData->Euro_Draft = draft;
544                 }
545             }
546         } else if (update_path == _T("design.length")) {
547             if (pTargetData->DimB == 0) {
548                 if (value.HasMember(_T("overall"))) {
549                     pTargetData->Euro_Length = value[_T("overall")].AsDouble();
550                     pTargetData->DimA = value[_T("overall")].AsInt();
551                     pTargetData->DimB = 0;
552                 }
553             }
554         } else if (update_path == _T("sensors.ais.class")) {
555             auto aisclass = value.AsString();
556             if (aisclass == _T("A") ) { pTargetData->Class = AIS_CLASS_A; }
557             else if (aisclass == _T("B")) {
558                 pTargetData->Class = AIS_CLASS_B;
559                 pTargetData->NavStatus = UNDEFINED; // Class B targets have no status.  Enforce this...
560             }
561             else if (aisclass == _T("BASE")) { pTargetData->Class = AIS_BASE; }
562             else if (aisclass == _T("ATON")) { pTargetData->Class = AIS_ATON; }
563         } else if (update_path == _T("sensors.ais.fromBow")) {
564             if(pTargetData->DimB == 0 && pTargetData->DimA != 0) {
565                 int length = pTargetData->DimA;
566                 pTargetData->DimA = value.AsInt();
567                 pTargetData->DimB = length - value.AsInt();
568             }
569         } else if (update_path == _T("design.beam")) {
570             if (pTargetData->DimD == 0) {
571                 pTargetData->Euro_Beam = value.AsDouble();
572                 pTargetData->DimC = value.AsInt();
573                 pTargetData->DimD = 0;
574             }
575         } else if (update_path == _T("sensors.ais.fromCenter")) {
576             if(pTargetData->DimD == 0 && pTargetData->DimC != 0) {
577                 int beam = pTargetData->DimC;
578                 int center = beam / 2;
579                 pTargetData->DimC = center + value.AsInt();
580                 pTargetData->DimD = beam - pTargetData->DimC;
581             }
582         } else if (update_path == _T("navigation.state")) {
583             auto state = value.AsString();
584             if (state == _T("motoring")) { pTargetData->NavStatus = UNDERWAY_USING_ENGINE; }
585             else if (state == _T("anchored")) { pTargetData->NavStatus = AT_ANCHOR; }
586             else if (state == _T("not under command")) { pTargetData->NavStatus = NOT_UNDER_COMMAND; }
587             else if (state == _T("restricted manouverability")) { pTargetData->NavStatus = RESTRICTED_MANOEUVRABILITY; }
588             else if (state == _T("constrained by draft")) { pTargetData->NavStatus = CONSTRAINED_BY_DRAFT; }
589             else if (state == _T("moored")) { pTargetData->NavStatus = MOORED; }
590             else if (state == _T("aground")) { pTargetData->NavStatus = AGROUND; }
591             else if (state == _T("fishing")) { pTargetData->NavStatus = FISHING; }
592             else if (state == _T("sailing")) { pTargetData->NavStatus = UNDERWAY_SAILING; }
593             else if (state == _T("hazardous material high speed")) { pTargetData->NavStatus = HSC; }
594             else if (state == _T("hazardous material wing in ground")) { pTargetData->NavStatus = WIG; }
595             else if (state == _T("ais-sart")) { pTargetData->NavStatus = RESERVED_14; }
596             else { pTargetData->NavStatus = UNDEFINED; }
597         } else if (update_path == _T("navigation.destination.commonName")) {
598             const wxString &destination = value.AsString();
599             strncpy(pTargetData->Destination,
600                 destination.c_str(), 20);
601         } else if (update_path == _T("navigation.specialManeuver")) {
602             if (_T("not available") != value.AsString() && pTargetData->IMO < 1) {
603                 const wxString &bluesign = value.AsString();
604                 if ( _T("not engaged")== bluesign){
605                     pTargetData->blue_paddle = 1;
606                 }
607                 if (_T("engaged") == bluesign) {
608                     pTargetData->blue_paddle = 2;
609                 }
610                 pTargetData->b_blue_paddle = pTargetData->blue_paddle == 2 ? true: false;
611             }
612         } else if (update_path == _T("sensors.ais.designatedAreaCode")) {
613             if (value.AsInt() == 200) { pTargetData->b_hasInlandDac = true; } // European inland
614         } else if (update_path == _T("sensors.ais.functionalId")) {
615             if (value.AsInt() == 10 &&  // "Inland ship static and voyage related data"
616                 pTargetData->b_hasInlandDac) {
617                 pTargetData->b_isEuroInland = true;
618             }
619         } else if (update_path == _T("")) {
620             if(value.HasMember(_T("name"))) {
621                 const wxString &name = value[_T("name")].AsString();
622                 strncpy(pTargetData->ShipName, name.c_str(), 20 );
623                 pTargetData->b_nameValid = true;
624                 pTargetData->MID = 123; // Indicates a name from SignalK
625             } else if (value.HasMember(_T("registrations"))) {
626                 const wxString &imo = value[_T("registrations")][_T("imo")].AsString();
627                 pTargetData->IMO = wxAtoi(imo.Right(7));
628             } else if (value.HasMember(_T("communication"))) {
629                 const wxString &callsign = value[_T("communication")][_T("callsignVhf")].AsString();
630                 strncpy(pTargetData->CallSign, callsign.c_str(), 7);
631             }
632             if(value.HasMember("mmsi")) {
633                 long mmsi;
634                 if (value[_T("mmsi")].AsString().ToLong(&mmsi)) {
635                     pTargetData->MMSI = mmsi;
636 
637                     if (97 == mmsi / 10000000) {
638                         pTargetData->Class = AIS_SART;
639                     }
640                     if (111 == mmsi / 1000000) {
641                         pTargetData->b_SarAircraftPosnReport = true;
642                     }
643 
644                     AISshipNameCache(pTargetData, AISTargetNamesC, AISTargetNamesNC, mmsi);
645                 }
646             }
647         } else {
648             wxLogMessage(wxString::Format(_T("** AIS_Decoder::updateItem: unhandled path %s"), update_path));
649 #if 1
650             wxString dbg;
651             wxJSONWriter writer;
652             writer.Write(item, dbg);
653             wxString msg( _T("update: ") );
654             msg.append(dbg);
655             wxLogMessage(msg);
656 #endif
657 
658         }
659     }
660 }
661 
662 //----------------------------------------------------------------------------------
663 //      Decode a single AIVDO sentence to a Generic Position Report
664 //----------------------------------------------------------------------------------
DecodeSingleVDO(const wxString & str,GenericPosDatEx * pos,wxString * accumulator)665 AIS_Error AIS_Decoder::DecodeSingleVDO( const wxString& str, GenericPosDatEx *pos, wxString *accumulator )
666 {
667     //  Make some simple tests for validity
668     if( str.Len() > 100 )
669         return AIS_NMEAVDX_TOO_LONG;
670 
671     if( !NMEACheckSumOK( str ) )
672         return AIS_NMEAVDX_CHECKSUM_BAD;
673 
674     if( !pos )
675         return AIS_GENERIC_ERROR;
676 
677     if( !accumulator )
678         return AIS_GENERIC_ERROR;
679 
680     //  We only process AIVDO messages
681     if( !str.Mid( 1, 5 ).IsSameAs( _T("AIVDO") ) )
682         return AIS_GENERIC_ERROR;
683 
684     //  Use a tokenizer to pull out the first 4 fields
685     wxStringTokenizer tkz( str, _T(",") );
686 
687     wxString token;
688     token = tkz.GetNextToken();         // !xxVDx
689 
690     token = tkz.GetNextToken();
691     int nsentences = atoi( token.mb_str() );
692 
693     token = tkz.GetNextToken();
694     int isentence = atoi( token.mb_str() );
695 
696     token = tkz.GetNextToken();         // skip 2 fields
697     token = tkz.GetNextToken();
698 
699     wxString string_to_parse;
700     string_to_parse.Clear();
701 
702     //  Fill the output structure with all NANs
703     pos->kLat = NAN;
704     pos->kLon = NAN;
705     pos->kCog = NAN;
706     pos->kSog = NAN;
707     pos->kHdt = NAN;
708     pos->kVar = NAN;
709     pos->kHdm = NAN;
710 
711     //  Simple case first
712     //  First and only part of a one-part sentence
713     if( ( 1 == nsentences ) && ( 1 == isentence ) ) {
714         string_to_parse = tkz.GetNextToken();         // the encapsulated data
715     }
716 
717     else if( nsentences > 1 ) {
718         if( 1 == isentence ) {
719             *accumulator = tkz.GetNextToken();         // the encapsulated data
720         }
721 
722         else {
723             accumulator->Append(tkz.GetNextToken() );
724         }
725 
726         if( isentence == nsentences ) {         // ready to parse
727             string_to_parse = *accumulator;
728         }
729     }
730 
731     if( string_to_parse.IsEmpty() && (nsentences > 1) ) {      // not ready, so return with NAN
732         return AIS_INCOMPLETE_MULTIPART;                       // and non-zero return
733     }
734 
735 
736     //  Create the bit accessible string
737     AIS_Bitstring strbit( string_to_parse.mb_str() );
738 
739     AIS_Target_Data TargetData;
740 
741     bool bdecode_result = Parse_VDXBitstring( &strbit, &TargetData );
742 
743     if(bdecode_result) {
744         switch(TargetData.MID)
745         {
746             case 1:
747             case 2:
748             case 3:
749             case 18:
750             {
751                 if( !TargetData.b_positionDoubtful ) {
752                     pos->kLat = TargetData.Lat;
753                     pos->kLon = TargetData.Lon;
754                 }
755                 else {
756                     pos->kLat = NAN;
757                     pos->kLon = NAN;
758                 }
759 
760                 if(TargetData.COG == 360.0)
761                     pos->kCog = NAN;
762                 else
763                     pos->kCog = TargetData.COG;
764 
765 
766                 if(TargetData.SOG > 102.2)
767                     pos->kSog = NAN;
768                 else
769                     pos->kSog = TargetData.SOG;
770 
771                 if((int)TargetData.HDG == 511)
772                     pos->kHdt = NAN;
773                 else
774                     pos->kHdt = TargetData.HDG;
775 
776                 //  VDO messages do not contain variation or magnetic heading
777                 pos->kVar = NAN;
778                 pos->kHdm = NAN;
779                 break;
780             }
781             default:
782                 return AIS_GENERIC_ERROR;       // unrecognised sentence
783         }
784 
785         return AIS_NoError;
786     }
787     else
788         return AIS_GENERIC_ERROR;
789 }
790 
791 
792 //----------------------------------------------------------------------------------------
793 //      Decode NMEA VDM/VDO/FRPOS/DSCDSE/TTM/TLL/OSD/RSD/TLB/WPL sentence to AIS Target(s)
794 //----------------------------------------------------------------------------------------
Decode(const wxString & str)795 AIS_Error AIS_Decoder::Decode( const wxString& str )
796 {
797     AIS_Error ret = AIS_GENERIC_ERROR;
798     wxString string_to_parse;
799 
800     double gpsg_lat, gpsg_lon, gpsg_mins, gpsg_degs;
801     double gpsg_cog, gpsg_sog, gpsg_utc_time;
802     int gpsg_utc_hour = 0;
803     int gpsg_utc_min = 0;
804     int gpsg_utc_sec = 0;
805     char gpsg_name_str[21];
806     wxString gpsg_date;
807 
808     bool bdecode_result = false;
809 
810     int gpsg_mmsi = 0;
811     int arpa_mmsi = 0;
812     int aprs_mmsi = 0;
813     int mmsi = 0;
814 
815     long arpa_tgt_num = 0;
816     double arpa_sog = 0.;
817     double arpa_cog = 0.;
818     double arpa_lat = 0.;
819     double arpa_lon = 0.;
820     double arpa_dist = 0.;
821     double arpa_brg = 0.;
822     wxString arpa_brgunit;
823     wxString arpa_status;
824     wxString arpa_distunit;
825     wxString arpa_cogunit;
826     wxString arpa_reftarget;
827     double arpa_mins, arpa_degs;
828     double arpa_utc_time;
829     int arpa_utc_hour = 0;
830     int arpa_utc_min = 0;
831     int arpa_utc_sec = 0;
832     char arpa_name_str[21];
833     bool arpa_lost = true;
834     bool arpa_nottracked = false;
835 
836     double aprs_lat = 0.;
837     double aprs_lon = 0.;
838     char aprs_name_str[21];
839     double aprs_mins, aprs_degs;
840 
841     AIS_Target_Data *pTargetData = 0;
842     AIS_Target_Data *pStaleTarget = NULL;
843     bool bnewtarget = false;
844     int last_report_ticks;
845 
846     //  Make some simple tests for validity
847 
848     if( str.Len() > 100 ) return AIS_NMEAVDX_TOO_LONG;
849 
850     if( !NMEACheckSumOK( str ) ) {
851             return AIS_NMEAVDX_CHECKSUM_BAD;
852     }
853     if( str.Mid( 1, 2 ).IsSameAs( _T("CD") ) ) {
854         ProcessDSx( str );
855         return AIS_NoError;
856     }
857     else if( str.Mid( 3, 3 ).IsSameAs( _T("TTM") ) ) {
858     //$--TTM,xx,x.x,x.x,a,x.x,x.x,a,x.x,x.x,a,c--c,a,a*hh <CR><LF>
859     //or
860     //$--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>
861         wxString string( str );
862         wxStringTokenizer tkz( string, _T(",*") );
863 
864         wxString token;
865         token = tkz.GetNextToken(); //Sentence (xxTTM)
866         token = tkz.GetNextToken(); //1) Target Number
867         token.ToLong( &arpa_tgt_num );
868         token = tkz.GetNextToken(); // 2)Target Distance
869         token.ToDouble( &arpa_dist );
870         token = tkz.GetNextToken(); //3) Bearing from own ship
871         token.ToDouble( &arpa_brg );
872         arpa_brgunit = tkz.GetNextToken(); //4) Bearing Units
873         if ( arpa_brgunit == _T("R") )
874         {
875             if ( std::isnan(arpa_ref_hdg) )
876             {
877                 if ( !std::isnan(gHdt) )
878                     arpa_brg += gHdt;
879                 else
880                     arpa_brg += gCog;
881             }
882             else
883                 arpa_brg += arpa_ref_hdg;
884             if ( arpa_brg >= 360. )
885                 arpa_brg -= 360.;
886         }
887         token = tkz.GetNextToken(); //5) Target speed
888         token.ToDouble( &arpa_sog );
889         token = tkz.GetNextToken(); //6) Target Course
890         token.ToDouble( &arpa_cog );
891         arpa_cogunit = tkz.GetNextToken(); //7) Course Units
892         if ( arpa_cogunit == _T("R") )
893         {
894             if ( std::isnan(arpa_ref_hdg) )
895             {
896                 if ( !std::isnan(gHdt) )
897                     arpa_cog += gHdt;
898                 else
899                     arpa_cog += gCog;
900             }
901             else
902                 arpa_cog += arpa_ref_hdg;
903             if ( arpa_cog >= 360. )
904                 arpa_cog -= 360.;
905         }
906         token = tkz.GetNextToken(); //8) Distance of closest-point-of-approach
907         token = tkz.GetNextToken(); //9) Time until closest-point-of-approach "-" means increasing
908         arpa_distunit = tkz.GetNextToken(); //10)Speed/ dist unit
909         token = tkz.GetNextToken(); //11) Target name
910         if ( token == wxEmptyString )
911             token = wxString::Format( _T("ARPA %d"), arpa_tgt_num );
912         int len = wxMin(token.Length(),20);
913         strncpy( arpa_name_str, token.mb_str(), len );
914         arpa_name_str[len] = 0;
915         arpa_status = tkz.GetNextToken(); //12) Target Status
916         if ( arpa_status != _T( "L" ) ) {
917             arpa_lost = false;
918         } else if ( arpa_status != wxEmptyString )
919             arpa_nottracked = true;
920         arpa_reftarget = tkz.GetNextToken(); //13) Reference Target
921         if ( tkz.HasMoreTokens() )
922         {
923             token = tkz.GetNextToken();
924             token.ToDouble( &arpa_utc_time );
925             arpa_utc_hour = (int) ( arpa_utc_time / 10000.0 );
926             arpa_utc_min = (int) ( arpa_utc_time / 100.0 ) - arpa_utc_hour * 100;
927             arpa_utc_sec = (int) arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
928         } else {
929             arpa_utc_hour = wxDateTime::Now().ToUTC().GetHour();
930             arpa_utc_min = wxDateTime::Now().ToUTC().GetMinute();
931             arpa_utc_sec = wxDateTime::Now().ToUTC().GetSecond();
932         }
933 
934         if( arpa_distunit == _T("K") )
935         {
936             arpa_dist = fromUsrDistance( arpa_dist, DISTANCE_KM );
937             arpa_sog = fromUsrSpeed(arpa_sog, SPEED_KMH);
938         } else if( arpa_distunit == _T("S") ) {
939             arpa_dist = fromUsrDistance( arpa_dist, DISTANCE_MI );
940             arpa_sog = fromUsrSpeed(arpa_sog, SPEED_MPH);
941         }
942 
943         mmsi = arpa_mmsi = 199200000 + arpa_tgt_num; // 199 is INMARSAT-A MID, should not occur ever in AIS stream + we make sure we are out of the hashes for GPSGate buddies by being above 1992*
944     } else if( str.Mid( 3, 3 ).IsSameAs( _T("TLL") ) ) {
945     //$--TLL,xx,llll.lll,a,yyyyy.yyy,a,c--c,hhmmss.ss,a,a*hh<CR><LF>
946     //"$RATLL,01,5603.370,N,01859.976,E,ALPHA,015200.36,T,*75\r\n"
947         wxString aprs_tll_str;
948         wxString string( str );
949         wxStringTokenizer tkz( string, _T(",*") );
950 
951         wxString token;
952         aprs_tll_str = tkz.GetNextToken(); //Sentence (xxTLL)
953         token = tkz.GetNextToken(); //1) Target number 00 - 99
954         token.ToLong( &arpa_tgt_num );
955         token = tkz.GetNextToken(); //2) Latitude, N/S
956         token.ToDouble( &arpa_lat );
957         arpa_degs = (int) ( arpa_lat / 100.0 );
958         arpa_mins = arpa_lat - arpa_degs * 100.0;
959         arpa_lat = arpa_degs + arpa_mins / 60.0;
960         token = tkz.GetNextToken(); // hemisphere N or S
961         if( token.Mid( 0, 1 ).Contains( _T("S") ) == true || token.Mid( 0, 1 ).Contains( _T("s") ) == true )
962             arpa_lat = 0. - arpa_lat;
963         token = tkz.GetNextToken(); //3) Longitude, E/W
964         token.ToDouble( &arpa_lon );
965         arpa_degs = (int) ( arpa_lon / 100.0 );
966         arpa_mins = arpa_lon - arpa_degs * 100.0;
967         arpa_lon = arpa_degs + arpa_mins / 60.0;
968         token = tkz.GetNextToken(); // hemisphere E or W
969         if( token.Mid( 0, 1 ).Contains( _T("W") ) == true || token.Mid( 0, 1 ).Contains( _T("w") ) == true )
970             arpa_lon = 0. - arpa_lon;
971         token = tkz.GetNextToken(); //4) Target name
972         if ( token == wxEmptyString )
973             token = wxString::Format( _T("ARPA %d"), arpa_tgt_num );
974         int len = wxMin(token.Length(),20);
975         strncpy( arpa_name_str, token.mb_str(), len );
976         arpa_name_str[len] = 0;
977         token = tkz.GetNextToken(); //5) UTC of data
978         token.ToDouble( &arpa_utc_time );
979         arpa_utc_hour = (int) ( arpa_utc_time / 10000.0 );
980         arpa_utc_min = (int) ( arpa_utc_time / 100.0 ) - arpa_utc_hour * 100;
981         arpa_utc_sec = (int) arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
982         arpa_status = tkz.GetNextToken(); //6) Target status: L = lost,tracked target has beenlost Q = query,target in the process of acquisition T = tracking
983         if( arpa_status != _T("L") )
984             arpa_lost = false;
985         else if ( arpa_status != wxEmptyString )
986             arpa_nottracked = true;
987         arpa_reftarget = tkz.GetNextToken(); //7) Reference target=R,null otherwise
988         mmsi = arpa_mmsi = 199200000 + arpa_tgt_num; // 199 is INMARSAT-A MID, should not occur ever in AIS stream + we make sure we are out of the hashes for GPSGate buddies by being above 1992*
989     } else if( str.Mid( 3, 3 ).IsSameAs( _T("OSD") ) ) {
990     //$--OSD,x.x,A,x.x,a,x.x,a,x.x,x.x,a*hh <CR><LF>
991         wxString string( str );
992         wxStringTokenizer tkz( string, _T(",*") );
993 
994         wxString token;
995         token = tkz.GetNextToken(); //Sentence (xxOSD)
996         token = tkz.GetNextToken(); //1) Heading (true)
997         token.ToDouble( &arpa_ref_hdg );
998         //2) speed
999         //3) Vessel Course, degrees True
1000         //4) Course Reference, B/M/W/R/P (see note)
1001         //5) Vessel Speed
1002         //6) Speed Reference, B/M/W/R/P (see note)
1003         //7) Vessel Set, degrees True - Manually entered
1004         //8) Vessel drift (speed) - Manually entered
1005         //9) Speed Units K = km/h; N = Knots; S = statute miles/h
1006     } else if( str.Mid( 3, 3 ).IsSameAs( _T("WPL") ) ) {
1007     //** $--WPL,llll.ll,a,yyyyy.yy,a,c--c*hh<CR><LF>
1008         wxString string( str );
1009         wxStringTokenizer tkz( string, _T(",*") );
1010 
1011         wxString token;
1012         token = tkz.GetNextToken(); //Sentence (xxWPL)
1013         token = tkz.GetNextToken(); //1) Latitude, N/S
1014         token.ToDouble( &aprs_lat );
1015         aprs_degs = (int) ( aprs_lat / 100.0 );
1016         aprs_mins = aprs_lat - aprs_degs * 100.0;
1017         aprs_lat = aprs_degs + aprs_mins / 60.0;
1018         token = tkz.GetNextToken();            //2) hemisphere N or S
1019         if( token.Mid( 0, 1 ).Contains( _T("S") ) == true || token.Mid( 0, 1 ).Contains( _T("s") ) == true )
1020             aprs_lat = 0. - aprs_lat;
1021         token = tkz.GetNextToken(); //3) Longitude, E/W
1022         token.ToDouble( &aprs_lon );
1023         aprs_degs = (int) ( aprs_lon / 100.0 );
1024         aprs_mins = aprs_lon - aprs_degs * 100.0;
1025         aprs_lon = aprs_degs + aprs_mins / 60.0;
1026         token = tkz.GetNextToken();            //4) hemisphere E or W
1027         if( token.Mid( 0, 1 ).Contains( _T("W") ) == true || token.Mid( 0, 1 ).Contains( _T("w") ) == true )
1028             aprs_lon = 0. - aprs_lon;
1029         token = tkz.GetNextToken(); //5) Target name
1030         int len = wxMin(token.Length(),20);
1031         int i, hash = 0;
1032         strncpy( aprs_name_str, token.mb_str(), len );
1033         aprs_name_str[len] = 0;
1034         hash = 0;
1035         for( i = 0; i < len; i++ ) {
1036             hash = hash * 10;
1037             hash += (int) ( aprs_name_str[i] );
1038             while( hash >= 100000 )
1039                 hash = hash / 100000;
1040         }
1041         mmsi = aprs_mmsi = 199300000 + hash; // 199 is INMARSAT-A MID, should not occur ever in AIS stream + we make sure we are out of the hashes for GPSGate buddies and ARPA by being above 1993*
1042     } else if( str.Mid( 1, 5 ).IsSameAs( _T("FRPOS") ) ) {
1043         // parse a GpsGate Position message            $FRPOS,.....
1044 
1045         //  Use a tokenizer to pull out the first 9 fields
1046         wxString string( str );
1047         wxStringTokenizer tkz( string, _T(",*") );
1048 
1049         wxString token;
1050         token = tkz.GetNextToken();         // !$FRPOS
1051 
1052         token = tkz.GetNextToken();            //    latitude DDMM.MMMM
1053         token.ToDouble( &gpsg_lat );
1054         gpsg_degs = (int) ( gpsg_lat / 100.0 );
1055         gpsg_mins = gpsg_lat - gpsg_degs * 100.0;
1056         gpsg_lat = gpsg_degs + gpsg_mins / 60.0;
1057 
1058         token = tkz.GetNextToken();            //  hemisphere N or S
1059         if( token.Mid( 0, 1 ).Contains( _T("S") ) == true || token.Mid( 0, 1 ).Contains( _T("s") ) == true )  gpsg_lat = 0. - gpsg_lat;
1060 
1061         token = tkz.GetNextToken();            // longitude DDDMM.MMMM
1062         token.ToDouble( &gpsg_lon );
1063         gpsg_degs = (int) ( gpsg_lon / 100.0 );
1064         gpsg_mins = gpsg_lon - gpsg_degs * 100.0;
1065         gpsg_lon = gpsg_degs + gpsg_mins / 60.0;
1066 
1067         token = tkz.GetNextToken();            // hemisphere E or W
1068         if( token.Mid( 0, 1 ).Contains( _T("W") ) == true || token.Mid( 0, 1 ).Contains( _T("w") ) == true ) gpsg_lon = 0. - gpsg_lon;
1069 
1070         token = tkz.GetNextToken();            //    altitude AA.a
1071         //    token.toDouble(&gpsg_alt);
1072 
1073         token = tkz.GetNextToken();            //  speed over ground SSS.SS knots
1074         token.ToDouble( &gpsg_sog );
1075 
1076         token = tkz.GetNextToken();            //  heading over ground HHH.hh degrees
1077         token.ToDouble( &gpsg_cog );
1078 
1079         token = tkz.GetNextToken();            // date DDMMYY
1080         gpsg_date = token;
1081 
1082         token = tkz.GetNextToken();            // time UTC hhmmss.dd
1083         token.ToDouble( &gpsg_utc_time );
1084         gpsg_utc_hour = (int) ( gpsg_utc_time / 10000.0 );
1085         gpsg_utc_min = (int) ( gpsg_utc_time / 100.0 ) - gpsg_utc_hour * 100;
1086         gpsg_utc_sec = (int) gpsg_utc_time - gpsg_utc_hour * 10000 - gpsg_utc_min * 100;
1087 
1088         // now comes the name, followed by in * and NMEA checksum
1089 
1090         token = tkz.GetNextToken();
1091         int i, len, hash = 0;
1092         len = wxMin(wxStrlen(token),20);
1093         strncpy( gpsg_name_str, token.mb_str(), len );
1094         gpsg_name_str[len] = 0;
1095         for( i = 0; i < len; i++ ) {
1096             hash = hash * 10;
1097             hash += (int) ( token[i] );
1098             while( hash >= 100000 )
1099                 hash = hash / 100000;
1100         }
1101         gpsg_mmsi = 199000000 + hash;  // 199 is INMARSAT-A MID, should not occur ever in AIS stream
1102         mmsi = gpsg_mmsi;
1103     } else if( !str.Mid( 3, 2 ).IsSameAs( _T("VD") ) ) {
1104         return AIS_NMEAVDX_BAD;
1105     }
1106 
1107     //  OK, looks like the sentence is OK
1108 
1109         //  Use a tokenizer to pull out the first 4 fields
1110         wxString string( str );
1111         wxStringTokenizer tkz( string, _T(",") );
1112 
1113         wxString token;
1114         token = tkz.GetNextToken();         // !xxVDx
1115 
1116         token = tkz.GetNextToken();
1117         nsentences = atoi( token.mb_str() );
1118 
1119         token = tkz.GetNextToken();
1120         isentence = atoi( token.mb_str() );
1121 
1122         token = tkz.GetNextToken();
1123         long lsequence_id = 0;
1124         token.ToLong( &lsequence_id );
1125 
1126         token = tkz.GetNextToken();
1127         long lchannel;
1128         token.ToLong( &lchannel );
1129         //  Now, some decisions
1130 
1131         string_to_parse.Clear();
1132 
1133         //  Simple case first
1134         //  First and only part of a one-part sentence
1135         if( ( 1 == nsentences ) && ( 1 == isentence ) ) {
1136             string_to_parse = tkz.GetNextToken();         // the encapsulated data
1137         }
1138 
1139         else if( nsentences > 1 ) {
1140             if( 1 == isentence ) {
1141                 sentence_accumulator = tkz.GetNextToken();         // the encapsulated data
1142             }
1143 
1144             else {
1145                 sentence_accumulator += tkz.GetNextToken();
1146             }
1147 
1148             if( isentence == nsentences ) {
1149                 string_to_parse = sentence_accumulator;
1150             }
1151         }
1152 
1153         if( mmsi || ( !string_to_parse.IsEmpty() && ( string_to_parse.Len() < AIS_MAX_MESSAGE_LEN ) ) ) {
1154 
1155             //  Create the bit accessible string
1156             wxCharBuffer abuf = string_to_parse.ToUTF8();
1157             if( !abuf.data() )                            // badly formed sentence?
1158                 return AIS_GENERIC_ERROR;
1159 
1160             AIS_Bitstring strbit( abuf.data() );
1161 
1162             //  Extract the MMSI
1163             if( !mmsi ) mmsi = strbit.GetInt( 9, 30 );
1164             long mmsi_long = mmsi;
1165             //  Search the current AISTargetList for an MMSI match
1166             AIS_Target_Hash::iterator it = AISTargetList->find( mmsi );
1167             if( it == AISTargetList->end() )                  // not found
1168                     {
1169                 pTargetData = new AIS_Target_Data;
1170                 bnewtarget = true;
1171                 m_n_targets++;
1172             } else {
1173                 pTargetData = it->second;          // find current entry
1174                 pStaleTarget = pTargetData;        // save a pointer to stale data
1175             }
1176             for(unsigned int i=0 ; i < g_MMSI_Props_Array.GetCount() ; i++){
1177                 MMSIProperties *props =  g_MMSI_Props_Array[i];
1178                 if(mmsi == props->MMSI){
1179                     // Check to see if this MMSI has been configured to be ignored completely...
1180                     if(props->m_bignore)
1181                         return AIS_NoError;
1182                     // Check to see if this MMSI wants VDM translated to VDO or whether we want to persist it's track...
1183                     else if (props->m_bVDM){
1184 
1185                         //Only single line VDM messages to be translated
1186                         if( str.Mid( 3, 9 ).IsSameAs( wxT("VDM,1,1,,") ) )
1187                         {
1188                             int message_ID = strbit.GetInt( 1, 6 );        // Parse on message ID
1189                             // Only translate the dynamic positionreport messages (1, 2, 3 or 18)
1190                             if ( (message_ID <= 3) || (message_ID == 18) )
1191                             {
1192                                 // set OwnShip to prevent target from being drawn
1193                                 pTargetData->b_OwnShip = true;
1194                                 //Rename nmea sentence to AIVDO and calc a new checksum
1195                                 wxString aivdostr = str;
1196                                 aivdostr.replace(1, 5, "AIVDO");
1197                                 unsigned char calculated_checksum = 0;
1198                                 wxString::iterator i;
1199                                 for( i = aivdostr.begin()+1; i != aivdostr.end() && *i != '*'; ++i)
1200                                     calculated_checksum ^= static_cast<unsigned char> (*i);
1201                                 // if i is not at least 3 positons befoere end, there is no checksum added
1202                                 // so also no need to add one now.
1203                                 if ( i <= aivdostr.end()-3 )
1204                                     aivdostr.replace( i+1, i+3, wxString::Format(_("%02X"), calculated_checksum));
1205 
1206                                 gps_watchdog_timeout_ticks = 60;  //increase watchdog time up to 1 minute
1207                                 //add the changed sentence into nmea stream
1208                                 OCPN_DataStreamEvent event( wxEVT_OCPN_DATASTREAM, 0 );
1209                                 std::string s = std::string( aivdostr.mb_str() );
1210                                 event.SetNMEAString( s );
1211                                 event.SetStream( NULL );
1212                                 g_pMUX->AddPendingEvent( event );
1213                             }
1214                         }
1215                         return AIS_NoError;
1216                     }
1217                     else
1218                         break;
1219                 }
1220             }
1221 
1222             //  Grab the stale targets's last report time
1223              wxDateTime now = wxDateTime::Now();
1224             now.MakeGMT();
1225 
1226             if( pStaleTarget )
1227                 last_report_ticks = pStaleTarget->PositionReportTicks;
1228             else
1229                 last_report_ticks = now.GetTicks();
1230 
1231             // Delete the stale AIS Target selectable point
1232             if( pStaleTarget )
1233                 pSelectAIS->DeleteSelectablePoint( (void *) mmsi_long, SELTYPE_AISTARGET );
1234 
1235             if (pTargetData) {
1236               if( gpsg_mmsi ) {
1237                 pTargetData->PositionReportTicks = now.GetTicks();
1238                 pTargetData->StaticReportTicks = now.GetTicks();
1239                 pTargetData->m_utc_hour = gpsg_utc_hour;
1240                 pTargetData->m_utc_min = gpsg_utc_min;
1241                 pTargetData->m_utc_sec = gpsg_utc_sec;
1242                 pTargetData->m_date_string = gpsg_date;
1243                 pTargetData->MMSI = gpsg_mmsi;
1244                 pTargetData->NavStatus = 0; // underway
1245                 pTargetData->Lat = gpsg_lat;
1246                 pTargetData->Lon = gpsg_lon;
1247                 pTargetData->b_positionOnceValid = true;
1248                 pTargetData->COG = gpsg_cog;
1249                 pTargetData->SOG = gpsg_sog;
1250                 pTargetData->ShipType = 52; // buddy
1251                 pTargetData->Class = AIS_GPSG_BUDDY;
1252                 memcpy( pTargetData->ShipName, gpsg_name_str, SHIP_NAME_LEN );
1253                 pTargetData->b_nameValid = true;
1254                 pTargetData->b_active = true;
1255                 pTargetData->b_lost = false;
1256 
1257                 bdecode_result = true;
1258               } else if( arpa_mmsi ) {
1259                 pTargetData->m_utc_hour = arpa_utc_hour;
1260                 pTargetData->m_utc_min = arpa_utc_min;
1261                 pTargetData->m_utc_sec = arpa_utc_sec;
1262                 pTargetData->MMSI = arpa_mmsi;
1263                 pTargetData->NavStatus = 15; // undefined
1264                 if( str.Mid( 3, 3 ).IsSameAs( _T("TLL") ) ) {
1265                     if( !bnewtarget ) {
1266                         int age_of_last = ( now.GetTicks() - pTargetData->PositionReportTicks );
1267                         if ( age_of_last > 0 ) {
1268                             ll_gc_ll_reverse( pTargetData->Lat, pTargetData->Lon, arpa_lat, arpa_lon, &pTargetData->COG, &pTargetData->SOG );
1269                             pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
1270                         }
1271                     }
1272                     pTargetData->Lat = arpa_lat;
1273                     pTargetData->Lon = arpa_lon;
1274                 } else if( str.Mid( 3, 3 ).IsSameAs( _T("TTM") ) ) {
1275                     if( arpa_dist != 0. ) //Not a new or turned off target
1276                         ll_gc_ll( gLat, gLon, arpa_brg, arpa_dist, &pTargetData->Lat, &pTargetData->Lon );
1277                     else
1278                         arpa_lost = true;
1279                     pTargetData->COG = arpa_cog;
1280                     pTargetData->SOG = arpa_sog;
1281                 }
1282                 pTargetData->PositionReportTicks = now.GetTicks();
1283                 pTargetData->StaticReportTicks = now.GetTicks();
1284                 pTargetData->b_positionOnceValid = true;
1285                 pTargetData->ShipType = 55; // arpa
1286                 pTargetData->Class = AIS_ARPA;
1287 
1288                 memcpy( pTargetData->ShipName, arpa_name_str, SHIP_NAME_LEN );
1289                 if( arpa_status != _T("Q") )
1290                     pTargetData->b_nameValid = true;
1291                 else
1292                     pTargetData->b_nameValid = false;
1293                 pTargetData->b_active = !arpa_lost;
1294                 pTargetData->b_lost = arpa_nottracked;
1295 
1296                 bdecode_result = true;
1297               } else if( aprs_mmsi ) {
1298                 pTargetData->m_utc_hour = now.GetHour();
1299                 pTargetData->m_utc_min = now.GetMinute();
1300                 pTargetData->m_utc_sec = now.GetSecond();
1301                 pTargetData->MMSI = aprs_mmsi;
1302                 pTargetData->NavStatus = 15; // undefined
1303                 if( !bnewtarget ) {
1304                     int age_of_last = (now.GetTicks() - pTargetData->PositionReportTicks);
1305                     if ( age_of_last > 0 ) {
1306                         ll_gc_ll_reverse( pTargetData->Lat, pTargetData->Lon, aprs_lat, aprs_lon, &pTargetData->COG, &pTargetData->SOG );
1307                         pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
1308                     }
1309                 }
1310                 pTargetData->PositionReportTicks = now.GetTicks();
1311                 pTargetData->StaticReportTicks = now.GetTicks();
1312                 pTargetData->Lat = aprs_lat;
1313                 pTargetData->Lon = aprs_lon;
1314                 pTargetData->b_positionOnceValid = true;
1315                 pTargetData->ShipType = 56; // aprs
1316                 pTargetData->Class = AIS_APRS;
1317                 memcpy( pTargetData->ShipName, aprs_name_str, SHIP_NAME_LEN );
1318                 pTargetData->b_nameValid = true;
1319                 pTargetData->b_active = true;
1320                 pTargetData->b_lost = false;
1321 
1322                 bdecode_result = true;
1323               } else{
1324                 // The normal Plain-Old AIS target code path....
1325                 bdecode_result = Parse_VDXBitstring( &strbit, pTargetData );       // Parse the new data
1326               }
1327               //     Update the most recent report period
1328               pTargetData->RecentPeriod = pTargetData->PositionReportTicks - last_report_ticks;
1329             }
1330             ret = AIS_NoError;
1331         } else{
1332             ret = AIS_Partial;                // accumulating parts of a multi-sentence message
1333             pTargetData = 0;
1334         }
1335 
1336 
1337         if(pTargetData){
1338             //  pTargetData is valid, either new or existing. Continue processing
1339 
1340             m_pLatestTargetData = pTargetData;
1341 
1342             if( str.Mid( 3, 3 ).IsSameAs( _T("VDO") ) )
1343                 pTargetData->b_OwnShip = true;
1344             else{
1345                 //set  mmsi-props to default values
1346                 pTargetData->b_OwnShip = false;
1347                 if ( 0 == m_persistent_tracks.count( mmsi ) ) {
1348                     //Normal target
1349                     pTargetData->b_PersistTrack = false;
1350                 } else {
1351                     // The track persistency enabled in the query window
1352                     pTargetData->b_PersistTrack = true;
1353                 }
1354                 pTargetData->b_NoTrack = false;
1355             }
1356 
1357             //  If the message was decoded correctly
1358             //  Update the AIS Target information
1359             if( bdecode_result ) {
1360                 AISshipNameCache(pTargetData, AISTargetNamesC, AISTargetNamesNC, mmsi);
1361                 ( *AISTargetList )[pTargetData->MMSI] = pTargetData;  // update the hash table entry
1362 
1363                 if( !pTargetData->area_notices.empty() ) {
1364                     AIS_Target_Hash::iterator it = AIS_AreaNotice_Sources->find( pTargetData->MMSI );
1365                     if( it == AIS_AreaNotice_Sources->end() )
1366                         ( *AIS_AreaNotice_Sources ) [pTargetData->MMSI] = pTargetData;
1367                 }
1368 
1369 
1370                 //  If this is not an ownship message, update the AIS Target in the Selectable list, and update the CPA info
1371                 if( !pTargetData->b_OwnShip ) {
1372                     if( pTargetData->b_positionOnceValid ) {
1373                         long mmsi_long = pTargetData->MMSI;
1374                         SelectItem *pSel = pSelectAIS->AddSelectablePoint( pTargetData->Lat,
1375                                 pTargetData->Lon, (void *) mmsi_long, SELTYPE_AISTARGET );
1376                         pSel->SetUserData( pTargetData->MMSI );
1377                     }
1378 
1379 
1380                     //    Calculate CPA info for this target immediately
1381                     UpdateOneCPA( pTargetData );
1382 
1383                     //    Update this target's track
1384                     if( pTargetData->b_show_track )
1385                         UpdateOneTrack( pTargetData );
1386                 }
1387                 // TODO add ais message call
1388                 SendJSONMsg( pTargetData );
1389             } else {
1390     //             printf("Unrecognised AIS message ID: %d\n", pTargetData->MID);
1391                 if( bnewtarget ) {
1392                     delete pTargetData;                           // this target is not going to be used
1393                     m_n_targets--;
1394                 } else {
1395                     //  If this is not an ownship message, update the AIS Target in the Selectable list
1396                     //  even if the message type was not recognized
1397                     if( !pTargetData->b_OwnShip ) {
1398                         if( pTargetData->b_positionOnceValid ) {
1399                             long mmsi_long = pTargetData->MMSI;
1400                             SelectItem *pSel = pSelectAIS->AddSelectablePoint( pTargetData->Lat,
1401                                                 pTargetData->Lon, (void *) mmsi_long, SELTYPE_AISTARGET );
1402                             pSel->SetUserData( pTargetData->MMSI );
1403                         }
1404                     }
1405                 }
1406             }
1407         }
1408 
1409     n_msgs++;
1410 #ifdef AIS_DEBUG
1411     if((n_msgs % 10000) == 0)
1412     printf("n_msgs %10d m_n_targets: %6d  n_msg1: %10d  n_msg5+24: %10d \n", n_msgs, m_n_targets, n_msg1, n_msg5 + n_msg24);
1413 #endif
1414 
1415     return ret;
1416 }
1417 
getAISTarget(long mmsi,AIS_Target_Data * & pTargetData,AIS_Target_Data * & pStaleTarget,bool & bnewtarget,int & last_report_ticks,wxDateTime & now)1418 void AIS_Decoder::getAISTarget(long mmsi,
1419                                AIS_Target_Data *&pTargetData,
1420                                AIS_Target_Data *&pStaleTarget,
1421                                bool &bnewtarget,
1422                                int &last_report_ticks,
1423                                wxDateTime &now)
1424 {
1425     now= wxDateTime::Now();
1426     AIS_Target_Hash_wxImplementation_HashTable::iterator it = AISTargetList->find(mmsi);
1427     if( it == AISTargetList->end() )                  // not found
1428     {
1429         pTargetData = new AIS_Target_Data;
1430         bnewtarget = true;
1431         m_n_targets++;
1432     } else {
1433         pTargetData = it->second;          // find current entry
1434         pStaleTarget = pTargetData;        // save a pointer to stale data
1435     }
1436 
1437     //  Grab the stale targets's last report time
1438     now.MakeGMT();
1439 
1440     if( pStaleTarget )
1441         last_report_ticks = pStaleTarget->PositionReportTicks;
1442     else
1443         last_report_ticks = now.GetTicks();
1444 
1445     // Delete the stale AIS Target selectable point
1446     if( pStaleTarget )
1447         pSelectAIS->DeleteSelectablePoint( (void *) mmsi, SELTYPE_AISTARGET );
1448 }
1449 
ProcessDSx(const wxString & str,bool b_take_dsc)1450 AIS_Target_Data *AIS_Decoder::ProcessDSx( const wxString& str, bool b_take_dsc )
1451 {
1452     double dsc_lat = 0.;
1453     double dsc_lon = 0.;
1454     double dsc_mins, dsc_degs, dsc_tmp, dsc_addr;
1455     double dse_tmp, dse_addr;
1456     double dse_lat = 0.;
1457     double dse_lon = 0.;
1458     long dsc_fmt, dsc_quadrant;
1459 
1460     int dsc_mmsi = 0;
1461     int dse_mmsi = 0;
1462     int mmsi = 0;
1463 
1464     AIS_Target_Data *pTargetData = NULL;
1465 
1466     // parse a DSC Position message            $CDDSx,.....
1467     //  Use a tokenizer to pull out the first 9 fields
1468     wxString string( str );
1469     wxStringTokenizer tkz( string, _T(",*") );
1470 
1471     wxString token;
1472     token = tkz.GetNextToken();         // !$CDDS
1473 
1474     if( str.Mid( 3, 3 ).IsSameAs( _T("DSC") ) ) {
1475         m_dsc_last_string = str;
1476 
1477         token = tkz.GetNextToken(); // format specifier (02-area,12-distress,16-allships,20-individual,...)
1478         token.ToLong( &dsc_fmt );
1479 
1480         token = tkz.GetNextToken();       // address i.e. mmsi*10 for received msg, or area spec
1481         token.ToDouble( &dsc_addr );
1482         dsc_mmsi = 0 - (int) ( dsc_addr / 10 ); // as per NMEA 0183 3.01
1483 
1484         token = tkz.GetNextToken();         // category
1485         token = tkz.GetNextToken();         // nature of distress or telecommand1
1486         token = tkz.GetNextToken();         // comm type or telecommand2
1487 
1488         token = tkz.GetNextToken();         // position or channel/freq
1489         token.ToDouble( &dsc_tmp );
1490 
1491         token = tkz.GetNextToken();         // time or tel. no.
1492         token = tkz.GetNextToken();         // mmsi of ship in distress
1493         token = tkz.GetNextToken();         // nature of distress
1494         token = tkz.GetNextToken();         // acknowledgement
1495         token = tkz.GetNextToken();         // expansion indicator
1496 
1497         dsc_quadrant = (int) (dsc_tmp / 1000000000.0);
1498 
1499         if(dsc_quadrant > 3)                // Position is "Unspecified", or 9999999999
1500             return NULL;
1501 
1502         dsc_lat = (int) ( dsc_tmp / 100000.0 );
1503         dsc_lon = dsc_tmp - dsc_lat * 100000.0;
1504         dsc_lat = dsc_lat - dsc_quadrant * 10000;
1505         dsc_degs = (int) ( dsc_lat / 100.0 );
1506         dsc_mins = dsc_lat - dsc_degs * 100.0;
1507         dsc_lat = dsc_degs + dsc_mins / 60.0;
1508 
1509         dsc_degs = (int) ( dsc_lon / 100.0 );
1510         dsc_mins = dsc_lon - dsc_degs * 100.0;
1511         dsc_lon = dsc_degs + dsc_mins / 60.0;
1512         switch( dsc_quadrant ) {
1513             case 0: break;                                             // NE
1514             case 1: dsc_lon = -dsc_lon; break;                         // NW
1515             case 2: dsc_lat = -dsc_lat; break;                         // SE
1516             case 3: dsc_lon = -dsc_lon; dsc_lat = -dsc_lat; break;     // SW
1517             default: break;
1518         }
1519         if( dsc_fmt != 02 )
1520             mmsi = (int) dsc_mmsi;
1521     } else if( str.Mid( 3, 3 ).IsSameAs( _T("DSE") ) ) {
1522 
1523         token = tkz.GetNextToken();         // total number of sentences
1524         token = tkz.GetNextToken();         // sentence number
1525         token = tkz.GetNextToken();         // query/rely flag
1526         token = tkz.GetNextToken();         // vessel MMSI
1527         token.ToDouble( &dse_addr );
1528         dse_mmsi = 0 - (int) ( dse_addr / 10 ); // as per NMEA 0183 3.01
1529 
1530         token = tkz.GetNextToken();         // code field
1531         token = tkz.GetNextToken();         // data field - position - 2*4 digits latlon .mins
1532         token.ToDouble( &dse_tmp );
1533         dse_lat = (int) ( dse_tmp / 10000.0 );
1534         dse_lon = (int) ( dse_tmp - dse_lat * 10000.0 );
1535         dse_lat = dse_lat / 600000.0;
1536         dse_lon = dse_lon / 600000.0;
1537 
1538         mmsi = (int) dse_mmsi;
1539     }
1540 
1541     //  Get the last report time for this target, if it exists
1542     wxDateTime now = wxDateTime::Now();
1543     now.MakeGMT();
1544     int last_report_ticks = now.GetTicks();
1545 
1546     //  Search the current AISTargetList for an MMSI match
1547     AIS_Target_Hash::iterator it = AISTargetList->find( mmsi );
1548     AIS_Target_Data *pStaleTarget = NULL;
1549     if( it == AISTargetList->end() ) {                 // not found
1550     } else {
1551         pStaleTarget = it->second;          // find current entry
1552         last_report_ticks = pStaleTarget->PositionReportTicks;
1553     }
1554 
1555     if( dsc_mmsi ){
1556         //      Create a tentative target, but do not post it pending receipt of extended data
1557         m_ptentative_dsctarget = new AIS_Target_Data;
1558 
1559         m_ptentative_dsctarget->PositionReportTicks = now.GetTicks();
1560         m_ptentative_dsctarget->StaticReportTicks = now.GetTicks();
1561 
1562         m_ptentative_dsctarget->MMSI = mmsi;
1563         m_ptentative_dsctarget->NavStatus = 0; // underway
1564         m_ptentative_dsctarget->Lat = dsc_lat;
1565         m_ptentative_dsctarget->Lon = dsc_lon;
1566         m_ptentative_dsctarget->b_positionOnceValid = true;
1567         m_ptentative_dsctarget->COG = 0;
1568         m_ptentative_dsctarget->SOG = 0;
1569         m_ptentative_dsctarget->ShipType = dsc_fmt; // DSC report
1570         m_ptentative_dsctarget->Class = AIS_DSC;
1571         m_ptentative_dsctarget->b_nameValid = true;
1572         if( dsc_fmt == 12 ) {
1573             snprintf( m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN, "DISTRESS %d", std::abs(mmsi));
1574         }
1575         else {
1576             snprintf( m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN, "POSITION %d", std::abs(mmsi));
1577         }
1578 
1579         m_ptentative_dsctarget->b_active = true;
1580         m_ptentative_dsctarget->b_lost = false;
1581         m_ptentative_dsctarget->RecentPeriod = m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
1582 
1583         //      Start a timer, looking for an expected DSE extension message
1584         if( !b_take_dsc )
1585             m_dsc_timer.Start( 500, wxTIMER_ONE_SHOT);
1586     }
1587 
1588     //    Got an extension message, or the timer expired and no extension is expected
1589     if( dse_mmsi || b_take_dsc){
1590         if( m_ptentative_dsctarget ){
1591 
1592             //  stop the timer for sure
1593             m_dsc_timer.Stop();
1594 
1595             //  Update the extended information
1596             if( dse_mmsi ) {
1597                 m_ptentative_dsctarget->Lat = m_ptentative_dsctarget->Lat + ( ( m_ptentative_dsctarget->Lat ) >= 0 ? dse_lat : -dse_lat );
1598                 m_ptentative_dsctarget->Lon = m_ptentative_dsctarget->Lon + ( ( m_ptentative_dsctarget->Lon ) >= 0 ? dse_lon : -dse_lon );
1599             }
1600 
1601             //     Update the most recent report period
1602             m_ptentative_dsctarget->RecentPeriod = m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
1603 
1604             //  And post the target
1605 
1606             //  Search the current AISTargetList for an MMSI match
1607             AIS_Target_Hash::iterator it = AISTargetList->find( mmsi );
1608             if( it == AISTargetList->end() ) {                 // not found
1609                 pTargetData = m_ptentative_dsctarget;
1610             } else {
1611                 pTargetData = it->second;          // find current entry
1612                 AISTargetTrackList *ptrack = pTargetData->m_ptrack;
1613                 pTargetData->CloneFrom( m_ptentative_dsctarget);  // this will make an empty track list
1614 
1615                 delete pTargetData->m_ptrack;           // get rid of the new empty one
1616                 pTargetData->m_ptrack = ptrack;         // and substitute the old track list
1617 
1618                 delete m_ptentative_dsctarget;
1619             }
1620 
1621             //  Reset for next time
1622             m_ptentative_dsctarget = NULL;
1623 
1624             m_pLatestTargetData = pTargetData;
1625 
1626             ( *AISTargetList )[pTargetData->MMSI] = pTargetData;            // update the hash table entry
1627 
1628             long mmsi_long = pTargetData->MMSI;
1629 
1630             // Delete any stale Target selectable point
1631             if( pStaleTarget )
1632                 pSelectAIS->DeleteSelectablePoint( (void *) mmsi_long, SELTYPE_AISTARGET );
1633             //  And add the updated target
1634             SelectItem *pSel = pSelectAIS->AddSelectablePoint( pTargetData->Lat,
1635                                                                pTargetData->Lon, (void *) mmsi_long, SELTYPE_AISTARGET );
1636             pSel->SetUserData( pTargetData->MMSI );
1637 
1638            //    Calculate CPA info for this target immediately
1639             UpdateOneCPA( pTargetData );
1640 
1641            //    Update this target's track
1642             if( pTargetData->b_show_track )
1643                 UpdateOneTrack( pTargetData );
1644         }
1645 
1646     }
1647 
1648     return pTargetData;
1649 }
1650 
1651 
1652 //----------------------------------------------------------------------------
1653 //      Parse a NMEA VDM/VDO Bitstring
1654 //----------------------------------------------------------------------------
Parse_VDXBitstring(AIS_Bitstring * bstr,AIS_Target_Data * ptd)1655 bool AIS_Decoder::Parse_VDXBitstring( AIS_Bitstring *bstr, AIS_Target_Data *ptd )
1656 {
1657     bool parse_result = false;
1658     bool b_posn_report = false;
1659 
1660     wxDateTime now = wxDateTime::Now();
1661     now.MakeGMT( );
1662     int message_ID = bstr->GetInt( 1, 6 );        // Parse on message ID
1663     ptd->MID = message_ID;
1664     ptd->MMSI = bstr->GetInt( 9, 30 );           // MMSI is always in the same spot in the bitstream
1665 
1666     switch( message_ID ){
1667         case 1:                                 // Position Report
1668         case 2:
1669         case 3: {
1670             n_msg1++;
1671 
1672             ptd->NavStatus = bstr->GetInt( 39, 4 );
1673             ptd->SOG = 0.1 * ( bstr->GetInt( 51, 10 ) );
1674 
1675             int lon = bstr->GetInt( 62, 28 );
1676             if( lon & 0x08000000 )                    // negative?
1677             lon |= 0xf0000000;
1678             double lon_tentative = lon / 600000.;
1679 
1680             int lat = bstr->GetInt( 90, 27 );
1681             if( lat & 0x04000000 )                    // negative?
1682             lat |= 0xf8000000;
1683             double lat_tentative = lat / 600000.;
1684 
1685             if( ( lon_tentative <= 180. ) && ( lat_tentative <= 90. ) ) // Ship does not report Lat or Lon "unavailable"
1686                     {
1687                 ptd->Lon = lon_tentative;
1688                 ptd->Lat = lat_tentative;
1689                 ptd->b_positionDoubtful = false;
1690                 ptd->b_positionOnceValid = true;          // Got the position at least once
1691                 ptd->PositionReportTicks = now.GetTicks();
1692             } else
1693                 ptd->b_positionDoubtful = true;
1694 
1695             //    decode balance of message....
1696             ptd->COG = 0.1 * ( bstr->GetInt( 117, 12 ) );
1697             ptd->HDG = 1.0 * ( bstr->GetInt( 129, 9 ) );
1698 
1699             ptd->ROTAIS = bstr->GetInt( 43, 8 );
1700             double rot_dir = 1.0;
1701 
1702             if( ptd->ROTAIS == 128 ) ptd->ROTAIS = -128;              // not available codes as -128
1703             else if( ( ptd->ROTAIS & 0x80 ) == 0x80 ) {
1704                 ptd->ROTAIS = ptd->ROTAIS - 256;       // convert to twos complement
1705                 rot_dir = -1.0;
1706             }
1707 
1708             ptd->ROTIND = wxRound( rot_dir * pow( ( ( (double) ptd->ROTAIS ) / 4.733 ), 2 ) ); // Convert to indicated ROT
1709 
1710             ptd->m_utc_sec = bstr->GetInt( 138, 6 );
1711 
1712             if( ( 1 == message_ID ) || ( 2 == message_ID ) )      // decode SOTDMA per 7.6.7.2.2
1713                     {
1714                 ptd->SyncState = bstr->GetInt( 151, 2 );
1715                 ptd->SlotTO = bstr->GetInt( 153, 2 );
1716                 if( ( ptd->SlotTO == 1 ) && ( ptd->SyncState == 0 ) ) // UTCDirect follows
1717                         {
1718                     ptd->m_utc_hour = bstr->GetInt( 155, 5 );
1719 
1720                     ptd->m_utc_min = bstr->GetInt( 160, 7 );
1721 
1722                     if( ( ptd->m_utc_hour < 24 ) && ( ptd->m_utc_min < 60 )
1723                             && ( ptd->m_utc_sec < 60 ) ) {
1724                         wxDateTime rx_time( ptd->m_utc_hour, ptd->m_utc_min, ptd->m_utc_sec );
1725                         rx_ticks = rx_time.GetTicks();
1726                         if( !b_firstrx ) {
1727                             first_rx_ticks = rx_ticks;
1728                             b_firstrx = true;
1729                         }
1730                     }
1731                 }
1732             }
1733 
1734             //    Capture Euro Inland special passing arrangement signal ("stbd-stbd")
1735             ptd->blue_paddle = bstr->GetInt( 144, 2 );
1736             ptd->b_blue_paddle = ( ptd->blue_paddle == 2 );             // paddle is set
1737 
1738             ptd->Class = AIS_CLASS_A;
1739 
1740             //    Check for SART and friends by looking at first two digits of MMSI
1741             int mmsi_start = ptd->MMSI / 10000000;
1742 
1743             if( mmsi_start == 97 ) {
1744                 ptd->Class = AIS_SART;
1745                 ptd->StaticReportTicks = now.GetTicks(); // won't get a static report, so fake it here
1746 
1747                 //    On receipt of Msg 3, force any existing SART target out of acknowledge mode
1748                 //    by adjusting its ack_time to yesterday
1749                 //    This will cause any previously "Acknowledged" SART to re-alert.
1750 
1751                 //    On reflection, re-alerting seems a little excessive in real life use.
1752                 //    After all, the target is on-screen, and in the AIS target list.
1753                 //    So lets just honor the programmed ACK timout value for SART targets as well
1754                 //ptd->m_ack_time = wxDateTime::Now() - wxTimeSpan::Day();
1755             }
1756 
1757             parse_result = true;                // so far so good
1758             b_posn_report = true;
1759 
1760             break;
1761         }
1762 
1763         case 18: {
1764             ptd->NavStatus = UNDEFINED;         // Class B targets have no status.  Enforce this...
1765 
1766             ptd->SOG = 0.1 * ( bstr->GetInt( 47, 10 ) );
1767 
1768 	    int lon = bstr->GetInt( 58, 28 );
1769             if( lon & 0x08000000 )                    // negative?
1770             lon |= 0xf0000000;
1771             double lon_tentative = lon / 600000.;
1772 
1773             int lat = bstr->GetInt( 86, 27 );
1774             if( lat & 0x04000000 )                    // negative?
1775             lat |= 0xf8000000;
1776             double lat_tentative = lat / 600000.;
1777 
1778             if( ( lon_tentative <= 180. ) && ( lat_tentative <= 90. ) ) // Ship does not report Lat or Lon "unavailable"
1779                     {
1780                 ptd->Lon = lon_tentative;
1781                 ptd->Lat = lat_tentative;
1782                 ptd->b_positionDoubtful = false;
1783                 ptd->b_positionOnceValid = true;          // Got the position at least once
1784                 ptd->PositionReportTicks = now.GetTicks();
1785             } else
1786                 ptd->b_positionDoubtful = true;
1787 
1788             ptd->COG = 0.1 * ( bstr->GetInt( 113, 12 ) );
1789             ptd->HDG = 1.0 * ( bstr->GetInt( 125, 9 ) );
1790 
1791             ptd->m_utc_sec = bstr->GetInt( 134, 6 );
1792 
1793             ptd->Class = AIS_CLASS_B;
1794 
1795             parse_result = true;                // so far so good
1796             b_posn_report = true;
1797 
1798             break;
1799         }
1800 
1801         case 19: {                              // Class B mes_ID 19 Is same as mes_ID 18 until bit 139
1802             ptd->NavStatus = UNDEFINED;         // Class B targets have no status.  Enforce this...
1803             ptd->SOG = 0.1 * (bstr->GetInt(47, 10));
1804             int lon = bstr->GetInt(58, 28);
1805             if (lon & 0x08000000)                    // negative?
1806                 lon |= 0xf0000000;
1807             double lon_tentative = lon / 600000.;
1808 
1809             int lat = bstr->GetInt(86, 27);
1810             if (lat & 0x04000000)                    // negative?
1811                 lat |= 0xf8000000;
1812             double lat_tentative = lat / 600000.;
1813 
1814             if ((lon_tentative <= 180.) && (lat_tentative <= 90.)) // Ship does not report Lat or Lon "unavailable"
1815             {
1816                 ptd->Lon = lon_tentative;
1817                 ptd->Lat = lat_tentative;
1818                 ptd->b_positionDoubtful = false;
1819                 ptd->b_positionOnceValid = true;          // Got the position at least once
1820                 ptd->PositionReportTicks = now.GetTicks();
1821             } else
1822                 ptd->b_positionDoubtful = true;
1823 
1824             ptd->COG = 0.1 * (bstr->GetInt(113, 12));
1825             ptd->HDG = 1.0 * (bstr->GetInt(125, 9));
1826             ptd->m_utc_sec = bstr->GetInt(134, 6);
1827             //From bit 140 and forward data as of mes 5
1828             bstr->GetStr(144, 120, &ptd->ShipName[0], 20);
1829             ptd->b_nameValid = true;
1830             ptd->ShipType = (unsigned char)bstr->GetInt(264, 8);
1831             ptd->DimA = bstr->GetInt(272, 9);
1832             ptd->DimB = bstr->GetInt(281, 9);
1833             ptd->DimC = bstr->GetInt(290, 6);
1834             ptd->DimD = bstr->GetInt(296, 6);
1835 
1836             ptd->Class = AIS_CLASS_B;
1837             parse_result = true;         // so far so good
1838             b_posn_report = true;
1839 
1840             break;
1841         }
1842 
1843 
1844         case 27: {
1845             // Long-range automatic identification system broadcast message
1846             // This message is used for long-range detection of AIS Class A and Class B vessels (typically by satellite).
1847 
1848             // Define the constant to do the covertion from the internal encoded
1849             // position in message 27. The position is less accuate :  1/10 minute position resolution.
1850             int bitCorrection = 10;
1851             int resolution = 10;
1852 
1853              // Default aout of bounce values.
1854             double lon_tentative = 181.;
1855             double lat_tentative = 91.;
1856 
1857             #ifdef AIS_DEBUG
1858                 printf("AIS Message 27 - received:\r\n");
1859                 printf("MMSI      : %i\r\n", ptd->MMSI );
1860             #endif
1861 
1862             // It can be both a CLASS A and a CLASS B vessel - We have decided for CLASS A
1863             // TODO: Lookup to see if we have seen it as a CLASS B, and adjust.
1864             ptd->Class = AIS_CLASS_A;
1865 
1866             ptd->NavStatus = bstr->GetInt( 39, 4 );
1867 
1868             int  lon = bstr->GetInt( 45,18 ) ;
1869             int lat = bstr->GetInt( 63, 17 );
1870 
1871             lat_tentative = lat;
1872             lon_tentative = lon;
1873 
1874             // Negative latitude?
1875             if( lat >= (0x4000000 >> bitCorrection)) {
1876                 lat_tentative = (0x8000000 >> bitCorrection) - lat;
1877                 lat_tentative *= -1;
1878             }
1879 
1880             // Negative longitude?
1881             if(lon >= (0x8000000 >> bitCorrection)) {
1882                 lon_tentative = (0x10000000 >> bitCorrection) - lon;
1883                 lon_tentative *= -1;
1884             }
1885 
1886             // Decode the internal position format.
1887             lat_tentative = lat_tentative / resolution / 60.0;
1888             lon_tentative = lon_tentative / resolution / 60.0;
1889 
1890             #ifdef AIS_DEBUG
1891                 printf("Latitude  : %f\r\n", lat_tentative  );
1892                 printf("Longitude : %f\r\n", lon_tentative  );
1893             #endif
1894 
1895             // Get the latency of the position report.
1896             int positionLatency = bstr->GetInt( 95, 1 );
1897 
1898             if ((lon_tentative <= 180.) && (lat_tentative <= 90.)) // Ship does not report Lat or Lon "unavailable"
1899             {
1900                 ptd->Lon = lon_tentative;
1901                 ptd->Lat = lat_tentative;
1902                 ptd->b_positionDoubtful = false;
1903                 ptd->b_positionOnceValid = true;          // Got the position at least once
1904                 if( positionLatency == 0 )
1905                 {
1906                     // The position is less than 5 seconds old.
1907                     #ifdef AIS_DEBUG
1908                         printf("Low latency position report.\r\n");
1909                     #endif
1910                     ptd->PositionReportTicks = now.GetTicks();
1911                 }
1912             }
1913             else
1914                 ptd->b_positionDoubtful = true;
1915 
1916             ptd->SOG = 1.0 * (bstr->GetInt( 80, 6 ));
1917             ptd->COG = 1.0 * (bstr->GetInt( 85, 9 ));
1918 
1919             b_posn_report = true;
1920             parse_result = true;
1921             break;
1922         }
1923 
1924 
1925         case 5: {
1926             n_msg5++;
1927             ptd->Class = AIS_CLASS_A;
1928 
1929 //          Get the AIS Version indicator
1930 //          0 = station compliant with Recommendation ITU-R M.1371-1
1931 //          1 = station compliant with Recommendation ITU-R M.1371-3
1932 //          2-3 = station compliant with future editions
1933             int AIS_version_indicator = bstr->GetInt( 39, 2 );
1934             if( AIS_version_indicator < 4 ) {
1935                 ptd->IMO = bstr->GetInt( 41, 30 );
1936 
1937                 bstr->GetStr( 71, 42, &ptd->CallSign[0], 7 );
1938                 bstr->GetStr( 113, 120, &ptd->ShipName[0], 20 );
1939                 ptd->b_nameValid = true;
1940 
1941                 ptd->ShipType = (unsigned char) bstr->GetInt( 233, 8 );
1942 
1943                 ptd->DimA = bstr->GetInt( 241, 9 );
1944                 ptd->DimB = bstr->GetInt( 250, 9 );
1945                 ptd->DimC = bstr->GetInt( 259, 6 );
1946                 ptd->DimD = bstr->GetInt( 265, 6 );
1947 
1948                 ptd->ETA_Mo = bstr->GetInt( 275, 4 );
1949                 ptd->ETA_Day = bstr->GetInt( 279, 5 );
1950                 ptd->ETA_Hr = bstr->GetInt( 284, 5 );
1951                 ptd->ETA_Min = bstr->GetInt( 289, 6 );
1952 
1953                 ptd->Draft = (double) ( bstr->GetInt( 295, 8 ) ) / 10.0;
1954 
1955                 bstr->GetStr( 303, 120, &ptd->Destination[0], 20 );
1956 
1957                 ptd->StaticReportTicks = now.GetTicks();
1958 
1959                 parse_result = true;
1960             }
1961 
1962             break;
1963         }
1964 
1965         case 24: {
1966             int part_number = bstr->GetInt( 39, 2 );
1967             if( 0 == part_number ) {
1968                 bstr->GetStr( 41, 120, &ptd->ShipName[0], 20 );
1969                 ptd->b_nameValid = true;
1970                 parse_result = true;
1971                 n_msg24++;
1972             } else if( 1 == part_number ) {
1973                 ptd->ShipType = (unsigned char) bstr->GetInt( 41, 8 );
1974                 bstr->GetStr( 91, 42, &ptd->CallSign[0], 7 );
1975 
1976                 ptd->DimA = bstr->GetInt( 133, 9 );
1977                 ptd->DimB = bstr->GetInt( 142, 9 );
1978                 ptd->DimC = bstr->GetInt( 151, 6 );
1979                 ptd->DimD = bstr->GetInt( 157, 6 );
1980                 parse_result = true;
1981             }
1982             break;
1983         }
1984         case 4:                                    // base station
1985         {
1986             ptd->Class = AIS_BASE;
1987 
1988             ptd->m_utc_hour = bstr->GetInt( 62, 5 );
1989             ptd->m_utc_min = bstr->GetInt( 67, 6 );
1990             ptd->m_utc_sec = bstr->GetInt( 73, 6 );
1991             //                              (79,  1);
1992             int lon = bstr->GetInt( 80, 28 );
1993             if( lon & 0x08000000 )                    // negative?
1994             lon |= 0xf0000000;
1995             double lon_tentative = lon / 600000.;
1996 
1997             int lat = bstr->GetInt( 108, 27 );
1998             if( lat & 0x04000000 )                    // negative?
1999             lat |= 0xf8000000;
2000             double lat_tentative = lat / 600000.;
2001 
2002             if( ( lon_tentative <= 180. ) && ( lat_tentative <= 90. ) ) // Ship does not report Lat or Lon "unavailable"
2003                     {
2004                 ptd->Lon = lon_tentative;
2005                 ptd->Lat = lat_tentative;
2006                 ptd->b_positionDoubtful = false;
2007                 ptd->b_positionOnceValid = true;          // Got the position at least once
2008                 ptd->PositionReportTicks = now.GetTicks();
2009             } else
2010                 ptd->b_positionDoubtful = true;
2011 
2012             ptd->COG = -1.;
2013             ptd->HDG = 511;
2014             ptd->SOG = -1.;
2015 
2016             parse_result = true;
2017             b_posn_report = true;
2018 
2019             break;
2020         }
2021         case 9:                   // Special Position Report (Standard SAR Aircraft Position Report)
2022         {
2023             ptd->SOG =  bstr->GetInt( 51, 10 ) ;
2024 
2025             int lon = bstr->GetInt( 62, 28 );
2026             if( lon & 0x08000000 )                    // negative?
2027             lon |= 0xf0000000;
2028             double lon_tentative = lon / 600000.;
2029 
2030             int lat = bstr->GetInt( 90, 27 );
2031             if( lat & 0x04000000 )                    // negative?
2032             lat |= 0xf8000000;
2033             double lat_tentative = lat / 600000.;
2034 
2035             if( ( lon_tentative <= 180. ) && ( lat_tentative <= 90. ) ) // Ship does not report Lat or Lon "unavailable"
2036                     {
2037                         ptd->Lon = lon_tentative;
2038                         ptd->Lat = lat_tentative;
2039                         ptd->b_positionDoubtful = false;
2040                         ptd->b_positionOnceValid = true;          // Got the position at least once
2041                         ptd->PositionReportTicks = now.GetTicks();
2042                     } else
2043                         ptd->b_positionDoubtful = true;
2044 
2045             //    decode balance of message....
2046             ptd->COG = 0.1 * ( bstr->GetInt( 117, 12 ) );
2047 
2048             int alt_tent = bstr->GetInt( 39, 12 );
2049             ptd->altitude = alt_tent;
2050 
2051             ptd->b_SarAircraftPosnReport = true;
2052 
2053             parse_result = true;
2054             b_posn_report = true;
2055 
2056             break;
2057         }
2058         case 21:                                    // Test Message (Aid to Navigation)
2059         {
2060             ptd->ShipType = (unsigned char) bstr->GetInt( 39, 5 );
2061             ptd->IMO = 0;
2062             ptd->SOG = 0;
2063             ptd->HDG = 0;
2064             ptd->COG = 0;
2065             ptd->ROTAIS = -128;                 // i.e. not available
2066             ptd->DimA = bstr->GetInt( 220, 9 );
2067             ptd->DimB = bstr->GetInt( 229, 9 );
2068             ptd->DimC = bstr->GetInt( 238, 6 );
2069             ptd->DimD = bstr->GetInt( 244, 6 );
2070             ptd->Draft = 0;
2071 
2072             ptd->m_utc_sec = bstr->GetInt( 254, 6 );
2073 
2074             int offpos = bstr->GetInt( 260, 1 ); // off position flag
2075             int virt = bstr->GetInt( 270, 1 ); // virtual flag
2076 
2077             if( virt ) ptd->NavStatus = ATON_VIRTUAL;
2078             else
2079                 ptd->NavStatus = ATON_REAL;
2080             if( ptd->m_utc_sec <= 59 /*&& !virt*/) {
2081                 ptd->NavStatus += 1;
2082                 if( offpos ) ptd->NavStatus += 1;
2083             }
2084 
2085             bstr->GetStr( 44, 120, &ptd->ShipName[0], 20 ); // short name only, extension wont fit in Ship structure
2086 
2087             if( bstr->GetBitCount() > 276 ) {
2088                 int nx = ( ( bstr->GetBitCount() - 272 ) / 6 ) * 6;
2089                 bstr->GetStr( 273, nx, &ptd->ShipNameExtension[0], 14 );
2090                 ptd->ShipNameExtension[14] = 0;
2091             } else {
2092                 ptd->ShipNameExtension[0] = 0;
2093             }
2094 
2095             ptd->b_nameValid = true;
2096 
2097             parse_result = true;                // so far so good
2098 
2099             ptd->Class = AIS_ATON;
2100 
2101             int lon = bstr->GetInt( 165, 28 );
2102 
2103             if( lon & 0x08000000 )                    // negative?
2104             lon |= 0xf0000000;
2105             double lon_tentative = lon / 600000.;
2106 
2107             int lat = bstr->GetInt( 193, 27 );
2108 
2109             if( lat & 0x04000000 )                    // negative?
2110             lat |= 0xf8000000;
2111             double lat_tentative = lat / 600000.;
2112 
2113             if( ( lon_tentative <= 180. ) && ( lat_tentative <= 90. ) ) // Ship does not report Lat or Lon "unavailable"
2114                     {
2115                 ptd->Lon = lon_tentative;
2116                 ptd->Lat = lat_tentative;
2117                 ptd->b_positionDoubtful = false;
2118                 ptd->b_positionOnceValid = true;          // Got the position at least once
2119                 ptd->PositionReportTicks = now.GetTicks();
2120             } else
2121                 ptd->b_positionDoubtful = true;
2122 
2123             b_posn_report = true;
2124             break;
2125         }
2126         case 8:                                    // Binary Broadcast
2127         {
2128             int dac = bstr->GetInt( 41, 10 );
2129             int fi = bstr->GetInt( 51, 6 );
2130             if( dac == 200 )                  // European inland
2131                     {
2132                 if( fi == 10 )              // "Inland ship static and voyage related data"
2133                         {
2134                     ptd->b_isEuroInland = true;
2135 
2136                     bstr->GetStr( 57, 48, &ptd->Euro_VIN[0], 8 );
2137                     ptd->Euro_Length = ( (double) bstr->GetInt( 105, 13 ) ) / 10.0;
2138                     ptd->Euro_Beam = ( (double) bstr->GetInt( 118, 10 ) ) / 10.0;
2139                     ptd->UN_shiptype = bstr->GetInt( 128, 14 );
2140                     ptd->Euro_Draft = ( (double) bstr->GetInt( 145, 11 ) ) / 100.0;
2141                     parse_result = true;
2142                 }
2143             }
2144             if( dac == 1 )                     // IMO
2145                     {
2146                 if( fi == 22 )                 // Area Notice
2147                         {
2148                     if( bstr->GetBitCount() >= 111 ) {
2149                         Ais8_001_22 an;
2150                         an.link_id = bstr->GetInt( 57, 10 );
2151                         an.notice_type = bstr->GetInt( 67, 7 );
2152                         an.month = bstr->GetInt( 74, 4 );
2153                         an.day = bstr->GetInt( 78, 5 );
2154                         an.hour = bstr->GetInt( 83, 5 );
2155                         an.minute = bstr->GetInt( 88, 6 );
2156                         an.duration_minutes = bstr->GetInt( 94, 18 );
2157 
2158                         wxDateTime now = wxDateTime::Now();
2159                         now.MakeGMT();
2160 
2161                         an.start_time.Set( an.day, wxDateTime::Month( an.month - 1 ), now.GetYear(),
2162                                 an.hour, an.minute );
2163 
2164                         // msg is not supposed to be transmitted more than a day before it comes into effect,
2165                         // so a start_time less than a day or two away might indicate a month rollover
2166                         if( an.start_time > now + wxTimeSpan::Hours( 48 ) ) an.start_time.Set(
2167                                 an.day, wxDateTime::Month( an.month - 1 ), now.GetYear() - 1,
2168                                 an.hour, an.minute );
2169 
2170                         an.expiry_time = an.start_time + wxTimeSpan::Minutes( an.duration_minutes );
2171 
2172                         // msg is not supposed to be transmitted beyond expiration, so taking into account a
2173                         // fudge factor for clock issues, assume an expiry date in the past indicates incorrect year
2174                         if( an.expiry_time < now - wxTimeSpan::Hours( 24 ) ) {
2175                             an.start_time.Set( an.day, wxDateTime::Month( an.month - 1 ),
2176                                     now.GetYear() + 1, an.hour, an.minute );
2177                             an.expiry_time = an.start_time
2178                                     + wxTimeSpan::Minutes( an.duration_minutes );
2179                         }
2180 
2181                         int subarea_count = ( bstr->GetBitCount() - 111 ) / 87;
2182                         for( int i = 0; i < subarea_count; ++i ) {
2183                             int base = 111 + i * 87;
2184                             Ais8_001_22_SubArea sa;
2185                             sa.shape = bstr->GetInt( base + 1, 3 );
2186                             int scale_factor = 1;
2187                             if( sa.shape == AIS8_001_22_SHAPE_TEXT ) {
2188                                 char t[15];
2189                                 t[14] = 0;
2190                                 bstr->GetStr( base + 4, 84, t, 14 );
2191                                 sa.text = wxString( t, wxConvUTF8 );
2192                             } else {
2193                                 int scale_multipliers[4] = { 1, 10, 100, 1000 };
2194                                 scale_factor = scale_multipliers[bstr->GetInt( base + 4, 2 )];
2195                                 switch( sa.shape ){
2196                                     case AIS8_001_22_SHAPE_CIRCLE:
2197                                     case AIS8_001_22_SHAPE_SECTOR:
2198                                         sa.radius_m = bstr->GetInt( base + 58, 12 ) * scale_factor;
2199                                         // FALL THROUGH
2200                                     case AIS8_001_22_SHAPE_RECT:
2201                                         sa.longitude = bstr->GetInt( base + 6, 25, true ) / 60000.0;
2202                                         sa.latitude = bstr->GetInt( base + 31, 24, true ) / 60000.0;
2203                                         break;
2204                                     case AIS8_001_22_SHAPE_POLYLINE:
2205                                     case AIS8_001_22_SHAPE_POLYGON:
2206                                         for( int i = 0; i < 4; ++i ) {
2207                                             sa.angles[i] = bstr->GetInt( base + 6 + i * 20, 10 )
2208                                                     * 0.5;
2209                                             sa.dists_m[i] = bstr->GetInt( base + 16 + i * 20, 10 )
2210                                                     * scale_factor;
2211                                         }
2212                                 }
2213                                 if( sa.shape == AIS8_001_22_SHAPE_RECT ) {
2214                                     sa.e_dim_m = bstr->GetInt( base + 58, 8 ) * scale_factor;
2215                                     sa.n_dim_m = bstr->GetInt( base + 66, 8 ) * scale_factor;
2216                                     sa.orient_deg = bstr->GetInt( base + 74, 9 );
2217                                 }
2218                                 if( sa.shape == AIS8_001_22_SHAPE_SECTOR ) {
2219                                     sa.left_bound_deg = bstr->GetInt( 70, 9 );
2220                                     sa.right_bound_deg = bstr->GetInt( 79, 9 );
2221                                 }
2222                             }
2223                             an.sub_areas.push_back( sa );
2224                         }
2225                         ptd->area_notices[an.link_id] = an;
2226                         parse_result = true;
2227                     }
2228                 }
2229             }
2230             break;
2231         }
2232         case 14:                                    // Safety Related Broadcast
2233         {
2234             //  Always capture the MSG_14 text
2235             char msg_14_text[968];
2236             if( bstr->GetBitCount() > 40 ) {
2237                 int nx = ( ( bstr->GetBitCount() - 40 ) / 6 ) * 6;
2238                 int nd = bstr->GetStr( 41, nx, msg_14_text, 968 );
2239                 nd = wxMax(0, nd);
2240                 nd = wxMin(nd, 967);
2241                 msg_14_text[nd] = 0;
2242                 ptd->MSG_14_text = wxString( msg_14_text, wxConvUTF8 );
2243             }
2244             parse_result = true;                // so far so good
2245 
2246             break;
2247         }
2248 
2249         case 6:                                    // Addressed Binary Message
2250         {
2251             break;
2252         }
2253         case 7:                                    // Binary Ack
2254         {
2255             break;
2256         }
2257         default: {
2258             break;
2259         }
2260 
2261     }
2262 
2263     if( b_posn_report ) ptd->b_lost = false;
2264 
2265     if( true == parse_result ) {
2266         //      Revalidate the target under some conditions
2267         if( !ptd->b_active && !ptd->b_positionDoubtful && b_posn_report ) ptd->b_active = true;
2268     }
2269 
2270     return parse_result;
2271 }
2272 
NMEACheckSumOK(const wxString & str_in)2273 bool AIS_Decoder::NMEACheckSumOK( const wxString& str_in )
2274 {
2275 
2276     unsigned char checksum_value = 0;
2277     int sentence_hex_sum;
2278 
2279     wxCharBuffer buf = str_in.ToUTF8();
2280     if( !buf.data())
2281         return false;                           // cannot decode string
2282 
2283     char str_ascii[AIS_MAX_MESSAGE_LEN + 1];
2284     strncpy( str_ascii, buf.data(), AIS_MAX_MESSAGE_LEN );
2285     str_ascii[AIS_MAX_MESSAGE_LEN] = '\0';
2286 
2287     int string_length = strlen( str_ascii );
2288 
2289     int payload_length = 0;
2290     while( ( payload_length < string_length ) && ( str_ascii[payload_length] != '*' ) ) // look for '*'
2291         payload_length++;
2292 
2293     if( payload_length == string_length ) return false; // '*' not found at all, no checksum
2294 
2295     int index = 1; // Skip over the $ at the begining of the sentence
2296 
2297     while( index < payload_length ) {
2298         checksum_value ^= str_ascii[index];
2299         index++;
2300     }
2301 
2302     if( string_length > 4 ) {
2303         char scanstr[3];
2304         scanstr[0] = str_ascii[payload_length + 1];
2305         scanstr[1] = str_ascii[payload_length + 2];
2306         scanstr[2] = 0;
2307         sscanf( scanstr, "%2x", &sentence_hex_sum );
2308 
2309         if( sentence_hex_sum == checksum_value ) return true;
2310     }
2311 
2312     return false;
2313 }
2314 
UpdateAllCPA(void)2315 void AIS_Decoder::UpdateAllCPA( void )
2316 {
2317     //    Iterate thru all the targets
2318     AIS_Target_Hash::iterator it;
2319     AIS_Target_Hash *current_targets = GetTargetList();
2320 
2321     for( it = ( *current_targets ).begin(); it != ( *current_targets ).end(); ++it ) {
2322         AIS_Target_Data *td = it->second;
2323 
2324         if( NULL != td ) UpdateOneCPA( td );
2325     }
2326 }
2327 
UpdateAllTracks(void)2328 void AIS_Decoder::UpdateAllTracks( void )
2329 {
2330     //    Iterate thru all the targets
2331     AIS_Target_Hash::iterator it;
2332     AIS_Target_Hash *current_targets = GetTargetList();
2333 
2334     for( it = ( *current_targets ).begin(); it != ( *current_targets ).end(); ++it ) {
2335         AIS_Target_Data *td = it->second;
2336 
2337         if( NULL != td ) UpdateOneTrack( td );
2338     }
2339 }
2340 
UpdateOneTrack(AIS_Target_Data * ptarget)2341 void AIS_Decoder::UpdateOneTrack( AIS_Target_Data *ptarget )
2342 {
2343    if( !ptarget->b_positionOnceValid ) return;
2344     // Reject for unbelievable jumps (corrupted/bad data)
2345     if ( ptarget->m_ptrack->GetCount() > 0 )
2346     {
2347         AISTargetTrackPoint *LastTrackpoint =  ptarget->m_ptrack->GetLast()->GetData();
2348         if ( fabs( LastTrackpoint->m_lat - ptarget->Lat ) > .1  || fabs( LastTrackpoint->m_lon - ptarget->Lon ) > .1 )
2349         {
2350             // after an unlikely jump in pos, the last trackpoint might also be wrong
2351             // just to be sure we do delete this one as well.
2352             ptarget->m_ptrack->pop_back();
2353             ptarget->b_positionDoubtful = true;
2354             return;
2355         }
2356     }
2357 
2358     //    Add the newest point
2359     AISTargetTrackPoint *ptrackpoint = new AISTargetTrackPoint;
2360     ptrackpoint->m_lat = ptarget->Lat;
2361     ptrackpoint->m_lon = ptarget->Lon;
2362     ptrackpoint->m_time = wxDateTime::Now().GetTicks();
2363 
2364     ptarget->m_ptrack->Append( ptrackpoint );
2365 
2366     if( ptarget->b_PersistTrack )
2367     {
2368         Track *t;
2369         if ( 0 == m_persistent_tracks.count( ptarget->MMSI ) )
2370         {
2371             t = new Track();
2372             t->SetName( wxString::Format( _T("AIS %s (%u) %s %s"), ptarget->GetFullName().c_str(), ptarget->MMSI, wxDateTime::Now().FormatISODate().c_str(), wxDateTime::Now().FormatISOTime().c_str() ) );
2373             pTrackList->Append( t );
2374             pConfig->AddNewTrack( t );
2375             m_persistent_tracks[ptarget->MMSI] = t;
2376         }
2377         else
2378         {
2379             t = m_persistent_tracks[ptarget->MMSI];
2380         }
2381         TrackPoint *tp = t->GetLastPoint();
2382         vector2D point( ptrackpoint->m_lon, ptrackpoint->m_lat );
2383         TrackPoint *tp1 = t->AddNewPoint( point, wxDateTime(ptrackpoint->m_time).ToUTC() );
2384         if( tp )
2385         {
2386             pSelect->AddSelectableTrackSegment( tp->m_lat, tp->m_lon, tp1->m_lat,
2387                 tp1->m_lon, tp, tp1, t );
2388         }
2389 
2390 //We do not want dependency on the GUI here, do we?
2391 //        if( pRouteManagerDialog && pRouteManagerDialog->IsShown() )
2392 //                pRouteManagerDialog->UpdateTrkListCtrl();
2393     }
2394 
2395     //    Walk the list, removing any track points that are older than the stipulated time
2396 
2397     time_t test_time = wxDateTime::Now().GetTicks() - (time_t) ( g_AISShowTracks_Mins * 60 );
2398 
2399     wxAISTargetTrackListNode *node = ptarget->m_ptrack->GetFirst();
2400     while( node ) {
2401         AISTargetTrackPoint *ptrack_point = node->GetData();
2402 
2403         if( ptrack_point->m_time < test_time ) {
2404             if( ptarget->m_ptrack->DeleteObject( ptrack_point ) ) {
2405                 node = ptarget->m_ptrack->GetFirst();                // restart the list
2406             }
2407         } else
2408             node = node->GetNext();
2409     }
2410 }
2411 
DeletePersistentTrack(Track * track)2412 void AIS_Decoder::DeletePersistentTrack( Track *track )
2413 {
2414     for(std::map<int, Track*>::iterator iterator = m_persistent_tracks.begin(); iterator != m_persistent_tracks.end(); iterator++)
2415     {
2416         if( iterator->second == track )
2417         {
2418             m_persistent_tracks.erase(iterator);
2419             break;
2420         }
2421     }
2422 }
2423 
UpdateAllAlarms(void)2424 void AIS_Decoder::UpdateAllAlarms( void )
2425 {
2426     m_bGeneralAlert = false;                // no alerts yet
2427 
2428     //    Iterate thru all the targets
2429     AIS_Target_Hash::iterator it;
2430     AIS_Target_Hash *current_targets = GetTargetList();
2431 
2432     for( it = ( *current_targets ).begin(); it != ( *current_targets ).end(); ++it ) {
2433         AIS_Target_Data *td = it->second;
2434 
2435         if( NULL != td ) {
2436             //  Maintain General Alert
2437             if( !m_bGeneralAlert ) {
2438                 //    Quick check on basic condition
2439                 if( ( td->CPA < g_CPAWarn_NM ) && ( td->TCPA > 0 ) && ( td->Class != AIS_ATON ) && ( td->Class != AIS_BASE ) )
2440                     m_bGeneralAlert = true;
2441 
2442                 //    Some options can suppress general alerts
2443                 if( g_bAIS_CPA_Alert_Suppress_Moored && ( td->SOG <= g_ShowMoored_Kts ) )
2444                     m_bGeneralAlert = false;
2445 
2446                 //    Skip distant targets if requested
2447                 if( ( g_bCPAMax ) && ( td->Range_NM > g_CPAMax_NM ) )
2448                     m_bGeneralAlert = false;
2449 
2450                 //    Skip if TCPA is too long
2451                 if( ( g_bTCPA_Max ) && ( td->TCPA > g_TCPA_Max ) )
2452                     m_bGeneralAlert = false;
2453 
2454                 //  SART targets always alert if "Active"
2455                 if( td->Class == AIS_SART && td->NavStatus == 14)
2456                     m_bGeneralAlert = true;
2457 
2458                 //  DSC Distress targets always alert
2459                 if( ( td->Class == AIS_DSC ) && ( td->ShipType == 12 ) )
2460                     m_bGeneralAlert = true;
2461             }
2462 
2463             ais_alert_type this_alarm = AIS_NO_ALERT;
2464 
2465             //  SART targets always alert if "Active"
2466             if( td->Class == AIS_SART && td->NavStatus == 14)
2467                 this_alarm = AIS_ALERT_SET;
2468 
2469             //  DSC Distress targets always alert
2470             if( ( td->Class == AIS_DSC ) && ( td->ShipType == 12 ) )
2471                     this_alarm = AIS_ALERT_SET;
2472 
2473             if( g_bCPAWarn && td->b_active && td->b_positionOnceValid &&
2474                 ( td->Class != AIS_SART ) && ( td->Class != AIS_DSC ) ) {
2475                 //      Skip anchored/moored(interpreted as low speed) targets if requested
2476                 if( ( g_bHideMoored ) && ( td->SOG <= g_ShowMoored_Kts ) ) {       // dsr
2477                     td->n_alert_state = AIS_NO_ALERT;
2478                     continue;
2479                 }
2480 
2481                 //    No Alert on moored(interpreted as low speed) targets if so requested
2482                 if( g_bAIS_CPA_Alert_Suppress_Moored && ( td->SOG <= g_ShowMoored_Kts ) ) {    // dsr
2483                     td->n_alert_state = AIS_NO_ALERT;
2484                     continue;
2485                 }
2486 
2487                 //    No alert for my Follower
2488                 bool hit = false;
2489                 for(unsigned int i=0 ; i < g_MMSI_Props_Array.GetCount() ; i++){
2490                     MMSIProperties *props =  g_MMSI_Props_Array[i];
2491                     if(td->MMSI == props->MMSI){
2492                         if (props->m_bFollower) {
2493                             hit = true;
2494                             td->n_alert_state = AIS_NO_ALERT;
2495                         }
2496                         break;
2497                     }
2498                 }
2499                 if (hit) continue;
2500 
2501                 //    Skip distant targets if requested
2502                 if( g_bCPAMax ) {
2503                     if( td->Range_NM > g_CPAMax_NM ) {
2504                         td->n_alert_state = AIS_NO_ALERT;
2505                         continue;
2506                     }
2507                 }
2508 
2509                 if( ( td->CPA < g_CPAWarn_NM ) && ( td->TCPA > 0 ) && ( td->Class != AIS_ATON ) && ( td->Class != AIS_BASE )) {
2510                     if( g_bTCPA_Max ) {
2511                         if( td->TCPA < g_TCPA_Max ) this_alarm = AIS_ALERT_SET;
2512                     } else
2513                         this_alarm = AIS_ALERT_SET;
2514                 }
2515             }
2516 
2517 
2518             //    Maintain the timer for in_ack flag
2519             //  SART and DSC targets always maintain ack timeout
2520 
2521             if( g_bAIS_ACK_Timeout || (td->Class == AIS_SART) || ((td->Class == AIS_DSC) && (td->ShipType == 12))) {
2522                 if( td->b_in_ack_timeout ) {
2523                     wxTimeSpan delta = wxDateTime::Now() - td->m_ack_time;
2524                     if( delta.GetMinutes() > g_AckTimeout_Mins ) td->b_in_ack_timeout = false;
2525                 }
2526             } else {
2527                 //  Not using ack timeouts.
2528                 //  If a target has been acknowledged, leave it ack'ed until it goes out of AIS_ALARM_SET state
2529                 if( td->b_in_ack_timeout ){
2530                     if( this_alarm == AIS_NO_ALERT )
2531                         td->b_in_ack_timeout = false;
2532                 }
2533             }
2534 
2535             td->n_alert_state = this_alarm;
2536 
2537         }
2538     }
2539 }
2540 
UpdateOneCPA(AIS_Target_Data * ptarget)2541 void AIS_Decoder::UpdateOneCPA( AIS_Target_Data *ptarget )
2542 {
2543     ptarget->Range_NM = -1.;            // Defaults
2544     ptarget->Brg = -1.;
2545 
2546     //    Compute the current Range/Brg to the target
2547     //    This should always be possible even if GPS data is not valid
2548     //    because O must always have a position for own-ship. Plugins need
2549     //    AIS target range and bearing from own-ship position even if GPS is not valid.
2550     double brg, dist;
2551     DistanceBearingMercator( ptarget->Lat, ptarget->Lon, gLat, gLon, &brg, &dist );
2552     ptarget->Range_NM = dist;
2553     ptarget->Brg = brg;
2554 
2555     if( dist <= 1e-5 ) ptarget->Brg = -1.0;             // Brg is undefined if Range == 0.
2556 
2557     if( !ptarget->b_positionOnceValid || !bGPSValid ) {
2558         ptarget->bCPA_Valid = false;
2559         return;
2560     }
2561 
2562     //    There can be no collision between ownship and itself....
2563     //    This can happen if AIVDO messages are received, and there is another source of ownship position, like NMEA GLL
2564     //    The two positions are always temporally out of sync, and one will always be exactly in front of the other one.
2565     if( ptarget->b_OwnShip ) {
2566         ptarget->CPA = 100;
2567         ptarget->TCPA = -100;
2568         ptarget->bCPA_Valid = false;
2569         return;
2570     }
2571 
2572     double cpa_calc_ownship_cog = gCog;
2573     double cpa_calc_target_cog = ptarget->COG;
2574 
2575 //    Ownship is not reporting valid SOG, so no way to calculate CPA
2576     if( std::isnan(gSog) || ( gSog > 102.2 ) ) {
2577         ptarget->bCPA_Valid = false;
2578         return;
2579     }
2580 
2581 //    Ownship is maybe anchored and not reporting COG
2582     if( std::isnan(gCog) || gCog == 360.0 ) {
2583         if( gSog < .01 ) cpa_calc_ownship_cog = 0.;          // substitute value
2584                                                              // for the case where SOG ~= 0, and COG is unknown.
2585         else {
2586             ptarget->bCPA_Valid = false;
2587             return;
2588         }
2589     }
2590 
2591 //    Target is maybe anchored and not reporting COG
2592     if( ptarget->COG == 360.0 ) {
2593         if( ptarget->SOG > 102.2 ) {
2594             ptarget->bCPA_Valid = false;
2595             return;
2596         } else if( ptarget->SOG < .01 ) cpa_calc_target_cog = 0.;           // substitute value
2597                                                                             // for the case where SOG ~= 0, and COG is unknown.
2598         else {
2599             ptarget->bCPA_Valid = false;
2600             return;
2601         }
2602     }
2603 
2604     //    Express the SOGs as meters per hour
2605     double v0 = gSog * 1852.;
2606     double v1 = ptarget->SOG * 1852.;
2607 
2608     if( ( v0 < 1e-6 ) && ( v1 < 1e-6 ) ) {
2609         ptarget->TCPA = 0.;
2610         ptarget->CPA = 0.;
2611 
2612         ptarget->bCPA_Valid = false;
2613     } else {
2614         //    Calculate the TCPA first
2615 
2616         //    Working on a Reduced Lat/Lon orthogonal plotting sheet....
2617         //    Get easting/northing to target,  in meters
2618 
2619         double east1 = ( ptarget->Lon - gLon ) * 60 * 1852;
2620         double north1 = ( ptarget->Lat - gLat ) * 60 * 1852;
2621 
2622         double east = east1 * ( cos( gLat * PI / 180. ) );
2623 
2624         double north = north1;
2625 
2626         //    Convert COGs trigonometry to standard unit circle
2627         double cosa = cos( ( 90. - cpa_calc_ownship_cog ) * PI / 180. );
2628         double sina = sin( ( 90. - cpa_calc_ownship_cog ) * PI / 180. );
2629         double cosb = cos( ( 90. - cpa_calc_target_cog ) * PI / 180. );
2630         double sinb = sin( ( 90. - cpa_calc_target_cog ) * PI / 180. );
2631 
2632         //    These will be useful
2633         double fc = ( v0 * cosa ) - ( v1 * cosb );
2634         double fs = ( v0 * sina ) - ( v1 * sinb );
2635 
2636         double d = ( fc * fc ) + ( fs * fs );
2637         double tcpa;
2638 
2639         // the tracks are almost parallel
2640         if( fabs( d ) < 1e-6 ) tcpa = 0.;
2641         else
2642             //    Here is the equation for t, which will be in hours
2643             tcpa = ( ( fc * east ) + ( fs * north ) ) / d;
2644 
2645         //    Convert to minutes
2646         ptarget->TCPA = tcpa * 60.;
2647 
2648         //    Calculate CPA
2649         //    Using TCPA, predict ownship and target positions
2650 
2651         double OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA, TargetLonCPA;
2652 
2653         ll_gc_ll( gLat, gLon, cpa_calc_ownship_cog, gSog * tcpa, &OwnshipLatCPA, &OwnshipLonCPA );
2654         ll_gc_ll( ptarget->Lat, ptarget->Lon, cpa_calc_target_cog, ptarget->SOG * tcpa,
2655                 &TargetLatCPA, &TargetLonCPA );
2656 
2657         //   And compute the distance
2658         ptarget->CPA = DistGreatCircle( OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA, TargetLonCPA );
2659 
2660         ptarget->bCPA_Valid = true;
2661 
2662         if( ptarget->TCPA < 0 ) ptarget->bCPA_Valid = false;
2663     }
2664 }
2665 
OnSoundFinishedAISAudio(wxCommandEvent & event)2666 void AIS_Decoder::OnSoundFinishedAISAudio( wxCommandEvent& event )
2667 {
2668     // By clearing this flag the main event loop will trigger repeated
2669     // sounds for as long as the alert condition remains.
2670     m_bAIS_AlertPlaying = false;
2671 }
2672 
OnTimerDSC(wxTimerEvent & event)2673 void AIS_Decoder::OnTimerDSC( wxTimerEvent& event )
2674 {
2675     //  Timer expired, no CDDSE message was received, so accept the latest CDDSC message
2676     if(m_ptentative_dsctarget){
2677         ProcessDSx( m_dsc_last_string, true );
2678     }
2679 }
2680 
2681 
OnTimerAIS(wxTimerEvent & event)2682 void AIS_Decoder::OnTimerAIS( wxTimerEvent& event )
2683 {
2684     TimerAIS.Stop();
2685 
2686     //    Scrub the target hash list
2687     //    removing any targets older than stipulated age
2688 
2689     wxDateTime now = wxDateTime::Now();
2690     now.MakeGMT();
2691 
2692     AIS_Target_Hash::iterator it;
2693     AIS_Target_Hash *current_targets = GetTargetList();
2694 
2695     it = ( *current_targets ).begin();
2696     std::vector<int> remove_array;                    // collector for MMSI of targets to be removed
2697 
2698     while( it != ( *current_targets ).end() ) {
2699         AIS_Target_Data *td = it->second;
2700 
2701         if( NULL == td )                        // This should never happen, but I saw it once....
2702                 {
2703             current_targets->erase( it );
2704             break;                          // leave the loop
2705         }
2706 
2707         int target_posn_age = now.GetTicks() - td->PositionReportTicks;
2708         int target_static_age = now.GetTicks() - td->StaticReportTicks;
2709 
2710         //        Global variables controlling lost target handling
2711         //g_bMarkLost
2712         //g_MarkLost_Mins       // Minutes until black "cross out
2713         //g_bRemoveLost
2714         //g_RemoveLost_Mins);   // minutes until target is removed from screen and internal lists
2715 
2716         //g_bInlandEcdis
2717 
2718         //      Mark lost targets if specified
2719         double removelost_Mins = fmax(g_RemoveLost_Mins,g_MarkLost_Mins);
2720 
2721         if (g_bInlandEcdis && (td->Class != AIS_ARPA)) {
2722             double iECD_LostTimeOut = 0.0;
2723             //special rules apply for europe inland ecdis timeout settings. overrule option settings
2724             //Won't apply for ARPA targets where the radar has all control
2725             if ( td->Class == AIS_CLASS_B){
2726                 if( (td->NavStatus == MOORED) || (td->NavStatus == AT_ANCHOR) )
2727                     iECD_LostTimeOut = 18 * 60;
2728                 else
2729                     iECD_LostTimeOut = 180;
2730 
2731             }
2732             if ( td->Class == AIS_CLASS_A){
2733                 if( (td->NavStatus == MOORED) || (td->NavStatus == AT_ANCHOR) ){
2734                     if(td->SOG < 3.)
2735                         iECD_LostTimeOut = 18 * 60;
2736                     else
2737                         iECD_LostTimeOut = 60;
2738                 }
2739                 else
2740                     iECD_LostTimeOut = 60;
2741             }
2742 
2743             if( ( target_posn_age > iECD_LostTimeOut ) && ( td->Class != AIS_GPSG_BUDDY ) )
2744                     td->b_active = false;
2745 
2746             removelost_Mins = (2 * iECD_LostTimeOut) / 60.;
2747         }
2748         else if( g_bMarkLost ) {
2749             if( ( target_posn_age > g_MarkLost_Mins * 60 ) && ( td->Class != AIS_GPSG_BUDDY ) )
2750                 td->b_active = false;
2751         }
2752 
2753         if( td->Class == AIS_SART )
2754             removelost_Mins = 18.0;
2755 
2756         //      Remove lost targets if specified
2757 
2758         if( g_bRemoveLost || g_bInlandEcdis ) {
2759             bool b_arpalost = ( td->Class == AIS_ARPA  && td->b_lost ); //A lost ARPA target would be deleted at once
2760             if ( ( ( target_posn_age > removelost_Mins * 60 ) && ( td->Class != AIS_GPSG_BUDDY ) ) || b_arpalost ) {
2761                 //      So mark the target as lost, with unknown position, and make it not selectable
2762                 td->b_lost = true;
2763                 td->b_positionOnceValid = false;
2764                 td->COG = 360.0;
2765                 td->SOG = 103.0;
2766                 td->HDG = 511.0;
2767                 td->ROTAIS = -128;
2768 
2769                 SendJSONMsg(td);
2770 
2771                 long mmsi_long = td->MMSI;
2772                 pSelectAIS->DeleteSelectablePoint( (void *) mmsi_long, SELTYPE_AISTARGET );
2773 
2774                 //      If we have not seen a static report in 3 times the removal spec,
2775                 //      then remove the target from all lists
2776                 //      or a lost ARPA target.
2777                 if ( target_static_age > removelost_Mins * 60 * 3 || b_arpalost ) {
2778                     td->b_removed = true;
2779                     SendJSONMsg(td);
2780                     remove_array.push_back(td->MMSI);         //Add this target to removal list
2781                 }
2782             }
2783         }
2784 
2785         // Remove any targets specified as to be "ignored", so that they won't trigger phantom alerts (e.g. SARTs)
2786         for(unsigned int i=0 ; i < g_MMSI_Props_Array.GetCount() ; i++){
2787             MMSIProperties *props =  g_MMSI_Props_Array[i];
2788             if(td->MMSI == props->MMSI){
2789                 if(props->m_bignore) {
2790                     remove_array.push_back(td->MMSI);         //Add this target to removal list
2791                     td->b_removed = true;
2792                     SendJSONMsg(td);
2793                 }
2794                 break;
2795             }
2796         }
2797 
2798 
2799         ++it;
2800     }
2801 
2802     //  Remove all the targets collected in remove_array in one pass
2803     for(unsigned int i=0 ; i < remove_array.size() ; i++){
2804         AIS_Target_Hash::iterator itd = current_targets->find( remove_array[i] );
2805         if(itd != current_targets->end() ){
2806             AIS_Target_Data *td = itd->second;
2807             current_targets->erase(itd);
2808             delete td;
2809         }
2810     }
2811 
2812     UpdateAllCPA();
2813     UpdateAllAlarms();
2814 
2815     //    Update the general suppression flag
2816     m_bSuppressed = false;
2817     if( g_bAIS_CPA_Alert_Suppress_Moored || g_bHideMoored
2818         || (g_bShowScaled && g_bAllowShowScaled) )
2819         m_bSuppressed = true;
2820 
2821     m_bAIS_Audio_Alert_On = false;            // default, may be set on
2822 
2823     //    Process any Alarms
2824 
2825     //    If the AIS Alert Dialog is not currently shown....
2826 
2827     //    Scan all targets, looking for SART, DSC Distress, and CPA incursions
2828     //    In the case of multiple targets of the same type, select the shortest range or shortest TCPA
2829     AIS_Target_Data *palert_target = NULL;
2830 
2831     if( NULL == g_pais_alert_dialog_active ) {
2832         pAISMOBRoute = NULL;                // Reset the AISMOB auto route.
2833         double tcpa_min = 1e6;             // really long
2834         double sart_range = 1e6;
2835         AIS_Target_Data *palert_target_cpa = NULL;
2836         AIS_Target_Data *palert_target_sart = NULL;
2837         AIS_Target_Data *palert_target_dsc = NULL;
2838 
2839         for( it = ( *current_targets ).begin(); it != ( *current_targets ).end(); ++it ) {
2840             AIS_Target_Data *td = it->second;
2841             if( td ) {
2842                 if( (td->Class != AIS_SART) &&  (td->Class != AIS_DSC) ) {
2843 
2844                     if( g_bAIS_CPA_Alert && td->b_active ) {
2845                         if( ( AIS_ALERT_SET == td->n_alert_state ) && !td->b_in_ack_timeout ) {
2846                             if( td->TCPA < tcpa_min ) {
2847                                 tcpa_min = td->TCPA;
2848                                 palert_target_cpa = td;
2849                             }
2850                         }
2851                     }
2852                 }
2853                 else if( (td->Class == AIS_DSC ) && ( td->ShipType == 12) ){
2854                     if( td->b_active ) {
2855                         if( ( AIS_ALERT_SET == td->n_alert_state ) && !td->b_in_ack_timeout ) {
2856                             palert_target_dsc = td;
2857                         }
2858                     }
2859                 }
2860 
2861                 else if( td->Class == AIS_SART ){
2862                     if( td->b_active ) {
2863                         if( ( AIS_ALERT_SET == td->n_alert_state ) && !td->b_in_ack_timeout ) {
2864                             if( td->Range_NM < sart_range ) {
2865                                 tcpa_min = sart_range;
2866                                 palert_target_sart = td;
2867                             }
2868                         }
2869                     }
2870                 }
2871             }
2872         }
2873 
2874         //    Which of multiple targets?
2875         //    Give priority to SART targets, then DSC Distress, then CPA incursion
2876 
2877         palert_target = palert_target_cpa;
2878 
2879         if( palert_target_sart )
2880             palert_target = palert_target_sart;
2881 
2882         if( palert_target_dsc )
2883             palert_target = palert_target_dsc;
2884 
2885 
2886         //    Show the alert
2887         if( palert_target ) {
2888 
2889             bool b_jumpto = (palert_target->Class == AIS_SART) || (palert_target->Class == AIS_DSC);
2890             bool b_createWP = palert_target->Class == AIS_DSC;
2891             bool b_ack = palert_target->Class != AIS_DSC;
2892 
2893         //    Show the Alert dialog
2894 
2895 //      See FS# 968/998
2896 //      If alert occurs while OCPN is iconized to taskbar, then clicking the taskbar icon
2897 //      only brings up the Alert dialog, and not the entire application.
2898 //      This is an OS specific behavior, not seen on linux or Mac.
2899 //      This patch will allow the audio alert to occur, and the visual alert will pop up soon
2900 //      after the user selects the OCPN icon from the taskbar. (on the next timer tick, probably)
2901 
2902 #ifndef __OCPN__ANDROID__
2903             if( gFrame->IsIconized() || !gFrame->IsActive() )
2904                 gFrame->RequestUserAttention();
2905 #endif
2906 
2907             if( !gFrame->IsIconized() ){
2908                 AISTargetAlertDialog *pAISAlertDialog = new AISTargetAlertDialog();
2909                 pAISAlertDialog->Create( palert_target->MMSI, m_parent_frame, this,
2910                                          b_jumpto, b_createWP, b_ack,
2911                                          -1, _("AIS Alert"));
2912                 g_Platform->PositionAISAlert(pAISAlertDialog);
2913 
2914                 g_pais_alert_dialog_active = pAISAlertDialog;
2915                 pAISAlertDialog->Show();                     // Show modeless, so it stays on the screen
2916             }
2917 
2918             //    Audio alert if requested
2919             m_bAIS_Audio_Alert_On = true;             // always on when alert is first shown
2920         }
2921     }
2922 
2923     //    The AIS Alert dialog is already shown.  If the  dialog MMSI number is still alerted, update the dialog
2924     //    otherwise, destroy the dialog
2925     else {
2926         palert_target = Get_Target_Data_From_MMSI( g_pais_alert_dialog_active->Get_Dialog_MMSI() );
2927 
2928         if( palert_target ) {
2929             if( ( ( AIS_ALERT_SET == palert_target->n_alert_state )
2930                     && !palert_target->b_in_ack_timeout )
2931                     || ( palert_target->Class == AIS_SART ) ) {
2932                 g_pais_alert_dialog_active->UpdateText();
2933             } else {
2934                 g_pais_alert_dialog_active->Close();
2935                 m_bAIS_Audio_Alert_On = false;
2936             }
2937 
2938             if( true == palert_target->b_suppress_audio )
2939                 m_bAIS_Audio_Alert_On = false;
2940             else
2941                 m_bAIS_Audio_Alert_On = true;
2942         } else {                                               // this should not happen, however...
2943             g_pais_alert_dialog_active->Close();
2944             m_bAIS_Audio_Alert_On = false;
2945         }
2946 
2947     }
2948 
2949     //    At this point, the audio flag is set
2950     //    Honor the global flag
2951     if( !g_bAIS_CPA_Alert_Audio )
2952         m_bAIS_Audio_Alert_On = false;
2953 
2954     if( m_bAIS_Audio_Alert_On ) {
2955         if (!m_AIS_Sound) {
2956             m_AIS_Sound = SoundFactory();
2957         }
2958         if ( !AIS_AlertPlaying() ) {
2959             m_bAIS_AlertPlaying = true;
2960             m_AIS_Sound->SetCmd( g_CmdSoundString.mb_str( wxConvUTF8 ) );
2961             m_AIS_Sound->Load(g_sAIS_Alert_Sound_File, g_iSoundDeviceIndex);
2962             if ( m_AIS_Sound->IsOk( ) ) {
2963                 m_AIS_Sound->SetFinishedCallback( onSoundFinished, this );
2964                 if ( !m_AIS_Sound->Play( ) )
2965                     m_bAIS_AlertPlaying = false;
2966             }
2967             else
2968                 m_bAIS_AlertPlaying = false;
2969         }
2970     }
2971     //  If a SART Alert is active, check to see if the MMSI has special properties set
2972     //  indicating that this Alert is a MOB for THIS ship.
2973     if(palert_target && (palert_target->Class == AIS_SART) ){
2974         for(unsigned int i=0 ; i < g_MMSI_Props_Array.GetCount() ; i++){
2975             if(palert_target->MMSI == g_MMSI_Props_Array[i]->MMSI){
2976                 if(pAISMOBRoute)
2977                     gFrame->UpdateAISMOBRoute(palert_target);
2978                 else
2979                     gFrame->ActivateAISMOBRoute(palert_target);
2980                 break;
2981             }
2982         }
2983     }
2984 
2985     TimerAIS.Start( TIMER_AIS_MSEC, wxTIMER_CONTINUOUS );
2986 }
2987 
Get_Target_Data_From_MMSI(int mmsi)2988 AIS_Target_Data *AIS_Decoder::Get_Target_Data_From_MMSI( int mmsi )
2989 {
2990     if( AISTargetList->find( mmsi ) == AISTargetList->end() )     // if entry does not exist....
2991     return NULL;
2992     else
2993         return ( *AISTargetList )[mmsi];          // find current entry
2994 }
2995 
2996 
2997 
2998 ArrayOfMMSIProperties   g_MMSI_Props_Array;
2999 
3000 
3001 //      MMSIProperties Implementation
3002 
MMSIProperties(wxString & spec)3003 MMSIProperties::MMSIProperties( wxString &spec )
3004 {
3005     Init();
3006     wxStringTokenizer tkz( spec, _T(";") );
3007     wxString s;
3008 
3009     s = tkz.GetNextToken();
3010     long mmsil;
3011     s.ToLong(&mmsil);
3012     MMSI = (int)mmsil;
3013 
3014     s = tkz.GetNextToken();
3015     if(s.Len()){
3016         if(s.Upper() == _T("ALWAYS"))
3017             TrackType = TRACKTYPE_ALWAYS;
3018         else if(s.Upper() == _T("NEVER"))
3019             TrackType = TRACKTYPE_NEVER;
3020     }
3021 
3022     s = tkz.GetNextToken();
3023     if(s.Len()){
3024         if(s.Upper() == _T("IGNORE"))
3025             m_bignore = true;
3026     }
3027 
3028     s = tkz.GetNextToken();
3029     if(s.Len()){
3030         if(s.Upper() == _T("MOB"))
3031             m_bMOB = true;
3032     }
3033 
3034     s = tkz.GetNextToken();
3035     if(s.Len()){
3036         if(s.Upper() == _T("VDM"))
3037             m_bVDM = true;
3038     }
3039 
3040     s = tkz.GetNextToken();
3041     if (s.Len()){
3042         if (s.Upper() == _T("FOLLOWER"))
3043             m_bFollower = true;
3044     }
3045 
3046     s = tkz.GetNextToken();
3047     if(s.Len()){
3048         if(s.Upper() == _T("PERSIST"))
3049             m_bPersistentTrack = true;
3050     }
3051 
3052     s = tkz.GetNextToken();
3053     if (s.Len()){
3054         m_ShipName = s.Upper();
3055     }
3056 }
3057 
~MMSIProperties()3058 MMSIProperties::~MMSIProperties()
3059 {
3060 }
3061 
Init(void)3062 void MMSIProperties::Init(void )
3063 {
3064     MMSI = -1;
3065     TrackType = TRACKTYPE_DEFAULT;
3066     m_bignore = false;
3067     m_bMOB = false;
3068     m_bVDM = false;
3069     m_bFollower = false;
3070     m_bPersistentTrack = false;
3071     m_ShipName = wxEmptyString;
3072 }
3073 
Serialize(void)3074 wxString MMSIProperties::Serialize( void )
3075 {
3076     wxString sMMSI;
3077     wxString s;
3078 
3079     sMMSI.Printf(_T("%d"), MMSI);
3080     sMMSI << _T(";");
3081 
3082     if(TrackType){
3083         if(TRACKTYPE_ALWAYS == TrackType)
3084             sMMSI << _T("always");
3085         else if(TRACKTYPE_NEVER == TrackType)
3086             sMMSI << _T("never");
3087     }
3088     sMMSI << _T(";");
3089 
3090     if(m_bignore){
3091         sMMSI << _T("ignore");
3092     }
3093     sMMSI << _T(";");
3094 
3095     if(m_bMOB){
3096         sMMSI << _T("MOB");
3097     }
3098     sMMSI << _T(";");
3099 
3100     if(m_bVDM){
3101         sMMSI << _T("VDM");
3102     }
3103     sMMSI << _T(";");
3104 
3105     if (m_bFollower){
3106         sMMSI << _T("Follower");
3107     }
3108     sMMSI << _T(";");
3109 
3110     if(m_bPersistentTrack){
3111         sMMSI << _T("PERSIST");
3112     }
3113     sMMSI << _T(";");
3114 
3115     if (m_ShipName == wxEmptyString) {
3116         m_ShipName = GetShipNameFromFile(MMSI);
3117     }
3118     sMMSI << m_ShipName;
3119     return sMMSI;
3120 }
3121 
AISshipNameCache(AIS_Target_Data * pTargetData,AIS_Target_Name_Hash * AISTargetNamesC,AIS_Target_Name_Hash * AISTargetNamesNC,long mmsi)3122 void AISshipNameCache(AIS_Target_Data *pTargetData,
3123                       AIS_Target_Name_Hash *AISTargetNamesC,
3124                       AIS_Target_Name_Hash *AISTargetNamesNC,
3125                       long mmsi)
3126 {
3127     if (g_benableAISNameCache) {
3128         wxString ship_name = wxEmptyString;
3129 
3130         // Check for valid name data
3131         if (!pTargetData->b_nameValid) {
3132             AIS_Target_Name_Hash::iterator it = AISTargetNamesC->find(mmsi);
3133             if (it != AISTargetNamesC->end()) {
3134                 ship_name = (*AISTargetNamesC)[mmsi].Left(20);
3135                 strncpy(pTargetData->ShipName, ship_name.mb_str(), ship_name.length() + 1);
3136                 pTargetData->b_nameValid = true;
3137                 pTargetData->b_nameFromCache = true;
3138             }
3139             else
3140                 if (!g_bUseOnlyConfirmedAISName) {
3141                     it = AISTargetNamesNC->find(mmsi);
3142                     if (it != AISTargetNamesNC->end()) {
3143                         ship_name = (*AISTargetNamesNC)[mmsi].Left(20);
3144                         strncpy(pTargetData->ShipName, ship_name.mb_str(), ship_name.length() + 1);
3145                         pTargetData->b_nameValid = true;
3146                         pTargetData->b_nameFromCache = true;
3147                     }
3148                 }
3149         }
3150         // else there IS a valid name, lets check if it is in one of the hash lists.
3151         else if ((pTargetData->MID ==  5) || (pTargetData->MID == 24) ||
3152                  (pTargetData->MID == 19) || (pTargetData->MID == 123) ) { //123: Has got a name from SignalK
3153             //  This message contains ship static data, so has a name field
3154             pTargetData->b_nameFromCache = false;
3155             ship_name = trimAISField(pTargetData->ShipName);
3156             AIS_Target_Name_Hash::iterator itC = AISTargetNamesC->find(mmsi);
3157             AIS_Target_Name_Hash::iterator itNC = AISTargetNamesNC->find(mmsi);
3158             if (itC != AISTargetNamesC->end())
3159             {   //There is a confirmed entry for this mmsi
3160                 if ((*AISTargetNamesC)[mmsi] == ship_name)
3161                 {  //Received name is same as confirmed name
3162                     if (itNC != AISTargetNamesNC->end())
3163                     {  //there is also an entry in the NC list, delete it
3164                         AISTargetNamesNC->erase(itNC);
3165                     }
3166                 }
3167                 else
3168                 { //There is a confirmed name but that one is different
3169                     if (itNC != AISTargetNamesNC->end())
3170                     {  //there is an entry in the NC list check if name is same
3171                         if ((*AISTargetNamesNC)[mmsi] == ship_name)
3172                         {  //Same name is already in NC list so promote till confirmed list
3173                             (*AISTargetNamesC)[mmsi] = ship_name;
3174                             // And delete from NC list
3175                             AISTargetNamesNC->erase(itNC);
3176                         }
3177                         else { //A different name is in the NC list, update with received one
3178                             (*AISTargetNamesNC)[mmsi] = ship_name;
3179                         }
3180                         if (g_bUseOnlyConfirmedAISName)
3181                             strncpy(pTargetData->ShipName, (*AISTargetNamesC)[mmsi].mb_str(), (*AISTargetNamesC)[mmsi].Left(20).Length() + 1);
3182                     }
3183                 }
3184             }
3185             else { //No confirmed entry available
3186                 if (itNC != AISTargetNamesNC->end())
3187                 {  //there is  an entry in the NC list,
3188                     if ((*AISTargetNamesNC)[mmsi] == ship_name)
3189                     {  //Received name same as already in NC list, promote to confirmen
3190                         (*AISTargetNamesC)[mmsi] = ship_name;
3191                         // And delete from NC list
3192                         AISTargetNamesNC->erase(itNC);
3193                     }
3194                     else { //entry in NC list is not same as received one
3195                         (*AISTargetNamesNC)[mmsi] = ship_name;
3196                     }
3197                 }
3198                 else { //No entry in NC list so add it
3199                     (*AISTargetNamesNC)[mmsi] = ship_name;
3200                 }
3201                 if (g_bUseOnlyConfirmedAISName) { //copy back previous name
3202                     strncpy(pTargetData->ShipName, "Unknown             ", SHIP_NAME_LEN);
3203                 }
3204             }
3205         }
3206     }
3207 }
3208 
GetShipNameFromFile(int nmmsi)3209 wxString GetShipNameFromFile(int nmmsi)
3210 {
3211     wxString name = wxEmptyString;
3212     if (g_benableAISNameCache){
3213         std::ifstream infile(AISTargetNameFileName.mb_str());
3214         if (infile) {
3215             std::string line;
3216             while (getline(infile, line)) {
3217                 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()), _T(","));
3218                 if (nmmsi == wxAtoi(tokenizer.GetNextToken())) {
3219                     name = tokenizer.GetNextToken().Trim();
3220                     break;
3221                 }
3222                 else tokenizer.GetNextToken();
3223             }
3224         }
3225         infile.close();
3226     }
3227     return name;
3228 }
3229 
SendJSONMsg(AIS_Target_Data * pTarget)3230 void AIS_Decoder::SendJSONMsg(AIS_Target_Data* pTarget)
3231 {
3232     //  Only send messages if someone is listening...
3233     if(!g_pi_manager->GetJSONMessageTargetCount())
3234         return;
3235 
3236     // Do JSON message to all Plugin to inform of target
3237     wxJSONValue jMsg;
3238 
3239     wxLongLong t = ::wxGetLocalTimeMillis();
3240 
3241     jMsg[wxS("Source")] = wxS("AIS_Decoder");
3242     jMsg[wxT("Type")] = wxT("Information");
3243     jMsg[wxT("Msg")] = wxS("AIS Target");
3244     jMsg[wxT("MsgId")] = t.GetValue();
3245     jMsg[wxS("lat")] = pTarget->Lat;
3246     jMsg[wxS("lon")] = pTarget->Lon;
3247     jMsg[wxS("sog")] = pTarget->SOG;
3248     jMsg[wxS("cog")] = pTarget->COG;
3249     jMsg[wxS("hdg")] = pTarget->HDG;
3250     jMsg[wxS("mmsi")] = pTarget->MMSI;
3251     jMsg[wxS("class")] = pTarget->Class;
3252     jMsg[wxS("ownship")] = pTarget->b_OwnShip;
3253     jMsg[wxS("active")] = pTarget->b_active;
3254     jMsg[wxS("lost")] = pTarget->b_lost;
3255     wxString l_ShipName = wxString::FromUTF8(pTarget->ShipName);
3256     for(size_t i =0; i < l_ShipName.Len(); i++) {
3257         if(l_ShipName.GetChar(i) == '@') l_ShipName.SetChar(i, '\n');
3258     }
3259     jMsg[wxS("shipname")] = l_ShipName;
3260     wxString l_CallSign = wxString::FromUTF8(pTarget->CallSign);
3261     for(size_t i =0; i < l_CallSign.Len(); i++) {
3262         if(l_CallSign.GetChar(i) == '@') l_CallSign.SetChar(i, '\n');
3263     }
3264     jMsg[wxS("callsign")] = l_CallSign;
3265     jMsg[wxS("removed")] = pTarget->b_removed;
3266     g_pi_manager->SendJSONMessageToAllPlugins( wxT("AIS"), jMsg );
3267 }
3268