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(obs::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(obs::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(obs::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(obs::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(obs::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(obs::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(obs::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(obs::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;
2096 wxLogDebug(
"AisDecoder::OnEvtSignalK: %s", root.dump().c_str(); );
2098 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
2099 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
2100 bool bnewtarget =
false;
2101 int last_report_ticks;
2103 getAISTarget(mmsi, pTargetData, pStaleTarget, bnewtarget, last_report_ticks,
2106 pTargetData->MMSI = mmsi;
2107 getMmsiProperties(pTargetData);
2108 if (root.HasMember(
"updates") && root[
"updates"].IsArray()) {
2109 for (rapidjson::Value::ConstValueIterator itr = root[
"updates"].Begin();
2110 itr != root[
"updates"].End(); ++itr) {
2111 handleUpdate(pTargetData, bnewtarget, *itr);
2116 if (97 == mmsi / 10000000) {
2117 pTargetData->Class = AIS_SART;
2118 }
else if (1994 == mmsi / 100000) {
2120 pTargetData->Class = AIS_METEO;
2121 pTargetData->met_data.original_mmsi = origin_mmsi;
2122 pTargetData->met_data.stationID = meteo_SiteID;
2125 wxString met_name = pTargetData->ShipName;
2126 if (met_name.Find(
"METEO") == wxNOT_FOUND) {
2129 s_id << meteo_SiteID;
2130 id1 = wxAtoi(s_id.Mid(1, 3));
2131 id2 = wxAtoi(s_id.Mid(4, 3));
2132 met_name =
"METEO ";
2133 met_name << wxString::Format(
"%03d", (id1 + id2)).Right(3);
2134 strncpy(pTargetData->ShipName, met_name, SHIP_NAME_LEN - 1);
2136 pTargetData->b_nameValid =
true;
2137 pTargetData->MID = 123;
2138 pTargetData->COG = -1.;
2139 pTargetData->HDG = 511;
2140 pTargetData->SOG = -1.;
2141 pTargetData->b_NoTrack =
true;
2142 pTargetData->b_show_track =
false;
2144 pTargetData->b_OwnShip =
false;
2145 AISTargetList[pTargetData->MMSI] = pTargetData;
2149void AisDecoder::handleUpdate(
const std::shared_ptr<AisTargetData> &pTargetData,
2150 bool bnewtarget,
const rapidjson::Value &update) {
2151 wxString sfixtime =
"";
2153 if (update.HasMember(
"timestamp")) {
2154 sfixtime = update[
"timestamp"].GetString();
2156 if (update.HasMember(
"values") && update[
"values"].IsArray()) {
2157 for (rapidjson::Value::ConstValueIterator itr = update[
"values"].Begin();
2158 itr != update[
"values"].End(); ++itr) {
2159 updateItem(pTargetData, bnewtarget, *itr, sfixtime);
2162 wxDateTime now = wxDateTime::Now();
2163 pTargetData->m_utc_hour = now.ToUTC().GetHour();
2164 pTargetData->m_utc_min = now.ToUTC().GetMinute();
2165 pTargetData->m_utc_sec = now.ToUTC().GetSecond();
2167 pTargetData->b_active =
true;
2168 pTargetData->b_lost =
false;
2170 if (pTargetData->b_positionOnceValid) {
2171 long mmsi_long = pTargetData->MMSI;
2173 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
2174 (
void *)mmsi_long, SELTYPE_AISTARGET);
2175 pSel->SetUserData(pTargetData->MMSI);
2177 UpdateOneCPA(pTargetData.get());
2178 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
2181void AisDecoder::updateItem(
const std::shared_ptr<AisTargetData> &pTargetData,
2182 bool bnewtarget,
const rapidjson::Value &item,
2183 wxString &sfixtime)
const {
2184 if (item.HasMember(
"path") && item.HasMember(
"value")) {
2185 const wxString &update_path = item[
"path"].GetString();
2186 if (update_path ==
"navigation.position") {
2187 if (item[
"value"].HasMember(
"latitude") &&
2188 item[
"value"].HasMember(
"longitude")) {
2189 wxDateTime now = wxDateTime::Now();
2191 double lat = item[
"value"][
"latitude"].GetDouble();
2192 double lon = item[
"value"][
"longitude"].GetDouble();
2193 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
2194 pTargetData->PositionReportTicks = now.GetTicks();
2195 pTargetData->StaticReportTicks = now.GetTicks();
2196 pTargetData->Lat = lat;
2197 pTargetData->Lon = lon;
2198 pTargetData->b_positionOnceValid =
true;
2199 pTargetData->b_positionDoubtful =
false;
2206 }
else if (update_path ==
"navigation.speedOverGround" &&
2207 item[
"value"].IsNumber()) {
2208 pTargetData->SOG = item[
"value"].GetDouble() * ms_to_knot_factor;
2209 }
else if (update_path ==
"navigation.courseOverGroundTrue" &&
2210 item[
"value"].IsNumber()) {
2211 pTargetData->COG = GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2212 }
else if (update_path ==
"navigation.headingTrue" &&
2213 item[
"value"].IsNumber()) {
2214 pTargetData->HDG = GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2215 }
else if (update_path ==
"navigation.rateOfTurn" &&
2216 item[
"value"].IsNumber()) {
2217 pTargetData->ROTAIS = 4.733 * sqrt(item[
"value"].GetDouble());
2218 }
else if (update_path ==
"design.aisShipType") {
2219 if (item[
"value"].HasMember(
"id")) {
2220 if (!pTargetData->b_isDSCtarget) {
2221 pTargetData->ShipType = item[
"value"][
"id"].GetUint();
2224 }
else if (update_path ==
"atonType") {
2225 if (item[
"value"].HasMember(
"id")) {
2226 pTargetData->ShipType = item[
"value"][
"id"].GetUint();
2228 }
else if (update_path ==
"virtual") {
2229 if (item[
"value"].GetBool()) {
2230 pTargetData->NavStatus = ATON_VIRTUAL;
2232 pTargetData->NavStatus = ATON_REAL;
2234 }
else if (update_path ==
"offPosition") {
2235 if (item[
"value"].GetBool()) {
2236 if (ATON_REAL == pTargetData->NavStatus) {
2237 pTargetData->NavStatus = ATON_REAL_OFFPOSITION;
2238 }
else if (ATON_VIRTUAL == pTargetData->NavStatus) {
2239 pTargetData->NavStatus = ATON_VIRTUAL_OFFPOSITION;
2242 }
else if (update_path ==
"design.draft") {
2243 if (item[
"value"].HasMember(
"maximum") &&
2244 item[
"value"][
"maximum"].IsNumber()) {
2245 pTargetData->Draft = item[
"value"][
"maximum"].GetDouble();
2246 pTargetData->Euro_Draft = item[
"value"][
"maximum"].GetDouble();
2248 if (item[
"value"].HasMember(
"current") &&
2249 item[
"value"][
"current"].IsNumber()) {
2250 double draft = item[
"value"][
"current"].GetDouble();
2252 pTargetData->Draft = draft;
2253 pTargetData->Euro_Draft = draft;
2256 }
else if (update_path ==
"design.length") {
2257 if (pTargetData->DimB == 0) {
2258 if (item[
"value"].HasMember(
"overall")) {
2259 if (item[
"value"][
"overall"].IsNumber()) {
2260 pTargetData->Euro_Length = item[
"value"][
"overall"].GetDouble();
2261 pTargetData->DimA = item[
"value"][
"overall"].GetDouble();
2263 pTargetData->DimB = 0;
2266 }
else if (update_path ==
"sensors.ais.class") {
2267 wxString aisclass = item[
"value"].GetString();
2268 if (aisclass ==
"A") {
2269 if (!pTargetData->b_isDSCtarget) pTargetData->Class = AIS_CLASS_A;
2270 }
else if (aisclass ==
"B") {
2271 if (!pTargetData->b_isDSCtarget) {
2272 if (!isBuoyMmsi(pTargetData->MMSI))
2273 pTargetData->Class = AIS_CLASS_B;
2275 pTargetData->Class = AIS_BUOY;
2278 pTargetData->NavStatus = UNDEFINED;
2280 }
else if (aisclass ==
"BASE") {
2281 pTargetData->Class = AIS_BASE;
2282 }
else if (aisclass ==
"ATON") {
2283 pTargetData->Class = AIS_ATON;
2285 }
else if (update_path ==
"sensors.ais.fromBow") {
2286 if (pTargetData->DimB == 0 && pTargetData->DimA != 0) {
2287 int length = pTargetData->DimA;
2288 if (item[
"value"].IsNumber()) {
2289 pTargetData->DimA = item[
"value"].GetDouble();
2290 pTargetData->DimB = length - item[
"value"].GetDouble();
2293 }
else if (update_path ==
"design.beam") {
2294 if (pTargetData->DimD == 0) {
2295 if (item[
"value"].IsNumber()) {
2296 pTargetData->Euro_Beam = item[
"value"].GetDouble();
2297 pTargetData->DimC = item[
"value"].GetDouble();
2299 pTargetData->DimD = 0;
2301 }
else if (update_path ==
"sensors.ais.fromCenter") {
2302 if (pTargetData->DimD == 0 && pTargetData->DimC != 0) {
2303 int beam = pTargetData->DimC;
2304 int center = beam / 2;
2305 if (item[
"value"].IsNumber()) {
2308 pTargetData->DimC = center + item[
"value"].GetDouble();
2309 pTargetData->DimD = beam - pTargetData->DimC;
2312 }
else if (update_path ==
"navigation.state") {
2313 wxString state = item[
"value"].GetString();
2314 if (state ==
"motoring") {
2315 pTargetData->NavStatus = UNDERWAY_USING_ENGINE;
2316 }
else if (state ==
"anchored") {
2317 pTargetData->NavStatus = AT_ANCHOR;
2318 }
else if (state ==
"not under command") {
2319 pTargetData->NavStatus = NOT_UNDER_COMMAND;
2320 }
else if (state ==
"restricted manouverability") {
2321 pTargetData->NavStatus = RESTRICTED_MANOEUVRABILITY;
2322 }
else if (state ==
"constrained by draft") {
2323 pTargetData->NavStatus = CONSTRAINED_BY_DRAFT;
2324 }
else if (state ==
"moored") {
2325 pTargetData->NavStatus = MOORED;
2326 }
else if (state ==
"aground") {
2327 pTargetData->NavStatus = AGROUND;
2328 }
else if (state ==
"fishing") {
2329 pTargetData->NavStatus = FISHING;
2330 }
else if (state ==
"sailing") {
2331 pTargetData->NavStatus = UNDERWAY_SAILING;
2332 }
else if (state ==
"hazardous material high speed") {
2333 pTargetData->NavStatus = HSC;
2334 }
else if (state ==
"hazardous material wing in ground") {
2335 pTargetData->NavStatus = WIG;
2336 }
else if (state ==
"ais-sart") {
2337 pTargetData->NavStatus = RESERVED_14;
2339 pTargetData->NavStatus = UNDEFINED;
2341 }
else if (update_path ==
"navigation.destination.commonName") {
2342 const wxString &destination = item[
"value"].GetString();
2343 pTargetData->Destination[0] =
'\0';
2344 strncpy(pTargetData->Destination, destination.c_str(),
2345 DESTINATION_LEN - 1);
2346 }
else if (update_path ==
"navigation.destination.eta") {
2347 const wxString &eta = item[
"value"].GetString();
2352 pTargetData->ETA_Mo = tz.GetMonth() + 1;
2353 pTargetData->ETA_Day = tz.GetDay();
2354 pTargetData->ETA_Hr = tz.GetHour();
2355 pTargetData->ETA_Min = tz.GetMinute();
2357 }
else if (update_path ==
"navigation.specialManeuver") {
2358 if (strcmp(
"not available", item[
"value"].GetString()) != 0 &&
2359 pTargetData->IMO < 1) {
2360 const wxString &bluesign = item[
"value"].GetString();
2361 if (
"not engaged" == bluesign) {
2362 pTargetData->blue_paddle = 1;
2364 if (
"engaged" == bluesign) {
2365 pTargetData->blue_paddle = 2;
2367 pTargetData->b_blue_paddle =
2368 pTargetData->blue_paddle == 2 ? true :
false;
2370 }
else if (update_path ==
"sensors.ais.designatedAreaCode") {
2371 if (item[
"value"].GetInt() == 200) {
2372 pTargetData->b_hasInlandDac =
true;
2374 }
else if (update_path ==
"sensors.ais.functionalId") {
2375 if (item[
"value"].GetInt() == 10 && pTargetData->b_hasInlandDac) {
2377 pTargetData->b_isEuroInland =
true;
2381 }
else if (update_path ==
"environment.date") {
2382 const wxString &issued = item[
"value"].GetString();
2387 pTargetData->met_data.day = tz.GetDay();
2388 pTargetData->met_data.hour = tz.GetHour();
2389 pTargetData->met_data.minute = tz.GetMinute();
2391 }
else if (update_path ==
"environment.wind.averageSpeed" &&
2392 item[
"value"].IsNumber()) {
2393 pTargetData->met_data.wind_kn = MS2KNOTS(item[
"value"].GetDouble());
2394 }
else if (update_path ==
"environment.wind.gust" &&
2395 item[
"value"].IsNumber()) {
2396 pTargetData->met_data.wind_gust_kn = MS2KNOTS(item[
"value"].GetDouble());
2397 }
else if (update_path ==
"environment.wind.directionTrue" &&
2398 item[
"value"].IsNumber()) {
2399 pTargetData->met_data.wind_dir =
2400 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2401 }
else if (update_path ==
"environment.wind.gustDirectionTrue" &&
2402 item[
"value"].IsNumber()) {
2403 pTargetData->met_data.wind_gust_dir =
2404 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2405 }
else if (update_path ==
"environment.outside.temperature" &&
2406 item[
"value"].IsNumber()) {
2407 pTargetData->met_data.air_temp = KelvinToC(item[
"value"].GetDouble());
2408 }
else if (update_path ==
"environment.outside.relativeHumidity" &&
2409 item[
"value"].IsNumber()) {
2410 pTargetData->met_data.rel_humid = item[
"value"].GetDouble();
2411 }
else if (update_path ==
"environment.outside.dewPointTemperature" &&
2412 item[
"value"].IsNumber()) {
2413 pTargetData->met_data.dew_point = KelvinToC(item[
"value"].GetDouble());
2414 }
else if (update_path ==
"environment.outside.pressure" &&
2415 item[
"value"].IsNumber()) {
2416 pTargetData->met_data.airpress =
2417 static_cast<int>(item[
"value"].GetDouble() / 100);
2418 }
else if (update_path ==
"environment.water.level" &&
2419 item[
"value"].IsNumber()) {
2420 pTargetData->met_data.water_lev_dev = item[
"value"].GetDouble();
2421 }
else if (update_path ==
"environment.water.current.drift" &&
2422 item[
"value"].IsNumber()) {
2423 pTargetData->met_data.current = MS2KNOTS(item[
"value"].GetDouble());
2424 }
else if (update_path ==
"environment.water.current.set" &&
2425 item[
"value"].IsNumber()) {
2426 pTargetData->met_data.curr_dir =
2427 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2428 }
else if (update_path ==
"environment.water.levelTendencyValue" &&
2429 item[
"value"].IsNumber()) {
2430 pTargetData->met_data.water_lev_trend =
2431 static_cast<int>(item[
"value"].GetDouble());
2432 }
else if (update_path ==
"environment.water.levelTendency") {
2434 }
else if (update_path ==
"environment.water.waves.significantHeight" &&
2435 item[
"value"].IsNumber()) {
2436 pTargetData->met_data.wave_height = item[
"value"].GetDouble();
2437 }
else if (update_path ==
"environment.water.waves.period" &&
2438 item[
"value"].IsNumber()) {
2439 pTargetData->met_data.wave_period =
2440 static_cast<int>(item[
"value"].GetDouble());
2441 }
else if (update_path ==
"environment.water.waves.directionTrue" &&
2442 item[
"value"].IsNumber()) {
2443 pTargetData->met_data.wave_dir =
2444 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2445 }
else if (update_path ==
"environment.water.swell.height" &&
2446 item[
"value"].IsNumber()) {
2447 pTargetData->met_data.swell_height = item[
"value"].GetDouble();
2448 }
else if (update_path ==
"environment.water.swell.period" &&
2449 item[
"value"].IsNumber()) {
2450 pTargetData->met_data.swell_per =
2451 static_cast<int>(item[
"value"].GetDouble());
2452 }
else if (update_path ==
"environment.water.swell.directionTrue" &&
2453 item[
"value"].IsNumber()) {
2454 pTargetData->met_data.swell_dir =
2455 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2456 }
else if (update_path ==
"environment.water.temperature" &&
2457 item[
"value"].IsNumber()) {
2458 pTargetData->met_data.water_temp = KelvinToC(item[
"value"].GetDouble());
2459 }
else if (update_path ==
"environment.water.salinity" &&
2460 item[
"value"].IsNumber()) {
2461 pTargetData->met_data.salinity = item[
"value"].GetDouble();
2462 }
else if (update_path ==
"environment.water.ice") {
2464 }
else if (update_path ==
"environment.water.iceValue" &&
2465 item[
"value"].IsNumber()) {
2466 pTargetData->met_data.ice =
static_cast<int>(item[
"value"].GetDouble());
2467 }
else if (update_path ==
"environment.water.seaStateValue" &&
2468 item[
"value"].IsNumber()) {
2469 pTargetData->met_data.seastate =
2470 static_cast<int>(item[
"value"].GetDouble());
2471 }
else if (update_path ==
"environment.water.seaState") {
2473 }
else if (update_path ==
"environment.outside.precipitation") {
2475 }
else if (update_path ==
"environment.outside.precipitationValue" &&
2476 item[
"value"].IsNumber()) {
2477 pTargetData->met_data.precipitation =
2478 static_cast<int>(item[
"value"].GetDouble());
2479 }
else if (update_path ==
"environment.outside.pressureTendencyValue" &&
2480 item[
"value"].IsNumber()) {
2481 pTargetData->met_data.airpress_tend =
2482 static_cast<int>(item[
"value"].GetDouble());
2483 }
else if (update_path ==
"environment.outside.pressureTendency") {
2485 }
else if (update_path ==
"environment.outside.horizontalVisibility" &&
2486 item[
"value"].IsNumber()) {
2487 pTargetData->met_data.hor_vis =
2488 GEODESIC_METERS2NM(item[
"value"].GetDouble());
2489 }
else if (update_path ==
2490 "environment.outside.horizontalVisibility.overRange") {
2491 pTargetData->met_data.hor_vis_GT = item[
"value"].GetBool();
2492 }
else if (update_path.empty()) {
2493 if (item[
"value"].HasMember(
"name")) {
2494 const wxString &name = item[
"value"][
"name"].GetString();
2495 strncpy(pTargetData->ShipName, name.c_str(), SHIP_NAME_LEN - 1);
2496 pTargetData->b_nameValid =
true;
2497 pTargetData->MID = 123;
2498 }
else if (item[
"value"].HasMember(
"registrations")) {
2499 const wxString &imo = item[
"value"][
"registrations"][
"imo"].GetString();
2500 pTargetData->IMO = wxAtoi(imo.Right(7));
2501 }
else if (item[
"value"].HasMember(
"communication")) {
2502 const wxString &callsign =
2503 item[
"value"][
"communication"][
"callsignVhf"].GetString();
2504 strncpy(pTargetData->CallSign, callsign.c_str(), 7);
2506 if (item[
"value"].HasMember(
"mmsi") &&
2507 1994 != (pTargetData->MMSI) / 100000) {
2509 wxString tmp = item[
"value"][
"mmsi"].GetString();
2510 if (tmp.ToLong(&mmsi)) {
2511 pTargetData->MMSI = mmsi;
2513 if (97 == mmsi / 10000000) {
2514 pTargetData->Class = AIS_SART;
2516 if (111 == mmsi / 1000000) {
2517 pTargetData->b_SarAircraftPosnReport =
true;
2520 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
2525 wxLogMessage(wxString::Format(
2526 "** AisDecoder::updateItem: unhandled path %s", update_path));
2528 rapidjson::StringBuffer buffer;
2529 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2530 item.Accept(writer);
2531 wxString msg(
"update: ");
2532 msg.append(buffer.GetString());
2542AisError AisDecoder::DecodeSingleVDO(
const wxString &str,
GenericPosDatEx *pos,
2543 wxString *accumulator) {
2545 if (str.Len() > 128)
return AIS_NMEAVDX_TOO_LONG;
2547 if (!NMEACheckSumOK(str))
return AIS_NMEAVDX_CHECKSUM_BAD;
2549 if (!pos)
return AIS_GENERIC_ERROR;
2551 if (!accumulator)
return AIS_GENERIC_ERROR;
2554 if (!str.Mid(1, 5).IsSameAs(
"AIVDO"))
return AIS_GENERIC_ERROR;
2557 wxStringTokenizer tkz(str,
",");
2560 token = tkz.GetNextToken();
2562 token = tkz.GetNextToken();
2563 int nsentences = atoi(token.mb_str());
2565 token = tkz.GetNextToken();
2566 int isentence = atoi(token.mb_str());
2568 token = tkz.GetNextToken();
2569 token = tkz.GetNextToken();
2571 wxString string_to_parse;
2572 string_to_parse.Clear();
2585 if ((1 == nsentences) && (1 == isentence)) {
2586 string_to_parse = tkz.GetNextToken();
2589 else if (nsentences > 1) {
2590 if (1 == isentence) {
2591 *accumulator = tkz.GetNextToken();
2595 accumulator->Append(tkz.GetNextToken());
2598 if (isentence == nsentences) {
2599 string_to_parse = *accumulator;
2603 if (string_to_parse.IsEmpty() &&
2605 return AIS_INCOMPLETE_MULTIPART;
2614 auto TargetData = AisTargetDataMaker::GetInstance().GetTargetData();
2616 bool bdecode_result = Parse_VDXBitstring(&strbit, TargetData);
2618 if (bdecode_result) {
2619 switch (TargetData->MID) {
2624 if (!TargetData->b_positionDoubtful) {
2625 pos->
kLat = TargetData->Lat;
2626 pos->
kLon = TargetData->Lon;
2632 if (TargetData->COG == 360.0)
2635 pos->
kCog = TargetData->COG;
2637 if (TargetData->SOG > 102.2)
2640 pos->
kSog = TargetData->SOG;
2642 if ((
int)TargetData->HDG == 511)
2645 pos->
kHdt = TargetData->HDG;
2653 return AIS_GENERIC_ERROR;
2658 return AIS_GENERIC_ERROR;
2666AisError AisDecoder::DecodeN0183(
const wxString &str) {
2667 AisError ret = AIS_GENERIC_ERROR;
2668 wxString string_to_parse;
2670 double gpsg_lat, gpsg_lon, gpsg_mins, gpsg_degs;
2671 double gpsg_cog, gpsg_sog, gpsg_utc_time;
2672 int gpsg_utc_hour = 0;
2673 int gpsg_utc_min = 0;
2674 int gpsg_utc_sec = 0;
2675 char gpsg_name_str[21];
2678 bool bdecode_result =
false;
2685 long arpa_tgt_num = 0;
2686 double arpa_sog = 0.;
2687 double arpa_cog = 0.;
2688 double arpa_lat = 0.;
2689 double arpa_lon = 0.;
2690 double arpa_dist = 0.;
2691 double arpa_brg = 0.;
2692 wxString arpa_brgunit;
2693 wxString arpa_status;
2694 wxString arpa_distunit;
2695 wxString arpa_cogunit;
2696 double arpa_mins, arpa_degs;
2697 double arpa_utc_time;
2698 int arpa_utc_hour = 0;
2699 int arpa_utc_min = 0;
2700 int arpa_utc_sec = 0;
2701 char arpa_name_str[21];
2702 bool arpa_lost =
true;
2703 bool arpa_nottracked =
false;
2705 double aprs_lat = 0.;
2706 double aprs_lon = 0.;
2707 char aprs_name_str[21];
2708 double aprs_mins, aprs_degs;
2710 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
2711 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
2712 bool bnewtarget =
false;
2713 int last_report_ticks;
2717 if (str.Len() > 128)
return AIS_NMEAVDX_TOO_LONG;
2719 if (!NMEACheckSumOK(str)) {
2720 return AIS_NMEAVDX_CHECKSUM_BAD;
2722 if (str.Mid(1, 2).IsSameAs(
"CD")) {
2725 }
else if (str.Mid(3, 3).IsSameAs(
"TTM")) {
2729 wxStringTokenizer tkz(str,
",*");
2732 token = tkz.GetNextToken();
2733 token = tkz.GetNextToken();
2734 token.ToLong(&arpa_tgt_num);
2735 token = tkz.GetNextToken();
2736 token.ToDouble(&arpa_dist);
2737 token = tkz.GetNextToken();
2738 token.ToDouble(&arpa_brg);
2739 arpa_brgunit = tkz.GetNextToken();
2740 if (arpa_brgunit ==
"R") {
2741 if (std::isnan(arpa_ref_hdg)) {
2742 if (!std::isnan(
gHdt))
2747 arpa_brg += arpa_ref_hdg;
2748 if (arpa_brg >= 360.) arpa_brg -= 360.;
2750 token = tkz.GetNextToken();
2751 token.ToDouble(&arpa_sog);
2752 token = tkz.GetNextToken();
2753 token.ToDouble(&arpa_cog);
2754 arpa_cogunit = tkz.GetNextToken();
2755 if (arpa_cogunit ==
"R") {
2756 if (std::isnan(arpa_ref_hdg)) {
2757 if (!std::isnan(
gHdt))
2762 arpa_cog += arpa_ref_hdg;
2763 if (arpa_cog >= 360.) arpa_cog -= 360.;
2765 token = tkz.GetNextToken();
2766 token = tkz.GetNextToken();
2768 arpa_distunit = tkz.GetNextToken();
2769 token = tkz.GetNextToken();
2770 if (token ==
"") token = wxString::Format(
"ARPA %ld", arpa_tgt_num);
2771 int len = wxMin(token.Length(), 20);
2772 strncpy(arpa_name_str, token.mb_str(), len);
2773 arpa_name_str[len] = 0;
2774 arpa_status = tkz.GetNextToken();
2775 if (arpa_status !=
"L") {
2777 }
else if (arpa_status !=
"")
2778 arpa_nottracked =
true;
2779 wxString arpa_reftarget = tkz.GetNextToken();
2780 if (tkz.HasMoreTokens()) {
2781 token = tkz.GetNextToken();
2782 token.ToDouble(&arpa_utc_time);
2783 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2784 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2786 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2788 arpa_utc_hour = wxDateTime::Now().ToUTC().GetHour();
2789 arpa_utc_min = wxDateTime::Now().ToUTC().GetMinute();
2790 arpa_utc_sec = wxDateTime::Now().ToUTC().GetSecond();
2793 if (arpa_distunit ==
"K") {
2796 }
else if (arpa_distunit ==
"S") {
2801 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2805 }
else if (str.Mid(3, 3).IsSameAs(
"TLL")) {
2808 wxStringTokenizer tkz(str,
",*");
2811 wxString aprs_tll_str = tkz.GetNextToken();
2812 token = tkz.GetNextToken();
2813 token.ToLong(&arpa_tgt_num);
2814 token = tkz.GetNextToken();
2815 token.ToDouble(&arpa_lat);
2816 arpa_degs = (int)(arpa_lat / 100.0);
2817 arpa_mins = arpa_lat - arpa_degs * 100.0;
2818 arpa_lat = arpa_degs + arpa_mins / 60.0;
2819 token = tkz.GetNextToken();
2820 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2821 token.Mid(0, 1).Contains(
"s") ==
true)
2822 arpa_lat = 0. - arpa_lat;
2823 token = tkz.GetNextToken();
2824 token.ToDouble(&arpa_lon);
2825 arpa_degs = (int)(arpa_lon / 100.0);
2826 arpa_mins = arpa_lon - arpa_degs * 100.0;
2827 arpa_lon = arpa_degs + arpa_mins / 60.0;
2828 token = tkz.GetNextToken();
2829 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2830 token.Mid(0, 1).Contains(
"w") ==
true)
2831 arpa_lon = 0. - arpa_lon;
2832 token = tkz.GetNextToken();
2833 if (token ==
"") token = wxString::Format(
"ARPA %d", arpa_tgt_num);
2834 int len = wxMin(token.Length(), 20);
2835 strncpy(arpa_name_str, token.mb_str(), len);
2836 arpa_name_str[len] = 0;
2837 token = tkz.GetNextToken();
2838 token.ToDouble(&arpa_utc_time);
2839 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2840 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2842 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2847 if (arpa_status !=
"L")
2849 else if (arpa_status !=
"")
2850 arpa_nottracked =
true;
2851 wxString arpa_reftarget = tkz.GetNextToken();
2852 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2856 }
else if (str.Mid(3, 3).IsSameAs(
"OSD")) {
2858 wxStringTokenizer tkz(str,
",*");
2861 token = tkz.GetNextToken();
2862 token = tkz.GetNextToken();
2863 token.ToDouble(&arpa_ref_hdg);
2873 }
else if (g_bWplUsePosition && str.Mid(3, 3).IsSameAs(
"WPL")) {
2875 wxStringTokenizer tkz(str,
",*");
2878 token = tkz.GetNextToken();
2879 token = tkz.GetNextToken();
2880 token.ToDouble(&aprs_lat);
2881 aprs_degs = (int)(aprs_lat / 100.0);
2882 aprs_mins = aprs_lat - aprs_degs * 100.0;
2883 aprs_lat = aprs_degs + aprs_mins / 60.0;
2884 token = tkz.GetNextToken();
2885 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2886 token.Mid(0, 1).Contains(
"s") ==
true)
2887 aprs_lat = 0. - aprs_lat;
2888 token = tkz.GetNextToken();
2889 token.ToDouble(&aprs_lon);
2890 aprs_degs = (int)(aprs_lon / 100.0);
2891 aprs_mins = aprs_lon - aprs_degs * 100.0;
2892 aprs_lon = aprs_degs + aprs_mins / 60.0;
2893 token = tkz.GetNextToken();
2894 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2895 token.Mid(0, 1).Contains(
"w") ==
true)
2896 aprs_lon = 0. - aprs_lon;
2897 token = tkz.GetNextToken();
2898 int len = wxMin(token.Length(), 20);
2899 strncpy(aprs_name_str, token.mb_str(), len + 1);
2900 if (0 == g_WplAction) {
2902 aprs_name_str[len] = 0;
2903 for (i = 0; i < len; i++) {
2905 hash += (int)(aprs_name_str[i]);
2906 while (hash >= 100000) hash = hash / 100000;
2908 mmsi = aprs_mmsi = 199300000 + hash;
2912 }
else if (1 == g_WplAction) {
2914 new RoutePoint(aprs_lat, aprs_lon,
"", aprs_name_str,
"",
false);
2915 pWP->m_bIsolatedMark =
true;
2916 InsertWpt(pWP,
true);
2918 }
else if (str.Mid(1, 5).IsSameAs(
"FRPOS")) {
2922 wxStringTokenizer tkz(str,
",*");
2925 token = tkz.GetNextToken();
2927 token = tkz.GetNextToken();
2928 token.ToDouble(&gpsg_lat);
2929 gpsg_degs = (int)(gpsg_lat / 100.0);
2930 gpsg_mins = gpsg_lat - gpsg_degs * 100.0;
2931 gpsg_lat = gpsg_degs + gpsg_mins / 60.0;
2933 token = tkz.GetNextToken();
2934 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2935 token.Mid(0, 1).Contains(
"s") ==
true)
2936 gpsg_lat = 0. - gpsg_lat;
2938 token = tkz.GetNextToken();
2939 token.ToDouble(&gpsg_lon);
2940 gpsg_degs = (int)(gpsg_lon / 100.0);
2941 gpsg_mins = gpsg_lon - gpsg_degs * 100.0;
2942 gpsg_lon = gpsg_degs + gpsg_mins / 60.0;
2944 token = tkz.GetNextToken();
2945 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2946 token.Mid(0, 1).Contains(
"w") ==
true)
2947 gpsg_lon = 0. - gpsg_lon;
2949 token = tkz.GetNextToken();
2952 token = tkz.GetNextToken();
2953 token.ToDouble(&gpsg_sog);
2955 token = tkz.GetNextToken();
2956 token.ToDouble(&gpsg_cog);
2958 token = tkz.GetNextToken();
2961 token = tkz.GetNextToken();
2962 token.ToDouble(&gpsg_utc_time);
2963 gpsg_utc_hour = (int)(gpsg_utc_time / 10000.0);
2964 gpsg_utc_min = (int)(gpsg_utc_time / 100.0) - gpsg_utc_hour * 100;
2966 (int)gpsg_utc_time - gpsg_utc_hour * 10000 - gpsg_utc_min * 100;
2970 token = tkz.GetNextToken();
2971 int i, len, hash = 0;
2972 len = wxMin(wxStrlen(token), 20);
2973 strncpy(gpsg_name_str, token.mb_str(), len);
2974 gpsg_name_str[len] = 0;
2975 for (i = 0; i < len; i++) {
2977 hash += (int)(token[i]);
2978 while (hash >= 100000) hash = hash / 100000;
2981 gpsg_mmsi = 199000000 + hash;
2983 }
else if (!str.Mid(3, 2).IsSameAs(
"VD")) {
2984 return AIS_NMEAVDX_BAD;
2990 string_to_parse.Clear();
2992 if (str.Mid(3, 2).IsSameAs(
"VD")) {
2993 wxStringTokenizer tkz(str,
",");
2996 token = tkz.GetNextToken();
2998 token = tkz.GetNextToken();
2999 nsentences = atoi(token.mb_str());
3001 token = tkz.GetNextToken();
3002 isentence = atoi(token.mb_str());
3004 token = tkz.GetNextToken();
3005 long lsequence_id = 0;
3006 token.ToLong(&lsequence_id);
3008 token = tkz.GetNextToken();
3010 token.ToLong(&lchannel);
3015 if ((1 == nsentences) && (1 == isentence)) {
3016 string_to_parse = tkz.GetNextToken();
3019 else if (nsentences > 1) {
3020 if (1 == isentence) {
3021 sentence_accumulator = tkz.GetNextToken();
3025 sentence_accumulator += tkz.GetNextToken();
3028 if (isentence == nsentences) {
3029 string_to_parse = sentence_accumulator;
3034 if (mmsi || (!string_to_parse.IsEmpty() &&
3035 (string_to_parse.Len() < AIS_MAX_MESSAGE_LEN))) {
3037 wxCharBuffer abuf = string_to_parse.ToUTF8();
3039 return AIS_GENERIC_ERROR;
3044 if (!mmsi) mmsi = strbit.GetInt(9, 30);
3045 long mmsi_long = mmsi;
3048 int origin_mmsi = 0;
3049 int messID = strbit.GetInt(1, 6);
3050 int dac = strbit.GetInt(41, 10);
3051 int fi = strbit.GetInt(51, 6);
3053 int met_lon, met_lat;
3054 if (dac == 001 && fi == 31) {
3056 met_lon = strbit.GetInt(57, 25);
3057 met_lat = strbit.GetInt(82, 24);
3058 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 25, 0);
3061 }
else if (dac == 367 && fi == 33) {
3063 const int size = strbit.GetBitCount();
3064 if (size < 168)
return AIS_GENERIC_ERROR;
3065 const int startb = 56;
3066 const int slot_size = 112;
3067 const int extra_bits = (size - startb) % slot_size;
3068 if (extra_bits > 0)
return AIS_GENERIC_ERROR;
3070 int mes_type = strbit.GetInt(57, 4);
3071 int site_ID = strbit.GetInt(77, 7);
3072 if (mes_type == 0) {
3074 met_lon = strbit.GetInt(90, 28);
3075 met_lat = strbit.GetInt(118, 27);
3076 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 28, site_ID);
3081 int x_mmsi = AisMeteoNewMmsi(mmsi, 91, 181, 0, site_ID);
3087 return AIS_GENERIC_ERROR;
3096 auto it = AISTargetList.find(mmsi);
3097 if (it == AISTargetList.end())
3099 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3104 pTargetData->MMSI = mmsi;
3105 pTargetData->met_data.original_mmsi = origin_mmsi;
3108 pTargetData = it->second;
3111 pStaleTarget = pTargetData;
3113 pTargetData->MMSI = mmsi;
3114 pTargetData->met_data.original_mmsi = origin_mmsi;
3119 if (mmsi ==
static_cast<unsigned>(props->MMSI)) {
3121 if (TRACKTYPE_NEVER == props->TrackType) {
3122 pTargetData->b_show_track =
false;
3123 }
else if (TRACKTYPE_ALWAYS == props->TrackType) {
3124 pTargetData->b_show_track =
true;
3129 if (props->m_bignore)
return AIS_NoError;
3132 else if (props->m_bVDM) {
3134 if (str.Mid(3, 9).IsSameAs(
"VDM,1,1,,")) {
3135 int message_ID = strbit.GetInt(1, 6);
3138 if ((message_ID <= 3) || (message_ID == 18)) {
3140 pTargetData->b_OwnShip =
true;
3142 wxString aivdostr = str;
3143 aivdostr.replace(1, 5,
"AIVDO");
3144 unsigned char calculated_checksum = 0;
3145 wxString::iterator j;
3146 for (j = aivdostr.begin() + 1; j != aivdostr.end() && *j !=
'*';
3148 calculated_checksum ^=
static_cast<unsigned char>(*j);
3151 if (j <= aivdostr.end() - 3)
3154 wxString::Format(_(
"%02X"), calculated_checksum));
3156 gps_watchdog_timeout_ticks =
3159 std::string full_sentence = aivdostr.ToStdString();
3160 auto address = std::make_shared<NavAddr0183>(
"virtual");
3163 auto msg = std::make_shared<const Nmea0183Msg>(
3164 "AIVDO", full_sentence, address);
3165 NavMsgBus::GetInstance().
Notify(std::move(msg));
3175 wxDateTime now = wxDateTime::Now();
3179 last_report_ticks = pStaleTarget->PositionReportTicks;
3181 last_report_ticks = now.GetTicks();
3185 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
3189 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3190 pTargetData->PositionReportTicks = now.GetTicks();
3191 pTargetData->StaticReportTicks = now.GetTicks();
3192 pTargetData->m_utc_hour = gpsg_utc_hour;
3193 pTargetData->m_utc_min = gpsg_utc_min;
3194 pTargetData->m_utc_sec = gpsg_utc_sec;
3195 pTargetData->m_date_string = gpsg_date;
3196 pTargetData->MMSI = gpsg_mmsi;
3197 pTargetData->NavStatus = 0;
3198 pTargetData->Lat = gpsg_lat;
3199 pTargetData->Lon = gpsg_lon;
3200 pTargetData->b_positionOnceValid =
true;
3201 pTargetData->COG = gpsg_cog;
3202 pTargetData->SOG = gpsg_sog;
3203 pTargetData->ShipType = 52;
3204 pTargetData->Class = AIS_GPSG_BUDDY;
3205 memcpy(pTargetData->ShipName, gpsg_name_str,
sizeof(gpsg_name_str));
3206 pTargetData->b_nameValid =
true;
3207 pTargetData->b_active =
true;
3208 pTargetData->b_lost =
false;
3210 bdecode_result =
true;
3211 }
else if (arpa_mmsi) {
3212 pTargetData->m_utc_hour = arpa_utc_hour;
3213 pTargetData->m_utc_min = arpa_utc_min;
3214 pTargetData->m_utc_sec = arpa_utc_sec;
3215 pTargetData->MMSI = arpa_mmsi;
3216 pTargetData->NavStatus = 15;
3217 if (str.Mid(3, 3).IsSameAs(
"TLL")) {
3220 (now.GetTicks() - pTargetData->PositionReportTicks);
3221 if (age_of_last > 0) {
3222 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, arpa_lat,
3223 arpa_lon, &pTargetData->COG, &pTargetData->SOG);
3224 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3227 pTargetData->Lat = arpa_lat;
3228 pTargetData->Lon = arpa_lon;
3229 }
else if (str.Mid(3, 3).IsSameAs(
"TTM")) {
3230 if (arpa_dist != 0.)
3231 ll_gc_ll(
gLat,
gLon, arpa_brg, arpa_dist, &pTargetData->Lat,
3235 pTargetData->COG = arpa_cog;
3236 pTargetData->SOG = arpa_sog;
3238 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3239 pTargetData->PositionReportTicks = now.GetTicks();
3240 pTargetData->StaticReportTicks = now.GetTicks();
3241 pTargetData->b_positionOnceValid =
true;
3242 pTargetData->ShipType = 55;
3243 pTargetData->Class = AIS_ARPA;
3245 memcpy(pTargetData->ShipName, arpa_name_str,
sizeof(arpa_name_str));
3246 if (arpa_status !=
"Q")
3247 pTargetData->b_nameValid =
true;
3249 pTargetData->b_nameValid =
false;
3250 pTargetData->b_active = !arpa_lost;
3251 pTargetData->b_lost = arpa_nottracked;
3253 bdecode_result =
true;
3254 }
else if (aprs_mmsi) {
3255 pTargetData->m_utc_hour = now.GetHour();
3256 pTargetData->m_utc_min = now.GetMinute();
3257 pTargetData->m_utc_sec = now.GetSecond();
3258 pTargetData->MMSI = aprs_mmsi;
3259 pTargetData->NavStatus = 15;
3261 int age_of_last = (now.GetTicks() - pTargetData->PositionReportTicks);
3262 if (age_of_last > 0) {
3263 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, aprs_lat,
3264 aprs_lon, &pTargetData->COG, &pTargetData->SOG);
3265 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3268 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3269 pTargetData->PositionReportTicks = now.GetTicks();
3270 pTargetData->StaticReportTicks = now.GetTicks();
3271 pTargetData->Lat = aprs_lat;
3272 pTargetData->Lon = aprs_lon;
3273 pTargetData->b_positionOnceValid =
true;
3274 pTargetData->ShipType = 56;
3275 pTargetData->Class = AIS_APRS;
3276 memcpy(pTargetData->ShipName, aprs_name_str,
sizeof(aprs_name_str));
3277 pTargetData->b_nameValid =
true;
3278 pTargetData->b_active =
true;
3279 pTargetData->b_lost =
false;
3281 bdecode_result =
true;
3285 Parse_VDXBitstring(&strbit, pTargetData);
3289 getMmsiProperties(pTargetData);
3292 pTargetData->RecentPeriod =
3293 pTargetData->PositionReportTicks - last_report_ticks;
3298 pTargetData =
nullptr;
3303 CommitAISTarget(pTargetData, str, bdecode_result, bnewtarget);
3308 if ((n_msgs % 10000) == 0)
3309 printf(
"n_msgs %10d m_n_targets: %6d n_msg1: %10d n_msg5+24: %10d \n",
3310 n_msgs, m_n_targets, n_msg1, n_msg5 + n_msg24);
3316void AisDecoder::CommitAISTarget(
3317 const std::shared_ptr<AisTargetData> &pTargetData,
const wxString &str,
3318 bool message_valid,
bool new_target) {
3319 m_pLatestTargetData = pTargetData;
3321 if (!str.IsEmpty()) {
3322 if (str.Mid(3, 3).IsSameAs(
"VDO"))
3323 pTargetData->b_OwnShip =
true;
3325 pTargetData->b_OwnShip =
false;
3328 if (!pTargetData->b_OwnShip) {
3330 if (0 == m_persistent_tracks.count(pTargetData->MMSI)) {
3332 pTargetData->b_PersistTrack =
false;
3337 pTargetData->b_mPropPersistTrack = props->m_bPersistentTrack;
3344 pTargetData->b_NoTrack =
false;
3349 if (message_valid) {
3351 if (pTargetData->MMSI) {
3352 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
3355 AISTargetList[pTargetData->MMSI] =
3358 if (!pTargetData->area_notices.empty()) {
3359 auto it = AIS_AreaNotice_Sources.find(pTargetData->MMSI);
3360 if (it == AIS_AreaNotice_Sources.end())
3361 AIS_AreaNotice_Sources[pTargetData->MMSI] = pTargetData;
3366 if (!pTargetData->b_OwnShip) {
3367 if (pTargetData->b_positionOnceValid) {
3368 long mmsi_long = pTargetData->MMSI;
3370 pTargetData->Lat, pTargetData->Lon, (
void *)mmsi_long,
3372 pSel->SetUserData(pTargetData->MMSI);
3376 UpdateOneCPA(pTargetData.get());
3379 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3382 plugin_msg_evt.Notify(std::make_shared<AisTargetData>(*pTargetData),
"");
3392 if (!pTargetData->b_OwnShip) {
3393 if (pTargetData->b_positionOnceValid) {
3394 long mmsi_long = pTargetData->MMSI;
3396 pTargetData->Lat, pTargetData->Lon, (
void *)mmsi_long,
3398 pSel->SetUserData(pTargetData->MMSI);
3405void AisDecoder::getAISTarget(
long mmsi,
3406 std::shared_ptr<AisTargetData> &pTargetData,
3407 std::shared_ptr<AisTargetData> &pStaleTarget,
3408 bool &bnewtarget,
int &last_report_ticks,
3410 now = wxDateTime::Now();
3411 auto it = AISTargetList.find(mmsi);
3412 if (it == AISTargetList.end())
3414 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3418 pTargetData = it->second;
3419 pStaleTarget = pTargetData;
3426 last_report_ticks = pStaleTarget->PositionReportTicks;
3428 last_report_ticks = now.GetTicks();
3432 pSelectAIS->DeleteSelectablePoint((
void *)mmsi, SELTYPE_AISTARGET);
3435std::shared_ptr<AisTargetData> AisDecoder::ProcessDSx(
const wxString &str,
3437 double dsc_lat = 0.;
3438 double dsc_lon = 0.;
3439 double dsc_mins, dsc_degs, dsc_tmp, dsc_addr;
3441 double dse_lat = 0.;
3442 double dse_lon = 0.;
3443 long dsc_fmt, dsc_quadrant, dsc_cat, dsc_nature;
3446 int dsc_tx_mmsi = 0;
3448 double dse_cog = 0.;
3449 double dse_sog = 0.;
3450 wxString dse_shipName =
"";
3455 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
3459 wxStringTokenizer tkz(str,
",*");
3462 token = tkz.GetNextToken();
3464 if (str.Mid(3, 3).IsSameAs(
"DSC")) {
3465 m_dsc_last_string = str;
3467 token = tkz.GetNextToken();
3471 token = tkz.GetNextToken();
3473 if (dsc_fmt == 12 || dsc_fmt == 16) {
3474 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3476 token.ToDouble(&dsc_addr);
3477 dsc_mmsi = 0 - (int)(dsc_addr / 10);
3480 token = tkz.GetNextToken();
3481 token.ToLong(&dsc_cat);
3483 token = tkz.GetNextToken();
3484 if (!token.IsSameAs(
"")) {
3485 token.ToLong(&dsc_nature);
3489 token = tkz.GetNextToken();
3491 token = tkz.GetNextToken();
3492 token.ToDouble(&dsc_tmp);
3494 token = tkz.GetNextToken();
3495 token = tkz.GetNextToken();
3496 if (dsc_fmt == 16 && dsc_cat == 12 && !token.IsSameAs(
"")) {
3498 dsc_tx_mmsi = dsc_mmsi;
3499 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3501 token = tkz.GetNextToken();
3502 if (dsc_fmt == 16 && dsc_cat == 12) {
3503 if (!token.IsSameAs(
"")) {
3504 token.ToLong(&dsc_nature);
3508 token = tkz.GetNextToken();
3509 token = tkz.GetNextToken();
3511 dsc_quadrant = (int)(dsc_tmp / 1000000000.0);
3513 if (dsc_quadrant > 3)
3516 dsc_lat = (int)(dsc_tmp / 100000.0);
3517 dsc_lon = dsc_tmp - dsc_lat * 100000.0;
3518 dsc_lat = dsc_lat - dsc_quadrant * 10000;
3519 dsc_degs = (int)(dsc_lat / 100.0);
3520 dsc_mins = dsc_lat - dsc_degs * 100.0;
3521 dsc_lat = dsc_degs + dsc_mins / 60.0;
3523 dsc_degs = (int)(dsc_lon / 100.0);
3524 dsc_mins = dsc_lon - dsc_degs * 100.0;
3525 dsc_lon = dsc_degs + dsc_mins / 60.0;
3526 switch (dsc_quadrant) {
3542 if (dsc_fmt != 02) mmsi = (int)dsc_mmsi;
3544 }
else if (str.Mid(3, 3).IsSameAs(
"DSE")) {
3545 token = tkz.GetNextToken();
3546 token = tkz.GetNextToken();
3547 token = tkz.GetNextToken();
3548 token = tkz.GetNextToken();
3549 dse_mmsi = wxAtoi(token.Mid(
3555 token = tkz.GetNextToken();
3558 token.ToDouble(&dse_tmp);
3559 dse_lat = (int)(dse_tmp / 10000.0);
3560 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3561 dse_lat = dse_lat / 600000.0;
3562 dse_lon = dse_lon / 600000.0;
3565 while (tkz.HasMoreTokens()) {
3566 dseSymbol = tkz.GetNextToken();
3567 token = tkz.GetNextToken();
3568 if (dseSymbol.IsSameAs(
"00")) {
3569 token.ToDouble(&dse_tmp);
3570 dse_lat = (int)(dse_tmp / 10000.0);
3571 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3572 dse_lat = dse_lat / 600000.0;
3573 dse_lon = dse_lon / 600000.0;
3574 }
else if (dseSymbol.IsSameAs(
"01")) {
3575 }
else if (dseSymbol.IsSameAs(
"02")) {
3576 token.ToDouble(&dse_tmp);
3577 dse_sog = dse_tmp / 10.0;
3578 }
else if (dseSymbol.IsSameAs(
"03")) {
3579 token.ToDouble(&dse_tmp);
3580 dse_cog = dse_tmp / 10.0;
3581 }
else if (dseSymbol.IsSameAs(
"04")) {
3582 dse_shipName = DecodeDSEExpansionCharacters(token);
3583 }
else if (dseSymbol.IsSameAs(
"05")) {
3584 }
else if (dseSymbol.IsSameAs(
"06")) {
3587 mmsi = abs((
int)dse_mmsi);
3591 wxDateTime now = wxDateTime::Now();
3593 int last_report_ticks = now.GetTicks();
3596 auto it = AISTargetList.find(mmsi);
3597 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
3598 if (it == AISTargetList.end()) {
3600 pStaleTarget = it->second;
3601 last_report_ticks = pStaleTarget->PositionReportTicks;
3607 m_ptentative_dsctarget = AisTargetDataMaker::GetInstance().GetTargetData();
3609 m_ptentative_dsctarget->PositionReportTicks = now.GetTicks();
3610 m_ptentative_dsctarget->StaticReportTicks = now.GetTicks();
3612 m_ptentative_dsctarget->MMSI = mmsi;
3613 m_ptentative_dsctarget->NavStatus =
3615 m_ptentative_dsctarget->Lat = dsc_lat;
3616 m_ptentative_dsctarget->Lon = dsc_lon;
3617 m_ptentative_dsctarget->b_positionOnceValid =
true;
3618 m_ptentative_dsctarget->COG = 0;
3619 m_ptentative_dsctarget->SOG = 0;
3620 m_ptentative_dsctarget->ShipType = dsc_fmt;
3621 m_ptentative_dsctarget->Class = AIS_DSC;
3622 m_ptentative_dsctarget->b_isDSCtarget =
true;
3623 m_ptentative_dsctarget->b_nameValid =
true;
3624 if (dsc_fmt == 12 || (dsc_fmt == 16 && dsc_cat == 12)) {
3625 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN,
"DISTRESS %u",
3627 m_ptentative_dsctarget->m_dscNature = int(dsc_nature);
3628 m_ptentative_dsctarget->m_dscTXmmsi = dsc_tx_mmsi;
3630 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN,
"POSITION %u",
3634 m_ptentative_dsctarget->b_active =
true;
3635 m_ptentative_dsctarget->b_lost =
false;
3636 m_ptentative_dsctarget->RecentPeriod =
3637 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3640 if (!b_take_dsc) m_dsc_timer.Start(1000, wxTIMER_ONE_SHOT);
3645 if (dse_mmsi || b_take_dsc) {
3646 if (m_ptentative_dsctarget) {
3652 m_ptentative_dsctarget->Lat =
3653 m_ptentative_dsctarget->Lat +
3654 ((m_ptentative_dsctarget->Lat) >= 0 ? dse_lat : -dse_lat);
3655 m_ptentative_dsctarget->Lon =
3656 m_ptentative_dsctarget->Lon +
3657 ((m_ptentative_dsctarget->Lon) >= 0 ? dse_lon : -dse_lon);
3658 if (!dse_shipName.empty()) {
3659 memset(m_ptentative_dsctarget->ShipName,
'\0', SHIP_NAME_LEN);
3660 snprintf(m_ptentative_dsctarget->ShipName, dse_shipName.length(),
3661 "%s", dse_shipName.ToAscii().data());
3663 m_ptentative_dsctarget->COG = dse_cog;
3664 m_ptentative_dsctarget->SOG = dse_sog;
3668 m_ptentative_dsctarget->RecentPeriod =
3669 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3674 auto found = AISTargetList.find(mmsi);
3675 if (found == AISTargetList.end()) {
3676 pTargetData = m_ptentative_dsctarget;
3678 pTargetData = found->second;
3679 std::vector<AISTargetTrackPoint> ptrack =
3680 std::move(pTargetData->m_ptrack);
3681 pTargetData->CloneFrom(
3682 m_ptentative_dsctarget
3685 pTargetData->m_ptrack =
3692 m_ptentative_dsctarget =
nullptr;
3694 m_pLatestTargetData = pTargetData;
3696 AISTargetList[pTargetData->MMSI] =
3699 long mmsi_long = pTargetData->MMSI;
3703 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
3706 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
3707 (
void *)mmsi_long, SELTYPE_AISTARGET);
3708 pSel->SetUserData(pTargetData->MMSI);
3711 UpdateOneCPA(pTargetData.get());
3714 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3721bool AisDecoder::NMEACheckSumOK(
const wxString &str_in) {
3722 unsigned char checksum_value = 0;
3723 int sentence_hex_sum;
3725 wxCharBuffer buf = str_in.ToUTF8();
3726 if (!buf.data())
return false;
3728 char str_ascii[AIS_MAX_MESSAGE_LEN + 1];
3729 strncpy(str_ascii, buf.data(), AIS_MAX_MESSAGE_LEN);
3730 str_ascii[AIS_MAX_MESSAGE_LEN] =
'\0';
3732 int string_length = strlen(str_ascii);
3734 int payload_length = 0;
3735 while ((payload_length < string_length) &&
3736 (str_ascii[payload_length] !=
'*'))
3739 if (payload_length == string_length)
3744 while (index < payload_length) {
3745 checksum_value ^= str_ascii[index];
3749 if (string_length > 4) {
3751 scanstr[0] = str_ascii[payload_length + 1];
3752 scanstr[1] = str_ascii[payload_length + 2];
3754 sscanf(scanstr,
"%2x", &sentence_hex_sum);
3756 if (sentence_hex_sum == checksum_value)
return true;
3762void AisDecoder::UpdateAllCPA() {
3764 for (
const auto &it : GetTargetList()) {
3765 std::shared_ptr<AisTargetData> td = it.second;
3767 if (
nullptr != td) UpdateOneCPA(td.get());
3771void AisDecoder::UpdateAllTracks() {
3772 for (
const auto &it : GetTargetList()) {
3773 std::shared_ptr<AisTargetData> td = it.second;
3774 if (td) UpdateOneTrack(td.get());
3780 if (!ptarget->b_positionOnceValid)
return;
3782 if (!ptarget->m_ptrack.empty()) {
3784 if (fabs(LastTrackpoint.m_lat - ptarget->Lat) > .1 ||
3785 fabs(LastTrackpoint.m_lon - ptarget->Lon) > .1) {
3788 ptarget->m_ptrack.pop_back();
3789 ptarget->b_positionDoubtful =
true;
3796 if ((ptarget->PositionReportTicks - ptarget->LastPositionReportTicks) > 2) {
3799 ptrackpoint.m_lat = ptarget->Lat;
3800 ptrackpoint.m_lon = ptarget->Lon;
3801 ptrackpoint.m_time = wxDateTime::Now().GetTicks();
3803 ptarget->m_ptrack.push_back(ptrackpoint);
3805 if (ptarget->b_PersistTrack || ptarget->b_mPropPersistTrack) {
3807 if (0 == m_persistent_tracks.count(ptarget->MMSI)) {
3809 t->SetName(wxString::Format(
3810 "AIS %s (%u) %s %s", ptarget->GetFullName().c_str(), ptarget->MMSI,
3811 wxDateTime::Now().FormatISODate().c_str(),
3812 wxDateTime::Now().FormatISOTime().c_str()));
3815 m_persistent_tracks[ptarget->MMSI] = t;
3817 t = m_persistent_tracks[ptarget->MMSI];
3820 vector2D point(ptrackpoint.m_lon, ptrackpoint.m_lat);
3822 t->AddNewPoint(point, wxDateTime(ptrackpoint.m_time).ToUTC());
3825 pSelect->AddSelectableTrackSegment(tp->m_lat, tp->m_lon, tp1->m_lat,
3826 tp1->m_lon, tp, tp1, t);
3836 wxDateTime::Now().GetTicks() - (time_t)(g_AISShowTracks_Mins * 60);
3838 ptarget->m_ptrack.erase(
3839 std::remove_if(ptarget->m_ptrack.begin(), ptarget->m_ptrack.end(),
3841 return track.m_time < test_time;
3843 ptarget->m_ptrack.end());
3848void AisDecoder::DeletePersistentTrack(
const Track *track) {
3849 for (
auto it = m_persistent_tracks.begin(); it != m_persistent_tracks.end();
3851 if (it->second == track) {
3852 unsigned mmsi = it->first;
3853 m_persistent_tracks.erase(it);
3855 if (0 == m_persistent_tracks.count(mmsi)) {
3859 if (props->m_bPersistentTrack) {
3863 std::shared_ptr<AisTargetData> td =
3864 Get_Target_Data_From_MMSI(mmsi);
3866 props->m_bPersistentTrack =
false;
3867 td->b_mPropPersistTrack =
false;
3869 if (!m_callbacks.confirm_stop_track()) {
3870 props->m_bPersistentTrack =
true;
3882void AisDecoder::UpdateAllAlarms() {
3883 m_bGeneralAlert =
false;
3886 for (
const auto &it : GetTargetList()) {
3887 std::shared_ptr<AisTargetData> td = it.second;
3891 if (!m_bGeneralAlert) {
3893 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3894 (td->Class != AIS_ATON) && (td->Class != AIS_BASE))
3895 m_bGeneralAlert =
true;
3898 if (g_bAIS_CPA_Alert_Suppress_Moored && (td->SOG <= g_ShowMoored_Kts))
3899 m_bGeneralAlert =
false;
3902 if ((g_bCPAMax) && (td->Range_NM > g_CPAMax_NM))
3903 m_bGeneralAlert =
false;
3906 if ((g_bTCPA_Max) && (td->TCPA > g_TCPA_Max)) m_bGeneralAlert =
false;
3909 if (td->Class == AIS_SART && td->NavStatus == 14)
3910 m_bGeneralAlert =
true;
3913 if ((td->Class == AIS_DSC) &&
3914 ((td->ShipType == 12) || (td->ShipType == 16)))
3915 m_bGeneralAlert =
true;
3918 ais_alert_type this_alarm = AIS_NO_ALERT;
3921 if (td->Class == AIS_SART && td->NavStatus == 14)
3922 this_alarm = AIS_ALERT_SET;
3925 if ((td->Class == AIS_DSC) &&
3926 ((td->ShipType == 12) || (td->ShipType == 16)))
3927 this_alarm = AIS_ALERT_SET;
3929 if (g_bCPAWarn && td->b_active && td->b_positionOnceValid &&
3930 (td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
3933 if ((g_bHideMoored) && (td->SOG <= g_ShowMoored_Kts)) {
3934 td->n_alert_state = AIS_NO_ALERT;
3940 if (g_bAIS_CPA_Alert_Suppress_Moored &&
3941 (td->SOG <= g_ShowMoored_Kts)) {
3942 td->n_alert_state = AIS_NO_ALERT;
3948 if (td->Range_NM > g_CPAMax_NM) {
3949 td->n_alert_state = AIS_NO_ALERT;
3954 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3955 (td->Class != AIS_ATON) && (td->Class != AIS_BASE) &&
3956 (td->Class != AIS_METEO)) {
3958 if (td->TCPA < g_TCPA_Max) {
3959 if (td->b_isFollower)
3960 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3962 this_alarm = AIS_ALERT_SET;
3965 if (td->b_isFollower)
3966 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3968 this_alarm = AIS_ALERT_SET;
3975 if (td->Class == AIS_SART) {
3976 if (td->b_in_ack_timeout) {
3977 wxTimeSpan delta = wxDateTime::Now() - td->m_ack_time;
3979 if (delta.GetMinutes() >= 10) td->b_in_ack_timeout =
false;
3981 }
else if (g_bAIS_ACK_Timeout ||
3982 ((td->Class == AIS_DSC) &&
3983 ((td->ShipType == 12) || (td->ShipType == 16)))) {
3984 if (td->b_in_ack_timeout) {
3985 wxTimeSpan delta = wxDateTime::Now() - td->m_ack_time;
3986 if (delta.GetMinutes() > g_AckTimeout_Mins)
3987 td->b_in_ack_timeout =
false;
3993 if (td->b_in_ack_timeout) {
3994 if (this_alarm == AIS_NO_ALERT) td->b_in_ack_timeout =
false;
3998 td->n_alert_state = this_alarm;
4004 ptarget->Range_NM = -1.;
4013 DistanceBearingMercator(ptarget->Lat, ptarget->Lon,
gLat,
gLon, &brg, &dist);
4014 ptarget->Range_NM = dist;
4017 if (dist <= 1e-5) ptarget->Brg = -1.0;
4019 if (!ptarget->b_positionOnceValid || !
bGPSValid) {
4020 ptarget->bCPA_Valid =
false;
4024 if (ptarget->Class == AIS_METEO) {
4025 ptarget->bCPA_Valid =
false;
4034 if (ptarget->b_OwnShip) {
4036 ptarget->TCPA = -100;
4037 ptarget->bCPA_Valid =
false;
4041 double cpa_calc_ownship_cog =
gCog;
4042 double cpa_calc_target_cog = ptarget->COG;
4045 if (std::isnan(
gSog) || (
gSog > 102.2)) {
4046 ptarget->bCPA_Valid =
false;
4051 if (std::isnan(
gCog) ||
gCog == 360.0) {
4053 cpa_calc_ownship_cog =
4057 ptarget->bCPA_Valid =
false;
4063 if (ptarget->COG == 360.0) {
4064 if (ptarget->SOG > 102.2) {
4065 ptarget->bCPA_Valid =
false;
4067 }
else if (ptarget->SOG < .01) {
4068 cpa_calc_target_cog = 0.;
4071 ptarget->bCPA_Valid =
false;
4077 double v0 =
gSog * 1852.;
4078 double v1 = ptarget->SOG * 1852.;
4080 if ((v0 < 1e-6) && (v1 < 1e-6)) {
4084 ptarget->bCPA_Valid =
false;
4091 double east1 = (ptarget->Lon -
gLon) * 60 * 1852;
4092 double north1 = (ptarget->Lat -
gLat) * 60 * 1852;
4094 double east = east1 * (cos(
gLat * PI / 180.));
4096 double north = north1;
4099 double cosa = cos((90. - cpa_calc_ownship_cog) * PI / 180.);
4100 double sina = sin((90. - cpa_calc_ownship_cog) * PI / 180.);
4101 double cosb = cos((90. - cpa_calc_target_cog) * PI / 180.);
4102 double sinb = sin((90. - cpa_calc_target_cog) * PI / 180.);
4105 double fc = (v0 * cosa) - (v1 * cosb);
4106 double fs = (v0 * sina) - (v1 * sinb);
4108 double d = (fc * fc) + (fs * fs);
4116 tcpa = ((fc * east) + (fs * north)) / d;
4119 ptarget->TCPA = tcpa * 60.;
4124 double OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA, TargetLonCPA;
4126 ll_gc_ll(
gLat,
gLon, cpa_calc_ownship_cog,
gSog * tcpa, &OwnshipLatCPA,
4128 ll_gc_ll(ptarget->Lat, ptarget->Lon, cpa_calc_target_cog,
4129 ptarget->SOG * tcpa, &TargetLatCPA, &TargetLonCPA);
4132 ptarget->CPA = DistGreatCircle(OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA,
4135 ptarget->bCPA_Valid =
true;
4137 if (ptarget->TCPA < 0) ptarget->bCPA_Valid =
false;
4141void AisDecoder::OnTimerDSC(wxTimerEvent &event) {
4144 if (m_ptentative_dsctarget) {
4145 ProcessDSx(m_dsc_last_string,
true);
4149void AisDecoder::OnTimerAIS(wxTimerEvent &event) {
4154 wxDateTime now = wxDateTime::Now();
4157 std::unordered_map<int, std::shared_ptr<AisTargetData>> ¤t_targets =
4160 auto it = current_targets.begin();
4161 std::vector<int> remove_array;
4163 while (it != current_targets.end()) {
4167 current_targets.erase(it);
4172 std::shared_ptr<AisTargetData> xtd = it->second;
4174 int target_posn_age = now.GetTicks() - xtd->PositionReportTicks;
4175 int target_static_age = now.GetTicks() - xtd->StaticReportTicks;
4187 double removelost_Mins = fmax(g_RemoveLost_Mins, g_MarkLost_Mins);
4189 if (g_bInlandEcdis && (xtd->Class != AIS_ARPA)) {
4190 double iECD_LostTimeOut = 0.0;
4194 if (xtd->Class == AIS_CLASS_A) {
4195 if ((xtd->NavStatus == MOORED) || (xtd->NavStatus == AT_ANCHOR)) {
4197 iECD_LostTimeOut = 18 * 60;
4199 iECD_LostTimeOut = 60;
4201 iECD_LostTimeOut = 60;
4202 }
else if (xtd->Class == AIS_CLASS_B) {
4204 iECD_LostTimeOut = 18 * 60;
4206 iECD_LostTimeOut = 180;
4207 }
else if (xtd->Class == AIS_ATON || xtd->Class == AIS_BASE)
4208 iECD_LostTimeOut = 18 * 60;
4210 if ((target_posn_age > iECD_LostTimeOut) &&
4211 (xtd->Class != AIS_GPSG_BUDDY))
4212 xtd->b_active =
false;
4214 removelost_Mins = (2 * iECD_LostTimeOut) / 60.;
4216 }
else if (g_bMarkLost) {
4217 if ((target_posn_age > g_MarkLost_Mins * 60) &&
4218 (xtd->Class != AIS_GPSG_BUDDY) && (xtd->Class != AIS_SART))
4219 xtd->b_active =
false;
4222 if (xtd->Class == AIS_SART || xtd->Class == AIS_METEO)
4223 removelost_Mins = 18.0;
4227 if (g_bRemoveLost || g_bInlandEcdis) {
4229 (xtd->Class == AIS_ARPA &&
4231 if (((target_posn_age > removelost_Mins * 60) &&
4232 (xtd->Class != AIS_GPSG_BUDDY)) ||
4237 xtd->b_positionOnceValid =
false;
4245 long mmsi_long = xtd->MMSI;
4246 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
4251 if (target_static_age > removelost_Mins * 60 * 3 || b_arpalost) {
4252 xtd->b_removed =
true;
4254 remove_array.push_back(xtd->MMSI);
4263 if (xtd->MMSI == props->MMSI) {
4264 if (props->m_bignore) {
4265 remove_array.push_back(xtd->MMSI);
4266 xtd->b_removed =
true;
4275 remove_array.push_back(xtd->MMSI);
4276 xtd->b_removed =
true;
4284 for (
unsigned int i = 0; i < remove_array.size(); i++) {
4285 auto itd = current_targets.find(remove_array[i]);
4286 if (itd != current_targets.end()) {
4287 std::shared_ptr<AisTargetData> td = itd->second;
4288 current_targets.erase(itd);
4297 m_bSuppressed =
false;
4298 if (g_bAIS_CPA_Alert_Suppress_Moored || g_bHideMoored ||
4299 (g_bShowScaled && g_bAllowShowScaled))
4300 m_bSuppressed =
true;
4302 m_bAIS_Audio_Alert_On =
false;
4311 std::shared_ptr<AisTargetData> palert_target =
nullptr;
4313 if (!g_pais_alert_dialog_active) {
4315 double tcpa_min = 1e6;
4316 double sart_range = 1e6;
4317 std::shared_ptr<AisTargetData> palert_target_cpa =
nullptr;
4318 std::shared_ptr<AisTargetData> palert_target_sart =
nullptr;
4319 std::shared_ptr<AisTargetData> palert_target_dsc =
nullptr;
4321 for (it = current_targets.begin(); it != current_targets.end(); ++it) {
4322 std::shared_ptr<AisTargetData> td = it->second;
4324 if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
4325 if (g_bAIS_CPA_Alert && td->b_active) {
4326 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4327 if (td->TCPA < tcpa_min) {
4328 tcpa_min = td->TCPA;
4329 palert_target_cpa = td;
4333 }
else if ((td->Class == AIS_DSC) &&
4334 ((td->ShipType == 12) || (td->ShipType == 16))) {
4336 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4337 palert_target_dsc = td;
4340 td->b_isDSCtarget =
false;
4345 else if (td->Class == AIS_SART) {
4347 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4348 if (td->Range_NM < sart_range) {
4349 tcpa_min = sart_range;
4350 palert_target_sart = td;
4360 palert_target = palert_target_cpa;
4362 if (palert_target_sart) {
4363 palert_target = palert_target_sart;
4366 if (palert_target_dsc) {
4367 palert_target = palert_target_dsc;
4371 palert_target = Get_Target_Data_From_MMSI(m_callbacks.get_target_mmsi());
4374 if (palert_target)
info_update.Notify(palert_target,
"");
4376 TimerAIS.Start(TIMER_AIS_MSEC, wxTIMER_CONTINUOUS);
4379std::shared_ptr<AisTargetData> AisDecoder::Get_Target_Data_From_MMSI(
4381 if (AISTargetList.find(mmsi) == AISTargetList.end())
return nullptr;
4382 return AISTargetList[mmsi];
4389MmsiProperties::MmsiProperties(wxString &spec) {
4391 wxStringTokenizer tkz(spec,
";");
4394 s = tkz.GetNextToken();
4399 s = tkz.GetNextToken();
4401 if (s.Upper() ==
"ALWAYS")
4402 TrackType = TRACKTYPE_ALWAYS;
4403 else if (s.Upper() ==
"NEVER")
4404 TrackType = TRACKTYPE_NEVER;
4407 s = tkz.GetNextToken();
4409 if (s.Upper() ==
"IGNORE") m_bignore =
true;
4412 s = tkz.GetNextToken();
4414 if (s.Upper() ==
"MOB") m_bMOB =
true;
4417 s = tkz.GetNextToken();
4419 if (s.Upper() ==
"VDM") m_bVDM =
true;
4422 s = tkz.GetNextToken();
4424 if (s.Upper() ==
"FOLLOWER") m_bFollower =
true;
4427 s = tkz.GetNextToken();
4429 if (s.Upper() ==
"PERSIST") m_bPersistentTrack =
true;
4432 s = tkz.GetNextToken();
4434 m_ShipName = s.Upper();
4438MmsiProperties::~MmsiProperties() =
default;
4440void MmsiProperties::Init() {
4442 TrackType = TRACKTYPE_DEFAULT;
4446 m_bFollower =
false;
4447 m_bPersistentTrack =
false;
4451wxString MmsiProperties::Serialize() {
4455 sMMSI.Printf(
"%d", MMSI);
4459 if (TRACKTYPE_ALWAYS == TrackType)
4461 else if (TRACKTYPE_NEVER == TrackType)
4482 sMMSI <<
"Follower";
4486 if (m_bPersistentTrack) {
4491 if (m_ShipName ==
"") {
4492 m_ShipName = GetShipNameFromFile(MMSI);
4494 sMMSI << m_ShipName;
4499 AIS_Target_Name_Hash *AISTargetNamesC,
4500 AIS_Target_Name_Hash *AISTargetNamesNC,
long mmsi) {
4501 if (g_benableAISNameCache) {
4502 wxString ship_name =
"";
4505 if (!pTargetData->b_nameValid) {
4506 AIS_Target_Name_Hash::iterator it = AISTargetNamesC->find(mmsi);
4507 if (it != AISTargetNamesC->end()) {
4508 ship_name = (*AISTargetNamesC)[mmsi].Left(20);
4509 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4510 ship_name.length() + 1);
4511 pTargetData->b_nameValid =
true;
4512 pTargetData->b_nameFromCache =
true;
4513 }
else if (!g_bUseOnlyConfirmedAISName) {
4514 it = AISTargetNamesNC->find(mmsi);
4515 if (it != AISTargetNamesNC->end()) {
4516 ship_name = (*AISTargetNamesNC)[mmsi].Left(20);
4517 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4518 ship_name.length() + 1);
4519 pTargetData->b_nameValid =
true;
4520 pTargetData->b_nameFromCache =
true;
4525 else if ((pTargetData->MID == 5) || (pTargetData->MID == 24) ||
4526 (pTargetData->MID == 19) ||
4527 (pTargetData->MID == 123) ||
4528 (pTargetData->MID == 124)) {
4530 pTargetData->b_nameFromCache =
false;
4531 ship_name = trimAISField(pTargetData->ShipName);
4532 AIS_Target_Name_Hash::iterator itC = AISTargetNamesC->find(mmsi);
4533 AIS_Target_Name_Hash::iterator itNC = AISTargetNamesNC->find(mmsi);
4535 AISTargetNamesC->end()) {
4536 if ((*AISTargetNamesC)[mmsi] ==
4538 if (itNC != AISTargetNamesNC->end()) {
4540 AISTargetNamesNC->erase(itNC);
4543 if (itNC != AISTargetNamesNC->end()) {
4545 if ((*AISTargetNamesNC)[mmsi] ==
4548 (*AISTargetNamesC)[mmsi] = ship_name;
4550 AISTargetNamesNC->erase(itNC);
4553 (*AISTargetNamesNC)[mmsi] = ship_name;
4555 if (g_bUseOnlyConfirmedAISName)
4556 strncpy(pTargetData->ShipName, (*AISTargetNamesC)[mmsi].mb_str(),
4557 (*AISTargetNamesC)[mmsi].Left(20).Length() + 1);
4559 (*AISTargetNamesNC)[mmsi] = ship_name;
4564 AISTargetNamesNC->end()) {
4565 if ((*AISTargetNamesNC)[mmsi] ==
4568 (*AISTargetNamesC)[mmsi] = ship_name;
4570 AISTargetNamesNC->erase(itNC);
4572 (*AISTargetNamesNC)[mmsi] = ship_name;
4575 (*AISTargetNamesNC)[mmsi] = ship_name;
4577 if (g_bUseOnlyConfirmedAISName) {
4578 pTargetData->ShipName[SHIP_NAME_LEN - 1] =
'\0';
4579 strncpy(pTargetData->ShipName,
"Unknown ",
4587wxString GetShipNameFromFile(
int nmmsi) {
4589 if (g_benableAISNameCache) {
4593 while (getline(infile, line)) {
4594 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()),
",");
4595 if (nmmsi == wxAtoi(tokenizer.GetNextToken())) {
4596 name = tokenizer.GetNextToken().Trim();
4599 tokenizer.GetNextToken();
4607void AisDecoder::UpdateMMSItoNameFile(
const wxString &mmsi,
4608 const wxString &name) {
4612 std::map<wxString, wxString> mmsi_name_map;
4618 while (getline(infile, line)) {
4619 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()),
",");
4620 wxString file_mmsi = tokenizer.GetNextToken();
4621 wxString file_name = tokenizer.GetNextToken().Trim();
4622 mmsi_name_map[file_mmsi] = file_name;
4628 mmsi_name_map[mmsi] = name.Upper();
4633 for (
const auto &pair : mmsi_name_map) {
4634 std::string line = std::string(pair.first.mb_str()) +
"," +
4635 std::string(pair.second.mb_str()) +
"\n";
4642wxString AisDecoder::GetMMSItoNameEntry(
const wxString &mmsi) {
4643 return GetShipNameFromFile(wxAtoi(mmsi));
4647int AisMeteoNewMmsi(
int orig_mmsi,
int m_lat,
int m_lon,
int lon_bits = 0,
4651 if ((!lon_bits || lon_bits == 999) && siteID) {
4654 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4655 if (points.size()) {
4656 for (
const auto &point : points) {
4658 if (siteID == point.siteID && orig_mmsi == point.orig_mmsi) {
4660 new_mmsi = point.mmsi;
4666 if (!found && !lon_bits) {
4671 double lon_tentative = 181.;
4672 double lat_tentative = 91.;
4674 if (lon_bits == 25) {
4675 if (m_lon & 0x01000000)
4676 m_lon |= 0xFE000000;
4677 lon_tentative = m_lon / 60000.;
4679 if (m_lat & 0x00800000)
4680 m_lat |= 0xFF000000;
4681 lat_tentative = m_lat / 60000.;
4683 }
else if (lon_bits == 28) {
4684 if (m_lon & 0x08000000)
4685 m_lon |= 0xf0000000;
4686 lon_tentative = m_lon / 600000.;
4688 if (m_lat & 0x04000000)
4689 m_lat |= 0xf8000000;
4690 lat_tentative = m_lat / 600000.;
4695 wxString slon = wxString::Format(
"%0.3f", lon_tentative);
4696 wxString slat = wxString::Format(
"%0.3f", lat_tentative);
4704 static int nextMeteommsi = 199400000;
4705 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4707 if (lon_bits != 999 && !points.empty()) {
4708 wxString t_lat, t_lon;
4709 for (
const auto &point : points) {
4711 if (slat.IsSameAs(point.lat) && slon.IsSameAs(point.lon)) {
4713 new_mmsi = point.mmsi;
4722 points.emplace_back(
4723 AisMeteoPoint(nextMeteommsi, slat, slon, siteID, orig_mmsi));
4724 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
obs::EventVar info_update
Notified when AIS user dialogs should update.
obs::EventVar plugin_msg_evt
A JSON message should be sent.
obs::EventVar touch_state
Notified when gFrame->TouchAISActive() should be invoked.
obs::EventVar new_track
Notified on new track creation.
Add a new point to the list of Meteo stations.
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.
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.
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.