40#include <wx/datetime.h>
44#include <wx/textfile.h>
46#include <wx/tokenzr.h>
47#include <wx/filename.h>
51#include "rapidjson/document.h"
52#include "rapidjson/writer.h"
53#include "rapidjson/stringbuffer.h"
61#include "model/geodesic.h"
73static const long long lNaN = 0xfff8000000000000;
74#define NAN (*(double *)&lNaN)
77wxEvtHandler *g_pais_alert_dialog_active;
81wxString GetShipNameFromFile(
int);
105EVT_TIMER(TIMER_AIS1, AisDecoder::OnTimerAIS)
106EVT_TIMER(TIMER_DSC, AisDecoder::OnTimerDSC)
109static constexpr
double ms_to_knot_factor = 1.9438444924406;
115static
bool b_firstrx;
116static
int first_rx_ticks;
118static
double arpa_ref_hdg = NAN;
120static inline
double GeodesicRadToDeg(
double rads) {
121 return rads * 180.0 / M_PI;
124static inline double MS2KNOTS(
double ms) {
return ms * 1.9438444924406; }
126static bool isBuoyMmsi(
unsigned msi) {
131 int mid = msi / 1000000;
132 if ((mid > 200 && mid < 880) || mid >= 970) {
141int AisMeteoNewMmsi(
int,
int,
int,
int,
int);
145 AIS_Target_Name_Hash *AISTargetNamesC,
146 AIS_Target_Name_Hash *AISTargetNamesNC,
long mmsi);
147static void BuildERIShipTypeHash() {
148 make_hash_ERI(8000, _(
"Vessel, type unknown"));
149 make_hash_ERI(8150, _(
"Freightbarge"));
150 make_hash_ERI(8160, _(
"Tankbarge"));
151 make_hash_ERI(8163, _(
"Tankbarge, dry cargo as if liquid (e.g. cement)"));
152 make_hash_ERI(8450, _(
"Service vessel, police patrol, port service"));
153 make_hash_ERI(8430, _(
"Pushboat, single"));
154 make_hash_ERI(8510, _(
"Object, not otherwise specified"));
155 make_hash_ERI(8470, _(
"Object, towed, not otherwise specified"));
156 make_hash_ERI(8490, _(
"Bunkership"));
157 make_hash_ERI(8010, _(
"Motor freighter"));
158 make_hash_ERI(8020, _(
"Motor tanker"));
159 make_hash_ERI(8021, _(
"Motor tanker, liquid cargo, type N"));
160 make_hash_ERI(8022, _(
"Motor tanker, liquid cargo, type C"));
161 make_hash_ERI(8023, _(
"Motor tanker, dry cargo as if liquid (e.g. cement)"));
162 make_hash_ERI(8030, _(
"Container vessel"));
163 make_hash_ERI(8040, _(
"Gas tanker"));
164 make_hash_ERI(8050, _(
"Motor freighter, tug"));
165 make_hash_ERI(8060, _(
"Motor tanker, tug"));
166 make_hash_ERI(8070, _(
"Motor freighter with one or more ships alongside"));
167 make_hash_ERI(8080, _(
"Motor freighter with tanker"));
168 make_hash_ERI(8090, _(
"Motor freighter pushing one or more freighters"));
169 make_hash_ERI(8100, _(
"Motor freighter pushing at least one tank-ship"));
170 make_hash_ERI(8110, _(
"Tug, freighter"));
171 make_hash_ERI(8120, _(
"Tug, tanker"));
172 make_hash_ERI(8130, _(
"Tug freighter, coupled"));
173 make_hash_ERI(8140, _(
"Tug, freighter/tanker, coupled"));
174 make_hash_ERI(8161, _(
"Tankbarge, liquid cargo, type N"));
175 make_hash_ERI(8162, _(
"Tankbarge, liquid cargo, type C"));
176 make_hash_ERI(8170, _(
"Freightbarge with containers"));
177 make_hash_ERI(8180, _(
"Tankbarge, gas"));
178 make_hash_ERI(8210, _(
"Pushtow, one cargo barge"));
179 make_hash_ERI(8220, _(
"Pushtow, two cargo barges"));
180 make_hash_ERI(8230, _(
"Pushtow, three cargo barges"));
181 make_hash_ERI(8240, _(
"Pushtow, four cargo barges"));
182 make_hash_ERI(8250, _(
"Pushtow, five cargo barges"));
183 make_hash_ERI(8260, _(
"Pushtow, six cargo barges"));
184 make_hash_ERI(8270, _(
"Pushtow, seven cargo barges"));
185 make_hash_ERI(8280, _(
"Pushtow, eight cargo barges"));
186 make_hash_ERI(8290, _(
"Pushtow, nine or more barges"));
187 make_hash_ERI(8310, _(
"Pushtow, one tank/gas barge"));
189 _(
"Pushtow, two barges at least one tanker or gas barge"));
191 _(
"Pushtow, three barges at least one tanker or gas barge"));
193 _(
"Pushtow, four barges at least one tanker or gas barge"));
195 _(
"Pushtow, five barges at least one tanker or gas barge"));
197 _(
"Pushtow, six barges at least one tanker or gas barge"));
199 _(
"Pushtow, seven barges at least one tanker or gas barge"));
201 _(
"Pushtow, eight barges at least one tanker or gas barge"));
203 8390, _(
"Pushtow, nine or more barges at least one tanker or gas barge"));
204 make_hash_ERI(8400, _(
"Tug, single"));
205 make_hash_ERI(8410, _(
"Tug, one or more tows"));
206 make_hash_ERI(8420, _(
"Tug, assisting a vessel or linked combination"));
207 make_hash_ERI(8430, _(
"Pushboat, single"));
208 make_hash_ERI(8440, _(
"Passenger ship, ferry, cruise ship, red cross ship"));
209 make_hash_ERI(8441, _(
"Ferry"));
210 make_hash_ERI(8442, _(
"Red cross ship"));
211 make_hash_ERI(8443, _(
"Cruise ship"));
212 make_hash_ERI(8444, _(
"Passenger ship without accommodation"));
213 make_hash_ERI(8460, _(
"Vessel, work maintenance craft, floating derrick, "
214 "cable-ship, buoy-ship, dredge"));
215 make_hash_ERI(8480, _(
"Fishing boat"));
216 make_hash_ERI(8500, _(
"Barge, tanker, chemical"));
217 make_hash_ERI(1500, _(
"General cargo Vessel maritime"));
218 make_hash_ERI(1510, _(
"Unit carrier maritime"));
219 make_hash_ERI(1520, _(
"Bulk carrier maritime"));
220 make_hash_ERI(1530, _(
"Tanker"));
221 make_hash_ERI(1540, _(
"Liquified gas tanker"));
222 make_hash_ERI(1850, _(
"Pleasure craft, longer than 20 metres"));
223 make_hash_ERI(1900, _(
"Fast ship"));
224 make_hash_ERI(1910, _(
"Hydrofoil"));
228static wxString DecodeDSEExpansionCharacters(
const wxString &dseData) {
230 char lookupTable[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
' ',
231 'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
232 'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
233 'W',
'X',
'Y',
'Z',
'.',
',',
'-',
'/',
' '};
235 for (
size_t i = 0; i < dseData.length(); i += 2) {
237 lookupTable[strtol(dseData.Mid(i, 2).data(),
nullptr, 10)]);
242static void getMmsiProperties(std::shared_ptr<AisTargetData> &pTargetData) {
246 pTargetData->b_isFollower = props->m_bFollower;
247 pTargetData->b_mPropPersistTrack = props->m_bPersistentTrack;
248 if (TRACKTYPE_NEVER == props->TrackType) {
249 pTargetData->b_show_track =
false;
250 }
else if (TRACKTYPE_ALWAYS == props->TrackType) {
251 pTargetData->b_show_track =
true;
262 const std::shared_ptr<AisTargetData> &ptd) {
263 bool parse_result =
false;
264 bool b_posn_report =
false;
266 wxDateTime now = wxDateTime::Now();
268 int message_ID = bstr->
GetInt(1, 6);
269 ptd->MID = message_ID;
272 int met_mmsi = ptd->MMSI;
275 ptd->MMSI = bstr->
GetInt(9, 30);
277 switch (message_ID) {
283 ptd->NavStatus = bstr->
GetInt(39, 4);
284 ptd->SOG = 0.1 * (bstr->
GetInt(51, 10));
286 int lon = bstr->
GetInt(62, 28);
287 if (lon & 0x08000000)
289 double lon_tentative = lon / 600000.;
291 int lat = bstr->
GetInt(90, 27);
292 if (lat & 0x04000000)
294 double lat_tentative = lat / 600000.;
296 if ((lon_tentative <= 180.) &&
300 ptd->Lon = lon_tentative;
301 ptd->Lat = lat_tentative;
302 ptd->b_positionDoubtful =
false;
303 ptd->b_positionOnceValid =
true;
304 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
305 ptd->PositionReportTicks = now.GetTicks();
307 ptd->b_positionDoubtful =
true;
310 ptd->COG = 0.1 * (bstr->
GetInt(117, 12));
311 ptd->HDG = 1.0 * (bstr->
GetInt(129, 9));
313 ptd->ROTAIS = bstr->
GetInt(43, 8);
314 double rot_dir = 1.0;
316 if (ptd->ROTAIS == 128)
318 else if ((ptd->ROTAIS & 0x80) == 0x80) {
319 ptd->ROTAIS = ptd->ROTAIS - 256;
323 ptd->ROTIND = wxRound(rot_dir * pow((((
double)ptd->ROTAIS) / 4.733),
326 ptd->m_utc_sec = bstr->
GetInt(138, 6);
328 if ((1 == message_ID) ||
331 ptd->SyncState = bstr->
GetInt(151, 2);
332 ptd->SlotTO = bstr->
GetInt(153, 2);
333 if ((ptd->SlotTO == 1) && (ptd->SyncState == 0))
335 ptd->m_utc_hour = bstr->
GetInt(155, 5);
337 ptd->m_utc_min = bstr->
GetInt(160, 7);
339 if ((ptd->m_utc_hour < 24) && (ptd->m_utc_min < 60) &&
340 (ptd->m_utc_sec < 60)) {
341 wxDateTime rx_time(ptd->m_utc_hour, ptd->m_utc_min, ptd->m_utc_sec);
342 rx_ticks = rx_time.GetTicks();
344 first_rx_ticks = rx_ticks;
352 ptd->blue_paddle = bstr->
GetInt(144, 2);
353 ptd->b_blue_paddle = (ptd->blue_paddle == 2);
355 if (!ptd->b_isDSCtarget) ptd->Class = AIS_CLASS_A;
358 int mmsi_start = ptd->MMSI / 10000000;
360 if (mmsi_start == 97) {
361 ptd->Class = AIS_SART;
362 ptd->StaticReportTicks =
377 b_posn_report =
true;
386 ptd->SOG = 0.1 * (bstr->
GetInt(47, 10));
388 int lon = bstr->
GetInt(58, 28);
389 if (lon & 0x08000000)
391 double lon_tentative = lon / 600000.;
393 int lat = bstr->
GetInt(86, 27);
394 if (lat & 0x04000000)
396 double lat_tentative = lat / 600000.;
398 if ((lon_tentative <= 180.) &&
402 ptd->Lon = lon_tentative;
403 ptd->Lat = lat_tentative;
404 ptd->b_positionDoubtful =
false;
405 ptd->b_positionOnceValid =
true;
406 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
407 ptd->PositionReportTicks = now.GetTicks();
409 ptd->b_positionDoubtful =
true;
411 ptd->COG = 0.1 * (bstr->
GetInt(113, 12));
412 ptd->HDG = 1.0 * (bstr->
GetInt(125, 9));
414 ptd->m_utc_sec = bstr->
GetInt(134, 6);
416 if (!ptd->b_isDSCtarget) {
417 if (!isBuoyMmsi(ptd->MMSI))
418 ptd->Class = AIS_CLASS_B;
420 ptd->Class = AIS_BUOY;
423 b_posn_report =
true;
431 ptd->SOG = 0.1 * (bstr->
GetInt(47, 10));
432 int lon = bstr->
GetInt(58, 28);
433 if (lon & 0x08000000)
435 double lon_tentative = lon / 600000.;
437 int lat = bstr->
GetInt(86, 27);
438 if (lat & 0x04000000)
440 double lat_tentative = lat / 600000.;
442 if ((lon_tentative <= 180.) &&
446 ptd->Lon = lon_tentative;
447 ptd->Lat = lat_tentative;
448 ptd->b_positionDoubtful =
false;
449 ptd->b_positionOnceValid =
true;
450 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
451 ptd->PositionReportTicks = now.GetTicks();
453 ptd->b_positionDoubtful =
true;
455 ptd->COG = 0.1 * (bstr->
GetInt(113, 12));
456 ptd->HDG = 1.0 * (bstr->
GetInt(125, 9));
457 ptd->m_utc_sec = bstr->
GetInt(134, 6);
459 bstr->GetStr(144, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
460 ptd->b_nameValid =
true;
461 if (!ptd->b_isDSCtarget) {
462 ptd->ShipType = (
unsigned char)bstr->
GetInt(264, 8);
464 ptd->DimA = bstr->
GetInt(272, 9);
465 ptd->DimB = bstr->
GetInt(281, 9);
466 ptd->DimC = bstr->
GetInt(290, 6);
467 ptd->DimD = bstr->
GetInt(296, 6);
469 if (!ptd->b_isDSCtarget) {
471 if (!isBuoyMmsi(ptd->MMSI))
472 ptd->Class = AIS_CLASS_B;
474 ptd->Class = AIS_BUOY;
477 b_posn_report =
true;
490 int bitCorrection = 10;
494 double lon_tentative = 181.;
495 double lat_tentative = 91.;
498 printf(
"AIS Message 27 - received:\r\n");
499 printf(
"MMSI : %i\r\n", ptd->MMSI);
505 if (!ptd->b_isDSCtarget) ptd->Class = AIS_CLASS_A;
507 ptd->NavStatus = bstr->
GetInt(39, 4);
509 int lon = bstr->
GetInt(45, 18);
510 int lat = bstr->
GetInt(63, 17);
516 if (lat >= (0x4000000 >> bitCorrection)) {
517 lat_tentative = (0x8000000 >> bitCorrection) - lat;
522 if (lon >= (0x8000000 >> bitCorrection)) {
523 lon_tentative = (0x10000000 >> bitCorrection) - lon;
528 lat_tentative = lat_tentative / resolution / 60.0;
529 lon_tentative = lon_tentative / resolution / 60.0;
532 printf(
"Latitude : %f\r\n", lat_tentative);
533 printf(
"Longitude : %f\r\n", lon_tentative);
537 int positionLatency = bstr->
GetInt(95, 1);
539 if ((lon_tentative <= 180.) &&
543 ptd->Lon = lon_tentative;
544 ptd->Lat = lat_tentative;
545 ptd->b_positionDoubtful =
false;
546 ptd->b_positionOnceValid =
true;
547 if (positionLatency == 0) {
550 printf(
"Low latency position report.\r\n");
552 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
553 ptd->PositionReportTicks = now.GetTicks();
556 ptd->b_positionDoubtful =
true;
558 ptd->SOG = 1.0 * (bstr->
GetInt(80, 6));
559 ptd->COG = 1.0 * (bstr->
GetInt(85, 9));
561 b_posn_report =
true;
567 if (bstr->GetBitCount() >= 424) {
569 if (!ptd->b_isDSCtarget) ptd->Class = AIS_CLASS_A;
575 int AIS_version_indicator = bstr->
GetInt(39, 2);
576 if (AIS_version_indicator < 4) {
577 ptd->IMO = bstr->
GetInt(41, 30);
579 bstr->GetStr(71, 42, &ptd->CallSign[0], 7);
580 bstr->GetStr(113, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
581 ptd->b_nameValid =
true;
582 if (!ptd->b_isDSCtarget) {
583 ptd->ShipType = (
unsigned char)bstr->
GetInt(233, 8);
586 ptd->DimA = bstr->
GetInt(241, 9);
587 ptd->DimB = bstr->
GetInt(250, 9);
588 ptd->DimC = bstr->
GetInt(259, 6);
589 ptd->DimD = bstr->
GetInt(265, 6);
591 ptd->ETA_Mo = bstr->
GetInt(275, 4);
592 ptd->ETA_Day = bstr->
GetInt(279, 5);
593 ptd->ETA_Hr = bstr->
GetInt(284, 5);
594 ptd->ETA_Min = bstr->
GetInt(289, 6);
596 ptd->Draft = (double)(bstr->
GetInt(295, 8)) / 10.0;
598 bstr->GetStr(303, 120, &ptd->Destination[0], DESTINATION_LEN - 1);
600 ptd->StaticReportTicks = now.GetTicks();
609 int part_number = bstr->
GetInt(39, 2);
610 if (0 == part_number) {
611 bstr->GetStr(41, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
612 ptd->b_nameValid =
true;
615 }
else if (1 == part_number) {
616 if (!ptd->b_isDSCtarget) {
617 ptd->ShipType = (
unsigned char)bstr->
GetInt(41, 8);
619 bstr->GetStr(91, 42, &ptd->CallSign[0], 7);
621 ptd->DimA = bstr->
GetInt(133, 9);
622 ptd->DimB = bstr->
GetInt(142, 9);
623 ptd->DimC = bstr->
GetInt(151, 6);
624 ptd->DimD = bstr->
GetInt(157, 6);
631 ptd->Class = AIS_BASE;
633 ptd->m_utc_hour = bstr->
GetInt(62, 5);
634 ptd->m_utc_min = bstr->
GetInt(67, 6);
635 ptd->m_utc_sec = bstr->
GetInt(73, 6);
637 int lon = bstr->
GetInt(80, 28);
638 if (lon & 0x08000000)
640 double lon_tentative = lon / 600000.;
642 int lat = bstr->
GetInt(108, 27);
643 if (lat & 0x04000000)
645 double lat_tentative = lat / 600000.;
647 if ((lon_tentative <= 180.) &&
651 ptd->Lon = lon_tentative;
652 ptd->Lat = lat_tentative;
653 ptd->b_positionDoubtful =
false;
654 ptd->b_positionOnceValid =
true;
655 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
656 ptd->PositionReportTicks = now.GetTicks();
658 ptd->b_positionDoubtful =
true;
665 b_posn_report =
true;
671 ptd->SOG = bstr->
GetInt(51, 10);
673 int lon = bstr->
GetInt(62, 28);
674 if (lon & 0x08000000)
676 double lon_tentative = lon / 600000.;
678 int lat = bstr->
GetInt(90, 27);
679 if (lat & 0x04000000)
681 double lat_tentative = lat / 600000.;
683 if ((lon_tentative <= 180.) &&
687 ptd->Lon = lon_tentative;
688 ptd->Lat = lat_tentative;
689 ptd->b_positionDoubtful =
false;
690 ptd->b_positionOnceValid =
true;
691 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
692 ptd->PositionReportTicks = now.GetTicks();
694 ptd->b_positionDoubtful =
true;
697 ptd->COG = 0.1 * (bstr->
GetInt(117, 12));
699 int alt_tent = bstr->
GetInt(39, 12);
700 ptd->altitude = alt_tent;
702 ptd->b_SarAircraftPosnReport =
true;
705 b_posn_report =
true;
711 ptd->ShipType = (
unsigned char)bstr->
GetInt(39, 5);
717 ptd->DimA = bstr->
GetInt(220, 9);
718 ptd->DimB = bstr->
GetInt(229, 9);
719 ptd->DimC = bstr->
GetInt(238, 6);
720 ptd->DimD = bstr->
GetInt(244, 6);
723 ptd->m_utc_sec = bstr->
GetInt(254, 6);
725 int offpos = bstr->
GetInt(260, 1);
726 int virt = bstr->
GetInt(270, 1);
729 ptd->NavStatus = ATON_VIRTUAL;
731 ptd->NavStatus = ATON_REAL;
732 if (ptd->m_utc_sec <= 59 ) {
734 if (offpos) ptd->NavStatus += 1;
737 bstr->GetStr(44, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
740 if (bstr->GetBitCount() > 276) {
741 int nx = ((bstr->GetBitCount() - 272) / 6) * 6;
742 bstr->GetStr(273, nx, &ptd->ShipNameExtension[0], 14);
743 ptd->ShipNameExtension[14] = 0;
745 ptd->ShipNameExtension[0] = 0;
748 ptd->b_nameValid =
true;
752 ptd->Class = AIS_ATON;
754 int lon = bstr->
GetInt(165, 28);
756 if (lon & 0x08000000)
758 double lon_tentative = lon / 600000.;
760 int lat = bstr->
GetInt(193, 27);
762 if (lat & 0x04000000)
764 double lat_tentative = lat / 600000.;
766 if ((lon_tentative <= 180.) &&
770 ptd->Lon = lon_tentative;
771 ptd->Lat = lat_tentative;
772 ptd->b_positionDoubtful =
false;
773 ptd->b_positionOnceValid =
true;
774 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
775 ptd->PositionReportTicks = now.GetTicks();
777 ptd->b_positionDoubtful =
true;
779 b_posn_report =
true;
784 int dac = bstr->
GetInt(41, 10);
785 int fi = bstr->
GetInt(51, 6);
791 ptd->b_isEuroInland =
true;
793 bstr->GetStr(57, 48, &ptd->Euro_VIN[0], 8);
794 ptd->Euro_Length = ((double)bstr->
GetInt(105, 13)) / 10.0;
795 ptd->Euro_Beam = ((double)bstr->
GetInt(118, 10)) / 10.0;
796 ptd->UN_shiptype = bstr->
GetInt(128, 14);
797 ptd->Euro_Draft = ((double)bstr->
GetInt(145, 11)) / 100.0;
801 if (dac == 1 || dac == 366)
805 if (bstr->GetBitCount() >= 111) {
807 an.link_id = bstr->
GetInt(57, 10);
808 an.notice_type = bstr->
GetInt(67, 7);
809 an.month = bstr->
GetInt(74, 4);
810 an.day = bstr->
GetInt(78, 5);
811 an.hour = bstr->
GetInt(83, 5);
812 an.minute = bstr->
GetInt(88, 6);
813 an.duration_minutes = bstr->
GetInt(94, 18);
815 wxDateTime now_ = wxDateTime::Now();
818 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
819 now_.GetYear(), an.hour, an.minute);
824 if (an.start_time > now_ + wxTimeSpan::Hours(48))
825 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
826 now_.GetYear() - 1, an.hour, an.minute);
829 an.start_time + wxTimeSpan::Minutes(an.duration_minutes);
834 if (an.expiry_time < now_ - wxTimeSpan::Hours(24)) {
835 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
836 now_.GetYear() + 1, an.hour, an.minute);
838 an.start_time + wxTimeSpan::Minutes(an.duration_minutes);
843 int subarea_len = 87;
846 float pos_scale = 60000.0;
854 pos_scale = 600000.0;
859 int subarea_count = (bstr->GetBitCount() - 111) / subarea_len;
860 for (
int i = 0; i < subarea_count; ++i) {
861 int base = 111 + i * subarea_len;
863 sa.shape = bstr->
GetInt(base + 1, 3);
864 int scale_factor = 1;
865 if (sa.shape == AIS8_001_22_SHAPE_TEXT) {
868 bstr->GetStr(base + 4, subarea_len - 3, t, 14);
869 sa.text = wxString(t, wxConvUTF8);
871 int scale_multipliers[4] = {1, 10, 100, 1000};
872 scale_factor = scale_multipliers[bstr->
GetInt(base + 4, 2)];
874 case AIS8_001_22_SHAPE_SECTOR:
875 sa.left_bound_deg = bstr->
GetInt(
876 base + 6 + lon_len + lat_len + prec_size + 12, 9);
877 sa.right_bound_deg = bstr->
GetInt(
878 base + 6 + lon_len + lat_len + prec_size + 12 + 9, 9);
879 case AIS8_001_22_SHAPE_CIRCLE:
881 bstr->
GetInt(base + 6 + lon_len + lat_len + 3, 12) *
884 case AIS8_001_22_SHAPE_RECT:
886 bstr->
GetInt(base + 6, lon_len,
true) / pos_scale;
888 bstr->
GetInt(base + 6 + lon_len, lat_len,
true) /
891 bstr->
GetInt(base + 6 + lon_len + lat_len + prec_size,
896 base + 6 + lon_len + lat_len + prec_size + 8, 8) *
898 sa.orient_deg = bstr->
GetInt(
899 base + 6 + lon_len + lat_len + prec_size + 8 + 8, 9);
901 case AIS8_001_22_SHAPE_POLYLINE:
902 case AIS8_001_22_SHAPE_POLYGON:
903 for (
int j = 0; j < 4; ++j) {
904 sa.angles[j] = bstr->
GetInt(base + 6 + j * 20, 10) * 0.5;
906 bstr->
GetInt(base + 16 + j * 20, 10) * scale_factor;
910 an.sub_areas.push_back(sa);
912 ptd->area_notices[an.link_id] = an;
919 if (bstr->GetBitCount() >= 360) {
921 if (met_mmsi != 666) ptd->MMSI = met_mmsi;
924 double lon_tentative = 181.;
925 double lat_tentative = 91.;
927 int lon = bstr->
GetInt(57, 25);
928 int lat = bstr->
GetInt(82, 24);
930 if (lon & 0x01000000)
932 lon_tentative = lon / 60000.;
934 if (lat & 0x00800000)
936 lat_tentative = lat / 60000.;
938 ptd->Lon = lon_tentative;
939 ptd->Lat = lat_tentative;
942 wxString x = ptd->ShipName;
943 if (x.Find(
"METEO") == wxNOT_FOUND) {
945 wxString slat = wxString::Format(
"%0.3f", lat_tentative);
946 wxString slon = wxString::Format(
"%0.3f", lon_tentative);
949 wxString nameID =
"METEO ";
950 nameID << wxString::Format(
"%0.3f", abs(id1) + abs(id2)).Right(3);
951 strncpy(ptd->ShipName, nameID, SHIP_NAME_LEN - 1);
954 ptd->met_data.pos_acc = bstr->
GetInt(106, 1);
955 ptd->met_data.day = bstr->
GetInt(107, 5);
956 ptd->met_data.hour = bstr->
GetInt(112, 5);
957 ptd->met_data.minute = bstr->
GetInt(117, 6);
958 ptd->met_data.wind_kn = bstr->
GetInt(123, 7);
959 ptd->met_data.wind_gust_kn = bstr->
GetInt(130, 7);
960 ptd->met_data.wind_dir = bstr->
GetInt(137, 9);
961 ptd->met_data.wind_gust_dir = bstr->
GetInt(146, 9);
963 int tmp = bstr->
GetInt(155, 11);
964 if (tmp & 0x00000400)
966 ptd->met_data.air_temp = tmp / 10.;
967 ptd->met_data.rel_humid = bstr->
GetInt(166, 7);
968 int dew = bstr->
GetInt(173, 10);
969 if (dew & 0x00000200)
971 ptd->met_data.dew_point = dew / 10.;
976 ptd->met_data.airpress = bstr->
GetInt(183, 9) + 799;
977 ptd->met_data.airpress_tend = bstr->
GetInt(192, 2);
979 int horVis = bstr->
GetInt(194, 8);
980 if (horVis & 0x80u) {
982 ptd->met_data.hor_vis_GT =
true;
984 ptd->met_data.hor_vis_GT =
false;
986 ptd->met_data.hor_vis = horVis / 10.0;
988 ptd->met_data.water_lev_dev = (bstr->
GetInt(202, 12) / 100.) - 10.;
989 ptd->met_data.water_lev_trend = bstr->
GetInt(214, 2);
990 ptd->met_data.current = bstr->
GetInt(216, 8) / 10.;
991 ptd->met_data.curr_dir = bstr->
GetInt(224, 9);
992 ptd->met_data.wave_height = bstr->
GetInt(277, 8) / 10.;
993 ptd->met_data.wave_period = bstr->
GetInt(285, 6);
994 ptd->met_data.wave_dir = bstr->
GetInt(291, 9);
995 ptd->met_data.swell_height = bstr->
GetInt(300, 8) / 10;
996 ptd->met_data.swell_per = bstr->
GetInt(308, 6);
997 ptd->met_data.swell_dir = bstr->
GetInt(314, 9);
998 ptd->met_data.seastate = bstr->
GetInt(323, 4);
1000 int wt = bstr->
GetInt(327, 10);
1001 if (wt & 0x00000200)
1003 ptd->met_data.water_temp = wt / 10.;
1005 ptd->met_data.precipitation = bstr->
GetInt(337, 3);
1006 ptd->met_data.salinity = bstr->
GetInt(340, 9) / 10.;
1007 ptd->met_data.ice = bstr->
GetInt(349, 2);
1009 ptd->Class = AIS_METEO;
1013 ptd->b_NoTrack =
true;
1014 ptd->b_show_track =
false;
1015 ptd->b_positionDoubtful =
false;
1016 ptd->b_positionOnceValid =
true;
1017 b_posn_report =
true;
1018 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
1019 ptd->PositionReportTicks = now.GetTicks();
1020 ptd->b_nameValid =
true;
1022 parse_result =
true;
1028 if (dac == 367 && fi == 33) {
1032 const int size = bstr->GetBitCount();
1035 if (met_mmsi != 666) ptd->MMSI = met_mmsi;
1036 const int startbits = 56;
1037 const int slotsize = 112;
1038 const int slots_count = (size - startbits) / slotsize;
1040 for (
int slot = 0; slot < slots_count; slot++) {
1041 slotbit = slot * slotsize;
1042 int type = bstr->
GetInt(slotbit + 57, 4);
1043 ptd->met_data.hour = bstr->
GetInt(slotbit + 66, 5);
1044 ptd->met_data.minute = bstr->
GetInt(slotbit + 71, 6);
1045 int Site_ID = bstr->
GetInt(slotbit + 77, 7);
1048 if (!ptd->b_nameValid) {
1049 wxString nameID =
"METEO Site: ";
1051 strncpy(ptd->ShipName, nameID, SHIP_NAME_LEN - 1);
1052 ptd->b_nameValid =
true;
1056 int lon = bstr->
GetInt(slotbit + 90, 28);
1057 if (lon & 0x08000000)
1059 ptd->Lon = lon / 600000.;
1061 int lat = bstr->
GetInt(slotbit + 118, 27);
1062 if (lat & 0x04000000)
1064 ptd->Lat = lat / 600000.;
1065 ptd->b_positionOnceValid =
true;
1067 }
else if (type == 1) {
1068 bstr->GetStr(slotbit + 84, 84, &ptd->ShipName[0], SHIP_NAME_LEN);
1069 ptd->b_nameValid =
true;
1071 }
else if (type == 2) {
1073 int descr = bstr->
GetInt(slotbit + 116, 3);
1074 if (descr == 1 || descr == 2) {
1075 ptd->met_data.wind_kn = bstr->
GetInt(slotbit + 84, 7);
1076 ptd->met_data.wind_gust_kn = bstr->
GetInt(slotbit + 91, 7);
1077 ptd->met_data.wind_dir = bstr->
GetInt(slotbit + 98, 9);
1078 ptd->met_data.wind_gust_dir = bstr->
GetInt(slotbit + 107, 9);
1081 }
else if (type == 3) {
1083 int descr = bstr->
GetInt(slotbit + 108, 3);
1084 if (descr == 1 || descr == 2) {
1085 int wltype = bstr->
GetInt(slotbit + 84, 1);
1086 int wl = bstr->
GetInt(slotbit + 85, 16);
1087 if (wl & 0x00004000)
1091 ptd->met_data.water_level = wl / 100.;
1093 ptd->met_data.water_lev_dev = wl / 100.;
1095 ptd->met_data.water_lev_trend = bstr->
GetInt(slotbit + 101, 2);
1096 ptd->met_data.vertical_ref = bstr->
GetInt(slotbit + 103, 5);
1098 }
else if (type == 6) {
1099 ptd->met_data.current = bstr->
GetInt(slotbit + 102, 8) / 10.0;
1100 ptd->met_data.curr_dir = bstr->
GetInt(slotbit + 110, 9);
1101 }
else if (type == 7) {
1103 bstr->
GetInt(slotbit + 111, 3);
1104 if (swell_descr == 1 || swell_descr == 2) {
1105 ptd->met_data.swell_height =
1106 bstr->
GetInt(slotbit + 84, 8) / 10.0;
1107 ptd->met_data.swell_per = bstr->
GetInt(slotbit + 92, 6);
1108 ptd->met_data.swell_dir = bstr->
GetInt(slotbit + 98, 9);
1110 ptd->met_data.seastate = bstr->
GetInt(slotbit + 107, 4);
1111 int wt_descr = bstr->
GetInt(slotbit + 131, 3);
1112 if (wt_descr == 1 || wt_descr == 2)
1113 ptd->met_data.water_temp =
1114 bstr->
GetInt(slotbit + 114, 10) / 10. - 10.;
1116 int wawe_descr = bstr->
GetInt(slotbit + 157, 3);
1117 if (wawe_descr == 1 || wawe_descr == 2) {
1118 ptd->met_data.wave_height =
1119 bstr->
GetInt(slotbit + 134, 8) / 10.0;
1120 ptd->met_data.wave_period = bstr->
GetInt(slotbit + 142, 6);
1121 ptd->met_data.wave_dir = bstr->
GetInt(slotbit + 148, 9);
1123 ptd->met_data.salinity = bstr->
GetInt(slotbit + 160, 9 / 10.0);
1125 }
else if (type == 8) {
1126 ptd->met_data.water_temp =
1127 bstr->
GetInt(slotbit + 84, 10) / 10.0 - 10.0;
1128 ptd->met_data.salinity = bstr->
GetInt(slotbit + 120, 9) / 10.0;
1130 }
else if (type == 9) {
1131 int tmp = bstr->
GetInt(slotbit + 84, 11);
1132 if (tmp & 0x00000400)
1134 ptd->met_data.air_temp = tmp / 10.;
1135 int pp, precip = bstr->
GetInt(slotbit + 98, 2);
1146 ptd->met_data.precipitation = pp;
1147 ptd->met_data.hor_vis = bstr->
GetInt(slotbit + 100, 8) / 10.0;
1148 ptd->met_data.dew_point =
1149 bstr->
GetInt(slotbit + 108, 10) / 10.0 - 20.0;
1150 ptd->met_data.airpress = bstr->
GetInt(slotbit + 121, 9) + 799;
1151 ptd->met_data.airpress_tend = bstr->
GetInt(slotbit + 130, 2);
1152 ptd->met_data.salinity = bstr->
GetInt(slotbit + 135, 9) / 10.0;
1154 }
else if (type == 11) {
1156 int descr = bstr->
GetInt(slotbit + 113, 3);
1157 if (descr == 1 || descr == 2) {
1158 ptd->met_data.wind_kn = bstr->
GetInt(slotbit + 84, 7);
1159 ptd->met_data.wind_gust_kn = bstr->
GetInt(slotbit + 91, 7);
1160 ptd->met_data.wind_dir = bstr->
GetInt(slotbit + 98, 9);
1165 if (ptd->b_positionOnceValid) {
1166 ptd->Class = AIS_METEO;
1170 ptd->b_NoTrack =
true;
1171 ptd->b_show_track =
false;
1172 ptd->b_positionDoubtful =
false;
1173 b_posn_report =
true;
1174 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
1175 ptd->PositionReportTicks = now.GetTicks();
1176 ptd->b_nameValid =
true;
1178 parse_result =
true;
1188 char msg_14_text[968];
1189 if (bstr->GetBitCount() > 40) {
1190 int nx = ((bstr->GetBitCount() - 40) / 6) * 6;
1191 int nd = bstr->GetStr(41, nx, msg_14_text, 968);
1193 nd = wxMin(nd, 967);
1194 msg_14_text[nd] = 0;
1195 ptd->MSG_14_text = wxString(msg_14_text, wxConvUTF8);
1197 parse_result =
true;
1210 if (b_posn_report) ptd->b_lost =
false;
1212 if (
true == parse_result) {
1214 if (!ptd->b_active && !ptd->b_positionDoubtful && b_posn_report)
1215 ptd->b_active =
true;
1218 return parse_result;
1222 : m_signalk_selfid(
""), m_callbacks(callbacks) {
1224 AISTargetNamesC =
new AIS_Target_Name_Hash;
1225 AISTargetNamesNC =
new AIS_Target_Name_Hash;
1227 if (g_benableAISNameCache) {
1231 AIS_Target_Name_Hash *HashFile = AISTargetNamesNC;
1232 wxString line = infile.GetFirstLine();
1233 while (!infile.Eof()) {
1234 if (line.IsSameAs(
"+++==Confirmed Entry's==+++"))
1235 HashFile = AISTargetNamesC;
1237 if (line.IsSameAs(
"+++==Non Confirmed Entry's==+++"))
1238 HashFile = AISTargetNamesNC;
1240 wxStringTokenizer tokenizer(line,
",");
1241 int mmsi = wxAtoi(tokenizer.GetNextToken());
1242 wxString name = tokenizer.GetNextToken().Trim();
1243 (*HashFile)[mmsi] = name;
1246 line = infile.GetNextLine();
1253 BuildERIShipTypeHash();
1255 g_pais_alert_dialog_active =
nullptr;
1256 m_bAIS_Audio_Alert_On =
false;
1260 m_bAIS_AlertPlaying =
false;
1262 TimerAIS.SetOwner(
this, TIMER_AIS1);
1263 TimerAIS.Start(TIMER_AIS_MSEC, wxTIMER_CONTINUOUS);
1265 m_ptentative_dsctarget =
nullptr;
1266 m_dsc_timer.SetOwner(
this, TIMER_DSC);
1273 InitCommListeners();
1276AisDecoder::~AisDecoder() {
1286 wxString content =
"+++==Confirmed Entry's==+++";
1287 AIS_Target_Name_Hash::iterator it;
1288 for (it = AISTargetNamesC->begin(); it != AISTargetNamesC->end(); ++it) {
1289 content.append(
"\r\n");
1290 content.append(wxString::Format(
"%i", it->first));
1291 content.append(
",").append(it->second);
1293 content.append(
"\r\n");
1294 content.append(
"+++==Non Confirmed Entry's==+++");
1295 for (it = AISTargetNamesNC->begin(); it != AISTargetNamesNC->end(); ++it) {
1296 content.append(
"\r\n");
1297 content.append(wxString::Format(
"%i", it->first));
1298 content.append(
",").append(it->second);
1300 outfile.Write(content);
1304 AISTargetNamesC->clear();
1305 delete AISTargetNamesC;
1306 AISTargetNamesNC->clear();
1307 delete AISTargetNamesNC;
1312 m_AIS_Audio_Alert_Timer.Stop();
1317 "First message[1, 2] ticks: %d Last Message [1,2]ticks %d Difference: "
1319 first_rx_ticks, rx_ticks, rx_ticks - first_rx_ticks);
1323bool IsTargetOnTheIgnoreList(
const int &mmsi) {
1328 if (props->m_bignore) {
1336void AisDecoder::InitCommListeners() {
1342 listener_N0183_VDM.
Listen(n0183_msg_VDM,
this, EVT_N0183_VDM);
1345 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1346 HandleN0183_AIS(n0183_msg);
1351 listener_N0183_FRPOS.
Listen(n0183_msg_FRPOS,
this, EVT_N0183_FRPOS);
1353 Bind(EVT_N0183_FRPOS, [&](
const ObservedEvt &ev) {
1355 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1356 HandleN0183_AIS(n0183_msg);
1361 listener_N0183_CDDSC.
Listen(n0183_msg_CDDSC,
this, EVT_N0183_CDDSC);
1362 Bind(EVT_N0183_CDDSC, [&](
const ObservedEvt &ev) {
1364 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1365 HandleN0183_AIS(n0183_msg);
1370 listener_N0183_CDDSE.
Listen(n0183_msg_CDDSE,
this, EVT_N0183_CDDSE);
1371 Bind(EVT_N0183_CDDSE, [&](
const ObservedEvt &ev) {
1373 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1374 HandleN0183_AIS(n0183_msg);
1379 listener_N0183_TLL.
Listen(n0183_msg_TLL,
this, EVT_N0183_TLL);
1383 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1384 HandleN0183_AIS(n0183_msg);
1389 listener_N0183_TTM.
Listen(n0183_msg_ttm,
this, EVT_N0183_TTM);
1392 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1393 HandleN0183_AIS(n0183_msg);
1398 listener_N0183_OSD.
Listen(n0183_msg_OSD,
this, EVT_N0183_OSD);
1401 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1402 HandleN0183_AIS(n0183_msg);
1407 listener_N0183_WPL.
Listen(n0183_msg_WPL,
this, EVT_N0183_WPL);
1410 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1411 HandleN0183_AIS(n0183_msg);
1416 listener_SignalK.
Listen(sk_msg,
this, EVT_SIGNALK);
1418 HandleSignalK(UnpackEvtPointer<SignalkMsg>(ev));
1423 Nmea2000Msg n2k_msg_129038(
static_cast<uint64_t
>(129038));
1424 listener_N2K_129038.
Listen(n2k_msg_129038,
this, EVT_N2K_129038);
1426 HandleN2K_129038(UnpackEvtPointer<Nmea2000Msg>(ev));
1431 Nmea2000Msg n2k_msg_129039(
static_cast<uint64_t
>(129039));
1432 listener_N2K_129039.
Listen(n2k_msg_129039,
this, EVT_N2K_129039);
1434 HandleN2K_129039(UnpackEvtPointer<Nmea2000Msg>(ev));
1439 Nmea2000Msg n2k_msg_129041(
static_cast<uint64_t
>(129041));
1440 listener_N2K_129041.
Listen(n2k_msg_129041,
this, EVT_N2K_129041);
1442 HandleN2K_129041(UnpackEvtPointer<Nmea2000Msg>(ev));
1447 Nmea2000Msg n2k_msg_129794(
static_cast<uint64_t
>(129794));
1448 listener_N2K_129794.
Listen(n2k_msg_129794,
this, EVT_N2K_129794);
1450 HandleN2K_129794(UnpackEvtPointer<Nmea2000Msg>(ev));
1455 Nmea2000Msg n2k_msg_129809(
static_cast<uint64_t
>(129809));
1456 listener_N2K_129809.
Listen(n2k_msg_129809,
this, EVT_N2K_129809);
1458 HandleN2K_129809(UnpackEvtPointer<Nmea2000Msg>(ev));
1463 Nmea2000Msg n2k_msg_129810(
static_cast<uint64_t
>(129810));
1464 listener_N2K_129810.
Listen(n2k_msg_129810,
this, EVT_N2K_129810);
1466 HandleN2K_129810(UnpackEvtPointer<Nmea2000Msg>(ev));
1471 Nmea2000Msg n2k_msg_129793(
static_cast<uint64_t
>(129793));
1472 listener_N2K_129793.
Listen(n2k_msg_129793,
this, EVT_N2K_129793);
1474 HandleN2K_129793(UnpackEvtPointer<Nmea2000Msg>(ev));
1478bool AisDecoder::HandleN0183_AIS(
const N0183MsgPtr &n0183_msg) {
1479 std::string str = n0183_msg->payload;
1480 wxString sentence(str.c_str());
1481 DecodeN0183(sentence);
1486bool AisDecoder::HandleN2K_129038(
const N2000MsgPtr &n2k_msg) {
1487 std::vector<unsigned char> v = n2k_msg->payload;
1490 tN2kAISRepeat Repeat;
1501 tN2kAISNavStatus NavStat = N2kaisns_Under_Way_Motoring;
1502 tN2kAISTransceiverInformation AISTransceiverInformation;
1504 if (ParseN2kPGN129038(v, MessageID, Repeat, UserID, Latitude, Longitude,
1505 Accuracy, RAIM, Seconds, COG, SOG, Heading, ROT,
1506 NavStat, AISTransceiverInformation)) {
1507 unsigned mmsi = UserID;
1509 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1513 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1514 bool bnewtarget =
false;
1516 auto it = AISTargetList.find(mmsi);
1517 if (it == AISTargetList.end())
1519 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1523 pTargetData = it->second;
1526 wxDateTime now = wxDateTime::Now();
1530 pTargetData->MMSI = mmsi;
1531 pTargetData->MID = MessageID;
1532 pTargetData->MMSI = mmsi;
1533 pTargetData->Class = AIS_CLASS_A;
1535 if (97 == pTargetData->MMSI / 10000000) {
1536 pTargetData->Class = AIS_SART;
1538 pTargetData->StaticReportTicks = now.GetTicks();
1540 pTargetData->NavStatus =
static_cast<ais_nav_status
>(NavStat);
1541 if (!N2kIsNA(SOG)) pTargetData->SOG = MS2KNOTS(SOG);
1542 if (!N2kIsNA(COG)) pTargetData->COG = GeodesicRadToDeg(COG);
1543 if (!N2kIsNA(Heading)) pTargetData->HDG = GeodesicRadToDeg(Heading);
1544 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
1545 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
1547 pTargetData->ROTAIS = ROT;
1559 pTargetData->b_OwnShip =
1560 AISTransceiverInformation ==
1561 tN2kAISTransceiverInformation::N2kaisown_information_not_broadcast;
1562 pTargetData->b_active =
true;
1563 pTargetData->b_lost =
false;
1564 pTargetData->b_positionOnceValid =
true;
1565 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1566 pTargetData->PositionReportTicks = now.GetTicks();
1568 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1569 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1578bool AisDecoder::HandleN2K_129039(
const N2000MsgPtr &n2k_msg) {
1579 std::vector<unsigned char> v = n2k_msg->payload;
1594 tN2kAISRepeat Repeat;
1604 tN2kAISTransceiverInformation AISTransceiverInformation;
1606 bool DSC, Band, Msg22, State, Display;
1609 if (ParseN2kPGN129039(v, MessageID, Repeat, UserID, Latitude, Longitude,
1610 Accuracy, RAIM, Seconds, COG, SOG,
1611 AISTransceiverInformation, Heading, Unit, Display, DSC,
1612 Band, Msg22, Mode, State)) {
1613 uint32_t mmsi = UserID;
1615 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1619 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1620 bool bnewtarget =
false;
1622 auto it = AISTargetList.find(mmsi);
1623 if (it == AISTargetList.end())
1625 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1629 pTargetData = it->second;
1632 wxDateTime now = wxDateTime::Now();
1636 pTargetData->MMSI = mmsi;
1637 pTargetData->MID = MessageID;
1638 if (!isBuoyMmsi(mmsi))
1639 pTargetData->Class = AIS_CLASS_B;
1641 pTargetData->Class = AIS_BUOY;
1643 pTargetData->NavStatus = UNDEFINED;
1644 if (!N2kIsNA(SOG)) pTargetData->SOG = MS2KNOTS(SOG);
1645 if (!N2kIsNA(COG)) pTargetData->COG = GeodesicRadToDeg(COG);
1646 if (!N2kIsNA(Heading)) pTargetData->HDG = GeodesicRadToDeg(Heading);
1647 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
1648 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
1650 pTargetData->b_positionOnceValid =
true;
1651 pTargetData->b_active =
true;
1652 pTargetData->b_lost =
false;
1653 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1654 pTargetData->PositionReportTicks = now.GetTicks();
1655 pTargetData->b_OwnShip =
1656 AISTransceiverInformation ==
1657 tN2kAISTransceiverInformation::N2kaisown_information_not_broadcast;
1659 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1660 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1670bool AisDecoder::HandleN2K_129041(
const N2000MsgPtr &n2k_msg) {
1671 std::vector<unsigned char> v = n2k_msg->payload;
1673 tN2kAISAtoNReportData data;
1676 struct tN2kAISAtoNReportData {
1678 tN2kAISRepeat Repeat;
1687 double PositionReferenceStarboard ;
1688 double PositionReferenceTrueNorth;
1689 tN2kAISAtoNType AtoNType;
1690 bool OffPositionIndicator;
1691 bool VirtualAtoNFlag;
1692 bool AssignedModeFlag;
1693 tN2kGNSStype GNSSType;
1695 tN2kAISTransceiverInformation AISTransceiverInformation;
1696 char AtoNName[34 + 1];
1699 if (ParseN2kPGN129041(v, data)) {
1700 uint32_t mmsi = data.UserID;
1702 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1706 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1707 bool bnewtarget =
false;
1709 auto it = AISTargetList.find(mmsi);
1710 if (it == AISTargetList.end())
1712 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1716 pTargetData = it->second;
1720 pTargetData->MMSI = mmsi;
1722 wxDateTime now = wxDateTime::Now();
1725 int offpos = data.OffPositionIndicator;
1726 int virt = data.VirtualAtoNFlag;
1729 pTargetData->NavStatus = ATON_VIRTUAL;
1731 pTargetData->NavStatus = ATON_REAL;
1733 pTargetData->m_utc_sec = data.Seconds;
1735 if (pTargetData->m_utc_sec <= 59) {
1736 pTargetData->NavStatus += 1;
1737 if (offpos) pTargetData->NavStatus += 1;
1740 data.AtoNName[34] = 0;
1741 strncpy(pTargetData->ShipName, data.AtoNName, SHIP_NAME_LEN - 1);
1742 pTargetData->ShipName[
sizeof(pTargetData->ShipName) - 1] =
'\0';
1743 pTargetData->b_nameValid =
true;
1744 pTargetData->MID = 124;
1746 pTargetData->ShipType = data.AtoNType;
1747 pTargetData->Class = AIS_ATON;
1749 if (!N2kIsNA(data.Longitude)) pTargetData->Lon = data.Longitude;
1750 if (!N2kIsNA(data.Latitude)) pTargetData->Lat = data.Latitude;
1751 pTargetData->b_positionDoubtful =
false;
1752 pTargetData->b_positionOnceValid =
true;
1753 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1754 pTargetData->PositionReportTicks = now.GetTicks();
1758 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1759 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1761 touch_state.Notify();
1768bool AisDecoder::HandleN2K_129794(
const N2000MsgPtr &n2k_msg) {
1769 std::vector<unsigned char> v = n2k_msg->payload;
1772 tN2kAISRepeat Repeat;
1776 char Name[SHIP_NAME_LEN];
1785 char Destination[DESTINATION_LEN];
1786 tN2kAISVersion AISversion;
1787 tN2kGNSStype GNSStype;
1789 tN2kAISTranceiverInfo AISinfo;
1791 if (ParseN2kPGN129794(v, MessageID, Repeat, UserID, IMOnumber, Callsign, Name,
1792 VesselType, Length, Beam, PosRefStbd, PosRefBow,
1793 ETAdate, ETAtime, Draught, Destination, AISversion,
1794 GNSStype, DTE, AISinfo)) {
1795 uint32_t mmsi = UserID;
1797 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1801 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1802 bool bnewtarget =
false;
1804 auto it = AISTargetList.find(mmsi);
1805 if (it == AISTargetList.end())
1807 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1811 pTargetData = it->second;
1815 pTargetData->MMSI = mmsi;
1816 strncpy(pTargetData->ShipName, Name, SHIP_NAME_LEN - 1);
1817 pTargetData->ShipName[
sizeof(pTargetData->ShipName) - 1] =
'\0';
1818 Name[
sizeof(Name) - 1] = 0;
1819 pTargetData->b_nameValid =
true;
1820 pTargetData->MID = 124;
1822 pTargetData->b_OwnShip =
1824 tN2kAISTranceiverInfo::N2kaisti_Own_information_not_broadcast;
1826 pTargetData->DimA = PosRefBow;
1827 pTargetData->DimB = Length - PosRefBow;
1828 pTargetData->DimC = Beam - PosRefStbd;
1829 pTargetData->DimD = PosRefStbd;
1830 pTargetData->Draft = Draught;
1831 pTargetData->IMO = IMOnumber;
1832 strncpy(pTargetData->CallSign, Callsign, CALL_SIGN_LEN - 1);
1833 pTargetData->CallSign[
sizeof(pTargetData->CallSign) - 1] =
'\0';
1834 pTargetData->ShipType = (
unsigned char)VesselType;
1835 strncpy(pTargetData->Destination, Destination, DESTINATION_LEN - 1);
1836 pTargetData->Destination[
sizeof(pTargetData->Destination) - 1] =
'\0';
1837 Destination[
sizeof(Destination) - 1] = 0;
1839 if (!N2kIsNA(ETAdate) && !N2kIsNA(ETAtime)) {
1840 long secs = (ETAdate * 24 * 3600) + wxRound(ETAtime);
1841 wxDateTime t((time_t)secs);
1843 wxDateTime tz = t.ToUTC();
1844 pTargetData->ETA_Mo = tz.GetMonth() + 1;
1845 pTargetData->ETA_Day = tz.GetDay();
1846 pTargetData->ETA_Hr = tz.GetHour();
1847 pTargetData->ETA_Min = tz.GetMinute();
1851 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1852 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1860bool AisDecoder::HandleN2K_129809(
const N2000MsgPtr &n2k_msg) {
1861 std::vector<unsigned char> v = n2k_msg->payload;
1864 tN2kAISRepeat Repeat;
1868 if (ParseN2kPGN129809(v, MessageID, Repeat, UserID, Name)) {
1869 unsigned mmsi = UserID;
1871 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1875 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1876 bool bnewtarget =
false;
1878 auto it = AISTargetList.find(mmsi);
1879 if (it == AISTargetList.end())
1881 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1885 pTargetData = it->second;
1889 pTargetData->MMSI = mmsi;
1890 Name[
sizeof(Name) - 1] = 0;
1891 strncpy(pTargetData->ShipName, Name, SHIP_NAME_LEN - 1);
1892 pTargetData->b_nameValid =
true;
1893 pTargetData->MID = 124;
1895 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1896 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1906bool AisDecoder::HandleN2K_129810(
const N2000MsgPtr &n2k_msg) {
1907 std::vector<unsigned char> v = n2k_msg->payload;
1910 tN2kAISRepeat Repeat;
1919 uint32_t MothershipID;
1921 if (ParseN2kPGN129810(v, MessageID, Repeat, UserID, VesselType, Vendor,
1922 Callsign, Length, Beam, PosRefStbd, PosRefBow,
1924 unsigned mmsi = UserID;
1926 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1930 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1931 bool bnewtarget =
false;
1933 auto it = AISTargetList.find(mmsi);
1934 if (it == AISTargetList.end())
1936 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1940 pTargetData = it->second;
1944 pTargetData->MMSI = mmsi;
1945 pTargetData->DimA = PosRefBow;
1946 pTargetData->DimB = Length - PosRefBow;
1947 pTargetData->DimC = Beam - PosRefStbd;
1948 pTargetData->DimD = PosRefStbd;
1949 strncpy(pTargetData->CallSign, Callsign, CALL_SIGN_LEN - 1);
1950 pTargetData->CallSign[
sizeof(pTargetData->CallSign) - 1] =
'\0';
1951 pTargetData->ShipType = (
unsigned char)VesselType;
1953 pSelectAIS->DeleteSelectablePoint((
void *)(long)mmsi, SELTYPE_AISTARGET);
1954 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1963bool AisDecoder::HandleN2K_129793(
const N2000MsgPtr &n2k_msg) {
1964 std::vector<unsigned char> v = n2k_msg->payload;
1967 tN2kAISRepeat Repeat;
1971 unsigned int SecondsSinceMidnight;
1972 unsigned int DaysSinceEpoch;
1974 if (ParseN2kPGN129793(v, MessageID, Repeat, UserID, Longitude, Latitude,
1975 SecondsSinceMidnight, DaysSinceEpoch)) {
1976 wxDateTime now = wxDateTime::Now();
1982 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1983 bool bnewtarget =
false;
1985 auto it = AISTargetList.find(mmsi);
1986 if (it == AISTargetList.end())
1988 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1992 pTargetData = it->second;
1996 pTargetData->MMSI = mmsi;
1997 pTargetData->Class = AIS_BASE;
1999 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
2000 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
2001 pTargetData->b_positionDoubtful =
false;
2002 pTargetData->b_positionOnceValid =
true;
2003 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
2004 pTargetData->PositionReportTicks = now.GetTicks();
2008 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
2009 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
2028void AisDecoder::HandleSignalK(
const SignalKMsgPtr &sK_msg) {
2029 rapidjson::Document root;
2031 root.Parse(sK_msg->raw_message);
2033 if (root.HasParseError())
return;
2035 if (root.HasMember(
"self")) {
2041 if (m_signalk_selfid.IsEmpty()) {
2046 int meteo_SiteID = 0;
2047 if (root.HasMember(
"context") && root[
"context"].IsString()) {
2048 wxString context = root[
"context"].GetString();
2049 if (context == m_signalk_selfid) {
2051 wxLogMessage(
"** Ignore context own ship..");
2055 wxString mmsi_string;
2056 if (context.StartsWith(
"vessels.urn:mrn:imo:mmsi:", &mmsi_string) ||
2057 context.StartsWith(
"atons.urn:mrn:imo:mmsi:", &mmsi_string) ||
2058 context.StartsWith(
"aircraft.urn:mrn:imo:mmsi:", &mmsi_string)) {
2061 if (mmsi_string.ToLong(&mmsi)) {
2066 }
else if (context.StartsWith(
"meteo.urn:mrn:imo:mmsi:", &mmsi_string)) {
2068 origin_mmsi = wxAtoi(wxString(mmsi_string).BeforeFirst(
':'));
2069 meteo_SiteID = wxAtoi(
'1' + wxString(mmsi_string).AfterFirst(
':'));
2072 int meteo_mmsi = AisMeteoNewMmsi(origin_mmsi, 0, 0, 999, meteo_SiteID);
2083 if (g_pMUX && g_pMUX->IsLogActive()) {
2085 logmsg.Printf(
"AIS :MMSI: %ld", mmsi);
2090 if (IsTargetOnTheIgnoreList(mmsi))
return;
2098 writer.
Write(root, dbg);
2100 wxString msg(
"AisDecoder::OnEvtSignalK: " );
2104 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
2105 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
2106 bool bnewtarget =
false;
2107 int last_report_ticks;
2109 getAISTarget(mmsi, pTargetData, pStaleTarget, bnewtarget, last_report_ticks,
2112 pTargetData->MMSI = mmsi;
2113 getMmsiProperties(pTargetData);
2114 if (root.HasMember(
"updates") && root[
"updates"].IsArray()) {
2115 for (rapidjson::Value::ConstValueIterator itr = root[
"updates"].Begin();
2116 itr != root[
"updates"].End(); ++itr) {
2117 handleUpdate(pTargetData, bnewtarget, *itr);
2122 if (97 == mmsi / 10000000) {
2123 pTargetData->Class = AIS_SART;
2124 }
else if (1994 == mmsi / 100000) {
2126 pTargetData->Class = AIS_METEO;
2127 pTargetData->met_data.original_mmsi = origin_mmsi;
2128 pTargetData->met_data.stationID = meteo_SiteID;
2131 wxString met_name = pTargetData->ShipName;
2132 if (met_name.Find(
"METEO") == wxNOT_FOUND) {
2135 s_id << meteo_SiteID;
2136 id1 = wxAtoi(s_id.Mid(1, 3));
2137 id2 = wxAtoi(s_id.Mid(4, 3));
2138 met_name =
"METEO ";
2139 met_name << wxString::Format(
"%03d", (id1 + id2)).Right(3);
2140 strncpy(pTargetData->ShipName, met_name, SHIP_NAME_LEN - 1);
2142 pTargetData->b_nameValid =
true;
2143 pTargetData->MID = 123;
2144 pTargetData->COG = -1.;
2145 pTargetData->HDG = 511;
2146 pTargetData->SOG = -1.;
2147 pTargetData->b_NoTrack =
true;
2148 pTargetData->b_show_track =
false;
2150 pTargetData->b_OwnShip =
false;
2151 AISTargetList[pTargetData->MMSI] = pTargetData;
2155void AisDecoder::handleUpdate(
const std::shared_ptr<AisTargetData> &pTargetData,
2156 bool bnewtarget,
const rapidjson::Value &update) {
2157 wxString sfixtime =
"";
2159 if (update.HasMember(
"timestamp")) {
2160 sfixtime = update[
"timestamp"].GetString();
2162 if (update.HasMember(
"values") && update[
"values"].IsArray()) {
2163 for (rapidjson::Value::ConstValueIterator itr = update[
"values"].Begin();
2164 itr != update[
"values"].End(); ++itr) {
2165 updateItem(pTargetData, bnewtarget, *itr, sfixtime);
2168 wxDateTime now = wxDateTime::Now();
2169 pTargetData->m_utc_hour = now.ToUTC().GetHour();
2170 pTargetData->m_utc_min = now.ToUTC().GetMinute();
2171 pTargetData->m_utc_sec = now.ToUTC().GetSecond();
2173 pTargetData->b_active =
true;
2174 pTargetData->b_lost =
false;
2176 if (pTargetData->b_positionOnceValid) {
2177 long mmsi_long = pTargetData->MMSI;
2179 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
2180 (
void *)mmsi_long, SELTYPE_AISTARGET);
2181 pSel->SetUserData(pTargetData->MMSI);
2183 UpdateOneCPA(pTargetData.get());
2184 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
2187void AisDecoder::updateItem(
const std::shared_ptr<AisTargetData> &pTargetData,
2188 bool bnewtarget,
const rapidjson::Value &item,
2189 wxString &sfixtime)
const {
2190 if (item.HasMember(
"path") && item.HasMember(
"value")) {
2191 const wxString &update_path = item[
"path"].GetString();
2192 if (update_path ==
"navigation.position") {
2193 if (item[
"value"].HasMember(
"latitude") &&
2194 item[
"value"].HasMember(
"longitude")) {
2195 wxDateTime now = wxDateTime::Now();
2197 double lat = item[
"value"][
"latitude"].GetDouble();
2198 double lon = item[
"value"][
"longitude"].GetDouble();
2199 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
2200 pTargetData->PositionReportTicks = now.GetTicks();
2201 pTargetData->StaticReportTicks = now.GetTicks();
2202 pTargetData->Lat = lat;
2203 pTargetData->Lon = lon;
2204 pTargetData->b_positionOnceValid =
true;
2205 pTargetData->b_positionDoubtful =
false;
2212 }
else if (update_path ==
"navigation.speedOverGround" &&
2213 item[
"value"].IsNumber()) {
2214 pTargetData->SOG = item[
"value"].GetDouble() * ms_to_knot_factor;
2215 }
else if (update_path ==
"navigation.courseOverGroundTrue" &&
2216 item[
"value"].IsNumber()) {
2217 pTargetData->COG = GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2218 }
else if (update_path ==
"navigation.headingTrue" &&
2219 item[
"value"].IsNumber()) {
2220 pTargetData->HDG = GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2221 }
else if (update_path ==
"navigation.rateOfTurn" &&
2222 item[
"value"].IsNumber()) {
2223 pTargetData->ROTAIS = 4.733 * sqrt(item[
"value"].GetDouble());
2224 }
else if (update_path ==
"design.aisShipType") {
2225 if (item[
"value"].HasMember(
"id")) {
2226 if (!pTargetData->b_isDSCtarget) {
2227 pTargetData->ShipType = item[
"value"][
"id"].GetUint();
2230 }
else if (update_path ==
"atonType") {
2231 if (item[
"value"].HasMember(
"id")) {
2232 pTargetData->ShipType = item[
"value"][
"id"].GetUint();
2234 }
else if (update_path ==
"virtual") {
2235 if (item[
"value"].GetBool()) {
2236 pTargetData->NavStatus = ATON_VIRTUAL;
2238 pTargetData->NavStatus = ATON_REAL;
2240 }
else if (update_path ==
"offPosition") {
2241 if (item[
"value"].GetBool()) {
2242 if (ATON_REAL == pTargetData->NavStatus) {
2243 pTargetData->NavStatus = ATON_REAL_OFFPOSITION;
2244 }
else if (ATON_VIRTUAL == pTargetData->NavStatus) {
2245 pTargetData->NavStatus = ATON_VIRTUAL_OFFPOSITION;
2248 }
else if (update_path ==
"design.draft") {
2249 if (item[
"value"].HasMember(
"maximum") &&
2250 item[
"value"][
"maximum"].IsNumber()) {
2251 pTargetData->Draft = item[
"value"][
"maximum"].GetDouble();
2252 pTargetData->Euro_Draft = item[
"value"][
"maximum"].GetDouble();
2254 if (item[
"value"].HasMember(
"current") &&
2255 item[
"value"][
"current"].IsNumber()) {
2256 double draft = item[
"value"][
"current"].GetDouble();
2258 pTargetData->Draft = draft;
2259 pTargetData->Euro_Draft = draft;
2262 }
else if (update_path ==
"design.length") {
2263 if (pTargetData->DimB == 0) {
2264 if (item[
"value"].HasMember(
"overall")) {
2265 if (item[
"value"][
"overall"].IsNumber()) {
2266 pTargetData->Euro_Length = item[
"value"][
"overall"].GetDouble();
2267 pTargetData->DimA = item[
"value"][
"overall"].GetDouble();
2269 pTargetData->DimB = 0;
2272 }
else if (update_path ==
"sensors.ais.class") {
2273 wxString aisclass = item[
"value"].GetString();
2274 if (aisclass ==
"A") {
2275 if (!pTargetData->b_isDSCtarget) pTargetData->Class = AIS_CLASS_A;
2276 }
else if (aisclass ==
"B") {
2277 if (!pTargetData->b_isDSCtarget) {
2278 if (!isBuoyMmsi(pTargetData->MMSI))
2279 pTargetData->Class = AIS_CLASS_B;
2281 pTargetData->Class = AIS_BUOY;
2284 pTargetData->NavStatus = UNDEFINED;
2286 }
else if (aisclass ==
"BASE") {
2287 pTargetData->Class = AIS_BASE;
2288 }
else if (aisclass ==
"ATON") {
2289 pTargetData->Class = AIS_ATON;
2291 }
else if (update_path ==
"sensors.ais.fromBow") {
2292 if (pTargetData->DimB == 0 && pTargetData->DimA != 0) {
2293 int length = pTargetData->DimA;
2294 if (item[
"value"].IsNumber()) {
2295 pTargetData->DimA = item[
"value"].GetDouble();
2296 pTargetData->DimB = length - item[
"value"].GetDouble();
2299 }
else if (update_path ==
"design.beam") {
2300 if (pTargetData->DimD == 0) {
2301 if (item[
"value"].IsNumber()) {
2302 pTargetData->Euro_Beam = item[
"value"].GetDouble();
2303 pTargetData->DimC = item[
"value"].GetDouble();
2305 pTargetData->DimD = 0;
2307 }
else if (update_path ==
"sensors.ais.fromCenter") {
2308 if (pTargetData->DimD == 0 && pTargetData->DimC != 0) {
2309 int beam = pTargetData->DimC;
2310 int center = beam / 2;
2311 if (item[
"value"].IsNumber()) {
2314 pTargetData->DimC = center + item[
"value"].GetDouble();
2315 pTargetData->DimD = beam - pTargetData->DimC;
2318 }
else if (update_path ==
"navigation.state") {
2319 wxString state = item[
"value"].GetString();
2320 if (state ==
"motoring") {
2321 pTargetData->NavStatus = UNDERWAY_USING_ENGINE;
2322 }
else if (state ==
"anchored") {
2323 pTargetData->NavStatus = AT_ANCHOR;
2324 }
else if (state ==
"not under command") {
2325 pTargetData->NavStatus = NOT_UNDER_COMMAND;
2326 }
else if (state ==
"restricted manouverability") {
2327 pTargetData->NavStatus = RESTRICTED_MANOEUVRABILITY;
2328 }
else if (state ==
"constrained by draft") {
2329 pTargetData->NavStatus = CONSTRAINED_BY_DRAFT;
2330 }
else if (state ==
"moored") {
2331 pTargetData->NavStatus = MOORED;
2332 }
else if (state ==
"aground") {
2333 pTargetData->NavStatus = AGROUND;
2334 }
else if (state ==
"fishing") {
2335 pTargetData->NavStatus = FISHING;
2336 }
else if (state ==
"sailing") {
2337 pTargetData->NavStatus = UNDERWAY_SAILING;
2338 }
else if (state ==
"hazardous material high speed") {
2339 pTargetData->NavStatus = HSC;
2340 }
else if (state ==
"hazardous material wing in ground") {
2341 pTargetData->NavStatus = WIG;
2342 }
else if (state ==
"ais-sart") {
2343 pTargetData->NavStatus = RESERVED_14;
2345 pTargetData->NavStatus = UNDEFINED;
2347 }
else if (update_path ==
"navigation.destination.commonName") {
2348 const wxString &destination = item[
"value"].GetString();
2349 pTargetData->Destination[0] =
'\0';
2350 strncpy(pTargetData->Destination, destination.c_str(),
2351 DESTINATION_LEN - 1);
2352 }
else if (update_path ==
"navigation.specialManeuver") {
2353 if (strcmp(
"not available", item[
"value"].GetString()) != 0 &&
2354 pTargetData->IMO < 1) {
2355 const wxString &bluesign = item[
"value"].GetString();
2356 if (
"not engaged" == bluesign) {
2357 pTargetData->blue_paddle = 1;
2359 if (
"engaged" == bluesign) {
2360 pTargetData->blue_paddle = 2;
2362 pTargetData->b_blue_paddle =
2363 pTargetData->blue_paddle == 2 ? true :
false;
2365 }
else if (update_path ==
"sensors.ais.designatedAreaCode") {
2366 if (item[
"value"].GetInt() == 200) {
2367 pTargetData->b_hasInlandDac =
true;
2369 }
else if (update_path ==
"sensors.ais.functionalId") {
2370 if (item[
"value"].GetInt() == 10 && pTargetData->b_hasInlandDac) {
2372 pTargetData->b_isEuroInland =
true;
2376 }
else if (update_path ==
"environment.date") {
2377 wxString issued = item[
"value"].GetString();
2382 pTargetData->met_data.day = tz.GetDay();
2383 pTargetData->met_data.hour = tz.GetHour();
2384 pTargetData->met_data.minute = tz.GetMinute();
2386 }
else if (update_path ==
"environment.wind.averageSpeed" &&
2387 item[
"value"].IsNumber()) {
2388 pTargetData->met_data.wind_kn = MS2KNOTS(item[
"value"].GetDouble());
2389 }
else if (update_path ==
"environment.wind.gust" &&
2390 item[
"value"].IsNumber()) {
2391 pTargetData->met_data.wind_gust_kn = MS2KNOTS(item[
"value"].GetDouble());
2392 }
else if (update_path ==
"environment.wind.directionTrue" &&
2393 item[
"value"].IsNumber()) {
2394 pTargetData->met_data.wind_dir =
2395 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2396 }
else if (update_path ==
"environment.wind.gustDirectionTrue" &&
2397 item[
"value"].IsNumber()) {
2398 pTargetData->met_data.wind_gust_dir =
2399 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2400 }
else if (update_path ==
"environment.outside.temperature" &&
2401 item[
"value"].IsNumber()) {
2402 pTargetData->met_data.air_temp = KelvinToC(item[
"value"].GetDouble());
2403 }
else if (update_path ==
"environment.outside.relativeHumidity" &&
2404 item[
"value"].IsNumber()) {
2405 pTargetData->met_data.rel_humid = item[
"value"].GetDouble();
2406 }
else if (update_path ==
"environment.outside.dewPointTemperature" &&
2407 item[
"value"].IsNumber()) {
2408 pTargetData->met_data.dew_point = KelvinToC(item[
"value"].GetDouble());
2409 }
else if (update_path ==
"environment.outside.pressure" &&
2410 item[
"value"].IsNumber()) {
2411 pTargetData->met_data.airpress =
2412 static_cast<int>(item[
"value"].GetDouble() / 100);
2413 }
else if (update_path ==
"environment.water.level" &&
2414 item[
"value"].IsNumber()) {
2415 pTargetData->met_data.water_lev_dev = item[
"value"].GetDouble();
2416 }
else if (update_path ==
"environment.water.current.drift" &&
2417 item[
"value"].IsNumber()) {
2418 pTargetData->met_data.current = MS2KNOTS(item[
"value"].GetDouble());
2419 }
else if (update_path ==
"environment.water.current.set" &&
2420 item[
"value"].IsNumber()) {
2421 pTargetData->met_data.curr_dir =
2422 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2423 }
else if (update_path ==
"environment.water.levelTendencyValue" &&
2424 item[
"value"].IsNumber()) {
2425 pTargetData->met_data.water_lev_trend =
2426 static_cast<int>(item[
"value"].GetDouble());
2427 }
else if (update_path ==
"environment.water.levelTendency") {
2429 }
else if (update_path ==
"environment.water.waves.significantHeight" &&
2430 item[
"value"].IsNumber()) {
2431 pTargetData->met_data.wave_height = item[
"value"].GetDouble();
2432 }
else if (update_path ==
"environment.water.waves.period" &&
2433 item[
"value"].IsNumber()) {
2434 pTargetData->met_data.wave_period =
2435 static_cast<int>(item[
"value"].GetDouble());
2436 }
else if (update_path ==
"environment.water.waves.directionTrue" &&
2437 item[
"value"].IsNumber()) {
2438 pTargetData->met_data.wave_dir =
2439 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2440 }
else if (update_path ==
"environment.water.swell.height" &&
2441 item[
"value"].IsNumber()) {
2442 pTargetData->met_data.swell_height = item[
"value"].GetDouble();
2443 }
else if (update_path ==
"environment.water.swell.period" &&
2444 item[
"value"].IsNumber()) {
2445 pTargetData->met_data.swell_per =
2446 static_cast<int>(item[
"value"].GetDouble());
2447 }
else if (update_path ==
"environment.water.swell.directionTrue" &&
2448 item[
"value"].IsNumber()) {
2449 pTargetData->met_data.swell_dir =
2450 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2451 }
else if (update_path ==
"environment.water.temperature" &&
2452 item[
"value"].IsNumber()) {
2453 pTargetData->met_data.water_temp = KelvinToC(item[
"value"].GetDouble());
2454 }
else if (update_path ==
"environment.water.salinity" &&
2455 item[
"value"].IsNumber()) {
2456 pTargetData->met_data.salinity = item[
"value"].GetDouble();
2457 }
else if (update_path ==
"environment.water.ice") {
2459 }
else if (update_path ==
"environment.water.iceValue" &&
2460 item[
"value"].IsNumber()) {
2461 pTargetData->met_data.ice =
static_cast<int>(item[
"value"].GetDouble());
2462 }
else if (update_path ==
"environment.water.seaStateValue" &&
2463 item[
"value"].IsNumber()) {
2464 pTargetData->met_data.seastate =
2465 static_cast<int>(item[
"value"].GetDouble());
2466 }
else if (update_path ==
"environment.water.seaState") {
2468 }
else if (update_path ==
"environment.outside.precipitation") {
2470 }
else if (update_path ==
"environment.outside.precipitationValue" &&
2471 item[
"value"].IsNumber()) {
2472 pTargetData->met_data.precipitation =
2473 static_cast<int>(item[
"value"].GetDouble());
2474 }
else if (update_path ==
"environment.outside.pressureTendencyValue" &&
2475 item[
"value"].IsNumber()) {
2476 pTargetData->met_data.airpress_tend =
2477 static_cast<int>(item[
"value"].GetDouble());
2478 }
else if (update_path ==
"environment.outside.pressureTendency") {
2480 }
else if (update_path ==
"environment.outside.horizontalVisibility" &&
2481 item[
"value"].IsNumber()) {
2482 pTargetData->met_data.hor_vis =
2483 GEODESIC_METERS2NM(item[
"value"].GetDouble());
2484 }
else if (update_path ==
2485 "environment.outside.horizontalVisibility.overRange") {
2486 pTargetData->met_data.hor_vis_GT = item[
"value"].GetBool();
2487 }
else if (update_path.empty()) {
2488 if (item[
"value"].HasMember(
"name")) {
2489 const wxString &name = item[
"value"][
"name"].GetString();
2490 strncpy(pTargetData->ShipName, name.c_str(), SHIP_NAME_LEN - 1);
2491 pTargetData->b_nameValid =
true;
2492 pTargetData->MID = 123;
2493 }
else if (item[
"value"].HasMember(
"registrations")) {
2494 const wxString &imo = item[
"value"][
"registrations"][
"imo"].GetString();
2495 pTargetData->IMO = wxAtoi(imo.Right(7));
2496 }
else if (item[
"value"].HasMember(
"communication")) {
2497 const wxString &callsign =
2498 item[
"value"][
"communication"][
"callsignVhf"].GetString();
2499 strncpy(pTargetData->CallSign, callsign.c_str(), 7);
2501 if (item[
"value"].HasMember(
"mmsi") &&
2502 1994 != (pTargetData->MMSI) / 100000) {
2504 wxString tmp = item[
"value"][
"mmsi"].GetString();
2505 if (tmp.ToLong(&mmsi)) {
2506 pTargetData->MMSI = mmsi;
2508 if (97 == mmsi / 10000000) {
2509 pTargetData->Class = AIS_SART;
2511 if (111 == mmsi / 1000000) {
2512 pTargetData->b_SarAircraftPosnReport =
true;
2515 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
2520 wxLogMessage(wxString::Format(
2521 "** AisDecoder::updateItem: unhandled path %s", update_path));
2523 rapidjson::StringBuffer buffer;
2524 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2525 item.Accept(writer);
2526 wxString msg(
"update: ");
2527 msg.append(buffer.GetString());
2537AisError AisDecoder::DecodeSingleVDO(
const wxString &str,
GenericPosDatEx *pos,
2538 wxString *accumulator) {
2540 if (str.Len() > 128)
return AIS_NMEAVDX_TOO_LONG;
2542 if (!NMEACheckSumOK(str))
return AIS_NMEAVDX_CHECKSUM_BAD;
2544 if (!pos)
return AIS_GENERIC_ERROR;
2546 if (!accumulator)
return AIS_GENERIC_ERROR;
2549 if (!str.Mid(1, 5).IsSameAs(
"AIVDO"))
return AIS_GENERIC_ERROR;
2552 wxStringTokenizer tkz(str,
",");
2555 token = tkz.GetNextToken();
2557 token = tkz.GetNextToken();
2558 int nsentences = atoi(token.mb_str());
2560 token = tkz.GetNextToken();
2561 int isentence = atoi(token.mb_str());
2563 token = tkz.GetNextToken();
2564 token = tkz.GetNextToken();
2566 wxString string_to_parse;
2567 string_to_parse.Clear();
2580 if ((1 == nsentences) && (1 == isentence)) {
2581 string_to_parse = tkz.GetNextToken();
2584 else if (nsentences > 1) {
2585 if (1 == isentence) {
2586 *accumulator = tkz.GetNextToken();
2590 accumulator->Append(tkz.GetNextToken());
2593 if (isentence == nsentences) {
2594 string_to_parse = *accumulator;
2598 if (string_to_parse.IsEmpty() &&
2600 return AIS_INCOMPLETE_MULTIPART;
2609 auto TargetData = AisTargetDataMaker::GetInstance().GetTargetData();
2611 bool bdecode_result = Parse_VDXBitstring(&strbit, TargetData);
2613 if (bdecode_result) {
2614 switch (TargetData->MID) {
2619 if (!TargetData->b_positionDoubtful) {
2620 pos->
kLat = TargetData->Lat;
2621 pos->
kLon = TargetData->Lon;
2627 if (TargetData->COG == 360.0)
2630 pos->
kCog = TargetData->COG;
2632 if (TargetData->SOG > 102.2)
2635 pos->
kSog = TargetData->SOG;
2637 if ((
int)TargetData->HDG == 511)
2640 pos->
kHdt = TargetData->HDG;
2648 return AIS_GENERIC_ERROR;
2653 return AIS_GENERIC_ERROR;
2661AisError AisDecoder::DecodeN0183(
const wxString &str) {
2662 AisError ret = AIS_GENERIC_ERROR;
2663 wxString string_to_parse;
2665 double gpsg_lat, gpsg_lon, gpsg_mins, gpsg_degs;
2666 double gpsg_cog, gpsg_sog, gpsg_utc_time;
2667 int gpsg_utc_hour = 0;
2668 int gpsg_utc_min = 0;
2669 int gpsg_utc_sec = 0;
2670 char gpsg_name_str[21];
2673 bool bdecode_result =
false;
2680 long arpa_tgt_num = 0;
2681 double arpa_sog = 0.;
2682 double arpa_cog = 0.;
2683 double arpa_lat = 0.;
2684 double arpa_lon = 0.;
2685 double arpa_dist = 0.;
2686 double arpa_brg = 0.;
2687 wxString arpa_brgunit;
2688 wxString arpa_status;
2689 wxString arpa_distunit;
2690 wxString arpa_cogunit;
2691 double arpa_mins, arpa_degs;
2692 double arpa_utc_time;
2693 int arpa_utc_hour = 0;
2694 int arpa_utc_min = 0;
2695 int arpa_utc_sec = 0;
2696 char arpa_name_str[21];
2697 bool arpa_lost =
true;
2698 bool arpa_nottracked =
false;
2700 double aprs_lat = 0.;
2701 double aprs_lon = 0.;
2702 char aprs_name_str[21];
2703 double aprs_mins, aprs_degs;
2705 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
2706 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
2707 bool bnewtarget =
false;
2708 int last_report_ticks;
2712 if (str.Len() > 128)
return AIS_NMEAVDX_TOO_LONG;
2714 if (!NMEACheckSumOK(str)) {
2715 return AIS_NMEAVDX_CHECKSUM_BAD;
2717 if (str.Mid(1, 2).IsSameAs(
"CD")) {
2720 }
else if (str.Mid(3, 3).IsSameAs(
"TTM")) {
2724 wxStringTokenizer tkz(str,
",*");
2727 token = tkz.GetNextToken();
2728 token = tkz.GetNextToken();
2729 token.ToLong(&arpa_tgt_num);
2730 token = tkz.GetNextToken();
2731 token.ToDouble(&arpa_dist);
2732 token = tkz.GetNextToken();
2733 token.ToDouble(&arpa_brg);
2734 arpa_brgunit = tkz.GetNextToken();
2735 if (arpa_brgunit ==
"R") {
2736 if (std::isnan(arpa_ref_hdg)) {
2737 if (!std::isnan(
gHdt))
2742 arpa_brg += arpa_ref_hdg;
2743 if (arpa_brg >= 360.) arpa_brg -= 360.;
2745 token = tkz.GetNextToken();
2746 token.ToDouble(&arpa_sog);
2747 token = tkz.GetNextToken();
2748 token.ToDouble(&arpa_cog);
2749 arpa_cogunit = tkz.GetNextToken();
2750 if (arpa_cogunit ==
"R") {
2751 if (std::isnan(arpa_ref_hdg)) {
2752 if (!std::isnan(
gHdt))
2757 arpa_cog += arpa_ref_hdg;
2758 if (arpa_cog >= 360.) arpa_cog -= 360.;
2760 token = tkz.GetNextToken();
2761 token = tkz.GetNextToken();
2763 arpa_distunit = tkz.GetNextToken();
2764 token = tkz.GetNextToken();
2765 if (token ==
"") token = wxString::Format(
"ARPA %ld", arpa_tgt_num);
2766 int len = wxMin(token.Length(), 20);
2767 strncpy(arpa_name_str, token.mb_str(), len);
2768 arpa_name_str[len] = 0;
2769 arpa_status = tkz.GetNextToken();
2770 if (arpa_status !=
"L") {
2772 }
else if (arpa_status !=
"")
2773 arpa_nottracked =
true;
2774 wxString arpa_reftarget = tkz.GetNextToken();
2775 if (tkz.HasMoreTokens()) {
2776 token = tkz.GetNextToken();
2777 token.ToDouble(&arpa_utc_time);
2778 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2779 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2781 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2783 arpa_utc_hour = wxDateTime::Now().ToUTC().GetHour();
2784 arpa_utc_min = wxDateTime::Now().ToUTC().GetMinute();
2785 arpa_utc_sec = wxDateTime::Now().ToUTC().GetSecond();
2788 if (arpa_distunit ==
"K") {
2791 }
else if (arpa_distunit ==
"S") {
2796 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2800 }
else if (str.Mid(3, 3).IsSameAs(
"TLL")) {
2803 wxStringTokenizer tkz(str,
",*");
2806 wxString aprs_tll_str = tkz.GetNextToken();
2807 token = tkz.GetNextToken();
2808 token.ToLong(&arpa_tgt_num);
2809 token = tkz.GetNextToken();
2810 token.ToDouble(&arpa_lat);
2811 arpa_degs = (int)(arpa_lat / 100.0);
2812 arpa_mins = arpa_lat - arpa_degs * 100.0;
2813 arpa_lat = arpa_degs + arpa_mins / 60.0;
2814 token = tkz.GetNextToken();
2815 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2816 token.Mid(0, 1).Contains(
"s") ==
true)
2817 arpa_lat = 0. - arpa_lat;
2818 token = tkz.GetNextToken();
2819 token.ToDouble(&arpa_lon);
2820 arpa_degs = (int)(arpa_lon / 100.0);
2821 arpa_mins = arpa_lon - arpa_degs * 100.0;
2822 arpa_lon = arpa_degs + arpa_mins / 60.0;
2823 token = tkz.GetNextToken();
2824 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2825 token.Mid(0, 1).Contains(
"w") ==
true)
2826 arpa_lon = 0. - arpa_lon;
2827 token = tkz.GetNextToken();
2828 if (token ==
"") token = wxString::Format(
"ARPA %d", arpa_tgt_num);
2829 int len = wxMin(token.Length(), 20);
2830 strncpy(arpa_name_str, token.mb_str(), len);
2831 arpa_name_str[len] = 0;
2832 token = tkz.GetNextToken();
2833 token.ToDouble(&arpa_utc_time);
2834 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2835 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2837 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2842 if (arpa_status !=
"L")
2844 else if (arpa_status !=
"")
2845 arpa_nottracked =
true;
2846 wxString arpa_reftarget = tkz.GetNextToken();
2847 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2851 }
else if (str.Mid(3, 3).IsSameAs(
"OSD")) {
2853 wxStringTokenizer tkz(str,
",*");
2856 token = tkz.GetNextToken();
2857 token = tkz.GetNextToken();
2858 token.ToDouble(&arpa_ref_hdg);
2868 }
else if (g_bWplUsePosition && str.Mid(3, 3).IsSameAs(
"WPL")) {
2870 wxStringTokenizer tkz(str,
",*");
2873 token = tkz.GetNextToken();
2874 token = tkz.GetNextToken();
2875 token.ToDouble(&aprs_lat);
2876 aprs_degs = (int)(aprs_lat / 100.0);
2877 aprs_mins = aprs_lat - aprs_degs * 100.0;
2878 aprs_lat = aprs_degs + aprs_mins / 60.0;
2879 token = tkz.GetNextToken();
2880 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2881 token.Mid(0, 1).Contains(
"s") ==
true)
2882 aprs_lat = 0. - aprs_lat;
2883 token = tkz.GetNextToken();
2884 token.ToDouble(&aprs_lon);
2885 aprs_degs = (int)(aprs_lon / 100.0);
2886 aprs_mins = aprs_lon - aprs_degs * 100.0;
2887 aprs_lon = aprs_degs + aprs_mins / 60.0;
2888 token = tkz.GetNextToken();
2889 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2890 token.Mid(0, 1).Contains(
"w") ==
true)
2891 aprs_lon = 0. - aprs_lon;
2892 token = tkz.GetNextToken();
2893 int len = wxMin(token.Length(), 20);
2894 strncpy(aprs_name_str, token.mb_str(), len + 1);
2895 if (0 == g_WplAction) {
2897 aprs_name_str[len] = 0;
2898 for (i = 0; i < len; i++) {
2900 hash += (int)(aprs_name_str[i]);
2901 while (hash >= 100000) hash = hash / 100000;
2903 mmsi = aprs_mmsi = 199300000 + hash;
2907 }
else if (1 == g_WplAction) {
2909 new RoutePoint(aprs_lat, aprs_lon,
"", aprs_name_str,
"",
false);
2910 pWP->m_bIsolatedMark =
true;
2911 InsertWpt(pWP,
true);
2913 }
else if (str.Mid(1, 5).IsSameAs(
"FRPOS")) {
2917 wxStringTokenizer tkz(str,
",*");
2920 token = tkz.GetNextToken();
2922 token = tkz.GetNextToken();
2923 token.ToDouble(&gpsg_lat);
2924 gpsg_degs = (int)(gpsg_lat / 100.0);
2925 gpsg_mins = gpsg_lat - gpsg_degs * 100.0;
2926 gpsg_lat = gpsg_degs + gpsg_mins / 60.0;
2928 token = tkz.GetNextToken();
2929 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2930 token.Mid(0, 1).Contains(
"s") ==
true)
2931 gpsg_lat = 0. - gpsg_lat;
2933 token = tkz.GetNextToken();
2934 token.ToDouble(&gpsg_lon);
2935 gpsg_degs = (int)(gpsg_lon / 100.0);
2936 gpsg_mins = gpsg_lon - gpsg_degs * 100.0;
2937 gpsg_lon = gpsg_degs + gpsg_mins / 60.0;
2939 token = tkz.GetNextToken();
2940 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2941 token.Mid(0, 1).Contains(
"w") ==
true)
2942 gpsg_lon = 0. - gpsg_lon;
2944 token = tkz.GetNextToken();
2947 token = tkz.GetNextToken();
2948 token.ToDouble(&gpsg_sog);
2950 token = tkz.GetNextToken();
2951 token.ToDouble(&gpsg_cog);
2953 token = tkz.GetNextToken();
2956 token = tkz.GetNextToken();
2957 token.ToDouble(&gpsg_utc_time);
2958 gpsg_utc_hour = (int)(gpsg_utc_time / 10000.0);
2959 gpsg_utc_min = (int)(gpsg_utc_time / 100.0) - gpsg_utc_hour * 100;
2961 (int)gpsg_utc_time - gpsg_utc_hour * 10000 - gpsg_utc_min * 100;
2965 token = tkz.GetNextToken();
2966 int i, len, hash = 0;
2967 len = wxMin(wxStrlen(token), 20);
2968 strncpy(gpsg_name_str, token.mb_str(), len);
2969 gpsg_name_str[len] = 0;
2970 for (i = 0; i < len; i++) {
2972 hash += (int)(token[i]);
2973 while (hash >= 100000) hash = hash / 100000;
2976 gpsg_mmsi = 199000000 + hash;
2978 }
else if (!str.Mid(3, 2).IsSameAs(
"VD")) {
2979 return AIS_NMEAVDX_BAD;
2985 string_to_parse.Clear();
2987 if (str.Mid(3, 2).IsSameAs(
"VD")) {
2988 wxStringTokenizer tkz(str,
",");
2991 token = tkz.GetNextToken();
2993 token = tkz.GetNextToken();
2994 nsentences = atoi(token.mb_str());
2996 token = tkz.GetNextToken();
2997 isentence = atoi(token.mb_str());
2999 token = tkz.GetNextToken();
3000 long lsequence_id = 0;
3001 token.ToLong(&lsequence_id);
3003 token = tkz.GetNextToken();
3005 token.ToLong(&lchannel);
3010 if ((1 == nsentences) && (1 == isentence)) {
3011 string_to_parse = tkz.GetNextToken();
3014 else if (nsentences > 1) {
3015 if (1 == isentence) {
3016 sentence_accumulator = tkz.GetNextToken();
3020 sentence_accumulator += tkz.GetNextToken();
3023 if (isentence == nsentences) {
3024 string_to_parse = sentence_accumulator;
3029 if (mmsi || (!string_to_parse.IsEmpty() &&
3030 (string_to_parse.Len() < AIS_MAX_MESSAGE_LEN))) {
3032 wxCharBuffer abuf = string_to_parse.ToUTF8();
3034 return AIS_GENERIC_ERROR;
3039 if (!mmsi) mmsi = strbit.GetInt(9, 30);
3040 long mmsi_long = mmsi;
3043 int origin_mmsi = 0;
3044 int messID = strbit.GetInt(1, 6);
3045 int dac = strbit.GetInt(41, 10);
3046 int fi = strbit.GetInt(51, 6);
3048 int met_lon, met_lat;
3049 if (dac == 001 && fi == 31) {
3051 met_lon = strbit.GetInt(57, 25);
3052 met_lat = strbit.GetInt(82, 24);
3053 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 25, 0);
3056 }
else if (dac == 367 && fi == 33) {
3058 const int size = strbit.GetBitCount();
3059 if (size < 168)
return AIS_GENERIC_ERROR;
3060 const int startb = 56;
3061 const int slot_size = 112;
3062 const int extra_bits = (size - startb) % slot_size;
3063 if (extra_bits > 0)
return AIS_GENERIC_ERROR;
3065 int mes_type = strbit.GetInt(57, 4);
3066 int site_ID = strbit.GetInt(77, 7);
3067 if (mes_type == 0) {
3069 met_lon = strbit.GetInt(90, 28);
3070 met_lat = strbit.GetInt(118, 27);
3071 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 28, site_ID);
3076 int x_mmsi = AisMeteoNewMmsi(mmsi, 91, 181, 0, site_ID);
3082 return AIS_GENERIC_ERROR;
3091 auto it = AISTargetList.find(mmsi);
3092 if (it == AISTargetList.end())
3094 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3099 pTargetData->MMSI = mmsi;
3100 pTargetData->met_data.original_mmsi = origin_mmsi;
3103 pTargetData = it->second;
3106 pStaleTarget = pTargetData;
3108 pTargetData->MMSI = mmsi;
3109 pTargetData->met_data.original_mmsi = origin_mmsi;
3114 if (mmsi ==
static_cast<unsigned>(props->MMSI)) {
3116 if (TRACKTYPE_NEVER == props->TrackType) {
3117 pTargetData->b_show_track =
false;
3118 }
else if (TRACKTYPE_ALWAYS == props->TrackType) {
3119 pTargetData->b_show_track =
true;
3124 if (props->m_bignore)
return AIS_NoError;
3127 else if (props->m_bVDM) {
3129 if (str.Mid(3, 9).IsSameAs(
"VDM,1,1,,")) {
3130 int message_ID = strbit.GetInt(1, 6);
3133 if ((message_ID <= 3) || (message_ID == 18)) {
3135 pTargetData->b_OwnShip =
true;
3137 wxString aivdostr = str;
3138 aivdostr.replace(1, 5,
"AIVDO");
3139 unsigned char calculated_checksum = 0;
3140 wxString::iterator j;
3141 for (j = aivdostr.begin() + 1; j != aivdostr.end() && *j !=
'*';
3143 calculated_checksum ^=
static_cast<unsigned char>(*j);
3146 if (j <= aivdostr.end() - 3)
3149 wxString::Format(_(
"%02X"), calculated_checksum));
3151 gps_watchdog_timeout_ticks =
3154 std::string full_sentence = aivdostr.ToStdString();
3155 auto address = std::make_shared<NavAddr0183>(
"virtual");
3158 auto msg = std::make_shared<const Nmea0183Msg>(
3159 "AIVDO", full_sentence, address);
3160 NavMsgBus::GetInstance().
Notify(std::move(msg));
3170 wxDateTime now = wxDateTime::Now();
3174 last_report_ticks = pStaleTarget->PositionReportTicks;
3176 last_report_ticks = now.GetTicks();
3180 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
3184 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3185 pTargetData->PositionReportTicks = now.GetTicks();
3186 pTargetData->StaticReportTicks = now.GetTicks();
3187 pTargetData->m_utc_hour = gpsg_utc_hour;
3188 pTargetData->m_utc_min = gpsg_utc_min;
3189 pTargetData->m_utc_sec = gpsg_utc_sec;
3190 pTargetData->m_date_string = gpsg_date;
3191 pTargetData->MMSI = gpsg_mmsi;
3192 pTargetData->NavStatus = 0;
3193 pTargetData->Lat = gpsg_lat;
3194 pTargetData->Lon = gpsg_lon;
3195 pTargetData->b_positionOnceValid =
true;
3196 pTargetData->COG = gpsg_cog;
3197 pTargetData->SOG = gpsg_sog;
3198 pTargetData->ShipType = 52;
3199 pTargetData->Class = AIS_GPSG_BUDDY;
3200 memcpy(pTargetData->ShipName, gpsg_name_str,
sizeof(gpsg_name_str));
3201 pTargetData->b_nameValid =
true;
3202 pTargetData->b_active =
true;
3203 pTargetData->b_lost =
false;
3205 bdecode_result =
true;
3206 }
else if (arpa_mmsi) {
3207 pTargetData->m_utc_hour = arpa_utc_hour;
3208 pTargetData->m_utc_min = arpa_utc_min;
3209 pTargetData->m_utc_sec = arpa_utc_sec;
3210 pTargetData->MMSI = arpa_mmsi;
3211 pTargetData->NavStatus = 15;
3212 if (str.Mid(3, 3).IsSameAs(
"TLL")) {
3215 (now.GetTicks() - pTargetData->PositionReportTicks);
3216 if (age_of_last > 0) {
3217 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, arpa_lat,
3218 arpa_lon, &pTargetData->COG, &pTargetData->SOG);
3219 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3222 pTargetData->Lat = arpa_lat;
3223 pTargetData->Lon = arpa_lon;
3224 }
else if (str.Mid(3, 3).IsSameAs(
"TTM")) {
3225 if (arpa_dist != 0.)
3226 ll_gc_ll(
gLat,
gLon, arpa_brg, arpa_dist, &pTargetData->Lat,
3230 pTargetData->COG = arpa_cog;
3231 pTargetData->SOG = arpa_sog;
3233 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3234 pTargetData->PositionReportTicks = now.GetTicks();
3235 pTargetData->StaticReportTicks = now.GetTicks();
3236 pTargetData->b_positionOnceValid =
true;
3237 pTargetData->ShipType = 55;
3238 pTargetData->Class = AIS_ARPA;
3240 memcpy(pTargetData->ShipName, arpa_name_str,
sizeof(arpa_name_str));
3241 if (arpa_status !=
"Q")
3242 pTargetData->b_nameValid =
true;
3244 pTargetData->b_nameValid =
false;
3245 pTargetData->b_active = !arpa_lost;
3246 pTargetData->b_lost = arpa_nottracked;
3248 bdecode_result =
true;
3249 }
else if (aprs_mmsi) {
3250 pTargetData->m_utc_hour = now.GetHour();
3251 pTargetData->m_utc_min = now.GetMinute();
3252 pTargetData->m_utc_sec = now.GetSecond();
3253 pTargetData->MMSI = aprs_mmsi;
3254 pTargetData->NavStatus = 15;
3256 int age_of_last = (now.GetTicks() - pTargetData->PositionReportTicks);
3257 if (age_of_last > 0) {
3258 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, aprs_lat,
3259 aprs_lon, &pTargetData->COG, &pTargetData->SOG);
3260 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3263 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3264 pTargetData->PositionReportTicks = now.GetTicks();
3265 pTargetData->StaticReportTicks = now.GetTicks();
3266 pTargetData->Lat = aprs_lat;
3267 pTargetData->Lon = aprs_lon;
3268 pTargetData->b_positionOnceValid =
true;
3269 pTargetData->ShipType = 56;
3270 pTargetData->Class = AIS_APRS;
3271 memcpy(pTargetData->ShipName, aprs_name_str,
sizeof(aprs_name_str));
3272 pTargetData->b_nameValid =
true;
3273 pTargetData->b_active =
true;
3274 pTargetData->b_lost =
false;
3276 bdecode_result =
true;
3280 Parse_VDXBitstring(&strbit, pTargetData);
3284 getMmsiProperties(pTargetData);
3287 pTargetData->RecentPeriod =
3288 pTargetData->PositionReportTicks - last_report_ticks;
3293 pTargetData =
nullptr;
3298 CommitAISTarget(pTargetData, str, bdecode_result, bnewtarget);
3303 if ((n_msgs % 10000) == 0)
3304 printf(
"n_msgs %10d m_n_targets: %6d n_msg1: %10d n_msg5+24: %10d \n",
3305 n_msgs, m_n_targets, n_msg1, n_msg5 + n_msg24);
3311void AisDecoder::CommitAISTarget(
3312 const std::shared_ptr<AisTargetData> &pTargetData,
const wxString &str,
3313 bool message_valid,
bool new_target) {
3314 m_pLatestTargetData = pTargetData;
3316 if (!str.IsEmpty()) {
3317 if (str.Mid(3, 3).IsSameAs(
"VDO"))
3318 pTargetData->b_OwnShip =
true;
3320 pTargetData->b_OwnShip =
false;
3323 if (!pTargetData->b_OwnShip) {
3325 if (0 == m_persistent_tracks.count(pTargetData->MMSI)) {
3327 pTargetData->b_PersistTrack =
false;
3332 pTargetData->b_mPropPersistTrack = props->m_bPersistentTrack;
3339 pTargetData->b_NoTrack =
false;
3344 if (message_valid) {
3346 if (pTargetData->MMSI) {
3347 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
3350 AISTargetList[pTargetData->MMSI] =
3353 if (!pTargetData->area_notices.empty()) {
3354 auto it = AIS_AreaNotice_Sources.find(pTargetData->MMSI);
3355 if (it == AIS_AreaNotice_Sources.end())
3356 AIS_AreaNotice_Sources[pTargetData->MMSI] = pTargetData;
3361 if (!pTargetData->b_OwnShip) {
3362 if (pTargetData->b_positionOnceValid) {
3363 long mmsi_long = pTargetData->MMSI;
3365 pTargetData->Lat, pTargetData->Lon, (
void *)mmsi_long,
3367 pSel->SetUserData(pTargetData->MMSI);
3371 UpdateOneCPA(pTargetData.get());
3374 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3387 if (!pTargetData->b_OwnShip) {
3388 if (pTargetData->b_positionOnceValid) {
3389 long mmsi_long = pTargetData->MMSI;
3391 pTargetData->Lat, pTargetData->Lon, (
void *)mmsi_long,
3393 pSel->SetUserData(pTargetData->MMSI);
3400void AisDecoder::getAISTarget(
long mmsi,
3401 std::shared_ptr<AisTargetData> &pTargetData,
3402 std::shared_ptr<AisTargetData> &pStaleTarget,
3403 bool &bnewtarget,
int &last_report_ticks,
3405 now = wxDateTime::Now();
3406 auto it = AISTargetList.find(mmsi);
3407 if (it == AISTargetList.end())
3409 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3413 pTargetData = it->second;
3414 pStaleTarget = pTargetData;
3421 last_report_ticks = pStaleTarget->PositionReportTicks;
3423 last_report_ticks = now.GetTicks();
3427 pSelectAIS->DeleteSelectablePoint((
void *)mmsi, SELTYPE_AISTARGET);
3430std::shared_ptr<AisTargetData> AisDecoder::ProcessDSx(
const wxString &str,
3432 double dsc_lat = 0.;
3433 double dsc_lon = 0.;
3434 double dsc_mins, dsc_degs, dsc_tmp, dsc_addr;
3436 double dse_lat = 0.;
3437 double dse_lon = 0.;
3438 long dsc_fmt, dsc_quadrant, dsc_cat, dsc_nature;
3441 int dsc_tx_mmsi = 0;
3443 double dse_cog = 0.;
3444 double dse_sog = 0.;
3445 wxString dse_shipName =
"";
3450 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
3454 wxStringTokenizer tkz(str,
",*");
3457 token = tkz.GetNextToken();
3459 if (str.Mid(3, 3).IsSameAs(
"DSC")) {
3460 m_dsc_last_string = str;
3462 token = tkz.GetNextToken();
3466 token = tkz.GetNextToken();
3468 if (dsc_fmt == 12 || dsc_fmt == 16) {
3469 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3471 token.ToDouble(&dsc_addr);
3472 dsc_mmsi = 0 - (int)(dsc_addr / 10);
3475 token = tkz.GetNextToken();
3476 token.ToLong(&dsc_cat);
3478 token = tkz.GetNextToken();
3479 if (!token.IsSameAs(
"")) {
3480 token.ToLong(&dsc_nature);
3484 token = tkz.GetNextToken();
3486 token = tkz.GetNextToken();
3487 token.ToDouble(&dsc_tmp);
3489 token = tkz.GetNextToken();
3490 token = tkz.GetNextToken();
3491 if (dsc_fmt == 16 && dsc_cat == 12 && !token.IsSameAs(
"")) {
3493 dsc_tx_mmsi = dsc_mmsi;
3494 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3496 token = tkz.GetNextToken();
3497 if (dsc_fmt == 16 && dsc_cat == 12) {
3498 if (!token.IsSameAs(
"")) {
3499 token.ToLong(&dsc_nature);
3503 token = tkz.GetNextToken();
3504 token = tkz.GetNextToken();
3506 dsc_quadrant = (int)(dsc_tmp / 1000000000.0);
3508 if (dsc_quadrant > 3)
3511 dsc_lat = (int)(dsc_tmp / 100000.0);
3512 dsc_lon = dsc_tmp - dsc_lat * 100000.0;
3513 dsc_lat = dsc_lat - dsc_quadrant * 10000;
3514 dsc_degs = (int)(dsc_lat / 100.0);
3515 dsc_mins = dsc_lat - dsc_degs * 100.0;
3516 dsc_lat = dsc_degs + dsc_mins / 60.0;
3518 dsc_degs = (int)(dsc_lon / 100.0);
3519 dsc_mins = dsc_lon - dsc_degs * 100.0;
3520 dsc_lon = dsc_degs + dsc_mins / 60.0;
3521 switch (dsc_quadrant) {
3537 if (dsc_fmt != 02) mmsi = (int)dsc_mmsi;
3539 }
else if (str.Mid(3, 3).IsSameAs(
"DSE")) {
3540 token = tkz.GetNextToken();
3541 token = tkz.GetNextToken();
3542 token = tkz.GetNextToken();
3543 token = tkz.GetNextToken();
3544 dse_mmsi = wxAtoi(token.Mid(
3550 token = tkz.GetNextToken();
3553 token.ToDouble(&dse_tmp);
3554 dse_lat = (int)(dse_tmp / 10000.0);
3555 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3556 dse_lat = dse_lat / 600000.0;
3557 dse_lon = dse_lon / 600000.0;
3560 while (tkz.HasMoreTokens()) {
3561 dseSymbol = tkz.GetNextToken();
3562 token = tkz.GetNextToken();
3563 if (dseSymbol.IsSameAs(
"00")) {
3564 token.ToDouble(&dse_tmp);
3565 dse_lat = (int)(dse_tmp / 10000.0);
3566 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3567 dse_lat = dse_lat / 600000.0;
3568 dse_lon = dse_lon / 600000.0;
3569 }
else if (dseSymbol.IsSameAs(
"01")) {
3570 }
else if (dseSymbol.IsSameAs(
"02")) {
3571 token.ToDouble(&dse_tmp);
3572 dse_sog = dse_tmp / 10.0;
3573 }
else if (dseSymbol.IsSameAs(
"03")) {
3574 token.ToDouble(&dse_tmp);
3575 dse_cog = dse_tmp / 10.0;
3576 }
else if (dseSymbol.IsSameAs(
"04")) {
3577 dse_shipName = DecodeDSEExpansionCharacters(token);
3578 }
else if (dseSymbol.IsSameAs(
"05")) {
3579 }
else if (dseSymbol.IsSameAs(
"06")) {
3582 mmsi = abs((
int)dse_mmsi);
3586 wxDateTime now = wxDateTime::Now();
3588 int last_report_ticks = now.GetTicks();
3591 auto it = AISTargetList.find(mmsi);
3592 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
3593 if (it == AISTargetList.end()) {
3595 pStaleTarget = it->second;
3596 last_report_ticks = pStaleTarget->PositionReportTicks;
3602 m_ptentative_dsctarget = AisTargetDataMaker::GetInstance().GetTargetData();
3604 m_ptentative_dsctarget->PositionReportTicks = now.GetTicks();
3605 m_ptentative_dsctarget->StaticReportTicks = now.GetTicks();
3607 m_ptentative_dsctarget->MMSI = mmsi;
3608 m_ptentative_dsctarget->NavStatus =
3610 m_ptentative_dsctarget->Lat = dsc_lat;
3611 m_ptentative_dsctarget->Lon = dsc_lon;
3612 m_ptentative_dsctarget->b_positionOnceValid =
true;
3613 m_ptentative_dsctarget->COG = 0;
3614 m_ptentative_dsctarget->SOG = 0;
3615 m_ptentative_dsctarget->ShipType = dsc_fmt;
3616 m_ptentative_dsctarget->Class = AIS_DSC;
3617 m_ptentative_dsctarget->b_isDSCtarget =
true;
3618 m_ptentative_dsctarget->b_nameValid =
true;
3619 if (dsc_fmt == 12 || (dsc_fmt == 16 && dsc_cat == 12)) {
3620 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN,
"DISTRESS %u",
3622 m_ptentative_dsctarget->m_dscNature = int(dsc_nature);
3623 m_ptentative_dsctarget->m_dscTXmmsi = dsc_tx_mmsi;
3625 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN,
"POSITION %u",
3629 m_ptentative_dsctarget->b_active =
true;
3630 m_ptentative_dsctarget->b_lost =
false;
3631 m_ptentative_dsctarget->RecentPeriod =
3632 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3635 if (!b_take_dsc) m_dsc_timer.Start(1000, wxTIMER_ONE_SHOT);
3640 if (dse_mmsi || b_take_dsc) {
3641 if (m_ptentative_dsctarget) {
3647 m_ptentative_dsctarget->Lat =
3648 m_ptentative_dsctarget->Lat +
3649 ((m_ptentative_dsctarget->Lat) >= 0 ? dse_lat : -dse_lat);
3650 m_ptentative_dsctarget->Lon =
3651 m_ptentative_dsctarget->Lon +
3652 ((m_ptentative_dsctarget->Lon) >= 0 ? dse_lon : -dse_lon);
3653 if (!dse_shipName.empty()) {
3654 memset(m_ptentative_dsctarget->ShipName,
'\0', SHIP_NAME_LEN);
3655 snprintf(m_ptentative_dsctarget->ShipName, dse_shipName.length(),
3656 "%s", dse_shipName.ToAscii().data());
3658 m_ptentative_dsctarget->COG = dse_cog;
3659 m_ptentative_dsctarget->SOG = dse_sog;
3663 m_ptentative_dsctarget->RecentPeriod =
3664 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3669 auto found = AISTargetList.find(mmsi);
3670 if (found == AISTargetList.end()) {
3671 pTargetData = m_ptentative_dsctarget;
3673 pTargetData = found->second;
3674 std::vector<AISTargetTrackPoint> ptrack =
3675 std::move(pTargetData->m_ptrack);
3676 pTargetData->CloneFrom(
3677 m_ptentative_dsctarget
3680 pTargetData->m_ptrack =
3687 m_ptentative_dsctarget =
nullptr;
3689 m_pLatestTargetData = pTargetData;
3691 AISTargetList[pTargetData->MMSI] =
3694 long mmsi_long = pTargetData->MMSI;
3698 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
3701 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
3702 (
void *)mmsi_long, SELTYPE_AISTARGET);
3703 pSel->SetUserData(pTargetData->MMSI);
3706 UpdateOneCPA(pTargetData.get());
3709 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3716bool AisDecoder::NMEACheckSumOK(
const wxString &str_in) {
3717 unsigned char checksum_value = 0;
3718 int sentence_hex_sum;
3720 wxCharBuffer buf = str_in.ToUTF8();
3721 if (!buf.data())
return false;
3723 char str_ascii[AIS_MAX_MESSAGE_LEN + 1];
3724 strncpy(str_ascii, buf.data(), AIS_MAX_MESSAGE_LEN);
3725 str_ascii[AIS_MAX_MESSAGE_LEN] =
'\0';
3727 int string_length = strlen(str_ascii);
3729 int payload_length = 0;
3730 while ((payload_length < string_length) &&
3731 (str_ascii[payload_length] !=
'*'))
3734 if (payload_length == string_length)
3739 while (index < payload_length) {
3740 checksum_value ^= str_ascii[index];
3744 if (string_length > 4) {
3746 scanstr[0] = str_ascii[payload_length + 1];
3747 scanstr[1] = str_ascii[payload_length + 2];
3749 sscanf(scanstr,
"%2x", &sentence_hex_sum);
3751 if (sentence_hex_sum == checksum_value)
return true;
3757void AisDecoder::UpdateAllCPA() {
3759 for (
const auto &it : GetTargetList()) {
3760 std::shared_ptr<AisTargetData> td = it.second;
3762 if (
nullptr != td) UpdateOneCPA(td.get());
3766void AisDecoder::UpdateAllTracks() {
3767 for (
const auto &it : GetTargetList()) {
3768 std::shared_ptr<AisTargetData> td = it.second;
3769 if (td) UpdateOneTrack(td.get());
3775 if (!ptarget->b_positionOnceValid)
return;
3777 if (!ptarget->m_ptrack.empty()) {
3779 if (fabs(LastTrackpoint.m_lat - ptarget->Lat) > .1 ||
3780 fabs(LastTrackpoint.m_lon - ptarget->Lon) > .1) {
3783 ptarget->m_ptrack.pop_back();
3784 ptarget->b_positionDoubtful =
true;
3791 if ((ptarget->PositionReportTicks - ptarget->LastPositionReportTicks) > 2) {
3794 ptrackpoint.m_lat = ptarget->Lat;
3795 ptrackpoint.m_lon = ptarget->Lon;
3796 ptrackpoint.m_time = wxDateTime::Now().GetTicks();
3798 ptarget->m_ptrack.push_back(ptrackpoint);
3800 if (ptarget->b_PersistTrack || ptarget->b_mPropPersistTrack) {
3802 if (0 == m_persistent_tracks.count(ptarget->MMSI)) {
3804 t->SetName(wxString::Format(
3805 "AIS %s (%u) %s %s", ptarget->GetFullName().c_str(), ptarget->MMSI,
3806 wxDateTime::Now().FormatISODate().c_str(),
3807 wxDateTime::Now().FormatISOTime().c_str()));
3810 m_persistent_tracks[ptarget->MMSI] = t;
3812 t = m_persistent_tracks[ptarget->MMSI];
3815 vector2D point(ptrackpoint.m_lon, ptrackpoint.m_lat);
3817 t->AddNewPoint(point, wxDateTime(ptrackpoint.m_time).ToUTC());
3820 pSelect->AddSelectableTrackSegment(tp->m_lat, tp->m_lon, tp1->m_lat,
3821 tp1->m_lon, tp, tp1, t);
3831 wxDateTime::Now().GetTicks() - (time_t)(g_AISShowTracks_Mins * 60);
3833 ptarget->m_ptrack.erase(
3834 std::remove_if(ptarget->m_ptrack.begin(), ptarget->m_ptrack.end(),
3836 return track.m_time < test_time;
3838 ptarget->m_ptrack.end());
3843void AisDecoder::DeletePersistentTrack(
const Track *track) {
3844 for (
auto it = m_persistent_tracks.begin(); it != m_persistent_tracks.end();
3846 if (it->second == track) {
3847 unsigned mmsi = it->first;
3848 m_persistent_tracks.erase(it);
3850 if (0 == m_persistent_tracks.count(mmsi)) {
3854 if (props->m_bPersistentTrack) {
3858 std::shared_ptr<AisTargetData> td =
3859 Get_Target_Data_From_MMSI(mmsi);
3861 props->m_bPersistentTrack =
false;
3862 td->b_mPropPersistTrack =
false;
3864 if (!m_callbacks.confirm_stop_track()) {
3865 props->m_bPersistentTrack =
true;
3877void AisDecoder::UpdateAllAlarms() {
3878 m_bGeneralAlert =
false;
3881 for (
const auto &it : GetTargetList()) {
3882 std::shared_ptr<AisTargetData> td = it.second;
3886 if (!m_bGeneralAlert) {
3888 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3889 (td->Class != AIS_ATON) && (td->Class != AIS_BASE))
3890 m_bGeneralAlert =
true;
3893 if (g_bAIS_CPA_Alert_Suppress_Moored && (td->SOG <= g_ShowMoored_Kts))
3894 m_bGeneralAlert =
false;
3897 if ((g_bCPAMax) && (td->Range_NM > g_CPAMax_NM))
3898 m_bGeneralAlert =
false;
3901 if ((g_bTCPA_Max) && (td->TCPA > g_TCPA_Max)) m_bGeneralAlert =
false;
3904 if (td->Class == AIS_SART && td->NavStatus == 14)
3905 m_bGeneralAlert =
true;
3908 if ((td->Class == AIS_DSC) &&
3909 ((td->ShipType == 12) || (td->ShipType == 16)))
3910 m_bGeneralAlert =
true;
3913 ais_alert_type this_alarm = AIS_NO_ALERT;
3916 if (td->Class == AIS_SART && td->NavStatus == 14)
3917 this_alarm = AIS_ALERT_SET;
3920 if ((td->Class == AIS_DSC) &&
3921 ((td->ShipType == 12) || (td->ShipType == 16)))
3922 this_alarm = AIS_ALERT_SET;
3924 if (g_bCPAWarn && td->b_active && td->b_positionOnceValid &&
3925 (td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
3928 if ((g_bHideMoored) && (td->SOG <= g_ShowMoored_Kts)) {
3929 td->n_alert_state = AIS_NO_ALERT;
3935 if (g_bAIS_CPA_Alert_Suppress_Moored &&
3936 (td->SOG <= g_ShowMoored_Kts)) {
3937 td->n_alert_state = AIS_NO_ALERT;
3943 if (td->Range_NM > g_CPAMax_NM) {
3944 td->n_alert_state = AIS_NO_ALERT;
3949 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3950 (td->Class != AIS_ATON) && (td->Class != AIS_BASE) &&
3951 (td->Class != AIS_METEO)) {
3953 if (td->TCPA < g_TCPA_Max) {
3954 if (td->b_isFollower)
3955 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3957 this_alarm = AIS_ALERT_SET;
3960 if (td->b_isFollower)
3961 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3963 this_alarm = AIS_ALERT_SET;
3971 if (g_bAIS_ACK_Timeout || (td->Class == AIS_SART) ||
3972 ((td->Class == AIS_DSC) &&
3973 ((td->ShipType == 12) || (td->ShipType == 16)))) {
3974 if (td->b_in_ack_timeout) {
3975 wxTimeSpan delta = wxDateTime::Now() - td->m_ack_time;
3976 if (delta.GetMinutes() > g_AckTimeout_Mins)
3977 td->b_in_ack_timeout =
false;
3983 if (td->b_in_ack_timeout) {
3984 if (this_alarm == AIS_NO_ALERT) td->b_in_ack_timeout =
false;
3988 td->n_alert_state = this_alarm;
3994 ptarget->Range_NM = -1.;
4003 DistanceBearingMercator(ptarget->Lat, ptarget->Lon,
gLat,
gLon, &brg, &dist);
4004 ptarget->Range_NM = dist;
4007 if (dist <= 1e-5) ptarget->Brg = -1.0;
4009 if (!ptarget->b_positionOnceValid || !
bGPSValid) {
4010 ptarget->bCPA_Valid =
false;
4014 if (ptarget->Class == AIS_METEO) {
4015 ptarget->bCPA_Valid =
false;
4024 if (ptarget->b_OwnShip) {
4026 ptarget->TCPA = -100;
4027 ptarget->bCPA_Valid =
false;
4031 double cpa_calc_ownship_cog =
gCog;
4032 double cpa_calc_target_cog = ptarget->COG;
4035 if (std::isnan(
gSog) || (
gSog > 102.2)) {
4036 ptarget->bCPA_Valid =
false;
4041 if (std::isnan(
gCog) ||
gCog == 360.0) {
4043 cpa_calc_ownship_cog =
4047 ptarget->bCPA_Valid =
false;
4053 if (ptarget->COG == 360.0) {
4054 if (ptarget->SOG > 102.2) {
4055 ptarget->bCPA_Valid =
false;
4057 }
else if (ptarget->SOG < .01) {
4058 cpa_calc_target_cog = 0.;
4061 ptarget->bCPA_Valid =
false;
4067 double v0 =
gSog * 1852.;
4068 double v1 = ptarget->SOG * 1852.;
4070 if ((v0 < 1e-6) && (v1 < 1e-6)) {
4074 ptarget->bCPA_Valid =
false;
4081 double east1 = (ptarget->Lon -
gLon) * 60 * 1852;
4082 double north1 = (ptarget->Lat -
gLat) * 60 * 1852;
4084 double east = east1 * (cos(
gLat * PI / 180.));
4086 double north = north1;
4089 double cosa = cos((90. - cpa_calc_ownship_cog) * PI / 180.);
4090 double sina = sin((90. - cpa_calc_ownship_cog) * PI / 180.);
4091 double cosb = cos((90. - cpa_calc_target_cog) * PI / 180.);
4092 double sinb = sin((90. - cpa_calc_target_cog) * PI / 180.);
4095 double fc = (v0 * cosa) - (v1 * cosb);
4096 double fs = (v0 * sina) - (v1 * sinb);
4098 double d = (fc * fc) + (fs * fs);
4106 tcpa = ((fc * east) + (fs * north)) / d;
4109 ptarget->TCPA = tcpa * 60.;
4114 double OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA, TargetLonCPA;
4116 ll_gc_ll(
gLat,
gLon, cpa_calc_ownship_cog,
gSog * tcpa, &OwnshipLatCPA,
4118 ll_gc_ll(ptarget->Lat, ptarget->Lon, cpa_calc_target_cog,
4119 ptarget->SOG * tcpa, &TargetLatCPA, &TargetLonCPA);
4122 ptarget->CPA = DistGreatCircle(OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA,
4125 ptarget->bCPA_Valid =
true;
4127 if (ptarget->TCPA < 0) ptarget->bCPA_Valid =
false;
4131void AisDecoder::OnTimerDSC(wxTimerEvent &event) {
4134 if (m_ptentative_dsctarget) {
4135 ProcessDSx(m_dsc_last_string,
true);
4139void AisDecoder::OnTimerAIS(wxTimerEvent &event) {
4144 wxDateTime now = wxDateTime::Now();
4147 std::unordered_map<int, std::shared_ptr<AisTargetData>> ¤t_targets =
4150 auto it = current_targets.begin();
4151 std::vector<int> remove_array;
4153 while (it != current_targets.end()) {
4157 current_targets.erase(it);
4162 std::shared_ptr<AisTargetData> xtd = it->second;
4164 int target_posn_age = now.GetTicks() - xtd->PositionReportTicks;
4165 int target_static_age = now.GetTicks() - xtd->StaticReportTicks;
4177 double removelost_Mins = fmax(g_RemoveLost_Mins, g_MarkLost_Mins);
4179 if (g_bInlandEcdis && (xtd->Class != AIS_ARPA)) {
4180 double iECD_LostTimeOut = 0.0;
4184 if (xtd->Class == AIS_CLASS_B) {
4185 if ((xtd->NavStatus == MOORED) || (xtd->NavStatus == AT_ANCHOR))
4186 iECD_LostTimeOut = 18 * 60;
4188 iECD_LostTimeOut = 180;
4190 if (xtd->Class == AIS_CLASS_A) {
4191 if ((xtd->NavStatus == MOORED) || (xtd->NavStatus == AT_ANCHOR)) {
4193 iECD_LostTimeOut = 18 * 60;
4195 iECD_LostTimeOut = 60;
4197 iECD_LostTimeOut = 60;
4200 if ((target_posn_age > iECD_LostTimeOut) &&
4201 (xtd->Class != AIS_GPSG_BUDDY))
4202 xtd->b_active =
false;
4204 removelost_Mins = (2 * iECD_LostTimeOut) / 60.;
4205 }
else if (g_bMarkLost) {
4206 if ((target_posn_age > g_MarkLost_Mins * 60) &&
4207 (xtd->Class != AIS_GPSG_BUDDY))
4208 xtd->b_active =
false;
4211 if (xtd->Class == AIS_SART || xtd->Class == AIS_METEO)
4212 removelost_Mins = 18.0;
4216 if (g_bRemoveLost || g_bInlandEcdis) {
4218 (xtd->Class == AIS_ARPA &&
4220 if (((target_posn_age > removelost_Mins * 60) &&
4221 (xtd->Class != AIS_GPSG_BUDDY)) ||
4226 xtd->b_positionOnceValid =
false;
4234 long mmsi_long = xtd->MMSI;
4235 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
4240 if (target_static_age > removelost_Mins * 60 * 3 || b_arpalost) {
4241 xtd->b_removed =
true;
4243 remove_array.push_back(xtd->MMSI);
4252 if (xtd->MMSI == props->MMSI) {
4253 if (props->m_bignore) {
4254 remove_array.push_back(xtd->MMSI);
4255 xtd->b_removed =
true;
4264 remove_array.push_back(xtd->MMSI);
4265 xtd->b_removed =
true;
4273 for (
unsigned int i = 0; i < remove_array.size(); i++) {
4274 auto itd = current_targets.find(remove_array[i]);
4275 if (itd != current_targets.end()) {
4276 std::shared_ptr<AisTargetData> td = itd->second;
4277 current_targets.erase(itd);
4286 m_bSuppressed =
false;
4287 if (g_bAIS_CPA_Alert_Suppress_Moored || g_bHideMoored ||
4288 (g_bShowScaled && g_bAllowShowScaled))
4289 m_bSuppressed =
true;
4291 m_bAIS_Audio_Alert_On =
false;
4300 std::shared_ptr<AisTargetData> palert_target =
nullptr;
4302 if (!g_pais_alert_dialog_active) {
4304 double tcpa_min = 1e6;
4305 double sart_range = 1e6;
4306 std::shared_ptr<AisTargetData> palert_target_cpa =
nullptr;
4307 std::shared_ptr<AisTargetData> palert_target_sart =
nullptr;
4308 std::shared_ptr<AisTargetData> palert_target_dsc =
nullptr;
4310 for (it = current_targets.begin(); it != current_targets.end(); ++it) {
4311 std::shared_ptr<AisTargetData> td = it->second;
4313 if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
4314 if (g_bAIS_CPA_Alert && td->b_active) {
4315 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4316 if (td->TCPA < tcpa_min) {
4317 tcpa_min = td->TCPA;
4318 palert_target_cpa = td;
4322 }
else if ((td->Class == AIS_DSC) &&
4323 ((td->ShipType == 12) || (td->ShipType == 16))) {
4325 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4326 palert_target_dsc = td;
4329 td->b_isDSCtarget =
false;
4334 else if (td->Class == AIS_SART) {
4336 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4337 if (td->Range_NM < sart_range) {
4338 tcpa_min = sart_range;
4339 palert_target_sart = td;
4349 palert_target = palert_target_cpa;
4351 if (palert_target_sart) {
4352 palert_target = palert_target_sart;
4355 if (palert_target_dsc) {
4356 palert_target = palert_target_dsc;
4360 palert_target = Get_Target_Data_From_MMSI(m_callbacks.get_target_mmsi());
4365 TimerAIS.Start(TIMER_AIS_MSEC, wxTIMER_CONTINUOUS);
4368std::shared_ptr<AisTargetData> AisDecoder::Get_Target_Data_From_MMSI(
4370 if (AISTargetList.find(mmsi) == AISTargetList.end())
return nullptr;
4371 return AISTargetList[mmsi];
4378MmsiProperties::MmsiProperties(wxString &spec) {
4380 wxStringTokenizer tkz(spec,
";");
4383 s = tkz.GetNextToken();
4388 s = tkz.GetNextToken();
4390 if (s.Upper() ==
"ALWAYS")
4391 TrackType = TRACKTYPE_ALWAYS;
4392 else if (s.Upper() ==
"NEVER")
4393 TrackType = TRACKTYPE_NEVER;
4396 s = tkz.GetNextToken();
4398 if (s.Upper() ==
"IGNORE") m_bignore =
true;
4401 s = tkz.GetNextToken();
4403 if (s.Upper() ==
"MOB") m_bMOB =
true;
4406 s = tkz.GetNextToken();
4408 if (s.Upper() ==
"VDM") m_bVDM =
true;
4411 s = tkz.GetNextToken();
4413 if (s.Upper() ==
"FOLLOWER") m_bFollower =
true;
4416 s = tkz.GetNextToken();
4418 if (s.Upper() ==
"PERSIST") m_bPersistentTrack =
true;
4421 s = tkz.GetNextToken();
4423 m_ShipName = s.Upper();
4427MmsiProperties::~MmsiProperties() =
default;
4429void MmsiProperties::Init() {
4431 TrackType = TRACKTYPE_DEFAULT;
4435 m_bFollower =
false;
4436 m_bPersistentTrack =
false;
4440wxString MmsiProperties::Serialize() {
4444 sMMSI.Printf(
"%d", MMSI);
4448 if (TRACKTYPE_ALWAYS == TrackType)
4450 else if (TRACKTYPE_NEVER == TrackType)
4471 sMMSI <<
"Follower";
4475 if (m_bPersistentTrack) {
4480 if (m_ShipName ==
"") {
4481 m_ShipName = GetShipNameFromFile(MMSI);
4483 sMMSI << m_ShipName;
4488 AIS_Target_Name_Hash *AISTargetNamesC,
4489 AIS_Target_Name_Hash *AISTargetNamesNC,
long mmsi) {
4490 if (g_benableAISNameCache) {
4491 wxString ship_name =
"";
4494 if (!pTargetData->b_nameValid) {
4495 AIS_Target_Name_Hash::iterator it = AISTargetNamesC->find(mmsi);
4496 if (it != AISTargetNamesC->end()) {
4497 ship_name = (*AISTargetNamesC)[mmsi].Left(20);
4498 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4499 ship_name.length() + 1);
4500 pTargetData->b_nameValid =
true;
4501 pTargetData->b_nameFromCache =
true;
4502 }
else if (!g_bUseOnlyConfirmedAISName) {
4503 it = AISTargetNamesNC->find(mmsi);
4504 if (it != AISTargetNamesNC->end()) {
4505 ship_name = (*AISTargetNamesNC)[mmsi].Left(20);
4506 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4507 ship_name.length() + 1);
4508 pTargetData->b_nameValid =
true;
4509 pTargetData->b_nameFromCache =
true;
4514 else if ((pTargetData->MID == 5) || (pTargetData->MID == 24) ||
4515 (pTargetData->MID == 19) ||
4516 (pTargetData->MID == 123) ||
4517 (pTargetData->MID == 124)) {
4519 pTargetData->b_nameFromCache =
false;
4520 ship_name = trimAISField(pTargetData->ShipName);
4521 AIS_Target_Name_Hash::iterator itC = AISTargetNamesC->find(mmsi);
4522 AIS_Target_Name_Hash::iterator itNC = AISTargetNamesNC->find(mmsi);
4524 AISTargetNamesC->end()) {
4525 if ((*AISTargetNamesC)[mmsi] ==
4527 if (itNC != AISTargetNamesNC->end()) {
4529 AISTargetNamesNC->erase(itNC);
4532 if (itNC != AISTargetNamesNC->end()) {
4534 if ((*AISTargetNamesNC)[mmsi] ==
4537 (*AISTargetNamesC)[mmsi] = ship_name;
4539 AISTargetNamesNC->erase(itNC);
4542 (*AISTargetNamesNC)[mmsi] = ship_name;
4544 if (g_bUseOnlyConfirmedAISName)
4545 strncpy(pTargetData->ShipName, (*AISTargetNamesC)[mmsi].mb_str(),
4546 (*AISTargetNamesC)[mmsi].Left(20).Length() + 1);
4548 (*AISTargetNamesNC)[mmsi] = ship_name;
4553 AISTargetNamesNC->end()) {
4554 if ((*AISTargetNamesNC)[mmsi] ==
4557 (*AISTargetNamesC)[mmsi] = ship_name;
4559 AISTargetNamesNC->erase(itNC);
4561 (*AISTargetNamesNC)[mmsi] = ship_name;
4564 (*AISTargetNamesNC)[mmsi] = ship_name;
4566 if (g_bUseOnlyConfirmedAISName) {
4567 pTargetData->ShipName[SHIP_NAME_LEN - 1] =
'\0';
4568 strncpy(pTargetData->ShipName,
"Unknown ",
4576wxString GetShipNameFromFile(
int nmmsi) {
4578 if (g_benableAISNameCache) {
4582 while (getline(infile, line)) {
4583 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()),
",");
4584 if (nmmsi == wxAtoi(tokenizer.GetNextToken())) {
4585 name = tokenizer.GetNextToken().Trim();
4588 tokenizer.GetNextToken();
4596void AisDecoder::UpdateMMSItoNameFile(
const wxString &mmsi,
4597 const wxString &name) {
4601 std::map<wxString, wxString> mmsi_name_map;
4607 while (getline(infile, line)) {
4608 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()),
",");
4609 wxString file_mmsi = tokenizer.GetNextToken();
4610 wxString file_name = tokenizer.GetNextToken().Trim();
4611 mmsi_name_map[file_mmsi] = file_name;
4617 mmsi_name_map[mmsi] = name.Upper();
4622 for (
const auto &pair : mmsi_name_map) {
4623 std::string line = std::string(pair.first.mb_str()) +
"," +
4624 std::string(pair.second.mb_str()) +
"\n";
4631wxString AisDecoder::GetMMSItoNameEntry(
const wxString &mmsi) {
4632 return GetShipNameFromFile(wxAtoi(mmsi));
4636int AisMeteoNewMmsi(
int orig_mmsi,
int m_lat,
int m_lon,
int lon_bits = 0,
4640 if ((!lon_bits || lon_bits == 999) && siteID) {
4643 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4644 if (points.size()) {
4645 for (
const auto &point : points) {
4647 if (siteID == point.siteID && orig_mmsi == point.orig_mmsi) {
4649 new_mmsi = point.mmsi;
4655 if (!found && !lon_bits) {
4660 double lon_tentative = 181.;
4661 double lat_tentative = 91.;
4663 if (lon_bits == 25) {
4664 if (m_lon & 0x01000000)
4665 m_lon |= 0xFE000000;
4666 lon_tentative = m_lon / 60000.;
4668 if (m_lat & 0x00800000)
4669 m_lat |= 0xFF000000;
4670 lat_tentative = m_lat / 60000.;
4672 }
else if (lon_bits == 28) {
4673 if (m_lon & 0x08000000)
4674 m_lon |= 0xf0000000;
4675 lon_tentative = m_lon / 600000.;
4677 if (m_lat & 0x04000000)
4678 m_lat |= 0xf8000000;
4679 lat_tentative = m_lat / 600000.;
4684 wxString slon = wxString::Format(
"%0.3f", lon_tentative);
4685 wxString slat = wxString::Format(
"%0.3f", lat_tentative);
4693 static int nextMeteommsi = 199400000;
4694 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4696 if (lon_bits != 999 && !points.empty()) {
4697 wxString t_lat, t_lon;
4698 for (
const auto &point : points) {
4700 if (slat.IsSameAs(point.lat) && slon.IsSameAs(point.lon)) {
4702 new_mmsi = point.mmsi;
4711 points.emplace_back(
4712 AisMeteoPoint(nextMeteommsi, slat, slon, siteID, orig_mmsi));
4713 new_mmsi = nextMeteommsi;
Select * pSelectAIS
Global instance.
wxString AISTargetNameFileName
Global instance.
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
unsigned g_OwnShipmmsi
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Select * pSelectAIS
Global instance.
wxString AISTargetNameFileName
Global instance.
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
unsigned g_OwnShipmmsi
Global instance.
Global state for AIS decoder.
int GetInt(int sp, int len, bool signed_flag=false)
sp is starting bit, 1-based
EventVar plugin_msg
A JSON message should be sent.
EventVar new_track
Notified on new track creation.
EventVar info_update
Notified when AIS user dialogs should update.
EventVar touch_state
Notified when gFrame->TouchAISActive() should be invoked.
Add a new point to the list of Meteo stations.
void Notify() override
Notify all listeners, no data supplied.
Process incoming AIS messages.
void LogInputMessage(const std::shared_ptr< const NavMsg > &msg, bool is_filtered, bool is_error, const wxString &error_msg="") const
Logs an input message with context information.
void Notify(std::shared_ptr< const NavMsg > message)
Accept message received by driver, make it available for upper layers.
A regular Nmea0183 message.
See: https://github.com/OpenCPN/OpenCPN/issues/2729#issuecomment-1179506343.
void Listen(const std::string &key, wxEvtHandler *listener, wxEventType evt)
Set object to send wxEventType ev to listener on changes in key.
Custom event class for OpenCPN's notification system.
std::shared_ptr< const void > GetSharedPtr() const
Gets the event's payload data.
Represents a waypoint or mark within the navigation system.
A parsed SignalK message over ipv4.
Represents a single point in a track.
Represents a track, which is a series of connected track points.
The JSON document writer.
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Raw messages layer, supports sending and recieving navmsg messages.
int g_iSpeedFormat
User-selected speed unit format for display and input.
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
Global variables stored in configuration file.
GUI constant definitions.
Meteo points are Meteorological and Hydrographic data received by NMEA0183 (AIS) VDM message 8 dac:00...
Multiplexer class and helpers.
const wxChar * ParseGPXDateTime(wxDateTime &dt, const wxChar *datetime)
This function parses a string containing a GPX time representation and returns a wxDateTime containin...
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
Navigation Utility Functions without GUI dependencies.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
double gHdt
True heading in degrees (0-359.99).
double gLat
Vessel's current latitude in decimal degrees.
double gCog
Course over ground in degrees (0-359.99).
double gSog
Speed over ground in knots.
double gLon
Vessel's current longitude in decimal degrees.
Position, course, speed, etc.
Waypoint or mark abstraction.
Route * pAISMOBRoute
Global instance.
Select * pSelect
Global instance.
Selected route, segment, waypoint, etc.
A generic position and navigation data structure.
double kCog
Course over ground in degrees.
double kHdt
True heading in degrees.
double kHdm
Magnetic heading in degrees.
double kLat
Latitude in decimal degrees.
double kSog
Speed over ground in knots.
double kVar
Magnetic variation in degrees.
double kLon
Longitude in decimal degrees.
std::vector< Track * > g_TrackList
Global instance.
Recorded track abstraction.