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;
568 if (!ptd->b_isDSCtarget) ptd->Class = AIS_CLASS_A;
574 int AIS_version_indicator = bstr->
GetInt(39, 2);
575 if (AIS_version_indicator < 4) {
576 ptd->IMO = bstr->
GetInt(41, 30);
578 bstr->GetStr(71, 42, &ptd->CallSign[0], 7);
579 bstr->GetStr(113, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
580 ptd->b_nameValid =
true;
581 if (!ptd->b_isDSCtarget) {
582 ptd->ShipType = (
unsigned char)bstr->
GetInt(233, 8);
585 ptd->DimA = bstr->
GetInt(241, 9);
586 ptd->DimB = bstr->
GetInt(250, 9);
587 ptd->DimC = bstr->
GetInt(259, 6);
588 ptd->DimD = bstr->
GetInt(265, 6);
590 ptd->ETA_Mo = bstr->
GetInt(275, 4);
591 ptd->ETA_Day = bstr->
GetInt(279, 5);
592 ptd->ETA_Hr = bstr->
GetInt(284, 5);
593 ptd->ETA_Min = bstr->
GetInt(289, 6);
595 ptd->Draft = (double)(bstr->
GetInt(295, 8)) / 10.0;
597 bstr->GetStr(303, 120, &ptd->Destination[0], DESTINATION_LEN - 1);
599 ptd->StaticReportTicks = now.GetTicks();
608 int part_number = bstr->
GetInt(39, 2);
609 if (0 == part_number) {
610 bstr->GetStr(41, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
611 ptd->b_nameValid =
true;
614 }
else if (1 == part_number) {
615 if (!ptd->b_isDSCtarget) {
616 ptd->ShipType = (
unsigned char)bstr->
GetInt(41, 8);
618 bstr->GetStr(91, 42, &ptd->CallSign[0], 7);
620 ptd->DimA = bstr->
GetInt(133, 9);
621 ptd->DimB = bstr->
GetInt(142, 9);
622 ptd->DimC = bstr->
GetInt(151, 6);
623 ptd->DimD = bstr->
GetInt(157, 6);
630 ptd->Class = AIS_BASE;
632 ptd->m_utc_hour = bstr->
GetInt(62, 5);
633 ptd->m_utc_min = bstr->
GetInt(67, 6);
634 ptd->m_utc_sec = bstr->
GetInt(73, 6);
636 int lon = bstr->
GetInt(80, 28);
637 if (lon & 0x08000000)
639 double lon_tentative = lon / 600000.;
641 int lat = bstr->
GetInt(108, 27);
642 if (lat & 0x04000000)
644 double lat_tentative = lat / 600000.;
646 if ((lon_tentative <= 180.) &&
650 ptd->Lon = lon_tentative;
651 ptd->Lat = lat_tentative;
652 ptd->b_positionDoubtful =
false;
653 ptd->b_positionOnceValid =
true;
654 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
655 ptd->PositionReportTicks = now.GetTicks();
657 ptd->b_positionDoubtful =
true;
664 b_posn_report =
true;
670 ptd->SOG = bstr->
GetInt(51, 10);
672 int lon = bstr->
GetInt(62, 28);
673 if (lon & 0x08000000)
675 double lon_tentative = lon / 600000.;
677 int lat = bstr->
GetInt(90, 27);
678 if (lat & 0x04000000)
680 double lat_tentative = lat / 600000.;
682 if ((lon_tentative <= 180.) &&
686 ptd->Lon = lon_tentative;
687 ptd->Lat = lat_tentative;
688 ptd->b_positionDoubtful =
false;
689 ptd->b_positionOnceValid =
true;
690 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
691 ptd->PositionReportTicks = now.GetTicks();
693 ptd->b_positionDoubtful =
true;
696 ptd->COG = 0.1 * (bstr->
GetInt(117, 12));
698 int alt_tent = bstr->
GetInt(39, 12);
699 ptd->altitude = alt_tent;
701 ptd->b_SarAircraftPosnReport =
true;
704 b_posn_report =
true;
710 ptd->ShipType = (
unsigned char)bstr->
GetInt(39, 5);
716 ptd->DimA = bstr->
GetInt(220, 9);
717 ptd->DimB = bstr->
GetInt(229, 9);
718 ptd->DimC = bstr->
GetInt(238, 6);
719 ptd->DimD = bstr->
GetInt(244, 6);
722 ptd->m_utc_sec = bstr->
GetInt(254, 6);
724 int offpos = bstr->
GetInt(260, 1);
725 int virt = bstr->
GetInt(270, 1);
728 ptd->NavStatus = ATON_VIRTUAL;
730 ptd->NavStatus = ATON_REAL;
731 if (ptd->m_utc_sec <= 59 ) {
733 if (offpos) ptd->NavStatus += 1;
736 bstr->GetStr(44, 120, &ptd->ShipName[0], SHIP_NAME_LEN);
739 if (bstr->GetBitCount() > 276) {
740 int nx = ((bstr->GetBitCount() - 272) / 6) * 6;
741 bstr->GetStr(273, nx, &ptd->ShipNameExtension[0], 14);
742 ptd->ShipNameExtension[14] = 0;
744 ptd->ShipNameExtension[0] = 0;
747 ptd->b_nameValid =
true;
751 ptd->Class = AIS_ATON;
753 int lon = bstr->
GetInt(165, 28);
755 if (lon & 0x08000000)
757 double lon_tentative = lon / 600000.;
759 int lat = bstr->
GetInt(193, 27);
761 if (lat & 0x04000000)
763 double lat_tentative = lat / 600000.;
765 if ((lon_tentative <= 180.) &&
769 ptd->Lon = lon_tentative;
770 ptd->Lat = lat_tentative;
771 ptd->b_positionDoubtful =
false;
772 ptd->b_positionOnceValid =
true;
773 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
774 ptd->PositionReportTicks = now.GetTicks();
776 ptd->b_positionDoubtful =
true;
778 b_posn_report =
true;
783 int dac = bstr->
GetInt(41, 10);
784 int fi = bstr->
GetInt(51, 6);
790 ptd->b_isEuroInland =
true;
792 bstr->GetStr(57, 48, &ptd->Euro_VIN[0], 8);
793 ptd->Euro_Length = ((double)bstr->
GetInt(105, 13)) / 10.0;
794 ptd->Euro_Beam = ((double)bstr->
GetInt(118, 10)) / 10.0;
795 ptd->UN_shiptype = bstr->
GetInt(128, 14);
796 ptd->Euro_Draft = ((double)bstr->
GetInt(145, 11)) / 100.0;
800 if (dac == 1 || dac == 366)
804 if (bstr->GetBitCount() >= 111) {
806 an.link_id = bstr->
GetInt(57, 10);
807 an.notice_type = bstr->
GetInt(67, 7);
808 an.month = bstr->
GetInt(74, 4);
809 an.day = bstr->
GetInt(78, 5);
810 an.hour = bstr->
GetInt(83, 5);
811 an.minute = bstr->
GetInt(88, 6);
812 an.duration_minutes = bstr->
GetInt(94, 18);
814 wxDateTime now_ = wxDateTime::Now();
817 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
818 now_.GetYear(), an.hour, an.minute);
823 if (an.start_time > now_ + wxTimeSpan::Hours(48))
824 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
825 now_.GetYear() - 1, an.hour, an.minute);
828 an.start_time + wxTimeSpan::Minutes(an.duration_minutes);
833 if (an.expiry_time < now_ - wxTimeSpan::Hours(24)) {
834 an.start_time.Set(an.day, wxDateTime::Month(an.month - 1),
835 now_.GetYear() + 1, an.hour, an.minute);
837 an.start_time + wxTimeSpan::Minutes(an.duration_minutes);
842 int subarea_len = 87;
845 float pos_scale = 60000.0;
853 pos_scale = 600000.0;
858 int subarea_count = (bstr->GetBitCount() - 111) / subarea_len;
859 for (
int i = 0; i < subarea_count; ++i) {
860 int base = 111 + i * subarea_len;
862 sa.shape = bstr->
GetInt(base + 1, 3);
863 int scale_factor = 1;
864 if (sa.shape == AIS8_001_22_SHAPE_TEXT) {
867 bstr->GetStr(base + 4, subarea_len - 3, t, 14);
868 sa.text = wxString(t, wxConvUTF8);
870 int scale_multipliers[4] = {1, 10, 100, 1000};
871 scale_factor = scale_multipliers[bstr->
GetInt(base + 4, 2)];
873 case AIS8_001_22_SHAPE_SECTOR:
874 sa.left_bound_deg = bstr->
GetInt(
875 base + 6 + lon_len + lat_len + prec_size + 12, 9);
876 sa.right_bound_deg = bstr->
GetInt(
877 base + 6 + lon_len + lat_len + prec_size + 12 + 9, 9);
878 case AIS8_001_22_SHAPE_CIRCLE:
880 bstr->
GetInt(base + 6 + lon_len + lat_len + 3, 12) *
883 case AIS8_001_22_SHAPE_RECT:
885 bstr->
GetInt(base + 6, lon_len,
true) / pos_scale;
887 bstr->
GetInt(base + 6 + lon_len, lat_len,
true) /
890 bstr->
GetInt(base + 6 + lon_len + lat_len + prec_size,
895 base + 6 + lon_len + lat_len + prec_size + 8, 8) *
897 sa.orient_deg = bstr->
GetInt(
898 base + 6 + lon_len + lat_len + prec_size + 8 + 8, 9);
900 case AIS8_001_22_SHAPE_POLYLINE:
901 case AIS8_001_22_SHAPE_POLYGON:
902 for (
int j = 0; j < 4; ++j) {
903 sa.angles[j] = bstr->
GetInt(base + 6 + j * 20, 10) * 0.5;
905 bstr->
GetInt(base + 16 + j * 20, 10) * scale_factor;
909 an.sub_areas.push_back(sa);
911 ptd->area_notices[an.link_id] = an;
918 if (bstr->GetBitCount() >= 360) {
920 if (met_mmsi != 666) ptd->MMSI = met_mmsi;
923 double lon_tentative = 181.;
924 double lat_tentative = 91.;
926 int lon = bstr->
GetInt(57, 25);
927 int lat = bstr->
GetInt(82, 24);
929 if (lon & 0x01000000)
931 lon_tentative = lon / 60000.;
933 if (lat & 0x00800000)
935 lat_tentative = lat / 60000.;
937 ptd->Lon = lon_tentative;
938 ptd->Lat = lat_tentative;
941 wxString x = ptd->ShipName;
942 if (x.Find(
"METEO") == wxNOT_FOUND) {
944 wxString slat = wxString::Format(
"%0.3f", lat_tentative);
945 wxString slon = wxString::Format(
"%0.3f", lon_tentative);
948 wxString nameID =
"METEO ";
949 nameID << wxString::Format(
"%0.3f", abs(id1) + abs(id2)).Right(3);
950 strncpy(ptd->ShipName, nameID, SHIP_NAME_LEN - 1);
953 ptd->met_data.pos_acc = bstr->
GetInt(106, 1);
954 ptd->met_data.day = bstr->
GetInt(107, 5);
955 ptd->met_data.hour = bstr->
GetInt(112, 5);
956 ptd->met_data.minute = bstr->
GetInt(117, 6);
957 ptd->met_data.wind_kn = bstr->
GetInt(123, 7);
958 ptd->met_data.wind_gust_kn = bstr->
GetInt(130, 7);
959 ptd->met_data.wind_dir = bstr->
GetInt(137, 9);
960 ptd->met_data.wind_gust_dir = bstr->
GetInt(146, 9);
962 int tmp = bstr->
GetInt(155, 11);
963 if (tmp & 0x00000400)
965 ptd->met_data.air_temp = tmp / 10.;
966 ptd->met_data.rel_humid = bstr->
GetInt(166, 7);
967 int dew = bstr->
GetInt(173, 10);
968 if (dew & 0x00000200)
970 ptd->met_data.dew_point = dew / 10.;
975 ptd->met_data.airpress = bstr->
GetInt(183, 9) + 799;
976 ptd->met_data.airpress_tend = bstr->
GetInt(192, 2);
978 int horVis = bstr->
GetInt(194, 8);
979 if (horVis & 0x80u) {
981 ptd->met_data.hor_vis_GT =
true;
983 ptd->met_data.hor_vis_GT =
false;
985 ptd->met_data.hor_vis = horVis / 10.0;
987 ptd->met_data.water_lev_dev = (bstr->
GetInt(202, 12) / 100.) - 10.;
988 ptd->met_data.water_lev_trend = bstr->
GetInt(214, 2);
989 ptd->met_data.current = bstr->
GetInt(216, 8) / 10.;
990 ptd->met_data.curr_dir = bstr->
GetInt(224, 9);
991 ptd->met_data.wave_height = bstr->
GetInt(277, 8) / 10.;
992 ptd->met_data.wave_period = bstr->
GetInt(285, 6);
993 ptd->met_data.wave_dir = bstr->
GetInt(291, 9);
994 ptd->met_data.swell_height = bstr->
GetInt(300, 8) / 10;
995 ptd->met_data.swell_per = bstr->
GetInt(308, 6);
996 ptd->met_data.swell_dir = bstr->
GetInt(314, 9);
997 ptd->met_data.seastate = bstr->
GetInt(323, 4);
999 int wt = bstr->
GetInt(327, 10);
1000 if (wt & 0x00000200)
1002 ptd->met_data.water_temp = wt / 10.;
1004 ptd->met_data.precipitation = bstr->
GetInt(337, 3);
1005 ptd->met_data.salinity = bstr->
GetInt(340, 9) / 10.;
1006 ptd->met_data.ice = bstr->
GetInt(349, 2);
1008 ptd->Class = AIS_METEO;
1012 ptd->b_NoTrack =
true;
1013 ptd->b_show_track =
false;
1014 ptd->b_positionDoubtful =
false;
1015 ptd->b_positionOnceValid =
true;
1016 b_posn_report =
true;
1017 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
1018 ptd->PositionReportTicks = now.GetTicks();
1019 ptd->b_nameValid =
true;
1021 parse_result =
true;
1027 if (dac == 367 && fi == 33) {
1031 const int size = bstr->GetBitCount();
1034 if (met_mmsi != 666) ptd->MMSI = met_mmsi;
1035 const int startbits = 56;
1036 const int slotsize = 112;
1037 const int slots_count = (size - startbits) / slotsize;
1039 for (
int slot = 0; slot < slots_count; slot++) {
1040 slotbit = slot * slotsize;
1041 int type = bstr->
GetInt(slotbit + 57, 4);
1042 ptd->met_data.hour = bstr->
GetInt(slotbit + 66, 5);
1043 ptd->met_data.minute = bstr->
GetInt(slotbit + 71, 6);
1044 int Site_ID = bstr->
GetInt(slotbit + 77, 7);
1047 if (!ptd->b_nameValid) {
1048 wxString nameID =
"METEO Site: ";
1050 strncpy(ptd->ShipName, nameID, SHIP_NAME_LEN - 1);
1051 ptd->b_nameValid =
true;
1055 int lon = bstr->
GetInt(slotbit + 90, 28);
1056 if (lon & 0x08000000)
1058 ptd->Lon = lon / 600000.;
1060 int lat = bstr->
GetInt(slotbit + 118, 27);
1061 if (lat & 0x04000000)
1063 ptd->Lat = lat / 600000.;
1064 ptd->b_positionOnceValid =
true;
1066 }
else if (type == 1) {
1067 bstr->GetStr(slotbit + 84, 84, &ptd->ShipName[0], SHIP_NAME_LEN);
1068 ptd->b_nameValid =
true;
1070 }
else if (type == 2) {
1072 int descr = bstr->
GetInt(slotbit + 116, 3);
1073 if (descr == 1 || descr == 2) {
1074 ptd->met_data.wind_kn = bstr->
GetInt(slotbit + 84, 7);
1075 ptd->met_data.wind_gust_kn = bstr->
GetInt(slotbit + 91, 7);
1076 ptd->met_data.wind_dir = bstr->
GetInt(slotbit + 98, 9);
1077 ptd->met_data.wind_gust_dir = bstr->
GetInt(slotbit + 107, 9);
1080 }
else if (type == 3) {
1082 int descr = bstr->
GetInt(slotbit + 108, 3);
1083 if (descr == 1 || descr == 2) {
1084 int wltype = bstr->
GetInt(slotbit + 84, 1);
1085 int wl = bstr->
GetInt(slotbit + 85, 16);
1086 if (wl & 0x00004000)
1090 ptd->met_data.water_level = wl / 100.;
1092 ptd->met_data.water_lev_dev = wl / 100.;
1094 ptd->met_data.water_lev_trend = bstr->
GetInt(slotbit + 101, 2);
1095 ptd->met_data.vertical_ref = bstr->
GetInt(slotbit + 103, 5);
1097 }
else if (type == 6) {
1098 ptd->met_data.current = bstr->
GetInt(slotbit + 102, 8) / 10.0;
1099 ptd->met_data.curr_dir = bstr->
GetInt(slotbit + 110, 9);
1100 }
else if (type == 7) {
1102 bstr->
GetInt(slotbit + 111, 3);
1103 if (swell_descr == 1 || swell_descr == 2) {
1104 ptd->met_data.swell_height =
1105 bstr->
GetInt(slotbit + 84, 8) / 10.0;
1106 ptd->met_data.swell_per = bstr->
GetInt(slotbit + 92, 6);
1107 ptd->met_data.swell_dir = bstr->
GetInt(slotbit + 98, 9);
1109 ptd->met_data.seastate = bstr->
GetInt(slotbit + 107, 4);
1110 int wt_descr = bstr->
GetInt(slotbit + 131, 3);
1111 if (wt_descr == 1 || wt_descr == 2)
1112 ptd->met_data.water_temp =
1113 bstr->
GetInt(slotbit + 114, 10) / 10. - 10.;
1115 int wawe_descr = bstr->
GetInt(slotbit + 157, 3);
1116 if (wawe_descr == 1 || wawe_descr == 2) {
1117 ptd->met_data.wave_height =
1118 bstr->
GetInt(slotbit + 134, 8) / 10.0;
1119 ptd->met_data.wave_period = bstr->
GetInt(slotbit + 142, 6);
1120 ptd->met_data.wave_dir = bstr->
GetInt(slotbit + 148, 9);
1122 ptd->met_data.salinity = bstr->
GetInt(slotbit + 160, 9 / 10.0);
1124 }
else if (type == 8) {
1125 ptd->met_data.water_temp =
1126 bstr->
GetInt(slotbit + 84, 10) / 10.0 - 10.0;
1127 ptd->met_data.salinity = bstr->
GetInt(slotbit + 120, 9) / 10.0;
1129 }
else if (type == 9) {
1130 int tmp = bstr->
GetInt(slotbit + 84, 11);
1131 if (tmp & 0x00000400)
1133 ptd->met_data.air_temp = tmp / 10.;
1134 int pp, precip = bstr->
GetInt(slotbit + 98, 2);
1145 ptd->met_data.precipitation = pp;
1146 ptd->met_data.hor_vis = bstr->
GetInt(slotbit + 100, 8) / 10.0;
1147 ptd->met_data.dew_point =
1148 bstr->
GetInt(slotbit + 108, 10) / 10.0 - 20.0;
1149 ptd->met_data.airpress = bstr->
GetInt(slotbit + 121, 9) + 799;
1150 ptd->met_data.airpress_tend = bstr->
GetInt(slotbit + 130, 2);
1151 ptd->met_data.salinity = bstr->
GetInt(slotbit + 135, 9) / 10.0;
1153 }
else if (type == 11) {
1155 int descr = bstr->
GetInt(slotbit + 113, 3);
1156 if (descr == 1 || descr == 2) {
1157 ptd->met_data.wind_kn = bstr->
GetInt(slotbit + 84, 7);
1158 ptd->met_data.wind_gust_kn = bstr->
GetInt(slotbit + 91, 7);
1159 ptd->met_data.wind_dir = bstr->
GetInt(slotbit + 98, 9);
1164 if (ptd->b_positionOnceValid) {
1165 ptd->Class = AIS_METEO;
1169 ptd->b_NoTrack =
true;
1170 ptd->b_show_track =
false;
1171 ptd->b_positionDoubtful =
false;
1172 b_posn_report =
true;
1173 ptd->LastPositionReportTicks = ptd->PositionReportTicks;
1174 ptd->PositionReportTicks = now.GetTicks();
1175 ptd->b_nameValid =
true;
1177 parse_result =
true;
1187 char msg_14_text[968];
1188 if (bstr->GetBitCount() > 40) {
1189 int nx = ((bstr->GetBitCount() - 40) / 6) * 6;
1190 int nd = bstr->GetStr(41, nx, msg_14_text, 968);
1192 nd = wxMin(nd, 967);
1193 msg_14_text[nd] = 0;
1194 ptd->MSG_14_text = wxString(msg_14_text, wxConvUTF8);
1196 parse_result =
true;
1209 if (b_posn_report) ptd->b_lost =
false;
1211 if (
true == parse_result) {
1213 if (!ptd->b_active && !ptd->b_positionDoubtful && b_posn_report)
1214 ptd->b_active =
true;
1217 return parse_result;
1221 : m_signalk_selfid(
""), m_callbacks(callbacks) {
1223 AISTargetNamesC =
new AIS_Target_Name_Hash;
1224 AISTargetNamesNC =
new AIS_Target_Name_Hash;
1226 if (g_benableAISNameCache) {
1230 AIS_Target_Name_Hash *HashFile = AISTargetNamesNC;
1231 wxString line = infile.GetFirstLine();
1232 while (!infile.Eof()) {
1233 if (line.IsSameAs(
"+++==Confirmed Entry's==+++"))
1234 HashFile = AISTargetNamesC;
1236 if (line.IsSameAs(
"+++==Non Confirmed Entry's==+++"))
1237 HashFile = AISTargetNamesNC;
1239 wxStringTokenizer tokenizer(line,
",");
1240 int mmsi = wxAtoi(tokenizer.GetNextToken());
1241 wxString name = tokenizer.GetNextToken().Trim();
1242 (*HashFile)[mmsi] = name;
1245 line = infile.GetNextLine();
1252 BuildERIShipTypeHash();
1254 g_pais_alert_dialog_active =
nullptr;
1255 m_bAIS_Audio_Alert_On =
false;
1259 m_bAIS_AlertPlaying =
false;
1261 TimerAIS.SetOwner(
this, TIMER_AIS1);
1262 TimerAIS.Start(TIMER_AIS_MSEC, wxTIMER_CONTINUOUS);
1264 m_ptentative_dsctarget =
nullptr;
1265 m_dsc_timer.SetOwner(
this, TIMER_DSC);
1272 InitCommListeners();
1275AisDecoder::~AisDecoder() {
1285 wxString content =
"+++==Confirmed Entry's==+++";
1286 AIS_Target_Name_Hash::iterator it;
1287 for (it = AISTargetNamesC->begin(); it != AISTargetNamesC->end(); ++it) {
1288 content.append(
"\r\n");
1289 content.append(wxString::Format(
"%i", it->first));
1290 content.append(
",").append(it->second);
1292 content.append(
"\r\n");
1293 content.append(
"+++==Non Confirmed Entry's==+++");
1294 for (it = AISTargetNamesNC->begin(); it != AISTargetNamesNC->end(); ++it) {
1295 content.append(
"\r\n");
1296 content.append(wxString::Format(
"%i", it->first));
1297 content.append(
",").append(it->second);
1299 outfile.Write(content);
1303 AISTargetNamesC->clear();
1304 delete AISTargetNamesC;
1305 AISTargetNamesNC->clear();
1306 delete AISTargetNamesNC;
1311 m_AIS_Audio_Alert_Timer.Stop();
1316 "First message[1, 2] ticks: %d Last Message [1,2]ticks %d Difference: "
1318 first_rx_ticks, rx_ticks, rx_ticks - first_rx_ticks);
1322bool IsTargetOnTheIgnoreList(
const int &mmsi) {
1327 if (props->m_bignore) {
1335void AisDecoder::InitCommListeners() {
1341 listener_N0183_VDM.
Listen(n0183_msg_VDM,
this, EVT_N0183_VDM);
1344 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1345 HandleN0183_AIS(n0183_msg);
1350 listener_N0183_FRPOS.
Listen(n0183_msg_FRPOS,
this, EVT_N0183_FRPOS);
1352 Bind(EVT_N0183_FRPOS, [&](
const ObservedEvt &ev) {
1354 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1355 HandleN0183_AIS(n0183_msg);
1360 listener_N0183_CDDSC.
Listen(n0183_msg_CDDSC,
this, EVT_N0183_CDDSC);
1361 Bind(EVT_N0183_CDDSC, [&](
const ObservedEvt &ev) {
1363 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1364 HandleN0183_AIS(n0183_msg);
1369 listener_N0183_CDDSE.
Listen(n0183_msg_CDDSE,
this, EVT_N0183_CDDSE);
1370 Bind(EVT_N0183_CDDSE, [&](
const ObservedEvt &ev) {
1372 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1373 HandleN0183_AIS(n0183_msg);
1378 listener_N0183_TLL.
Listen(n0183_msg_TLL,
this, EVT_N0183_TLL);
1382 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1383 HandleN0183_AIS(n0183_msg);
1388 listener_N0183_TTM.
Listen(n0183_msg_ttm,
this, EVT_N0183_TTM);
1391 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1392 HandleN0183_AIS(n0183_msg);
1397 listener_N0183_OSD.
Listen(n0183_msg_OSD,
this, EVT_N0183_OSD);
1400 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1401 HandleN0183_AIS(n0183_msg);
1406 listener_N0183_WPL.
Listen(n0183_msg_WPL,
this, EVT_N0183_WPL);
1409 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
1410 HandleN0183_AIS(n0183_msg);
1415 listener_SignalK.
Listen(sk_msg,
this, EVT_SIGNALK);
1417 HandleSignalK(UnpackEvtPointer<SignalkMsg>(ev));
1422 Nmea2000Msg n2k_msg_129038(
static_cast<uint64_t
>(129038));
1423 listener_N2K_129038.
Listen(n2k_msg_129038,
this, EVT_N2K_129038);
1425 HandleN2K_129038(UnpackEvtPointer<Nmea2000Msg>(ev));
1430 Nmea2000Msg n2k_msg_129039(
static_cast<uint64_t
>(129039));
1431 listener_N2K_129039.
Listen(n2k_msg_129039,
this, EVT_N2K_129039);
1433 HandleN2K_129039(UnpackEvtPointer<Nmea2000Msg>(ev));
1438 Nmea2000Msg n2k_msg_129041(
static_cast<uint64_t
>(129041));
1439 listener_N2K_129041.
Listen(n2k_msg_129041,
this, EVT_N2K_129041);
1441 HandleN2K_129041(UnpackEvtPointer<Nmea2000Msg>(ev));
1446 Nmea2000Msg n2k_msg_129794(
static_cast<uint64_t
>(129794));
1447 listener_N2K_129794.
Listen(n2k_msg_129794,
this, EVT_N2K_129794);
1449 HandleN2K_129794(UnpackEvtPointer<Nmea2000Msg>(ev));
1454 Nmea2000Msg n2k_msg_129809(
static_cast<uint64_t
>(129809));
1455 listener_N2K_129809.
Listen(n2k_msg_129809,
this, EVT_N2K_129809);
1457 HandleN2K_129809(UnpackEvtPointer<Nmea2000Msg>(ev));
1462 Nmea2000Msg n2k_msg_129810(
static_cast<uint64_t
>(129810));
1463 listener_N2K_129810.
Listen(n2k_msg_129810,
this, EVT_N2K_129810);
1465 HandleN2K_129810(UnpackEvtPointer<Nmea2000Msg>(ev));
1470 Nmea2000Msg n2k_msg_129793(
static_cast<uint64_t
>(129793));
1471 listener_N2K_129793.
Listen(n2k_msg_129793,
this, EVT_N2K_129793);
1473 HandleN2K_129793(UnpackEvtPointer<Nmea2000Msg>(ev));
1477bool AisDecoder::HandleN0183_AIS(
const N0183MsgPtr &n0183_msg) {
1478 std::string str = n0183_msg->payload;
1479 wxString sentence(str.c_str());
1480 DecodeN0183(sentence);
1485bool AisDecoder::HandleN2K_129038(
const N2000MsgPtr &n2k_msg) {
1486 std::vector<unsigned char> v = n2k_msg->payload;
1489 tN2kAISRepeat Repeat;
1500 tN2kAISNavStatus NavStat = N2kaisns_Under_Way_Motoring;
1501 tN2kAISTransceiverInformation AISTransceiverInformation;
1503 if (ParseN2kPGN129038(v, MessageID, Repeat, UserID, Latitude, Longitude,
1504 Accuracy, RAIM, Seconds, COG, SOG, Heading, ROT,
1505 NavStat, AISTransceiverInformation)) {
1506 unsigned mmsi = UserID;
1508 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1512 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1513 bool bnewtarget =
false;
1515 auto it = AISTargetList.find(mmsi);
1516 if (it == AISTargetList.end())
1518 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1522 pTargetData = it->second;
1525 wxDateTime now = wxDateTime::Now();
1529 pTargetData->MMSI = mmsi;
1530 pTargetData->MID = MessageID;
1531 pTargetData->MMSI = mmsi;
1532 pTargetData->Class = AIS_CLASS_A;
1534 if (97 == pTargetData->MMSI / 10000000) {
1535 pTargetData->Class = AIS_SART;
1537 pTargetData->StaticReportTicks = now.GetTicks();
1539 pTargetData->NavStatus =
static_cast<ais_nav_status
>(NavStat);
1540 if (!N2kIsNA(SOG)) pTargetData->SOG = MS2KNOTS(SOG);
1541 if (!N2kIsNA(COG)) pTargetData->COG = GeodesicRadToDeg(COG);
1542 if (!N2kIsNA(Heading)) pTargetData->HDG = GeodesicRadToDeg(Heading);
1543 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
1544 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
1546 pTargetData->ROTAIS = ROT;
1558 pTargetData->b_OwnShip =
1559 AISTransceiverInformation ==
1560 tN2kAISTransceiverInformation::N2kaisown_information_not_broadcast;
1561 pTargetData->b_active =
true;
1562 pTargetData->b_lost =
false;
1563 pTargetData->b_positionOnceValid =
true;
1564 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1565 pTargetData->PositionReportTicks = now.GetTicks();
1567 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1568 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1577bool AisDecoder::HandleN2K_129039(
const N2000MsgPtr &n2k_msg) {
1578 std::vector<unsigned char> v = n2k_msg->payload;
1593 tN2kAISRepeat Repeat;
1603 tN2kAISTransceiverInformation AISTransceiverInformation;
1605 bool DSC, Band, Msg22, State, Display;
1608 if (ParseN2kPGN129039(v, MessageID, Repeat, UserID, Latitude, Longitude,
1609 Accuracy, RAIM, Seconds, COG, SOG,
1610 AISTransceiverInformation, Heading, Unit, Display, DSC,
1611 Band, Msg22, Mode, State)) {
1612 uint32_t mmsi = UserID;
1614 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1618 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1619 bool bnewtarget =
false;
1621 auto it = AISTargetList.find(mmsi);
1622 if (it == AISTargetList.end())
1624 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1628 pTargetData = it->second;
1631 wxDateTime now = wxDateTime::Now();
1635 pTargetData->MMSI = mmsi;
1636 pTargetData->MID = MessageID;
1637 if (!isBuoyMmsi(mmsi))
1638 pTargetData->Class = AIS_CLASS_B;
1640 pTargetData->Class = AIS_BUOY;
1642 pTargetData->NavStatus = UNDEFINED;
1643 if (!N2kIsNA(SOG)) pTargetData->SOG = MS2KNOTS(SOG);
1644 if (!N2kIsNA(COG)) pTargetData->COG = GeodesicRadToDeg(COG);
1645 if (!N2kIsNA(Heading)) pTargetData->HDG = GeodesicRadToDeg(Heading);
1646 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
1647 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
1649 pTargetData->b_positionOnceValid =
true;
1650 pTargetData->b_active =
true;
1651 pTargetData->b_lost =
false;
1652 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1653 pTargetData->PositionReportTicks = now.GetTicks();
1654 pTargetData->b_OwnShip =
1655 AISTransceiverInformation ==
1656 tN2kAISTransceiverInformation::N2kaisown_information_not_broadcast;
1658 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1659 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1669bool AisDecoder::HandleN2K_129041(
const N2000MsgPtr &n2k_msg) {
1670 std::vector<unsigned char> v = n2k_msg->payload;
1672 tN2kAISAtoNReportData data;
1675 struct tN2kAISAtoNReportData {
1677 tN2kAISRepeat Repeat;
1686 double PositionReferenceStarboard ;
1687 double PositionReferenceTrueNorth;
1688 tN2kAISAtoNType AtoNType;
1689 bool OffPositionIndicator;
1690 bool VirtualAtoNFlag;
1691 bool AssignedModeFlag;
1692 tN2kGNSStype GNSSType;
1694 tN2kAISTransceiverInformation AISTransceiverInformation;
1695 char AtoNName[34 + 1];
1698 if (ParseN2kPGN129041(v, data)) {
1699 uint32_t mmsi = data.UserID;
1701 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1705 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1706 bool bnewtarget =
false;
1708 auto it = AISTargetList.find(mmsi);
1709 if (it == AISTargetList.end())
1711 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1715 pTargetData = it->second;
1719 pTargetData->MMSI = mmsi;
1721 wxDateTime now = wxDateTime::Now();
1724 int offpos = data.OffPositionIndicator;
1725 int virt = data.VirtualAtoNFlag;
1728 pTargetData->NavStatus = ATON_VIRTUAL;
1730 pTargetData->NavStatus = ATON_REAL;
1732 pTargetData->m_utc_sec = data.Seconds;
1734 if (pTargetData->m_utc_sec <= 59) {
1735 pTargetData->NavStatus += 1;
1736 if (offpos) pTargetData->NavStatus += 1;
1739 data.AtoNName[34] = 0;
1740 strncpy(pTargetData->ShipName, data.AtoNName, SHIP_NAME_LEN - 1);
1741 pTargetData->ShipName[
sizeof(pTargetData->ShipName) - 1] =
'\0';
1742 pTargetData->b_nameValid =
true;
1743 pTargetData->MID = 124;
1745 pTargetData->ShipType = data.AtoNType;
1746 pTargetData->Class = AIS_ATON;
1748 if (!N2kIsNA(data.Longitude)) pTargetData->Lon = data.Longitude;
1749 if (!N2kIsNA(data.Latitude)) pTargetData->Lat = data.Latitude;
1750 pTargetData->b_positionDoubtful =
false;
1751 pTargetData->b_positionOnceValid =
true;
1752 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
1753 pTargetData->PositionReportTicks = now.GetTicks();
1757 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1758 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1760 touch_state.Notify();
1767bool AisDecoder::HandleN2K_129794(
const N2000MsgPtr &n2k_msg) {
1768 std::vector<unsigned char> v = n2k_msg->payload;
1771 tN2kAISRepeat Repeat;
1775 char Name[SHIP_NAME_LEN];
1784 char Destination[DESTINATION_LEN];
1785 tN2kAISVersion AISversion;
1786 tN2kGNSStype GNSStype;
1788 tN2kAISTranceiverInfo AISinfo;
1790 if (ParseN2kPGN129794(v, MessageID, Repeat, UserID, IMOnumber, Callsign, Name,
1791 VesselType, Length, Beam, PosRefStbd, PosRefBow,
1792 ETAdate, ETAtime, Draught, Destination, AISversion,
1793 GNSStype, DTE, AISinfo)) {
1794 uint32_t mmsi = UserID;
1796 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1800 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1801 bool bnewtarget =
false;
1803 auto it = AISTargetList.find(mmsi);
1804 if (it == AISTargetList.end())
1806 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1810 pTargetData = it->second;
1814 pTargetData->MMSI = mmsi;
1815 strncpy(pTargetData->ShipName, Name, SHIP_NAME_LEN - 1);
1816 pTargetData->ShipName[
sizeof(pTargetData->ShipName) - 1] =
'\0';
1817 Name[
sizeof(Name) - 1] = 0;
1818 pTargetData->b_nameValid =
true;
1819 pTargetData->MID = 124;
1821 pTargetData->b_OwnShip =
1823 tN2kAISTranceiverInfo::N2kaisti_Own_information_not_broadcast;
1825 pTargetData->DimA = PosRefBow;
1826 pTargetData->DimB = Length - PosRefBow;
1827 pTargetData->DimC = Beam - PosRefStbd;
1828 pTargetData->DimD = PosRefStbd;
1829 pTargetData->Draft = Draught;
1830 pTargetData->IMO = IMOnumber;
1831 strncpy(pTargetData->CallSign, Callsign, CALL_SIGN_LEN - 1);
1832 pTargetData->CallSign[
sizeof(pTargetData->CallSign) - 1] =
'\0';
1833 pTargetData->ShipType = (
unsigned char)VesselType;
1834 strncpy(pTargetData->Destination, Destination, DESTINATION_LEN - 1);
1835 pTargetData->Destination[
sizeof(pTargetData->Destination) - 1] =
'\0';
1836 Destination[
sizeof(Destination) - 1] = 0;
1838 if (!N2kIsNA(ETAdate) && !N2kIsNA(ETAtime)) {
1839 long secs = (ETAdate * 24 * 3600) + wxRound(ETAtime);
1840 wxDateTime t((time_t)secs);
1842 wxDateTime tz = t.ToUTC();
1843 pTargetData->ETA_Mo = tz.GetMonth() + 1;
1844 pTargetData->ETA_Day = tz.GetDay();
1845 pTargetData->ETA_Hr = tz.GetHour();
1846 pTargetData->ETA_Min = tz.GetMinute();
1850 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1851 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1859bool AisDecoder::HandleN2K_129809(
const N2000MsgPtr &n2k_msg) {
1860 std::vector<unsigned char> v = n2k_msg->payload;
1863 tN2kAISRepeat Repeat;
1867 if (ParseN2kPGN129809(v, MessageID, Repeat, UserID, Name)) {
1868 unsigned mmsi = UserID;
1870 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1874 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1875 bool bnewtarget =
false;
1877 auto it = AISTargetList.find(mmsi);
1878 if (it == AISTargetList.end())
1880 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1884 pTargetData = it->second;
1888 pTargetData->MMSI = mmsi;
1889 Name[
sizeof(Name) - 1] = 0;
1890 strncpy(pTargetData->ShipName, Name, SHIP_NAME_LEN - 1);
1891 pTargetData->b_nameValid =
true;
1892 pTargetData->MID = 124;
1894 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
1895 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1905bool AisDecoder::HandleN2K_129810(
const N2000MsgPtr &n2k_msg) {
1906 std::vector<unsigned char> v = n2k_msg->payload;
1909 tN2kAISRepeat Repeat;
1918 uint32_t MothershipID;
1920 if (ParseN2kPGN129810(v, MessageID, Repeat, UserID, VesselType, Vendor,
1921 Callsign, Length, Beam, PosRefStbd, PosRefBow,
1923 unsigned mmsi = UserID;
1925 if (mmsi ==
g_OwnShipmmsi || IsTargetOnTheIgnoreList(mmsi))
return false;
1929 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1930 bool bnewtarget =
false;
1932 auto it = AISTargetList.find(mmsi);
1933 if (it == AISTargetList.end())
1935 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1939 pTargetData = it->second;
1943 pTargetData->MMSI = mmsi;
1944 pTargetData->DimA = PosRefBow;
1945 pTargetData->DimB = Length - PosRefBow;
1946 pTargetData->DimC = Beam - PosRefStbd;
1947 pTargetData->DimD = PosRefStbd;
1948 strncpy(pTargetData->CallSign, Callsign, CALL_SIGN_LEN - 1);
1949 pTargetData->CallSign[
sizeof(pTargetData->CallSign) - 1] =
'\0';
1950 pTargetData->ShipType = (
unsigned char)VesselType;
1952 pSelectAIS->DeleteSelectablePoint((
void *)(long)mmsi, SELTYPE_AISTARGET);
1953 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
1962bool AisDecoder::HandleN2K_129793(
const N2000MsgPtr &n2k_msg) {
1963 std::vector<unsigned char> v = n2k_msg->payload;
1966 tN2kAISRepeat Repeat;
1970 unsigned int SecondsSinceMidnight;
1971 unsigned int DaysSinceEpoch;
1973 if (ParseN2kPGN129793(v, MessageID, Repeat, UserID, Longitude, Latitude,
1974 SecondsSinceMidnight, DaysSinceEpoch)) {
1975 wxDateTime now = wxDateTime::Now();
1981 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
1982 bool bnewtarget =
false;
1984 auto it = AISTargetList.find(mmsi);
1985 if (it == AISTargetList.end())
1987 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
1991 pTargetData = it->second;
1995 pTargetData->MMSI = mmsi;
1996 pTargetData->Class = AIS_BASE;
1998 if (!N2kIsNA(Longitude)) pTargetData->Lon = Longitude;
1999 if (!N2kIsNA(Latitude)) pTargetData->Lat = Latitude;
2000 pTargetData->b_positionDoubtful =
false;
2001 pTargetData->b_positionOnceValid =
true;
2002 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
2003 pTargetData->PositionReportTicks = now.GetTicks();
2007 pSelectAIS->DeleteSelectablePoint((
void *)(
long)mmsi, SELTYPE_AISTARGET);
2008 CommitAISTarget(pTargetData,
"",
true, bnewtarget);
2027void AisDecoder::HandleSignalK(
const SignalKMsgPtr &sK_msg) {
2028 rapidjson::Document root;
2030 root.Parse(sK_msg->raw_message);
2032 if (root.HasParseError())
return;
2034 if (root.HasMember(
"self")) {
2040 if (m_signalk_selfid.IsEmpty()) {
2045 int meteo_SiteID = 0;
2046 if (root.HasMember(
"context") && root[
"context"].IsString()) {
2047 wxString context = root[
"context"].GetString();
2048 if (context == m_signalk_selfid) {
2050 wxLogMessage(
"** Ignore context own ship..");
2054 wxString mmsi_string;
2055 if (context.StartsWith(
"vessels.urn:mrn:imo:mmsi:", &mmsi_string) ||
2056 context.StartsWith(
"atons.urn:mrn:imo:mmsi:", &mmsi_string) ||
2057 context.StartsWith(
"aircraft.urn:mrn:imo:mmsi:", &mmsi_string)) {
2060 if (mmsi_string.ToLong(&mmsi)) {
2065 }
else if (context.StartsWith(
"meteo.urn:mrn:imo:mmsi:", &mmsi_string)) {
2067 origin_mmsi = wxAtoi(wxString(mmsi_string).BeforeFirst(
':'));
2068 meteo_SiteID = wxAtoi(
'1' + wxString(mmsi_string).AfterFirst(
':'));
2071 int meteo_mmsi = AisMeteoNewMmsi(origin_mmsi, 0, 0, 999, meteo_SiteID);
2082 if (g_pMUX && g_pMUX->IsLogActive()) {
2084 logmsg.Printf(
"AIS :MMSI: %ld", mmsi);
2089 if (IsTargetOnTheIgnoreList(mmsi))
return;
2097 writer.
Write(root, dbg);
2099 wxString msg(
"AisDecoder::OnEvtSignalK: " );
2103 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
2104 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
2105 bool bnewtarget =
false;
2106 int last_report_ticks;
2108 getAISTarget(mmsi, pTargetData, pStaleTarget, bnewtarget, last_report_ticks,
2111 pTargetData->MMSI = mmsi;
2112 getMmsiProperties(pTargetData);
2113 if (root.HasMember(
"updates") && root[
"updates"].IsArray()) {
2114 for (rapidjson::Value::ConstValueIterator itr = root[
"updates"].Begin();
2115 itr != root[
"updates"].End(); ++itr) {
2116 handleUpdate(pTargetData, bnewtarget, *itr);
2121 if (97 == mmsi / 10000000) {
2122 pTargetData->Class = AIS_SART;
2123 }
else if (1994 == mmsi / 100000) {
2125 pTargetData->Class = AIS_METEO;
2126 pTargetData->met_data.original_mmsi = origin_mmsi;
2127 pTargetData->met_data.stationID = meteo_SiteID;
2130 wxString met_name = pTargetData->ShipName;
2131 if (met_name.Find(
"METEO") == wxNOT_FOUND) {
2134 s_id << meteo_SiteID;
2135 id1 = wxAtoi(s_id.Mid(1, 3));
2136 id2 = wxAtoi(s_id.Mid(4, 3));
2137 met_name =
"METEO ";
2138 met_name << wxString::Format(
"%03d", (id1 + id2)).Right(3);
2139 strncpy(pTargetData->ShipName, met_name, SHIP_NAME_LEN - 1);
2141 pTargetData->b_nameValid =
true;
2142 pTargetData->MID = 123;
2143 pTargetData->COG = -1.;
2144 pTargetData->HDG = 511;
2145 pTargetData->SOG = -1.;
2146 pTargetData->b_NoTrack =
true;
2147 pTargetData->b_show_track =
false;
2149 pTargetData->b_OwnShip =
false;
2150 AISTargetList[pTargetData->MMSI] = pTargetData;
2154void AisDecoder::handleUpdate(
const std::shared_ptr<AisTargetData> &pTargetData,
2155 bool bnewtarget,
const rapidjson::Value &update) {
2156 wxString sfixtime =
"";
2158 if (update.HasMember(
"timestamp")) {
2159 sfixtime = update[
"timestamp"].GetString();
2161 if (update.HasMember(
"values") && update[
"values"].IsArray()) {
2162 for (rapidjson::Value::ConstValueIterator itr = update[
"values"].Begin();
2163 itr != update[
"values"].End(); ++itr) {
2164 updateItem(pTargetData, bnewtarget, *itr, sfixtime);
2167 wxDateTime now = wxDateTime::Now();
2168 pTargetData->m_utc_hour = now.ToUTC().GetHour();
2169 pTargetData->m_utc_min = now.ToUTC().GetMinute();
2170 pTargetData->m_utc_sec = now.ToUTC().GetSecond();
2172 pTargetData->b_active =
true;
2173 pTargetData->b_lost =
false;
2175 if (pTargetData->b_positionOnceValid) {
2176 long mmsi_long = pTargetData->MMSI;
2178 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
2179 (
void *)mmsi_long, SELTYPE_AISTARGET);
2180 pSel->SetUserData(pTargetData->MMSI);
2182 UpdateOneCPA(pTargetData.get());
2183 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
2186void AisDecoder::updateItem(
const std::shared_ptr<AisTargetData> &pTargetData,
2187 bool bnewtarget,
const rapidjson::Value &item,
2188 wxString &sfixtime)
const {
2189 if (item.HasMember(
"path") && item.HasMember(
"value")) {
2190 const wxString &update_path = item[
"path"].GetString();
2191 if (update_path ==
"navigation.position") {
2192 if (item[
"value"].HasMember(
"latitude") &&
2193 item[
"value"].HasMember(
"longitude")) {
2194 wxDateTime now = wxDateTime::Now();
2196 double lat = item[
"value"][
"latitude"].GetDouble();
2197 double lon = item[
"value"][
"longitude"].GetDouble();
2198 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
2199 pTargetData->PositionReportTicks = now.GetTicks();
2200 pTargetData->StaticReportTicks = now.GetTicks();
2201 pTargetData->Lat = lat;
2202 pTargetData->Lon = lon;
2203 pTargetData->b_positionOnceValid =
true;
2204 pTargetData->b_positionDoubtful =
false;
2211 }
else if (update_path ==
"navigation.speedOverGround" &&
2212 item[
"value"].IsNumber()) {
2213 pTargetData->SOG = item[
"value"].GetDouble() * ms_to_knot_factor;
2214 }
else if (update_path ==
"navigation.courseOverGroundTrue" &&
2215 item[
"value"].IsNumber()) {
2216 pTargetData->COG = GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2217 }
else if (update_path ==
"navigation.headingTrue" &&
2218 item[
"value"].IsNumber()) {
2219 pTargetData->HDG = GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2220 }
else if (update_path ==
"navigation.rateOfTurn" &&
2221 item[
"value"].IsNumber()) {
2222 pTargetData->ROTAIS = 4.733 * sqrt(item[
"value"].GetDouble());
2223 }
else if (update_path ==
"design.aisShipType") {
2224 if (item[
"value"].HasMember(
"id")) {
2225 if (!pTargetData->b_isDSCtarget) {
2226 pTargetData->ShipType = item[
"value"][
"id"].GetUint();
2229 }
else if (update_path ==
"atonType") {
2230 if (item[
"value"].HasMember(
"id")) {
2231 pTargetData->ShipType = item[
"value"][
"id"].GetUint();
2233 }
else if (update_path ==
"virtual") {
2234 if (item[
"value"].GetBool()) {
2235 pTargetData->NavStatus = ATON_VIRTUAL;
2237 pTargetData->NavStatus = ATON_REAL;
2239 }
else if (update_path ==
"offPosition") {
2240 if (item[
"value"].GetBool()) {
2241 if (ATON_REAL == pTargetData->NavStatus) {
2242 pTargetData->NavStatus = ATON_REAL_OFFPOSITION;
2243 }
else if (ATON_VIRTUAL == pTargetData->NavStatus) {
2244 pTargetData->NavStatus = ATON_VIRTUAL_OFFPOSITION;
2247 }
else if (update_path ==
"design.draft") {
2248 if (item[
"value"].HasMember(
"maximum") &&
2249 item[
"value"][
"maximum"].IsNumber()) {
2250 pTargetData->Draft = item[
"value"][
"maximum"].GetDouble();
2251 pTargetData->Euro_Draft = item[
"value"][
"maximum"].GetDouble();
2253 if (item[
"value"].HasMember(
"current") &&
2254 item[
"value"][
"current"].IsNumber()) {
2255 double draft = item[
"value"][
"current"].GetDouble();
2257 pTargetData->Draft = draft;
2258 pTargetData->Euro_Draft = draft;
2261 }
else if (update_path ==
"design.length") {
2262 if (pTargetData->DimB == 0) {
2263 if (item[
"value"].HasMember(
"overall")) {
2264 if (item[
"value"][
"overall"].IsNumber()) {
2265 pTargetData->Euro_Length = item[
"value"][
"overall"].GetDouble();
2266 pTargetData->DimA = item[
"value"][
"overall"].GetDouble();
2268 pTargetData->DimB = 0;
2271 }
else if (update_path ==
"sensors.ais.class") {
2272 wxString aisclass = item[
"value"].GetString();
2273 if (aisclass ==
"A") {
2274 if (!pTargetData->b_isDSCtarget) pTargetData->Class = AIS_CLASS_A;
2275 }
else if (aisclass ==
"B") {
2276 if (!pTargetData->b_isDSCtarget) {
2277 if (!isBuoyMmsi(pTargetData->MMSI))
2278 pTargetData->Class = AIS_CLASS_B;
2280 pTargetData->Class = AIS_BUOY;
2283 pTargetData->NavStatus = UNDEFINED;
2285 }
else if (aisclass ==
"BASE") {
2286 pTargetData->Class = AIS_BASE;
2287 }
else if (aisclass ==
"ATON") {
2288 pTargetData->Class = AIS_ATON;
2290 }
else if (update_path ==
"sensors.ais.fromBow") {
2291 if (pTargetData->DimB == 0 && pTargetData->DimA != 0) {
2292 int length = pTargetData->DimA;
2293 if (item[
"value"].IsNumber()) {
2294 pTargetData->DimA = item[
"value"].GetDouble();
2295 pTargetData->DimB = length - item[
"value"].GetDouble();
2298 }
else if (update_path ==
"design.beam") {
2299 if (pTargetData->DimD == 0) {
2300 if (item[
"value"].IsNumber()) {
2301 pTargetData->Euro_Beam = item[
"value"].GetDouble();
2302 pTargetData->DimC = item[
"value"].GetDouble();
2304 pTargetData->DimD = 0;
2306 }
else if (update_path ==
"sensors.ais.fromCenter") {
2307 if (pTargetData->DimD == 0 && pTargetData->DimC != 0) {
2308 int beam = pTargetData->DimC;
2309 int center = beam / 2;
2310 if (item[
"value"].IsNumber()) {
2313 pTargetData->DimC = center + item[
"value"].GetDouble();
2314 pTargetData->DimD = beam - pTargetData->DimC;
2317 }
else if (update_path ==
"navigation.state") {
2318 wxString state = item[
"value"].GetString();
2319 if (state ==
"motoring") {
2320 pTargetData->NavStatus = UNDERWAY_USING_ENGINE;
2321 }
else if (state ==
"anchored") {
2322 pTargetData->NavStatus = AT_ANCHOR;
2323 }
else if (state ==
"not under command") {
2324 pTargetData->NavStatus = NOT_UNDER_COMMAND;
2325 }
else if (state ==
"restricted manouverability") {
2326 pTargetData->NavStatus = RESTRICTED_MANOEUVRABILITY;
2327 }
else if (state ==
"constrained by draft") {
2328 pTargetData->NavStatus = CONSTRAINED_BY_DRAFT;
2329 }
else if (state ==
"moored") {
2330 pTargetData->NavStatus = MOORED;
2331 }
else if (state ==
"aground") {
2332 pTargetData->NavStatus = AGROUND;
2333 }
else if (state ==
"fishing") {
2334 pTargetData->NavStatus = FISHING;
2335 }
else if (state ==
"sailing") {
2336 pTargetData->NavStatus = UNDERWAY_SAILING;
2337 }
else if (state ==
"hazardous material high speed") {
2338 pTargetData->NavStatus = HSC;
2339 }
else if (state ==
"hazardous material wing in ground") {
2340 pTargetData->NavStatus = WIG;
2341 }
else if (state ==
"ais-sart") {
2342 pTargetData->NavStatus = RESERVED_14;
2344 pTargetData->NavStatus = UNDEFINED;
2346 }
else if (update_path ==
"navigation.destination.commonName") {
2347 const wxString &destination = item[
"value"].GetString();
2348 pTargetData->Destination[0] =
'\0';
2349 strncpy(pTargetData->Destination, destination.c_str(),
2350 DESTINATION_LEN - 1);
2351 }
else if (update_path ==
"navigation.specialManeuver") {
2352 if (strcmp(
"not available", item[
"value"].GetString()) != 0 &&
2353 pTargetData->IMO < 1) {
2354 const wxString &bluesign = item[
"value"].GetString();
2355 if (
"not engaged" == bluesign) {
2356 pTargetData->blue_paddle = 1;
2358 if (
"engaged" == bluesign) {
2359 pTargetData->blue_paddle = 2;
2361 pTargetData->b_blue_paddle =
2362 pTargetData->blue_paddle == 2 ? true :
false;
2364 }
else if (update_path ==
"sensors.ais.designatedAreaCode") {
2365 if (item[
"value"].GetInt() == 200) {
2366 pTargetData->b_hasInlandDac =
true;
2368 }
else if (update_path ==
"sensors.ais.functionalId") {
2369 if (item[
"value"].GetInt() == 10 && pTargetData->b_hasInlandDac) {
2371 pTargetData->b_isEuroInland =
true;
2375 }
else if (update_path ==
"environment.date") {
2376 wxString issued = item[
"value"].GetString();
2381 pTargetData->met_data.day = tz.GetDay();
2382 pTargetData->met_data.hour = tz.GetHour();
2383 pTargetData->met_data.minute = tz.GetMinute();
2385 }
else if (update_path ==
"environment.wind.averageSpeed" &&
2386 item[
"value"].IsNumber()) {
2387 pTargetData->met_data.wind_kn = MS2KNOTS(item[
"value"].GetDouble());
2388 }
else if (update_path ==
"environment.wind.gust" &&
2389 item[
"value"].IsNumber()) {
2390 pTargetData->met_data.wind_gust_kn = MS2KNOTS(item[
"value"].GetDouble());
2391 }
else if (update_path ==
"environment.wind.directionTrue" &&
2392 item[
"value"].IsNumber()) {
2393 pTargetData->met_data.wind_dir =
2394 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2395 }
else if (update_path ==
"environment.wind.gustDirectionTrue" &&
2396 item[
"value"].IsNumber()) {
2397 pTargetData->met_data.wind_gust_dir =
2398 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2399 }
else if (update_path ==
"environment.outside.temperature" &&
2400 item[
"value"].IsNumber()) {
2401 pTargetData->met_data.air_temp = KelvinToC(item[
"value"].GetDouble());
2402 }
else if (update_path ==
"environment.outside.relativeHumidity" &&
2403 item[
"value"].IsNumber()) {
2404 pTargetData->met_data.rel_humid = item[
"value"].GetDouble();
2405 }
else if (update_path ==
"environment.outside.dewPointTemperature" &&
2406 item[
"value"].IsNumber()) {
2407 pTargetData->met_data.dew_point = KelvinToC(item[
"value"].GetDouble());
2408 }
else if (update_path ==
"environment.outside.pressure" &&
2409 item[
"value"].IsNumber()) {
2410 pTargetData->met_data.airpress =
2411 static_cast<int>(item[
"value"].GetDouble() / 100);
2412 }
else if (update_path ==
"environment.water.level" &&
2413 item[
"value"].IsNumber()) {
2414 pTargetData->met_data.water_lev_dev = item[
"value"].GetDouble();
2415 }
else if (update_path ==
"environment.water.current.drift" &&
2416 item[
"value"].IsNumber()) {
2417 pTargetData->met_data.current = MS2KNOTS(item[
"value"].GetDouble());
2418 }
else if (update_path ==
"environment.water.current.set" &&
2419 item[
"value"].IsNumber()) {
2420 pTargetData->met_data.curr_dir =
2421 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2422 }
else if (update_path ==
"environment.water.levelTendencyValue" &&
2423 item[
"value"].IsNumber()) {
2424 pTargetData->met_data.water_lev_trend =
2425 static_cast<int>(item[
"value"].GetDouble());
2426 }
else if (update_path ==
"environment.water.levelTendency") {
2428 }
else if (update_path ==
"environment.water.waves.significantHeight" &&
2429 item[
"value"].IsNumber()) {
2430 pTargetData->met_data.wave_height = item[
"value"].GetDouble();
2431 }
else if (update_path ==
"environment.water.waves.period" &&
2432 item[
"value"].IsNumber()) {
2433 pTargetData->met_data.wave_period =
2434 static_cast<int>(item[
"value"].GetDouble());
2435 }
else if (update_path ==
"environment.water.waves.directionTrue" &&
2436 item[
"value"].IsNumber()) {
2437 pTargetData->met_data.wave_dir =
2438 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2439 }
else if (update_path ==
"environment.water.swell.height" &&
2440 item[
"value"].IsNumber()) {
2441 pTargetData->met_data.swell_height = item[
"value"].GetDouble();
2442 }
else if (update_path ==
"environment.water.swell.period" &&
2443 item[
"value"].IsNumber()) {
2444 pTargetData->met_data.swell_per =
2445 static_cast<int>(item[
"value"].GetDouble());
2446 }
else if (update_path ==
"environment.water.swell.directionTrue" &&
2447 item[
"value"].IsNumber()) {
2448 pTargetData->met_data.swell_dir =
2449 GEODESIC_RAD2DEG(item[
"value"].GetDouble());
2450 }
else if (update_path ==
"environment.water.temperature" &&
2451 item[
"value"].IsNumber()) {
2452 pTargetData->met_data.water_temp = KelvinToC(item[
"value"].GetDouble());
2453 }
else if (update_path ==
"environment.water.salinity" &&
2454 item[
"value"].IsNumber()) {
2455 pTargetData->met_data.salinity = item[
"value"].GetDouble();
2456 }
else if (update_path ==
"environment.water.ice") {
2458 }
else if (update_path ==
"environment.water.iceValue" &&
2459 item[
"value"].IsNumber()) {
2460 pTargetData->met_data.ice =
static_cast<int>(item[
"value"].GetDouble());
2461 }
else if (update_path ==
"environment.water.seaStateValue" &&
2462 item[
"value"].IsNumber()) {
2463 pTargetData->met_data.seastate =
2464 static_cast<int>(item[
"value"].GetDouble());
2465 }
else if (update_path ==
"environment.water.seaState") {
2467 }
else if (update_path ==
"environment.outside.precipitation") {
2469 }
else if (update_path ==
"environment.outside.precipitationValue" &&
2470 item[
"value"].IsNumber()) {
2471 pTargetData->met_data.precipitation =
2472 static_cast<int>(item[
"value"].GetDouble());
2473 }
else if (update_path ==
"environment.outside.pressureTendencyValue" &&
2474 item[
"value"].IsNumber()) {
2475 pTargetData->met_data.airpress_tend =
2476 static_cast<int>(item[
"value"].GetDouble());
2477 }
else if (update_path ==
"environment.outside.pressureTendency") {
2479 }
else if (update_path ==
"environment.outside.horizontalVisibility" &&
2480 item[
"value"].IsNumber()) {
2481 pTargetData->met_data.hor_vis =
2482 GEODESIC_METERS2NM(item[
"value"].GetDouble());
2483 }
else if (update_path ==
2484 "environment.outside.horizontalVisibility.overRange") {
2485 pTargetData->met_data.hor_vis_GT = item[
"value"].GetBool();
2486 }
else if (update_path.empty()) {
2487 if (item[
"value"].HasMember(
"name")) {
2488 const wxString &name = item[
"value"][
"name"].GetString();
2489 strncpy(pTargetData->ShipName, name.c_str(), SHIP_NAME_LEN - 1);
2490 pTargetData->b_nameValid =
true;
2491 pTargetData->MID = 123;
2492 }
else if (item[
"value"].HasMember(
"registrations")) {
2493 const wxString &imo = item[
"value"][
"registrations"][
"imo"].GetString();
2494 pTargetData->IMO = wxAtoi(imo.Right(7));
2495 }
else if (item[
"value"].HasMember(
"communication")) {
2496 const wxString &callsign =
2497 item[
"value"][
"communication"][
"callsignVhf"].GetString();
2498 strncpy(pTargetData->CallSign, callsign.c_str(), 7);
2500 if (item[
"value"].HasMember(
"mmsi") &&
2501 1994 != (pTargetData->MMSI) / 100000) {
2503 wxString tmp = item[
"value"][
"mmsi"].GetString();
2504 if (tmp.ToLong(&mmsi)) {
2505 pTargetData->MMSI = mmsi;
2507 if (97 == mmsi / 10000000) {
2508 pTargetData->Class = AIS_SART;
2510 if (111 == mmsi / 1000000) {
2511 pTargetData->b_SarAircraftPosnReport =
true;
2514 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
2519 wxLogMessage(wxString::Format(
2520 "** AisDecoder::updateItem: unhandled path %s", update_path));
2522 rapidjson::StringBuffer buffer;
2523 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2524 item.Accept(writer);
2525 wxString msg(
"update: ");
2526 msg.append(buffer.GetString());
2536AisError AisDecoder::DecodeSingleVDO(
const wxString &str,
GenericPosDatEx *pos,
2537 wxString *accumulator) {
2539 if (str.Len() > 128)
return AIS_NMEAVDX_TOO_LONG;
2541 if (!NMEACheckSumOK(str))
return AIS_NMEAVDX_CHECKSUM_BAD;
2543 if (!pos)
return AIS_GENERIC_ERROR;
2545 if (!accumulator)
return AIS_GENERIC_ERROR;
2548 if (!str.Mid(1, 5).IsSameAs(
"AIVDO"))
return AIS_GENERIC_ERROR;
2551 wxStringTokenizer tkz(str,
",");
2554 token = tkz.GetNextToken();
2556 token = tkz.GetNextToken();
2557 int nsentences = atoi(token.mb_str());
2559 token = tkz.GetNextToken();
2560 int isentence = atoi(token.mb_str());
2562 token = tkz.GetNextToken();
2563 token = tkz.GetNextToken();
2565 wxString string_to_parse;
2566 string_to_parse.Clear();
2579 if ((1 == nsentences) && (1 == isentence)) {
2580 string_to_parse = tkz.GetNextToken();
2583 else if (nsentences > 1) {
2584 if (1 == isentence) {
2585 *accumulator = tkz.GetNextToken();
2589 accumulator->Append(tkz.GetNextToken());
2592 if (isentence == nsentences) {
2593 string_to_parse = *accumulator;
2597 if (string_to_parse.IsEmpty() &&
2599 return AIS_INCOMPLETE_MULTIPART;
2608 auto TargetData = AisTargetDataMaker::GetInstance().GetTargetData();
2610 bool bdecode_result = Parse_VDXBitstring(&strbit, TargetData);
2612 if (bdecode_result) {
2613 switch (TargetData->MID) {
2618 if (!TargetData->b_positionDoubtful) {
2619 pos->
kLat = TargetData->Lat;
2620 pos->
kLon = TargetData->Lon;
2626 if (TargetData->COG == 360.0)
2629 pos->
kCog = TargetData->COG;
2631 if (TargetData->SOG > 102.2)
2634 pos->
kSog = TargetData->SOG;
2636 if ((
int)TargetData->HDG == 511)
2639 pos->
kHdt = TargetData->HDG;
2647 return AIS_GENERIC_ERROR;
2652 return AIS_GENERIC_ERROR;
2660AisError AisDecoder::DecodeN0183(
const wxString &str) {
2661 AisError ret = AIS_GENERIC_ERROR;
2662 wxString string_to_parse;
2664 double gpsg_lat, gpsg_lon, gpsg_mins, gpsg_degs;
2665 double gpsg_cog, gpsg_sog, gpsg_utc_time;
2666 int gpsg_utc_hour = 0;
2667 int gpsg_utc_min = 0;
2668 int gpsg_utc_sec = 0;
2669 char gpsg_name_str[21];
2672 bool bdecode_result =
false;
2679 long arpa_tgt_num = 0;
2680 double arpa_sog = 0.;
2681 double arpa_cog = 0.;
2682 double arpa_lat = 0.;
2683 double arpa_lon = 0.;
2684 double arpa_dist = 0.;
2685 double arpa_brg = 0.;
2686 wxString arpa_brgunit;
2687 wxString arpa_status;
2688 wxString arpa_distunit;
2689 wxString arpa_cogunit;
2690 double arpa_mins, arpa_degs;
2691 double arpa_utc_time;
2692 int arpa_utc_hour = 0;
2693 int arpa_utc_min = 0;
2694 int arpa_utc_sec = 0;
2695 char arpa_name_str[21];
2696 bool arpa_lost =
true;
2697 bool arpa_nottracked =
false;
2699 double aprs_lat = 0.;
2700 double aprs_lon = 0.;
2701 char aprs_name_str[21];
2702 double aprs_mins, aprs_degs;
2704 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
2705 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
2706 bool bnewtarget =
false;
2707 int last_report_ticks;
2711 if (str.Len() > 128)
return AIS_NMEAVDX_TOO_LONG;
2713 if (!NMEACheckSumOK(str)) {
2714 return AIS_NMEAVDX_CHECKSUM_BAD;
2716 if (str.Mid(1, 2).IsSameAs(
"CD")) {
2719 }
else if (str.Mid(3, 3).IsSameAs(
"TTM")) {
2723 wxStringTokenizer tkz(str,
",*");
2726 token = tkz.GetNextToken();
2727 token = tkz.GetNextToken();
2728 token.ToLong(&arpa_tgt_num);
2729 token = tkz.GetNextToken();
2730 token.ToDouble(&arpa_dist);
2731 token = tkz.GetNextToken();
2732 token.ToDouble(&arpa_brg);
2733 arpa_brgunit = tkz.GetNextToken();
2734 if (arpa_brgunit ==
"R") {
2735 if (std::isnan(arpa_ref_hdg)) {
2736 if (!std::isnan(
gHdt))
2741 arpa_brg += arpa_ref_hdg;
2742 if (arpa_brg >= 360.) arpa_brg -= 360.;
2744 token = tkz.GetNextToken();
2745 token.ToDouble(&arpa_sog);
2746 token = tkz.GetNextToken();
2747 token.ToDouble(&arpa_cog);
2748 arpa_cogunit = tkz.GetNextToken();
2749 if (arpa_cogunit ==
"R") {
2750 if (std::isnan(arpa_ref_hdg)) {
2751 if (!std::isnan(
gHdt))
2756 arpa_cog += arpa_ref_hdg;
2757 if (arpa_cog >= 360.) arpa_cog -= 360.;
2759 token = tkz.GetNextToken();
2760 token = tkz.GetNextToken();
2762 arpa_distunit = tkz.GetNextToken();
2763 token = tkz.GetNextToken();
2764 if (token ==
"") token = wxString::Format(
"ARPA %ld", arpa_tgt_num);
2765 int len = wxMin(token.Length(), 20);
2766 strncpy(arpa_name_str, token.mb_str(), len);
2767 arpa_name_str[len] = 0;
2768 arpa_status = tkz.GetNextToken();
2769 if (arpa_status !=
"L") {
2771 }
else if (arpa_status !=
"")
2772 arpa_nottracked =
true;
2773 wxString arpa_reftarget = tkz.GetNextToken();
2774 if (tkz.HasMoreTokens()) {
2775 token = tkz.GetNextToken();
2776 token.ToDouble(&arpa_utc_time);
2777 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2778 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2780 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2782 arpa_utc_hour = wxDateTime::Now().ToUTC().GetHour();
2783 arpa_utc_min = wxDateTime::Now().ToUTC().GetMinute();
2784 arpa_utc_sec = wxDateTime::Now().ToUTC().GetSecond();
2787 if (arpa_distunit ==
"K") {
2790 }
else if (arpa_distunit ==
"S") {
2795 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2799 }
else if (str.Mid(3, 3).IsSameAs(
"TLL")) {
2802 wxStringTokenizer tkz(str,
",*");
2805 wxString aprs_tll_str = tkz.GetNextToken();
2806 token = tkz.GetNextToken();
2807 token.ToLong(&arpa_tgt_num);
2808 token = tkz.GetNextToken();
2809 token.ToDouble(&arpa_lat);
2810 arpa_degs = (int)(arpa_lat / 100.0);
2811 arpa_mins = arpa_lat - arpa_degs * 100.0;
2812 arpa_lat = arpa_degs + arpa_mins / 60.0;
2813 token = tkz.GetNextToken();
2814 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2815 token.Mid(0, 1).Contains(
"s") ==
true)
2816 arpa_lat = 0. - arpa_lat;
2817 token = tkz.GetNextToken();
2818 token.ToDouble(&arpa_lon);
2819 arpa_degs = (int)(arpa_lon / 100.0);
2820 arpa_mins = arpa_lon - arpa_degs * 100.0;
2821 arpa_lon = arpa_degs + arpa_mins / 60.0;
2822 token = tkz.GetNextToken();
2823 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2824 token.Mid(0, 1).Contains(
"w") ==
true)
2825 arpa_lon = 0. - arpa_lon;
2826 token = tkz.GetNextToken();
2827 if (token ==
"") token = wxString::Format(
"ARPA %d", arpa_tgt_num);
2828 int len = wxMin(token.Length(), 20);
2829 strncpy(arpa_name_str, token.mb_str(), len);
2830 arpa_name_str[len] = 0;
2831 token = tkz.GetNextToken();
2832 token.ToDouble(&arpa_utc_time);
2833 arpa_utc_hour = (int)(arpa_utc_time / 10000.0);
2834 arpa_utc_min = (int)(arpa_utc_time / 100.0) - arpa_utc_hour * 100;
2836 (int)arpa_utc_time - arpa_utc_hour * 10000 - arpa_utc_min * 100;
2841 if (arpa_status !=
"L")
2843 else if (arpa_status !=
"")
2844 arpa_nottracked =
true;
2845 wxString arpa_reftarget = tkz.GetNextToken();
2846 mmsi = arpa_mmsi = 199200000 + arpa_tgt_num;
2850 }
else if (str.Mid(3, 3).IsSameAs(
"OSD")) {
2852 wxStringTokenizer tkz(str,
",*");
2855 token = tkz.GetNextToken();
2856 token = tkz.GetNextToken();
2857 token.ToDouble(&arpa_ref_hdg);
2867 }
else if (g_bWplUsePosition && str.Mid(3, 3).IsSameAs(
"WPL")) {
2869 wxStringTokenizer tkz(str,
",*");
2872 token = tkz.GetNextToken();
2873 token = tkz.GetNextToken();
2874 token.ToDouble(&aprs_lat);
2875 aprs_degs = (int)(aprs_lat / 100.0);
2876 aprs_mins = aprs_lat - aprs_degs * 100.0;
2877 aprs_lat = aprs_degs + aprs_mins / 60.0;
2878 token = tkz.GetNextToken();
2879 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2880 token.Mid(0, 1).Contains(
"s") ==
true)
2881 aprs_lat = 0. - aprs_lat;
2882 token = tkz.GetNextToken();
2883 token.ToDouble(&aprs_lon);
2884 aprs_degs = (int)(aprs_lon / 100.0);
2885 aprs_mins = aprs_lon - aprs_degs * 100.0;
2886 aprs_lon = aprs_degs + aprs_mins / 60.0;
2887 token = tkz.GetNextToken();
2888 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2889 token.Mid(0, 1).Contains(
"w") ==
true)
2890 aprs_lon = 0. - aprs_lon;
2891 token = tkz.GetNextToken();
2892 int len = wxMin(token.Length(), 20);
2893 strncpy(aprs_name_str, token.mb_str(), len + 1);
2894 if (0 == g_WplAction) {
2896 aprs_name_str[len] = 0;
2897 for (i = 0; i < len; i++) {
2899 hash += (int)(aprs_name_str[i]);
2900 while (hash >= 100000) hash = hash / 100000;
2902 mmsi = aprs_mmsi = 199300000 + hash;
2906 }
else if (1 == g_WplAction) {
2908 new RoutePoint(aprs_lat, aprs_lon,
"", aprs_name_str,
"",
false);
2909 pWP->m_bIsolatedMark =
true;
2910 InsertWpt(pWP,
true);
2912 }
else if (str.Mid(1, 5).IsSameAs(
"FRPOS")) {
2916 wxStringTokenizer tkz(str,
",*");
2919 token = tkz.GetNextToken();
2921 token = tkz.GetNextToken();
2922 token.ToDouble(&gpsg_lat);
2923 gpsg_degs = (int)(gpsg_lat / 100.0);
2924 gpsg_mins = gpsg_lat - gpsg_degs * 100.0;
2925 gpsg_lat = gpsg_degs + gpsg_mins / 60.0;
2927 token = tkz.GetNextToken();
2928 if (token.Mid(0, 1).Contains(
"S") ==
true ||
2929 token.Mid(0, 1).Contains(
"s") ==
true)
2930 gpsg_lat = 0. - gpsg_lat;
2932 token = tkz.GetNextToken();
2933 token.ToDouble(&gpsg_lon);
2934 gpsg_degs = (int)(gpsg_lon / 100.0);
2935 gpsg_mins = gpsg_lon - gpsg_degs * 100.0;
2936 gpsg_lon = gpsg_degs + gpsg_mins / 60.0;
2938 token = tkz.GetNextToken();
2939 if (token.Mid(0, 1).Contains(
"W") ==
true ||
2940 token.Mid(0, 1).Contains(
"w") ==
true)
2941 gpsg_lon = 0. - gpsg_lon;
2943 token = tkz.GetNextToken();
2946 token = tkz.GetNextToken();
2947 token.ToDouble(&gpsg_sog);
2949 token = tkz.GetNextToken();
2950 token.ToDouble(&gpsg_cog);
2952 token = tkz.GetNextToken();
2955 token = tkz.GetNextToken();
2956 token.ToDouble(&gpsg_utc_time);
2957 gpsg_utc_hour = (int)(gpsg_utc_time / 10000.0);
2958 gpsg_utc_min = (int)(gpsg_utc_time / 100.0) - gpsg_utc_hour * 100;
2960 (int)gpsg_utc_time - gpsg_utc_hour * 10000 - gpsg_utc_min * 100;
2964 token = tkz.GetNextToken();
2965 int i, len, hash = 0;
2966 len = wxMin(wxStrlen(token), 20);
2967 strncpy(gpsg_name_str, token.mb_str(), len);
2968 gpsg_name_str[len] = 0;
2969 for (i = 0; i < len; i++) {
2971 hash += (int)(token[i]);
2972 while (hash >= 100000) hash = hash / 100000;
2975 gpsg_mmsi = 199000000 + hash;
2977 }
else if (!str.Mid(3, 2).IsSameAs(
"VD")) {
2978 return AIS_NMEAVDX_BAD;
2984 string_to_parse.Clear();
2986 if (str.Mid(3, 2).IsSameAs(
"VD")) {
2987 wxStringTokenizer tkz(str,
",");
2990 token = tkz.GetNextToken();
2992 token = tkz.GetNextToken();
2993 nsentences = atoi(token.mb_str());
2995 token = tkz.GetNextToken();
2996 isentence = atoi(token.mb_str());
2998 token = tkz.GetNextToken();
2999 long lsequence_id = 0;
3000 token.ToLong(&lsequence_id);
3002 token = tkz.GetNextToken();
3004 token.ToLong(&lchannel);
3009 if ((1 == nsentences) && (1 == isentence)) {
3010 string_to_parse = tkz.GetNextToken();
3013 else if (nsentences > 1) {
3014 if (1 == isentence) {
3015 sentence_accumulator = tkz.GetNextToken();
3019 sentence_accumulator += tkz.GetNextToken();
3022 if (isentence == nsentences) {
3023 string_to_parse = sentence_accumulator;
3028 if (mmsi || (!string_to_parse.IsEmpty() &&
3029 (string_to_parse.Len() < AIS_MAX_MESSAGE_LEN))) {
3031 wxCharBuffer abuf = string_to_parse.ToUTF8();
3033 return AIS_GENERIC_ERROR;
3038 if (!mmsi) mmsi = strbit.GetInt(9, 30);
3039 long mmsi_long = mmsi;
3042 int origin_mmsi = 0;
3043 int messID = strbit.GetInt(1, 6);
3044 int dac = strbit.GetInt(41, 10);
3045 int fi = strbit.GetInt(51, 6);
3047 int met_lon, met_lat;
3048 if (dac == 001 && fi == 31) {
3050 met_lon = strbit.GetInt(57, 25);
3051 met_lat = strbit.GetInt(82, 24);
3052 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 25, 0);
3055 }
else if (dac == 367 && fi == 33) {
3057 const int size = strbit.GetBitCount();
3058 if (size < 168)
return AIS_GENERIC_ERROR;
3059 const int startb = 56;
3060 const int slot_size = 112;
3061 const int extra_bits = (size - startb) % slot_size;
3062 if (extra_bits > 0)
return AIS_GENERIC_ERROR;
3064 int mes_type = strbit.GetInt(57, 4);
3065 int site_ID = strbit.GetInt(77, 7);
3066 if (mes_type == 0) {
3068 met_lon = strbit.GetInt(90, 28);
3069 met_lat = strbit.GetInt(118, 27);
3070 mmsi = AisMeteoNewMmsi(mmsi, met_lat, met_lon, 28, site_ID);
3075 int x_mmsi = AisMeteoNewMmsi(mmsi, 91, 181, 0, site_ID);
3081 return AIS_GENERIC_ERROR;
3090 auto it = AISTargetList.find(mmsi);
3091 if (it == AISTargetList.end())
3093 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3098 pTargetData->MMSI = mmsi;
3099 pTargetData->met_data.original_mmsi = origin_mmsi;
3102 pTargetData = it->second;
3105 pStaleTarget = pTargetData;
3107 pTargetData->MMSI = mmsi;
3108 pTargetData->met_data.original_mmsi = origin_mmsi;
3113 if (mmsi ==
static_cast<unsigned>(props->MMSI)) {
3115 if (TRACKTYPE_NEVER == props->TrackType) {
3116 pTargetData->b_show_track =
false;
3117 }
else if (TRACKTYPE_ALWAYS == props->TrackType) {
3118 pTargetData->b_show_track =
true;
3123 if (props->m_bignore)
return AIS_NoError;
3126 else if (props->m_bVDM) {
3128 if (str.Mid(3, 9).IsSameAs(
"VDM,1,1,,")) {
3129 int message_ID = strbit.GetInt(1, 6);
3132 if ((message_ID <= 3) || (message_ID == 18)) {
3134 pTargetData->b_OwnShip =
true;
3136 wxString aivdostr = str;
3137 aivdostr.replace(1, 5,
"AIVDO");
3138 unsigned char calculated_checksum = 0;
3139 wxString::iterator j;
3140 for (j = aivdostr.begin() + 1; j != aivdostr.end() && *j !=
'*';
3142 calculated_checksum ^=
static_cast<unsigned char>(*j);
3145 if (j <= aivdostr.end() - 3)
3148 wxString::Format(_(
"%02X"), calculated_checksum));
3150 gps_watchdog_timeout_ticks =
3153 std::string full_sentence = aivdostr.ToStdString();
3154 auto address = std::make_shared<NavAddr0183>(
"virtual");
3157 auto msg = std::make_shared<const Nmea0183Msg>(
3158 "AIVDO", full_sentence, address);
3159 NavMsgBus::GetInstance().
Notify(std::move(msg));
3169 wxDateTime now = wxDateTime::Now();
3173 last_report_ticks = pStaleTarget->PositionReportTicks;
3175 last_report_ticks = now.GetTicks();
3179 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
3183 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3184 pTargetData->PositionReportTicks = now.GetTicks();
3185 pTargetData->StaticReportTicks = now.GetTicks();
3186 pTargetData->m_utc_hour = gpsg_utc_hour;
3187 pTargetData->m_utc_min = gpsg_utc_min;
3188 pTargetData->m_utc_sec = gpsg_utc_sec;
3189 pTargetData->m_date_string = gpsg_date;
3190 pTargetData->MMSI = gpsg_mmsi;
3191 pTargetData->NavStatus = 0;
3192 pTargetData->Lat = gpsg_lat;
3193 pTargetData->Lon = gpsg_lon;
3194 pTargetData->b_positionOnceValid =
true;
3195 pTargetData->COG = gpsg_cog;
3196 pTargetData->SOG = gpsg_sog;
3197 pTargetData->ShipType = 52;
3198 pTargetData->Class = AIS_GPSG_BUDDY;
3199 memcpy(pTargetData->ShipName, gpsg_name_str,
sizeof(gpsg_name_str));
3200 pTargetData->b_nameValid =
true;
3201 pTargetData->b_active =
true;
3202 pTargetData->b_lost =
false;
3204 bdecode_result =
true;
3205 }
else if (arpa_mmsi) {
3206 pTargetData->m_utc_hour = arpa_utc_hour;
3207 pTargetData->m_utc_min = arpa_utc_min;
3208 pTargetData->m_utc_sec = arpa_utc_sec;
3209 pTargetData->MMSI = arpa_mmsi;
3210 pTargetData->NavStatus = 15;
3211 if (str.Mid(3, 3).IsSameAs(
"TLL")) {
3214 (now.GetTicks() - pTargetData->PositionReportTicks);
3215 if (age_of_last > 0) {
3216 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, arpa_lat,
3217 arpa_lon, &pTargetData->COG, &pTargetData->SOG);
3218 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3221 pTargetData->Lat = arpa_lat;
3222 pTargetData->Lon = arpa_lon;
3223 }
else if (str.Mid(3, 3).IsSameAs(
"TTM")) {
3224 if (arpa_dist != 0.)
3225 ll_gc_ll(
gLat,
gLon, arpa_brg, arpa_dist, &pTargetData->Lat,
3229 pTargetData->COG = arpa_cog;
3230 pTargetData->SOG = arpa_sog;
3232 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3233 pTargetData->PositionReportTicks = now.GetTicks();
3234 pTargetData->StaticReportTicks = now.GetTicks();
3235 pTargetData->b_positionOnceValid =
true;
3236 pTargetData->ShipType = 55;
3237 pTargetData->Class = AIS_ARPA;
3239 memcpy(pTargetData->ShipName, arpa_name_str,
sizeof(arpa_name_str));
3240 if (arpa_status !=
"Q")
3241 pTargetData->b_nameValid =
true;
3243 pTargetData->b_nameValid =
false;
3244 pTargetData->b_active = !arpa_lost;
3245 pTargetData->b_lost = arpa_nottracked;
3247 bdecode_result =
true;
3248 }
else if (aprs_mmsi) {
3249 pTargetData->m_utc_hour = now.GetHour();
3250 pTargetData->m_utc_min = now.GetMinute();
3251 pTargetData->m_utc_sec = now.GetSecond();
3252 pTargetData->MMSI = aprs_mmsi;
3253 pTargetData->NavStatus = 15;
3255 int age_of_last = (now.GetTicks() - pTargetData->PositionReportTicks);
3256 if (age_of_last > 0) {
3257 ll_gc_ll_reverse(pTargetData->Lat, pTargetData->Lon, aprs_lat,
3258 aprs_lon, &pTargetData->COG, &pTargetData->SOG);
3259 pTargetData->SOG = pTargetData->SOG * 3600 / age_of_last;
3262 pTargetData->LastPositionReportTicks = pTargetData->PositionReportTicks;
3263 pTargetData->PositionReportTicks = now.GetTicks();
3264 pTargetData->StaticReportTicks = now.GetTicks();
3265 pTargetData->Lat = aprs_lat;
3266 pTargetData->Lon = aprs_lon;
3267 pTargetData->b_positionOnceValid =
true;
3268 pTargetData->ShipType = 56;
3269 pTargetData->Class = AIS_APRS;
3270 memcpy(pTargetData->ShipName, aprs_name_str,
sizeof(aprs_name_str));
3271 pTargetData->b_nameValid =
true;
3272 pTargetData->b_active =
true;
3273 pTargetData->b_lost =
false;
3275 bdecode_result =
true;
3279 Parse_VDXBitstring(&strbit, pTargetData);
3283 getMmsiProperties(pTargetData);
3286 pTargetData->RecentPeriod =
3287 pTargetData->PositionReportTicks - last_report_ticks;
3292 pTargetData =
nullptr;
3297 CommitAISTarget(pTargetData, str, bdecode_result, bnewtarget);
3302 if ((n_msgs % 10000) == 0)
3303 printf(
"n_msgs %10d m_n_targets: %6d n_msg1: %10d n_msg5+24: %10d \n",
3304 n_msgs, m_n_targets, n_msg1, n_msg5 + n_msg24);
3310void AisDecoder::CommitAISTarget(
3311 const std::shared_ptr<AisTargetData> &pTargetData,
const wxString &str,
3312 bool message_valid,
bool new_target) {
3313 m_pLatestTargetData = pTargetData;
3315 if (!str.IsEmpty()) {
3316 if (str.Mid(3, 3).IsSameAs(
"VDO"))
3317 pTargetData->b_OwnShip =
true;
3319 pTargetData->b_OwnShip =
false;
3322 if (!pTargetData->b_OwnShip) {
3324 if (0 == m_persistent_tracks.count(pTargetData->MMSI)) {
3326 pTargetData->b_PersistTrack =
false;
3331 pTargetData->b_mPropPersistTrack = props->m_bPersistentTrack;
3338 pTargetData->b_NoTrack =
false;
3343 if (message_valid) {
3345 if (pTargetData->MMSI) {
3346 AISshipNameCache(pTargetData.get(), AISTargetNamesC, AISTargetNamesNC,
3349 AISTargetList[pTargetData->MMSI] =
3352 if (!pTargetData->area_notices.empty()) {
3353 auto it = AIS_AreaNotice_Sources.find(pTargetData->MMSI);
3354 if (it == AIS_AreaNotice_Sources.end())
3355 AIS_AreaNotice_Sources[pTargetData->MMSI] = pTargetData;
3360 if (!pTargetData->b_OwnShip) {
3361 if (pTargetData->b_positionOnceValid) {
3362 long mmsi_long = pTargetData->MMSI;
3364 pTargetData->Lat, pTargetData->Lon, (
void *)mmsi_long,
3366 pSel->SetUserData(pTargetData->MMSI);
3370 UpdateOneCPA(pTargetData.get());
3373 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3386 if (!pTargetData->b_OwnShip) {
3387 if (pTargetData->b_positionOnceValid) {
3388 long mmsi_long = pTargetData->MMSI;
3390 pTargetData->Lat, pTargetData->Lon, (
void *)mmsi_long,
3392 pSel->SetUserData(pTargetData->MMSI);
3399void AisDecoder::getAISTarget(
long mmsi,
3400 std::shared_ptr<AisTargetData> &pTargetData,
3401 std::shared_ptr<AisTargetData> &pStaleTarget,
3402 bool &bnewtarget,
int &last_report_ticks,
3404 now = wxDateTime::Now();
3405 auto it = AISTargetList.find(mmsi);
3406 if (it == AISTargetList.end())
3408 pTargetData = AisTargetDataMaker::GetInstance().GetTargetData();
3412 pTargetData = it->second;
3413 pStaleTarget = pTargetData;
3420 last_report_ticks = pStaleTarget->PositionReportTicks;
3422 last_report_ticks = now.GetTicks();
3426 pSelectAIS->DeleteSelectablePoint((
void *)mmsi, SELTYPE_AISTARGET);
3429std::shared_ptr<AisTargetData> AisDecoder::ProcessDSx(
const wxString &str,
3431 double dsc_lat = 0.;
3432 double dsc_lon = 0.;
3433 double dsc_mins, dsc_degs, dsc_tmp, dsc_addr;
3435 double dse_lat = 0.;
3436 double dse_lon = 0.;
3437 long dsc_fmt, dsc_quadrant, dsc_cat, dsc_nature;
3440 int dsc_tx_mmsi = 0;
3442 double dse_cog = 0.;
3443 double dse_sog = 0.;
3444 wxString dse_shipName =
"";
3449 std::shared_ptr<AisTargetData> pTargetData =
nullptr;
3453 wxStringTokenizer tkz(str,
",*");
3456 token = tkz.GetNextToken();
3458 if (str.Mid(3, 3).IsSameAs(
"DSC")) {
3459 m_dsc_last_string = str;
3461 token = tkz.GetNextToken();
3465 token = tkz.GetNextToken();
3467 if (dsc_fmt == 12 || dsc_fmt == 16) {
3468 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3470 token.ToDouble(&dsc_addr);
3471 dsc_mmsi = 0 - (int)(dsc_addr / 10);
3474 token = tkz.GetNextToken();
3475 token.ToLong(&dsc_cat);
3477 token = tkz.GetNextToken();
3478 if (!token.IsSameAs(
"")) {
3479 token.ToLong(&dsc_nature);
3483 token = tkz.GetNextToken();
3485 token = tkz.GetNextToken();
3486 token.ToDouble(&dsc_tmp);
3488 token = tkz.GetNextToken();
3489 token = tkz.GetNextToken();
3490 if (dsc_fmt == 16 && dsc_cat == 12 && !token.IsSameAs(
"")) {
3492 dsc_tx_mmsi = dsc_mmsi;
3493 dsc_mmsi = wxAtoi(token.Mid(0, 9));
3495 token = tkz.GetNextToken();
3496 if (dsc_fmt == 16 && dsc_cat == 12) {
3497 if (!token.IsSameAs(
"")) {
3498 token.ToLong(&dsc_nature);
3502 token = tkz.GetNextToken();
3503 token = tkz.GetNextToken();
3505 dsc_quadrant = (int)(dsc_tmp / 1000000000.0);
3507 if (dsc_quadrant > 3)
3510 dsc_lat = (int)(dsc_tmp / 100000.0);
3511 dsc_lon = dsc_tmp - dsc_lat * 100000.0;
3512 dsc_lat = dsc_lat - dsc_quadrant * 10000;
3513 dsc_degs = (int)(dsc_lat / 100.0);
3514 dsc_mins = dsc_lat - dsc_degs * 100.0;
3515 dsc_lat = dsc_degs + dsc_mins / 60.0;
3517 dsc_degs = (int)(dsc_lon / 100.0);
3518 dsc_mins = dsc_lon - dsc_degs * 100.0;
3519 dsc_lon = dsc_degs + dsc_mins / 60.0;
3520 switch (dsc_quadrant) {
3536 if (dsc_fmt != 02) mmsi = (int)dsc_mmsi;
3538 }
else if (str.Mid(3, 3).IsSameAs(
"DSE")) {
3539 token = tkz.GetNextToken();
3540 token = tkz.GetNextToken();
3541 token = tkz.GetNextToken();
3542 token = tkz.GetNextToken();
3543 dse_mmsi = wxAtoi(token.Mid(
3549 token = tkz.GetNextToken();
3552 token.ToDouble(&dse_tmp);
3553 dse_lat = (int)(dse_tmp / 10000.0);
3554 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3555 dse_lat = dse_lat / 600000.0;
3556 dse_lon = dse_lon / 600000.0;
3559 while (tkz.HasMoreTokens()) {
3560 dseSymbol = tkz.GetNextToken();
3561 token = tkz.GetNextToken();
3562 if (dseSymbol.IsSameAs(
"00")) {
3563 token.ToDouble(&dse_tmp);
3564 dse_lat = (int)(dse_tmp / 10000.0);
3565 dse_lon = (int)(dse_tmp - dse_lat * 10000.0);
3566 dse_lat = dse_lat / 600000.0;
3567 dse_lon = dse_lon / 600000.0;
3568 }
else if (dseSymbol.IsSameAs(
"01")) {
3569 }
else if (dseSymbol.IsSameAs(
"02")) {
3570 token.ToDouble(&dse_tmp);
3571 dse_sog = dse_tmp / 10.0;
3572 }
else if (dseSymbol.IsSameAs(
"03")) {
3573 token.ToDouble(&dse_tmp);
3574 dse_cog = dse_tmp / 10.0;
3575 }
else if (dseSymbol.IsSameAs(
"04")) {
3576 dse_shipName = DecodeDSEExpansionCharacters(token);
3577 }
else if (dseSymbol.IsSameAs(
"05")) {
3578 }
else if (dseSymbol.IsSameAs(
"06")) {
3581 mmsi = abs((
int)dse_mmsi);
3585 wxDateTime now = wxDateTime::Now();
3587 int last_report_ticks = now.GetTicks();
3590 auto it = AISTargetList.find(mmsi);
3591 std::shared_ptr<AisTargetData> pStaleTarget =
nullptr;
3592 if (it == AISTargetList.end()) {
3594 pStaleTarget = it->second;
3595 last_report_ticks = pStaleTarget->PositionReportTicks;
3601 m_ptentative_dsctarget = AisTargetDataMaker::GetInstance().GetTargetData();
3603 m_ptentative_dsctarget->PositionReportTicks = now.GetTicks();
3604 m_ptentative_dsctarget->StaticReportTicks = now.GetTicks();
3606 m_ptentative_dsctarget->MMSI = mmsi;
3607 m_ptentative_dsctarget->NavStatus =
3609 m_ptentative_dsctarget->Lat = dsc_lat;
3610 m_ptentative_dsctarget->Lon = dsc_lon;
3611 m_ptentative_dsctarget->b_positionOnceValid =
true;
3612 m_ptentative_dsctarget->COG = 0;
3613 m_ptentative_dsctarget->SOG = 0;
3614 m_ptentative_dsctarget->ShipType = dsc_fmt;
3615 m_ptentative_dsctarget->Class = AIS_DSC;
3616 m_ptentative_dsctarget->b_isDSCtarget =
true;
3617 m_ptentative_dsctarget->b_nameValid =
true;
3618 if (dsc_fmt == 12 || (dsc_fmt == 16 && dsc_cat == 12)) {
3619 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN,
"DISTRESS %u",
3621 m_ptentative_dsctarget->m_dscNature = int(dsc_nature);
3622 m_ptentative_dsctarget->m_dscTXmmsi = dsc_tx_mmsi;
3624 snprintf(m_ptentative_dsctarget->ShipName, SHIP_NAME_LEN,
"POSITION %u",
3628 m_ptentative_dsctarget->b_active =
true;
3629 m_ptentative_dsctarget->b_lost =
false;
3630 m_ptentative_dsctarget->RecentPeriod =
3631 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3634 if (!b_take_dsc) m_dsc_timer.Start(1000, wxTIMER_ONE_SHOT);
3639 if (dse_mmsi || b_take_dsc) {
3640 if (m_ptentative_dsctarget) {
3646 m_ptentative_dsctarget->Lat =
3647 m_ptentative_dsctarget->Lat +
3648 ((m_ptentative_dsctarget->Lat) >= 0 ? dse_lat : -dse_lat);
3649 m_ptentative_dsctarget->Lon =
3650 m_ptentative_dsctarget->Lon +
3651 ((m_ptentative_dsctarget->Lon) >= 0 ? dse_lon : -dse_lon);
3652 if (!dse_shipName.empty()) {
3653 memset(m_ptentative_dsctarget->ShipName,
'\0', SHIP_NAME_LEN);
3654 snprintf(m_ptentative_dsctarget->ShipName, dse_shipName.length(),
3655 "%s", dse_shipName.ToAscii().data());
3657 m_ptentative_dsctarget->COG = dse_cog;
3658 m_ptentative_dsctarget->SOG = dse_sog;
3662 m_ptentative_dsctarget->RecentPeriod =
3663 m_ptentative_dsctarget->PositionReportTicks - last_report_ticks;
3668 auto found = AISTargetList.find(mmsi);
3669 if (found == AISTargetList.end()) {
3670 pTargetData = m_ptentative_dsctarget;
3672 pTargetData = found->second;
3673 std::vector<AISTargetTrackPoint> ptrack =
3674 std::move(pTargetData->m_ptrack);
3675 pTargetData->CloneFrom(
3676 m_ptentative_dsctarget
3679 pTargetData->m_ptrack =
3686 m_ptentative_dsctarget =
nullptr;
3688 m_pLatestTargetData = pTargetData;
3690 AISTargetList[pTargetData->MMSI] =
3693 long mmsi_long = pTargetData->MMSI;
3697 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
3700 pSelectAIS->AddSelectablePoint(pTargetData->Lat, pTargetData->Lon,
3701 (
void *)mmsi_long, SELTYPE_AISTARGET);
3702 pSel->SetUserData(pTargetData->MMSI);
3705 UpdateOneCPA(pTargetData.get());
3708 if (pTargetData->b_show_track) UpdateOneTrack(pTargetData.get());
3715bool AisDecoder::NMEACheckSumOK(
const wxString &str_in) {
3716 unsigned char checksum_value = 0;
3717 int sentence_hex_sum;
3719 wxCharBuffer buf = str_in.ToUTF8();
3720 if (!buf.data())
return false;
3722 char str_ascii[AIS_MAX_MESSAGE_LEN + 1];
3723 strncpy(str_ascii, buf.data(), AIS_MAX_MESSAGE_LEN);
3724 str_ascii[AIS_MAX_MESSAGE_LEN] =
'\0';
3726 int string_length = strlen(str_ascii);
3728 int payload_length = 0;
3729 while ((payload_length < string_length) &&
3730 (str_ascii[payload_length] !=
'*'))
3733 if (payload_length == string_length)
3738 while (index < payload_length) {
3739 checksum_value ^= str_ascii[index];
3743 if (string_length > 4) {
3745 scanstr[0] = str_ascii[payload_length + 1];
3746 scanstr[1] = str_ascii[payload_length + 2];
3748 sscanf(scanstr,
"%2x", &sentence_hex_sum);
3750 if (sentence_hex_sum == checksum_value)
return true;
3756void AisDecoder::UpdateAllCPA() {
3758 for (
const auto &it : GetTargetList()) {
3759 std::shared_ptr<AisTargetData> td = it.second;
3761 if (
nullptr != td) UpdateOneCPA(td.get());
3765void AisDecoder::UpdateAllTracks() {
3766 for (
const auto &it : GetTargetList()) {
3767 std::shared_ptr<AisTargetData> td = it.second;
3768 if (td) UpdateOneTrack(td.get());
3774 if (!ptarget->b_positionOnceValid)
return;
3776 if (!ptarget->m_ptrack.empty()) {
3778 if (fabs(LastTrackpoint.m_lat - ptarget->Lat) > .1 ||
3779 fabs(LastTrackpoint.m_lon - ptarget->Lon) > .1) {
3782 ptarget->m_ptrack.pop_back();
3783 ptarget->b_positionDoubtful =
true;
3790 if ((ptarget->PositionReportTicks - ptarget->LastPositionReportTicks) > 2) {
3793 ptrackpoint.m_lat = ptarget->Lat;
3794 ptrackpoint.m_lon = ptarget->Lon;
3795 ptrackpoint.m_time = wxDateTime::Now().GetTicks();
3797 ptarget->m_ptrack.push_back(ptrackpoint);
3799 if (ptarget->b_PersistTrack || ptarget->b_mPropPersistTrack) {
3801 if (0 == m_persistent_tracks.count(ptarget->MMSI)) {
3803 t->SetName(wxString::Format(
3804 "AIS %s (%u) %s %s", ptarget->GetFullName().c_str(), ptarget->MMSI,
3805 wxDateTime::Now().FormatISODate().c_str(),
3806 wxDateTime::Now().FormatISOTime().c_str()));
3809 m_persistent_tracks[ptarget->MMSI] = t;
3811 t = m_persistent_tracks[ptarget->MMSI];
3814 vector2D point(ptrackpoint.m_lon, ptrackpoint.m_lat);
3816 t->AddNewPoint(point, wxDateTime(ptrackpoint.m_time).ToUTC());
3819 pSelect->AddSelectableTrackSegment(tp->m_lat, tp->m_lon, tp1->m_lat,
3820 tp1->m_lon, tp, tp1, t);
3830 wxDateTime::Now().GetTicks() - (time_t)(g_AISShowTracks_Mins * 60);
3832 ptarget->m_ptrack.erase(
3833 std::remove_if(ptarget->m_ptrack.begin(), ptarget->m_ptrack.end(),
3835 return track.m_time < test_time;
3837 ptarget->m_ptrack.end());
3842void AisDecoder::DeletePersistentTrack(
const Track *track) {
3843 for (
auto it = m_persistent_tracks.begin(); it != m_persistent_tracks.end();
3845 if (it->second == track) {
3846 unsigned mmsi = it->first;
3847 m_persistent_tracks.erase(it);
3849 if (0 == m_persistent_tracks.count(mmsi)) {
3853 if (props->m_bPersistentTrack) {
3857 std::shared_ptr<AisTargetData> td =
3858 Get_Target_Data_From_MMSI(mmsi);
3860 props->m_bPersistentTrack =
false;
3861 td->b_mPropPersistTrack =
false;
3863 if (!m_callbacks.confirm_stop_track()) {
3864 props->m_bPersistentTrack =
true;
3876void AisDecoder::UpdateAllAlarms() {
3877 m_bGeneralAlert =
false;
3880 for (
const auto &it : GetTargetList()) {
3881 std::shared_ptr<AisTargetData> td = it.second;
3885 if (!m_bGeneralAlert) {
3887 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3888 (td->Class != AIS_ATON) && (td->Class != AIS_BASE))
3889 m_bGeneralAlert =
true;
3892 if (g_bAIS_CPA_Alert_Suppress_Moored && (td->SOG <= g_ShowMoored_Kts))
3893 m_bGeneralAlert =
false;
3896 if ((g_bCPAMax) && (td->Range_NM > g_CPAMax_NM))
3897 m_bGeneralAlert =
false;
3900 if ((g_bTCPA_Max) && (td->TCPA > g_TCPA_Max)) m_bGeneralAlert =
false;
3903 if (td->Class == AIS_SART && td->NavStatus == 14)
3904 m_bGeneralAlert =
true;
3907 if ((td->Class == AIS_DSC) &&
3908 ((td->ShipType == 12) || (td->ShipType == 16)))
3909 m_bGeneralAlert =
true;
3912 ais_alert_type this_alarm = AIS_NO_ALERT;
3915 if (td->Class == AIS_SART && td->NavStatus == 14)
3916 this_alarm = AIS_ALERT_SET;
3919 if ((td->Class == AIS_DSC) &&
3920 ((td->ShipType == 12) || (td->ShipType == 16)))
3921 this_alarm = AIS_ALERT_SET;
3923 if (g_bCPAWarn && td->b_active && td->b_positionOnceValid &&
3924 (td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
3927 if ((g_bHideMoored) && (td->SOG <= g_ShowMoored_Kts)) {
3928 td->n_alert_state = AIS_NO_ALERT;
3934 if (g_bAIS_CPA_Alert_Suppress_Moored &&
3935 (td->SOG <= g_ShowMoored_Kts)) {
3936 td->n_alert_state = AIS_NO_ALERT;
3942 if (td->Range_NM > g_CPAMax_NM) {
3943 td->n_alert_state = AIS_NO_ALERT;
3948 if ((td->CPA < g_CPAWarn_NM) && (td->TCPA > 0) &&
3949 (td->Class != AIS_ATON) && (td->Class != AIS_BASE) &&
3950 (td->Class != AIS_METEO)) {
3952 if (td->TCPA < g_TCPA_Max) {
3953 if (td->b_isFollower)
3954 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3956 this_alarm = AIS_ALERT_SET;
3959 if (td->b_isFollower)
3960 this_alarm = AIS_ALERT_NO_DIALOG_SET;
3962 this_alarm = AIS_ALERT_SET;
3970 if (g_bAIS_ACK_Timeout || (td->Class == AIS_SART) ||
3971 ((td->Class == AIS_DSC) &&
3972 ((td->ShipType == 12) || (td->ShipType == 16)))) {
3973 if (td->b_in_ack_timeout) {
3974 wxTimeSpan delta = wxDateTime::Now() - td->m_ack_time;
3975 if (delta.GetMinutes() > g_AckTimeout_Mins)
3976 td->b_in_ack_timeout =
false;
3982 if (td->b_in_ack_timeout) {
3983 if (this_alarm == AIS_NO_ALERT) td->b_in_ack_timeout =
false;
3987 td->n_alert_state = this_alarm;
3993 ptarget->Range_NM = -1.;
4002 DistanceBearingMercator(ptarget->Lat, ptarget->Lon,
gLat,
gLon, &brg, &dist);
4003 ptarget->Range_NM = dist;
4006 if (dist <= 1e-5) ptarget->Brg = -1.0;
4008 if (!ptarget->b_positionOnceValid || !
bGPSValid) {
4009 ptarget->bCPA_Valid =
false;
4013 if (ptarget->Class == AIS_METEO) {
4014 ptarget->bCPA_Valid =
false;
4023 if (ptarget->b_OwnShip) {
4025 ptarget->TCPA = -100;
4026 ptarget->bCPA_Valid =
false;
4030 double cpa_calc_ownship_cog =
gCog;
4031 double cpa_calc_target_cog = ptarget->COG;
4034 if (std::isnan(
gSog) || (
gSog > 102.2)) {
4035 ptarget->bCPA_Valid =
false;
4040 if (std::isnan(
gCog) ||
gCog == 360.0) {
4042 cpa_calc_ownship_cog =
4046 ptarget->bCPA_Valid =
false;
4052 if (ptarget->COG == 360.0) {
4053 if (ptarget->SOG > 102.2) {
4054 ptarget->bCPA_Valid =
false;
4056 }
else if (ptarget->SOG < .01) {
4057 cpa_calc_target_cog = 0.;
4060 ptarget->bCPA_Valid =
false;
4066 double v0 =
gSog * 1852.;
4067 double v1 = ptarget->SOG * 1852.;
4069 if ((v0 < 1e-6) && (v1 < 1e-6)) {
4073 ptarget->bCPA_Valid =
false;
4080 double east1 = (ptarget->Lon -
gLon) * 60 * 1852;
4081 double north1 = (ptarget->Lat -
gLat) * 60 * 1852;
4083 double east = east1 * (cos(
gLat * PI / 180.));
4085 double north = north1;
4088 double cosa = cos((90. - cpa_calc_ownship_cog) * PI / 180.);
4089 double sina = sin((90. - cpa_calc_ownship_cog) * PI / 180.);
4090 double cosb = cos((90. - cpa_calc_target_cog) * PI / 180.);
4091 double sinb = sin((90. - cpa_calc_target_cog) * PI / 180.);
4094 double fc = (v0 * cosa) - (v1 * cosb);
4095 double fs = (v0 * sina) - (v1 * sinb);
4097 double d = (fc * fc) + (fs * fs);
4105 tcpa = ((fc * east) + (fs * north)) / d;
4108 ptarget->TCPA = tcpa * 60.;
4113 double OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA, TargetLonCPA;
4115 ll_gc_ll(
gLat,
gLon, cpa_calc_ownship_cog,
gSog * tcpa, &OwnshipLatCPA,
4117 ll_gc_ll(ptarget->Lat, ptarget->Lon, cpa_calc_target_cog,
4118 ptarget->SOG * tcpa, &TargetLatCPA, &TargetLonCPA);
4121 ptarget->CPA = DistGreatCircle(OwnshipLatCPA, OwnshipLonCPA, TargetLatCPA,
4124 ptarget->bCPA_Valid =
true;
4126 if (ptarget->TCPA < 0) ptarget->bCPA_Valid =
false;
4130void AisDecoder::OnTimerDSC(wxTimerEvent &event) {
4133 if (m_ptentative_dsctarget) {
4134 ProcessDSx(m_dsc_last_string,
true);
4138void AisDecoder::OnTimerAIS(wxTimerEvent &event) {
4143 wxDateTime now = wxDateTime::Now();
4146 std::unordered_map<int, std::shared_ptr<AisTargetData>> ¤t_targets =
4149 auto it = current_targets.begin();
4150 std::vector<int> remove_array;
4152 while (it != current_targets.end()) {
4156 current_targets.erase(it);
4161 std::shared_ptr<AisTargetData> xtd = it->second;
4163 int target_posn_age = now.GetTicks() - xtd->PositionReportTicks;
4164 int target_static_age = now.GetTicks() - xtd->StaticReportTicks;
4176 double removelost_Mins = fmax(g_RemoveLost_Mins, g_MarkLost_Mins);
4178 if (g_bInlandEcdis && (xtd->Class != AIS_ARPA)) {
4179 double iECD_LostTimeOut = 0.0;
4183 if (xtd->Class == AIS_CLASS_B) {
4184 if ((xtd->NavStatus == MOORED) || (xtd->NavStatus == AT_ANCHOR))
4185 iECD_LostTimeOut = 18 * 60;
4187 iECD_LostTimeOut = 180;
4189 if (xtd->Class == AIS_CLASS_A) {
4190 if ((xtd->NavStatus == MOORED) || (xtd->NavStatus == AT_ANCHOR)) {
4192 iECD_LostTimeOut = 18 * 60;
4194 iECD_LostTimeOut = 60;
4196 iECD_LostTimeOut = 60;
4199 if ((target_posn_age > iECD_LostTimeOut) &&
4200 (xtd->Class != AIS_GPSG_BUDDY))
4201 xtd->b_active =
false;
4203 removelost_Mins = (2 * iECD_LostTimeOut) / 60.;
4204 }
else if (g_bMarkLost) {
4205 if ((target_posn_age > g_MarkLost_Mins * 60) &&
4206 (xtd->Class != AIS_GPSG_BUDDY))
4207 xtd->b_active =
false;
4210 if (xtd->Class == AIS_SART || xtd->Class == AIS_METEO)
4211 removelost_Mins = 18.0;
4215 if (g_bRemoveLost || g_bInlandEcdis) {
4217 (xtd->Class == AIS_ARPA &&
4219 if (((target_posn_age > removelost_Mins * 60) &&
4220 (xtd->Class != AIS_GPSG_BUDDY)) ||
4225 xtd->b_positionOnceValid =
false;
4233 long mmsi_long = xtd->MMSI;
4234 pSelectAIS->DeleteSelectablePoint((
void *)mmsi_long, SELTYPE_AISTARGET);
4239 if (target_static_age > removelost_Mins * 60 * 3 || b_arpalost) {
4240 xtd->b_removed =
true;
4242 remove_array.push_back(xtd->MMSI);
4251 if (xtd->MMSI == props->MMSI) {
4252 if (props->m_bignore) {
4253 remove_array.push_back(xtd->MMSI);
4254 xtd->b_removed =
true;
4263 remove_array.push_back(xtd->MMSI);
4264 xtd->b_removed =
true;
4272 for (
unsigned int i = 0; i < remove_array.size(); i++) {
4273 auto itd = current_targets.find(remove_array[i]);
4274 if (itd != current_targets.end()) {
4275 std::shared_ptr<AisTargetData> td = itd->second;
4276 current_targets.erase(itd);
4285 m_bSuppressed =
false;
4286 if (g_bAIS_CPA_Alert_Suppress_Moored || g_bHideMoored ||
4287 (g_bShowScaled && g_bAllowShowScaled))
4288 m_bSuppressed =
true;
4290 m_bAIS_Audio_Alert_On =
false;
4299 std::shared_ptr<AisTargetData> palert_target =
nullptr;
4301 if (!g_pais_alert_dialog_active) {
4303 double tcpa_min = 1e6;
4304 double sart_range = 1e6;
4305 std::shared_ptr<AisTargetData> palert_target_cpa =
nullptr;
4306 std::shared_ptr<AisTargetData> palert_target_sart =
nullptr;
4307 std::shared_ptr<AisTargetData> palert_target_dsc =
nullptr;
4309 for (it = current_targets.begin(); it != current_targets.end(); ++it) {
4310 std::shared_ptr<AisTargetData> td = it->second;
4312 if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
4313 if (g_bAIS_CPA_Alert && td->b_active) {
4314 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4315 if (td->TCPA < tcpa_min) {
4316 tcpa_min = td->TCPA;
4317 palert_target_cpa = td;
4321 }
else if ((td->Class == AIS_DSC) &&
4322 ((td->ShipType == 12) || (td->ShipType == 16))) {
4324 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4325 palert_target_dsc = td;
4328 td->b_isDSCtarget =
false;
4333 else if (td->Class == AIS_SART) {
4335 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
4336 if (td->Range_NM < sart_range) {
4337 tcpa_min = sart_range;
4338 palert_target_sart = td;
4348 palert_target = palert_target_cpa;
4350 if (palert_target_sart) {
4351 palert_target = palert_target_sart;
4354 if (palert_target_dsc) {
4355 palert_target = palert_target_dsc;
4359 palert_target = Get_Target_Data_From_MMSI(m_callbacks.get_target_mmsi());
4364 TimerAIS.Start(TIMER_AIS_MSEC, wxTIMER_CONTINUOUS);
4367std::shared_ptr<AisTargetData> AisDecoder::Get_Target_Data_From_MMSI(
4369 if (AISTargetList.find(mmsi) == AISTargetList.end())
return nullptr;
4370 return AISTargetList[mmsi];
4377MmsiProperties::MmsiProperties(wxString &spec) {
4379 wxStringTokenizer tkz(spec,
";");
4382 s = tkz.GetNextToken();
4387 s = tkz.GetNextToken();
4389 if (s.Upper() ==
"ALWAYS")
4390 TrackType = TRACKTYPE_ALWAYS;
4391 else if (s.Upper() ==
"NEVER")
4392 TrackType = TRACKTYPE_NEVER;
4395 s = tkz.GetNextToken();
4397 if (s.Upper() ==
"IGNORE") m_bignore =
true;
4400 s = tkz.GetNextToken();
4402 if (s.Upper() ==
"MOB") m_bMOB =
true;
4405 s = tkz.GetNextToken();
4407 if (s.Upper() ==
"VDM") m_bVDM =
true;
4410 s = tkz.GetNextToken();
4412 if (s.Upper() ==
"FOLLOWER") m_bFollower =
true;
4415 s = tkz.GetNextToken();
4417 if (s.Upper() ==
"PERSIST") m_bPersistentTrack =
true;
4420 s = tkz.GetNextToken();
4422 m_ShipName = s.Upper();
4426MmsiProperties::~MmsiProperties() =
default;
4428void MmsiProperties::Init() {
4430 TrackType = TRACKTYPE_DEFAULT;
4434 m_bFollower =
false;
4435 m_bPersistentTrack =
false;
4439wxString MmsiProperties::Serialize() {
4443 sMMSI.Printf(
"%d", MMSI);
4447 if (TRACKTYPE_ALWAYS == TrackType)
4449 else if (TRACKTYPE_NEVER == TrackType)
4470 sMMSI <<
"Follower";
4474 if (m_bPersistentTrack) {
4479 if (m_ShipName ==
"") {
4480 m_ShipName = GetShipNameFromFile(MMSI);
4482 sMMSI << m_ShipName;
4487 AIS_Target_Name_Hash *AISTargetNamesC,
4488 AIS_Target_Name_Hash *AISTargetNamesNC,
long mmsi) {
4489 if (g_benableAISNameCache) {
4490 wxString ship_name =
"";
4493 if (!pTargetData->b_nameValid) {
4494 AIS_Target_Name_Hash::iterator it = AISTargetNamesC->find(mmsi);
4495 if (it != AISTargetNamesC->end()) {
4496 ship_name = (*AISTargetNamesC)[mmsi].Left(20);
4497 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4498 ship_name.length() + 1);
4499 pTargetData->b_nameValid =
true;
4500 pTargetData->b_nameFromCache =
true;
4501 }
else if (!g_bUseOnlyConfirmedAISName) {
4502 it = AISTargetNamesNC->find(mmsi);
4503 if (it != AISTargetNamesNC->end()) {
4504 ship_name = (*AISTargetNamesNC)[mmsi].Left(20);
4505 strncpy(pTargetData->ShipName, ship_name.mb_str(),
4506 ship_name.length() + 1);
4507 pTargetData->b_nameValid =
true;
4508 pTargetData->b_nameFromCache =
true;
4513 else if ((pTargetData->MID == 5) || (pTargetData->MID == 24) ||
4514 (pTargetData->MID == 19) ||
4515 (pTargetData->MID == 123) ||
4516 (pTargetData->MID == 124)) {
4518 pTargetData->b_nameFromCache =
false;
4519 ship_name = trimAISField(pTargetData->ShipName);
4520 AIS_Target_Name_Hash::iterator itC = AISTargetNamesC->find(mmsi);
4521 AIS_Target_Name_Hash::iterator itNC = AISTargetNamesNC->find(mmsi);
4523 AISTargetNamesC->end()) {
4524 if ((*AISTargetNamesC)[mmsi] ==
4526 if (itNC != AISTargetNamesNC->end()) {
4528 AISTargetNamesNC->erase(itNC);
4531 if (itNC != AISTargetNamesNC->end()) {
4533 if ((*AISTargetNamesNC)[mmsi] ==
4536 (*AISTargetNamesC)[mmsi] = ship_name;
4538 AISTargetNamesNC->erase(itNC);
4541 (*AISTargetNamesNC)[mmsi] = ship_name;
4543 if (g_bUseOnlyConfirmedAISName)
4544 strncpy(pTargetData->ShipName, (*AISTargetNamesC)[mmsi].mb_str(),
4545 (*AISTargetNamesC)[mmsi].Left(20).Length() + 1);
4547 (*AISTargetNamesNC)[mmsi] = ship_name;
4552 AISTargetNamesNC->end()) {
4553 if ((*AISTargetNamesNC)[mmsi] ==
4556 (*AISTargetNamesC)[mmsi] = ship_name;
4558 AISTargetNamesNC->erase(itNC);
4560 (*AISTargetNamesNC)[mmsi] = ship_name;
4563 (*AISTargetNamesNC)[mmsi] = ship_name;
4565 if (g_bUseOnlyConfirmedAISName) {
4566 pTargetData->ShipName[SHIP_NAME_LEN - 1] =
'\0';
4567 strncpy(pTargetData->ShipName,
"Unknown ",
4575wxString GetShipNameFromFile(
int nmmsi) {
4577 if (g_benableAISNameCache) {
4581 while (getline(infile, line)) {
4582 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()),
",");
4583 if (nmmsi == wxAtoi(tokenizer.GetNextToken())) {
4584 name = tokenizer.GetNextToken().Trim();
4587 tokenizer.GetNextToken();
4595void AisDecoder::UpdateMMSItoNameFile(
const wxString &mmsi,
4596 const wxString &name) {
4600 std::map<wxString, wxString> mmsi_name_map;
4606 while (getline(infile, line)) {
4607 wxStringTokenizer tokenizer(wxString::FromUTF8(line.c_str()),
",");
4608 wxString file_mmsi = tokenizer.GetNextToken();
4609 wxString file_name = tokenizer.GetNextToken().Trim();
4610 mmsi_name_map[file_mmsi] = file_name;
4616 mmsi_name_map[mmsi] = name.Upper();
4621 for (
const auto &pair : mmsi_name_map) {
4622 std::string line = std::string(pair.first.mb_str()) +
"," +
4623 std::string(pair.second.mb_str()) +
"\n";
4630wxString AisDecoder::GetMMSItoNameEntry(
const wxString &mmsi) {
4631 return GetShipNameFromFile(wxAtoi(mmsi));
4635int AisMeteoNewMmsi(
int orig_mmsi,
int m_lat,
int m_lon,
int lon_bits = 0,
4639 if ((!lon_bits || lon_bits == 999) && siteID) {
4642 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4643 if (points.size()) {
4644 for (
const auto &point : points) {
4646 if (siteID == point.siteID && orig_mmsi == point.orig_mmsi) {
4648 new_mmsi = point.mmsi;
4654 if (!found && !lon_bits) {
4659 double lon_tentative = 181.;
4660 double lat_tentative = 91.;
4662 if (lon_bits == 25) {
4663 if (m_lon & 0x01000000)
4664 m_lon |= 0xFE000000;
4665 lon_tentative = m_lon / 60000.;
4667 if (m_lat & 0x00800000)
4668 m_lat |= 0xFF000000;
4669 lat_tentative = m_lat / 60000.;
4671 }
else if (lon_bits == 28) {
4672 if (m_lon & 0x08000000)
4673 m_lon |= 0xf0000000;
4674 lon_tentative = m_lon / 600000.;
4676 if (m_lat & 0x04000000)
4677 m_lat |= 0xf8000000;
4678 lat_tentative = m_lat / 600000.;
4683 wxString slon = wxString::Format(
"%0.3f", lon_tentative);
4684 wxString slat = wxString::Format(
"%0.3f", lat_tentative);
4692 static int nextMeteommsi = 199400000;
4693 auto &points = AisMeteoPoints::GetInstance().GetPoints();
4695 if (lon_bits != 999 && !points.empty()) {
4696 wxString t_lat, t_lon;
4697 for (
const auto &point : points) {
4699 if (slat.IsSameAs(point.lat) && slon.IsSameAs(point.lon)) {
4701 new_mmsi = point.mmsi;
4710 points.emplace_back(
4711 AisMeteoPoint(nextMeteommsi, slat, slon, siteID, orig_mmsi));
4712 new_mmsi = nextMeteommsi;
Select * pSelectAIS
Global instance.
wxString AISTargetNameFileName
Global instance.
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
unsigned g_OwnShipmmsi
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Select * pSelectAIS
Global instance.
wxString AISTargetNameFileName
Global instance.
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
unsigned g_OwnShipmmsi
Global instance.
Global state for AIS decoder.
int GetInt(int sp, int len, bool signed_flag=false)
sp is starting bit, 1-based
EventVar plugin_msg
A JSON message should be sent.
EventVar new_track
Notified on new track creation.
EventVar info_update
Notified when AIS user dialogs should update.
EventVar touch_state
Notified when gFrame->TouchAISActive() should be invoked.
Add a new point to the list of Meteo stations.
void Notify() override
Notify all listeners, no data supplied.
Process incoming AIS messages.
void LogInputMessage(const std::shared_ptr< const NavMsg > &msg, bool is_filtered, bool is_error, const wxString &error_msg="") const
Logs an input message with context information.
void Notify(std::shared_ptr< const NavMsg > message)
Accept message received by driver, make it available for upper layers.
A regular Nmea0183 message.
See: https://github.com/OpenCPN/OpenCPN/issues/2729#issuecomment-1179506343.
void Listen(const std::string &key, wxEvtHandler *listener, wxEventType evt)
Set object to send wxEventType ev to listener on changes in key.
Custom event class for OpenCPN's notification system.
std::shared_ptr< const void > GetSharedPtr() const
Gets the event's payload data.
Represents a waypoint or mark within the navigation system.
A parsed SignalK message over ipv4.
Represents a single point in a track.
Represents a track, which is a series of connected track points.
The JSON document writer.
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Raw messages layer, supports sending and recieving navmsg messages.
int g_iSpeedFormat
User-selected speed unit format for display and input.
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
Global variables stored in configuration file.
GUI constant definitions.
Meteo points are Meteorological and Hydrographic data received by NMEA0183 (AIS) VDM message 8 dac:00...
Multiplexer class and helpers.
const wxChar * ParseGPXDateTime(wxDateTime &dt, const wxChar *datetime)
This function parses a string containing a GPX time representation and returns a wxDateTime containin...
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
Navigation Utility Functions without GUI dependencies.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
double gHdt
True heading in degrees (0-359.99).
double gLat
Vessel's current latitude in decimal degrees.
double gCog
Course over ground in degrees (0-359.99).
double gSog
Speed over ground in knots.
double gLon
Vessel's current longitude in decimal degrees.
Position, course, speed, etc.
Waypoint or mark abstraction.
Route * pAISMOBRoute
Global instance.
Select * pSelect
Global instance.
Selected route, segment, waypoint, etc.
A generic position and navigation data structure.
double kCog
Course over ground in degrees.
double kHdt
True heading in degrees.
double kHdm
Magnetic heading in degrees.
double kLat
Latitude in decimal degrees.
double kSog
Speed over ground in knots.
double kVar
Magnetic variation in degrees.
double kLon
Longitude in decimal degrees.
std::vector< Track * > g_TrackList
Global instance.
Recorded track abstraction.