OpenCPN Partial API docs
Loading...
Searching...
No Matches
navutil_base.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Navigation Utility Functions without GUI deps
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2010 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26/**************************************************************************/
27/* Formats the coordinates to string */
28/**************************************************************************/
29
30#include <iomanip>
31#include <sstream>
32
33#include <wx/datetime.h>
34#include <wx/math.h>
35#include <wx/string.h>
36#include <wx/translation.h>
37
38#include "model/navutil_base.h"
39#include "model/own_ship.h"
40#include "vector2D.h"
41
43static wxTimeSpan RoundToMinutes(const wxTimeSpan &span) {
44 auto minutes = span.GetMinutes() % 60;
45 auto seconds = span.GetSeconds() % 60;
46 if (seconds > 30) minutes += 1;
47 return wxTimeSpan(span.GetHours(), minutes, 0);
48}
49
50wxString toSDMM(int NEflag, double a, bool hi_precision) {
51 wxString s;
52 double mpy;
53 short neg = 0;
54 int d;
55 long m;
56 double ang = a;
57 char c = 'N';
58
59 if (a < 0.0) {
60 a = -a;
61 neg = 1;
62 }
63 d = (int)a;
64 if (neg) d = -d;
65 if (NEflag) {
66 if (NEflag == 1) {
67 c = 'N';
68
69 if (neg) {
70 d = -d;
71 c = 'S';
72 }
73 } else if (NEflag == 2) {
74 c = 'E';
75
76 if (neg) {
77 d = -d;
78 c = 'W';
79 }
80 }
81 }
82
83 switch (g_iSDMMFormat) {
84 case 0:
85 mpy = 600.0;
86 if (hi_precision) mpy = mpy * 1000;
87
88 m = (long)wxRound((a - (double)d) * mpy);
89
90 if (!NEflag || NEflag < 1 || NEflag > 2) // Does it EVER happen?
91 {
92 if (hi_precision)
93 s.Printf(_T ( "%d%c %02ld.%04ld'" ), d, 0x00B0, m / 10000, m % 10000);
94 else
95 s.Printf(_T ( "%d%c %02ld.%01ld'" ), d, 0x00B0, m / 10, m % 10);
96 } else {
97 if (hi_precision)
98 if (NEflag == 1)
99 s.Printf(_T ( "%02d%c %02ld.%04ld' %c" ), d, 0x00B0, m / 10000,
100 (m % 10000), c);
101 else
102 s.Printf(_T ( "%03d%c %02ld.%04ld' %c" ), d, 0x00B0, m / 10000,
103 (m % 10000), c);
104 else if (NEflag == 1)
105 s.Printf(_T ( "%02d%c %02ld.%01ld' %c" ), d, 0x00B0, m / 10, (m % 10),
106 c);
107 else
108 s.Printf(_T ( "%03d%c %02ld.%01ld' %c" ), d, 0x00B0, m / 10, (m % 10),
109 c);
110 }
111 break;
112 case 1:
113 if (hi_precision)
114 s.Printf(_T ( "%03.6f" ),
115 ang); // cca 11 cm - the GPX precision is higher, but as we
116 // use hi_precision almost everywhere it would be a
117 // little too much....
118 else
119 s.Printf(_T ( "%03.4f" ), ang); // cca 11m
120 break;
121 case 2:
122 m = (long)((a - (double)d) * 60);
123 mpy = 10.0;
124 if (hi_precision) mpy = mpy * 100;
125 long sec = (long)((a - (double)d - (((double)m) / 60)) * 3600 * mpy);
126
127 if (!NEflag || NEflag < 1 || NEflag > 2) // Does it EVER happen?
128 {
129 if (hi_precision)
130 s.Printf(_T ( "%d%c %ld'%ld.%ld\"" ), d, 0x00B0, m, sec / 1000,
131 sec % 1000);
132 else
133 s.Printf(_T ( "%d%c %ld'%ld.%ld\"" ), d, 0x00B0, m, sec / 10,
134 sec % 10);
135 } else {
136 if (hi_precision)
137 if (NEflag == 1)
138 s.Printf(_T ( "%02d%c %02ld' %02ld.%03ld\" %c" ), d, 0x00B0, m,
139 sec / 1000, sec % 1000, c);
140 else
141 s.Printf(_T ( "%03d%c %02ld' %02ld.%03ld\" %c" ), d, 0x00B0, m,
142 sec / 1000, sec % 1000, c);
143 else if (NEflag == 1)
144 s.Printf(_T ( "%02d%c %02ld' %02ld.%ld\" %c" ), d, 0x00B0, m,
145 sec / 10, sec % 10, c);
146 else
147 s.Printf(_T ( "%03d%c %02ld' %02ld.%ld\" %c" ), d, 0x00B0, m,
148 sec / 10, sec % 10, c);
149 }
150 break;
151 }
152 return s;
153}
154
155/**************************************************************************/
156/* Converts the speed to the units selected by user */
157/**************************************************************************/
158double toUsrSpeed(double kts_speed, int unit) {
159 double ret = NAN;
160 if (unit == -1) unit = g_iSpeedFormat;
161 switch (unit) {
162 case SPEED_KTS: // kts
163 ret = kts_speed;
164 break;
165 case SPEED_MPH: // mph
166 ret = kts_speed * 1.15078;
167 break;
168 case SPEED_KMH: // km/h
169 ret = kts_speed * 1.852;
170 break;
171 case SPEED_MS: // m/s
172 ret = kts_speed * 0.514444444;
173 break;
174 }
175 return ret;
176}
177
178/**************************************************************************/
179/* Converts the wind speed to the units selected by user */
180/**************************************************************************/
181double toUsrWindSpeed(double kts_wspeed, int unit) {
182 double ret = NAN;
183 if (unit == -1) unit = g_iWindSpeedFormat;
184 switch (unit) {
185 case WSPEED_KTS: // kts
186 ret = kts_wspeed;
187 break;
188 case WSPEED_MS: // m/s
189 ret = kts_wspeed * 0.514444444;
190 break;
191 case WSPEED_MPH: // mph
192 ret = kts_wspeed * 1.15078;
193 break;
194 case WSPEED_KMH: // km/h
195 ret = kts_wspeed * 1.852;
196 break;
197 }
198 return ret;
199}
200
201/**************************************************************************/
202/* Converts the distance to the units selected by user */
203/**************************************************************************/
204double toUsrDistance(double nm_distance, int unit) {
205 double ret = NAN;
206 if (unit == -1) unit = g_iDistanceFormat;
207 switch (unit) {
208 case DISTANCE_NMI: // Nautical miles
209 ret = nm_distance;
210 break;
211 case DISTANCE_MI: // Statute miles
212 ret = nm_distance * 1.15078;
213 break;
214 case DISTANCE_KM:
215 ret = nm_distance * 1.852;
216 break;
217 case DISTANCE_M:
218 ret = nm_distance * 1852;
219 break;
220 case DISTANCE_FT:
221 ret = nm_distance * 6076.12;
222 break;
223 case DISTANCE_FA:
224 ret = nm_distance * 1012.68591;
225 break;
226 case DISTANCE_IN:
227 ret = nm_distance * 72913.4;
228 break;
229 case DISTANCE_CM:
230 ret = nm_distance * 185200;
231 break;
232 }
233 return ret;
234}
235
236/**************************************************************************/
237/* Converts the temperature to the units selected by user */
238/**************************************************************************/
239double toUsrTemp(double cel_temp, int unit) {
240 double ret = NAN;
241 if (unit == -1) unit = g_iTempFormat;
242 switch (unit) {
243 case TEMPERATURE_C: // Celsius
244 ret = cel_temp;
245 break;
246 case TEMPERATURE_F: // Fahrenheit
247 ret = (cel_temp * 9.0 / 5.0) + 32;
248 break;
249 case TEMPERATURE_K:
250 ret = cel_temp + 273.15;
251 break;
252 }
253 return ret;
254}
255
256/**************************************************************************/
257/* Returns the abbreviation of user selected temperature unit */
258/**************************************************************************/
259wxString getUsrTempUnit(int unit) {
260 wxString ret;
261 if (unit == -1) unit = g_iTempFormat;
262 switch (unit) {
263 case TEMPERATURE_C: // Celsius
264 ret = _("C");
265 break;
266 case TEMPERATURE_F: // Fahrenheit
267 ret = _("F");
268 break;
269 case TEMPERATURE_K: // Kelvin
270 ret = _("K");
271 break;
272 }
273 return ret;
274}
275
276/**************************************************************************/
277/* Returns the abbreviation of user selected distance unit */
278/**************************************************************************/
279wxString getUsrDistanceUnit(int unit) {
280 wxString ret;
281 if (unit == -1) unit = g_iDistanceFormat;
282 switch (unit) {
283 case DISTANCE_NMI: // Nautical miles
284 ret = _("NMi");
285 break;
286 case DISTANCE_MI: // Statute miles
287 ret = _("mi");
288 break;
289 case DISTANCE_KM:
290 ret = _("km");
291 break;
292 case DISTANCE_M:
293 ret = _("m");
294 break;
295 case DISTANCE_FT:
296 ret = _("ft");
297 break;
298 case DISTANCE_FA:
299 ret = _("fa");
300 break;
301 case DISTANCE_IN:
302 ret = _("in");
303 break;
304 case DISTANCE_CM:
305 ret = _("cm");
306 break;
307 }
308 return ret;
309}
310
311/**************************************************************************/
312/* Returns the abbreviation of user selected speed unit */
313/**************************************************************************/
314wxString getUsrSpeedUnit(int unit) {
315 wxString ret;
316 if (unit == -1) unit = g_iSpeedFormat;
317 switch (unit) {
318 case SPEED_KTS: // kts
319 ret = _("kts");
320 break;
321 case SPEED_MPH: // mph
322 ret = _("mph");
323 break;
324 case SPEED_KMH:
325 ret = _("km/h");
326 break;
327 case SPEED_MS:
328 ret = _("m/s");
329 break;
330 }
331 return ret;
332}
333
334/**************************************************************************/
335/* Returns the abbreviation of user selected wind speed unit */
336/**************************************************************************/
337wxString getUsrWindSpeedUnit(int unit) {
338 wxString ret;
339 if (unit == -1) unit = g_iWindSpeedFormat;
340 switch (unit) {
341 case WSPEED_KTS: // kts
342 ret = _("kts");
343 break;
344 case WSPEED_MS:
345 ret = _("m/s");
346 break;
347 case WSPEED_MPH: // mph
348 ret = _("mph");
349 break;
350 case WSPEED_KMH:
351 ret = _("km/h");
352 break;
353 }
354 return ret;
355}
356
357wxString FormatDistanceAdaptive(double distance) {
358 wxString result;
359 int unit = g_iDistanceFormat;
360 double usrDistance = toUsrDistance(distance, unit);
361 if (usrDistance < 0.1 &&
362 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI)) {
363 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
364 usrDistance = toUsrDistance(distance, unit);
365 }
366 wxString format;
367 if (usrDistance < 5.0) {
368 format = _T("%1.2f ");
369 } else if (usrDistance < 100.0) {
370 format = _T("%2.1f ");
371 } else if (usrDistance < 1000.0) {
372 format = _T("%3.0f ");
373 } else {
374 format = _T("%4.0f ");
375 }
376 result << wxString::Format(format, usrDistance) << getUsrDistanceUnit(unit);
377 return result;
378}
379
380/**************************************************************************/
381/* Converts the speed from the units selected by user to knots */
382/**************************************************************************/
383double fromUsrSpeed(double usr_speed, int unit, int default_val) {
384 double ret = NAN;
385
386 if (unit == -1) unit = default_val;
387 switch (unit) {
388 case SPEED_KTS: // kts
389 ret = usr_speed;
390 break;
391 case SPEED_MPH: // mph
392 ret = usr_speed / 1.15078;
393 break;
394 case SPEED_KMH: // km/h
395 ret = usr_speed / 1.852;
396 break;
397 case SPEED_MS: // m/s
398 ret = usr_speed / 0.514444444;
399 break;
400 }
401 return ret;
402}
403
404/**************************************************************************/
405/* Converts the distance from the units selected by user to NMi */
406/**************************************************************************/
407double fromUsrDistance(double usr_distance, int unit, int default_val) {
408 double ret = NAN;
409 if (unit == -1) unit = default_val;
410 switch (unit) {
411 case DISTANCE_NMI: // Nautical miles
412 ret = usr_distance;
413 break;
414 case DISTANCE_MI: // Statute miles
415 ret = usr_distance / 1.15078;
416 break;
417 case DISTANCE_KM:
418 ret = usr_distance / 1.852;
419 break;
420 case DISTANCE_M:
421 ret = usr_distance / 1852;
422 break;
423 case DISTANCE_FT:
424 ret = usr_distance / 6076.12;
425 break;
426 }
427 return ret;
428}
429
430/**************************************************************************/
431/* Converts the depth in meters to the units selected by user */
432/**************************************************************************/
433double toUsrDepth(double cel_depth, int unit) {
434 double ret = NAN;
435 if (unit == -1) unit = g_nDepthUnitDisplay;
436 switch (unit) {
437 case DEPTH_FT: // Feet
438 ret = cel_depth / 0.3048;
439 break;
440 case DEPTH_M: // Meters
441 ret = cel_depth;
442 break;
443 case DEPTH_FA:
444 ret = cel_depth / 0.3048 / 6;
445 break;
446 }
447 return ret;
448}
449
450/**************************************************************************/
451/* Converts the depth from the units selected by user to Meters */
452/**************************************************************************/
453double fromUsrDepth(double usr_depth, int unit) {
454 double ret = NAN;
455 if (unit == -1) unit = g_nDepthUnitDisplay;
456 switch (unit) {
457 case DEPTH_FT: // Feet
458 ret = usr_depth * 0.3048;
459 break;
460 case DEPTH_M: // Feet
461 ret = usr_depth;
462 break;
463 case DEPTH_FA: // Fathoms
464 ret = usr_depth * 0.3048 * 6;
465 break;
466 }
467 return ret;
468}
469
470/**************************************************************************/
471/* Returns the abbreviation of user selected depth unit */
472/**************************************************************************/
473wxString getUsrDepthUnit(int unit) {
474 wxString ret;
475 if (unit == -1) unit = g_nDepthUnitDisplay;
476 switch (unit) {
477 case DEPTH_FT: // Feet
478 ret = _("ft");
479 break;
480 case DEPTH_M: // Meters
481 ret = _("m");
482 break;
483 case DEPTH_FA: // Fathoms
484 ret = _("fa");
485 break;
486 }
487 return ret;
488}
489
490//---------------------------------------------------------------------------------
491// Vector Stuff for Hit Test Algorithm
492//---------------------------------------------------------------------------------
493double vGetLengthOfNormal(pVector2D a, pVector2D b, pVector2D n) {
494 vector2D c, vNormal;
495 vNormal.x = 0;
496 vNormal.y = 0;
497 //
498 // Obtain projection vector.
499 //
500 // c = ((a * b)/(|b|^2))*b
501 //
502 c.x = b->x * (vDotProduct(a, b) / vDotProduct(b, b));
503 c.y = b->y * (vDotProduct(a, b) / vDotProduct(b, b));
504 //
505 // Obtain perpendicular projection : e = a - c
506 //
507 vSubtractVectors(a, &c, &vNormal);
508 //
509 // Fill PROJECTION structure with appropriate values.
510 //
511 *n = vNormal;
512
513 return (vVectorMagnitude(&vNormal));
514}
515
516double vDotProduct(pVector2D v0, pVector2D v1) {
517 double dotprod;
518
519 dotprod =
520 (v0 == NULL || v1 == NULL) ? 0.0 : (v0->x * v1->x) + (v0->y * v1->y);
521
522 return (dotprod);
523}
524
525pVector2D vAddVectors(pVector2D v0, pVector2D v1, pVector2D v) {
526 if (v0 == NULL || v1 == NULL)
527 v = (pVector2D)NULL;
528 else {
529 v->x = v0->x + v1->x;
530 v->y = v0->y + v1->y;
531 }
532 return (v);
533}
534
535pVector2D vSubtractVectors(pVector2D v0, pVector2D v1, pVector2D v) {
536 if (v0 == NULL || v1 == NULL)
537 v = (pVector2D)NULL;
538 else {
539 v->x = v0->x - v1->x;
540 v->y = v0->y - v1->y;
541 }
542 return (v);
543}
544
545double vVectorSquared(pVector2D v0) {
546 double dS;
547
548 if (v0 == NULL)
549 dS = 0.0;
550 else
551 dS = ((v0->x * v0->x) + (v0->y * v0->y));
552 return (dS);
553}
554
555double vVectorMagnitude(pVector2D v0) {
556 double dMagnitude;
557
558 if (v0 == NULL)
559 dMagnitude = 0.0;
560 else
561 dMagnitude = sqrt(vVectorSquared(v0));
562 return (dMagnitude);
563}
564
565const wxChar *ParseGPXDateTime(wxDateTime &dt, const wxChar *datetime) {
566 long sign, hrs_west, mins_west;
567 const wxChar *end;
568
569 // Skip any leading whitespace
570 while (isspace(*datetime)) datetime++;
571
572 // Skip (and ignore) leading hyphen
573 if (*datetime == wxT('-')) datetime++;
574
575 // Parse and validate ISO 8601 date/time string
576 if ((end = dt.ParseFormat(datetime, wxT("%Y-%m-%dT%T"))) != NULL) {
577 // Invalid date/time
578 if (*end == 0) return NULL;
579
580 // ParseFormat outputs in UTC if the controlling
581 // wxDateTime class instance has not been initialized.
582
583 // Date/time followed by UTC time zone flag, so we are done
584 else if (*end == wxT('Z')) {
585 end++;
586 return end;
587 }
588
589 // Date/time followed by given number of hrs/mins west of UTC
590 else if (*end == wxT('+') || *end == wxT('-')) {
591 // Save direction from UTC
592 if (*end == wxT('+'))
593 sign = 1;
594 else
595 sign = -1;
596 end++;
597
598 // Parse hrs west of UTC
599 if (isdigit(*end) && isdigit(*(end + 1)) && *(end + 2) == wxT(':')) {
600 // Extract and validate hrs west of UTC
601 wxString(end).ToLong(&hrs_west);
602 if (hrs_west > 12) return NULL;
603 end += 3;
604
605 // Parse mins west of UTC
606 if (isdigit(*end) && isdigit(*(end + 1))) {
607 // Extract and validate mins west of UTC
608 wxChar mins[3];
609 mins[0] = *end;
610 mins[1] = *(end + 1);
611 mins[2] = 0;
612 wxString(mins).ToLong(&mins_west);
613 if (mins_west > 59) return NULL;
614
615 // Apply correction
616 dt -= sign * wxTimeSpan(hrs_west, mins_west, 0, 0);
617 return end + 2;
618 } else
619 // Missing mins digits
620 return NULL;
621 } else
622 // Missing hrs digits or colon
623 return NULL;
624 } else
625 // Unknown field after date/time (not UTC, not hrs/mins
626 // west of UTC)
627 return NULL;
628 } else
629 // Invalid ISO 8601 date/time
630 return NULL;
631}
632
633wxString formatTimeDelta(wxTimeSpan span) {
634 using namespace std;
635 // wxTimeSpan is returns complete span in different units.
636 // FIXME: (leamas) Replace with sane std::chrono.
637 stringstream ss;
638 ss << setfill(' ');
639 if (span.GetHours() > 0) span = RoundToMinutes(span);
640 if (span.GetDays() > 0) ss << setw(2) << span.GetDays() << "d ";
641 if (span.GetHours() > 0) {
642 ss << setw(2) << span.GetHours() % 24 << _("H ");
643 ss << setw(2) << span.GetMinutes() % 60 << _("M");
644 } else {
645 ss << setw(2) << span.GetMinutes() % 60 << _("M ");
646 ss << setw(2) << span.GetSeconds() % 60 << _("S");
647 }
648 return ss.str();
649}
650
651wxString formatTimeDelta(wxDateTime startTime, wxDateTime endTime) {
652 wxString timeStr;
653 if (startTime.IsValid() && endTime.IsValid()) {
654 wxTimeSpan span = endTime - startTime;
655 return formatTimeDelta(span);
656 } else {
657 return _("N/A");
658 }
659}
660
661wxString formatTimeDelta(wxLongLong secs) {
662 wxString timeStr;
663
664 wxTimeSpan span(0, 0, secs);
665 return formatTimeDelta(span);
666}
667
668/****************************************************************************/
669// Modified from the code posted by Andy Ross at
670// http://www.mail-archive.com/flightgear-devel@flightgear.org/msg06702.html
671// Basically, it looks for a list of decimal numbers embedded in the
672// string and uses the first three as degree, minutes and seconds. The
673// presence of a "S" or "W character indicates that the result is in a
674// hemisphere where the final answer must be negated. Non-number
675// characters are treated as whitespace separating numbers.
676//
677// So there are lots of bogus strings you can feed it to get a bogus
678// answer, but that's not surprising. It does, however, correctly parse
679// all the well-formed strings I can thing of to feed it. I've tried all
680// the following:
681//
682// 37°54.204' N
683// N37 54 12
684// 37°54'12"
685// 37.9034
686// 122°18.621' W
687// 122w 18 37
688// -122.31035
689/****************************************************************************/
690double fromDMM(wxString sdms) {
691 wchar_t buf[64];
692 char narrowbuf[64];
693 int i, len, top = 0;
694 double stk[32], sign = 1;
695
696 // First round of string modifications to accomodate some known strange
697 // formats
698 wxString replhelper;
699 replhelper = wxString::FromUTF8("´·"); // UKHO PDFs
700 sdms.Replace(replhelper, _T("."));
701 replhelper =
702 wxString::FromUTF8("\"·"); // Don't know if used, but to make sure
703 sdms.Replace(replhelper, _T("."));
704 replhelper = wxString::FromUTF8("·");
705 sdms.Replace(replhelper, _T("."));
706
707 replhelper =
708 wxString::FromUTF8("s. š."); // Another example: cs.wikipedia.org
709 // (someone was too active translating...)
710 sdms.Replace(replhelper, _T("N"));
711 replhelper = wxString::FromUTF8("j. š.");
712 sdms.Replace(replhelper, _T("S"));
713 sdms.Replace(_T("v. d."), _T("E"));
714 sdms.Replace(_T("z. d."), _T("W"));
715
716 // If the string contains hemisphere specified by a letter, then '-' is for
717 // sure a separator...
718 sdms.UpperCase();
719 if (sdms.Contains(_T("N")) || sdms.Contains(_T("S")) ||
720 sdms.Contains(_T("E")) || sdms.Contains(_T("W")))
721 sdms.Replace(_T("-"), _T(" "));
722
723 wcsncpy(buf, sdms.wc_str(wxConvUTF8), 63);
724 buf[63] = 0;
725 len = wxMin(wcslen(buf), sizeof(narrowbuf) - 1);
726 ;
727
728 for (i = 0; i < len; i++) {
729 wchar_t c = buf[i];
730 if ((c >= '0' && c <= '9') || c == '-' || c == '.' || c == '+') {
731 narrowbuf[i] = c;
732 continue; /* Digit characters are cool as is */
733 }
734 if (c == ',') {
735 narrowbuf[i] = '.'; /* convert to decimal dot */
736 continue;
737 }
738 if ((c | 32) == 'w' || (c | 32) == 's')
739 sign = -1; /* These mean "negate" (note case insensitivity) */
740 narrowbuf[i] = 0; /* Replace everything else with nuls */
741 }
742
743 /* Build a stack of doubles */
744 stk[0] = stk[1] = stk[2] = 0;
745 for (i = 0; i < len; i++) {
746 while (i < len && narrowbuf[i] == 0) i++;
747 if (i != len) {
748 stk[top++] = atof(narrowbuf + i);
749 i += strlen(narrowbuf + i);
750 }
751 }
752
753 return sign * (stk[0] + (stk[1] + stk[2] / 60) / 60);
754}
755
756double toMagnetic(double deg_true) {
757 if (!std::isnan(gVar)) {
758 if ((deg_true - gVar) > 360.)
759 return (deg_true - gVar - 360.);
760 else
761 return ((deg_true - gVar) >= 0.) ? (deg_true - gVar)
762 : (deg_true - gVar + 360.);
763 } else {
764 if ((deg_true - g_UserVar) > 360.)
765 return (deg_true - g_UserVar - 360.);
766 else
767 return ((deg_true - g_UserVar) >= 0.) ? (deg_true - g_UserVar)
768 : (deg_true - g_UserVar + 360.);
769 }
770}
771
772double toMagnetic(double deg_true, double variation) {
773 double degm = deg_true - variation;
774 if (degm >= 360.)
775 return degm - 360.;
776 else
777 return degm >= 0. ? degm : degm + 360.;
778}