OpenCPN Partial API docs
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 
34 #include <wx/datetime.h>
35 #include <wx/math.h>
36 #include <wx/string.h>
37 #include <wx/translation.h>
38 #include <wx/utils.h>
39 
40 #include "model/navutil_base.h"
41 #include "model/own_ship.h"
42 #include "vector2D.h"
43 
44 
46 static wxTimeSpan RoundToMinutes(const wxTimeSpan& span) {
47  auto minutes = span.GetMinutes() % 60;
48  auto seconds = span.GetSeconds() % 60;
49  if (seconds > 30) minutes += 1;
50  return wxTimeSpan(span.GetHours(), minutes, 0);
51 }
52 
53 wxString toSDMM(int NEflag, double a, bool hi_precision) {
54  wxString s;
55  double mpy;
56  short neg = 0;
57  int d;
58  long m;
59  double ang = a;
60  char c = 'N';
61 
62  if (a < 0.0) {
63  a = -a;
64  neg = 1;
65  }
66  d = (int)a;
67  if (neg) d = -d;
68  if (NEflag) {
69  if (NEflag == 1) {
70  c = 'N';
71 
72  if (neg) {
73  d = -d;
74  c = 'S';
75  }
76  } else if (NEflag == 2) {
77  c = 'E';
78 
79  if (neg) {
80  d = -d;
81  c = 'W';
82  }
83  }
84  }
85 
86  switch (g_iSDMMFormat) {
87  case 0:
88  mpy = 600.0;
89  if (hi_precision) mpy = mpy * 1000;
90 
91  m = (long)wxRound((a - (double)d) * mpy);
92 
93  if (!NEflag || NEflag < 1 || NEflag > 2) // Does it EVER happen?
94  {
95  if (hi_precision)
96  s.Printf(_T ( "%d%c %02ld.%04ld'" ), d, 0x00B0, m / 10000, m % 10000);
97  else
98  s.Printf(_T ( "%d%c %02ld.%01ld'" ), d, 0x00B0, m / 10, m % 10);
99  } else {
100  if (hi_precision)
101  if (NEflag == 1)
102  s.Printf(_T ( "%02d%c %02ld.%04ld' %c" ), d, 0x00B0, m / 10000,
103  (m % 10000), c);
104  else
105  s.Printf(_T ( "%03d%c %02ld.%04ld' %c" ), d, 0x00B0, m / 10000,
106  (m % 10000), c);
107  else if (NEflag == 1)
108  s.Printf(_T ( "%02d%c %02ld.%01ld' %c" ), d, 0x00B0, m / 10, (m % 10), c);
109  else
110  s.Printf(_T ( "%03d%c %02ld.%01ld' %c" ), d, 0x00B0, m / 10, (m % 10), c);
111  }
112  break;
113  case 1:
114  if (hi_precision)
115  s.Printf(_T ( "%03.6f" ),
116  ang); // cca 11 cm - the GPX precision is higher, but as we
117  // use hi_precision almost everywhere it would be a
118  // little too much....
119  else
120  s.Printf(_T ( "%03.4f" ), ang); // cca 11m
121  break;
122  case 2:
123  m = (long)((a - (double)d) * 60);
124  mpy = 10.0;
125  if (hi_precision) mpy = mpy * 100;
126  long sec = (long)((a - (double)d - (((double)m) / 60)) * 3600 * mpy);
127 
128  if (!NEflag || NEflag < 1 || NEflag > 2) // Does it EVER happen?
129  {
130  if (hi_precision)
131  s.Printf(_T ( "%d%c %ld'%ld.%ld\"" ), d, 0x00B0, m, sec / 1000,
132  sec % 1000);
133  else
134  s.Printf(_T ( "%d%c %ld'%ld.%ld\"" ), d, 0x00B0, m, sec / 10, 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, sec / 10,
145  sec % 10, c);
146  else
147  s.Printf(_T ( "%03d%c %02ld' %02ld.%ld\" %c" ), d, 0x00B0, m, sec / 10,
148  sec % 10, c);
149  }
150  break;
151  }
152  return s;
153 }
154 
155 /**************************************************************************/
156 /* Converts the speed to the units selected by user */
157 /**************************************************************************/
158 double 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 /**************************************************************************/
181 double 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 /**************************************************************************/
204 double 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 /**************************************************************************/
239 double 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 /**************************************************************************/
259 wxString 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 /**************************************************************************/
279 wxString 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 /**************************************************************************/
313 /* Returns the abbreviation of user selected speed unit */
314 /**************************************************************************/
315 wxString getUsrSpeedUnit(int unit) {
316  wxString ret;
317  if (unit == -1) unit = g_iSpeedFormat;
318  switch (unit) {
319  case SPEED_KTS: // kts
320  ret = _("kts");
321  break;
322  case SPEED_MPH: // mph
323  ret = _("mph");
324  break;
325  case SPEED_KMH:
326  ret = _("km/h");
327  break;
328  case SPEED_MS:
329  ret = _("m/s");
330  break;
331  }
332  return ret;
333 }
334 
335 /**************************************************************************/
336 /* Returns the abbreviation of user selected wind speed unit */
337 /**************************************************************************/
338 wxString getUsrWindSpeedUnit(int unit) {
339  wxString ret;
340  if (unit == -1) unit = g_iWindSpeedFormat;
341  switch (unit) {
342  case WSPEED_KTS: // kts
343  ret = _("kts");
344  break;
345  case WSPEED_MS:
346  ret = _("m/s");
347  break;
348  case WSPEED_MPH: // mph
349  ret = _("mph");
350  break;
351  case WSPEED_KMH:
352  ret = _("km/h");
353  break;
354 
355  }
356  return ret;
357 }
358 
359 wxString FormatDistanceAdaptive(double distance) {
360  wxString result;
361  int unit = g_iDistanceFormat;
362  double usrDistance = toUsrDistance(distance, unit);
363  if (usrDistance < 0.1 &&
364  (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI)) {
365  unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
366  usrDistance = toUsrDistance(distance, unit);
367  }
368  wxString format;
369  if (usrDistance < 5.0) {
370  format = _T("%1.2f ");
371  } else if (usrDistance < 100.0) {
372  format = _T("%2.1f ");
373  } else if (usrDistance < 1000.0) {
374  format = _T("%3.0f ");
375  } else {
376  format = _T("%4.0f ");
377  }
378  result << wxString::Format(format, usrDistance) << getUsrDistanceUnit(unit);
379  return result;
380 }
381 
382 /**************************************************************************/
383 /* Converts the speed from the units selected by user to knots */
384 /**************************************************************************/
385 double fromUsrSpeed(double usr_speed, int unit, int default_val) {
386  double ret = NAN;
387 
388  if (unit == -1) unit = default_val;
389  switch (unit) {
390  case SPEED_KTS: // kts
391  ret = usr_speed;
392  break;
393  case SPEED_MPH: // mph
394  ret = usr_speed / 1.15078;
395  break;
396  case SPEED_KMH: // km/h
397  ret = usr_speed / 1.852;
398  break;
399  case SPEED_MS: // m/s
400  ret = usr_speed / 0.514444444;
401  break;
402  }
403  return ret;
404 }
405 
406 /**************************************************************************/
407 /* Converts the distance from the units selected by user to NMi */
408 /**************************************************************************/
409 double fromUsrDistance(double usr_distance, int unit, int default_val) {
410  double ret = NAN;
411  if (unit == -1) unit = default_val;
412  switch (unit) {
413  case DISTANCE_NMI: // Nautical miles
414  ret = usr_distance;
415  break;
416  case DISTANCE_MI: // Statute miles
417  ret = usr_distance / 1.15078;
418  break;
419  case DISTANCE_KM:
420  ret = usr_distance / 1.852;
421  break;
422  case DISTANCE_M:
423  ret = usr_distance / 1852;
424  break;
425  case DISTANCE_FT:
426  ret = usr_distance / 6076.12;
427  break;
428  }
429  return ret;
430 }
431 
432 /**************************************************************************/
433 /* Converts the depth in meters to the units selected by user */
434 /**************************************************************************/
435 double toUsrDepth(double cel_depth, int unit) {
436  double ret = NAN;
437  if (unit == -1) unit = g_nDepthUnitDisplay;
438  switch (unit) {
439  case DEPTH_FT: // Feet
440  ret = cel_depth / 0.3048;
441  break;
442  case DEPTH_M: // Meters
443  ret = cel_depth ;
444  break;
445  case DEPTH_FA:
446  ret = cel_depth / 0.3048 / 6;
447  break;
448  }
449  return ret;
450 }
451 
452 /**************************************************************************/
453 /* Converts the depth from the units selected by user to Meters */
454 /**************************************************************************/
455 double fromUsrDepth(double usr_depth, int unit) {
456  double ret = NAN;
457  if (unit == -1) unit = g_nDepthUnitDisplay;
458  switch (unit) {
459  case DEPTH_FT: // Feet
460  ret = usr_depth * 0.3048;
461  break;
462  case DEPTH_M: // Feet
463  ret = usr_depth;
464  break;
465  case DEPTH_FA: // Fathoms
466  ret = usr_depth * 0.3048 * 6;
467  break;
468  }
469  return ret;
470 }
471 
472 /**************************************************************************/
473 /* Returns the abbreviation of user selected depth unit */
474 /**************************************************************************/
475 wxString getUsrDepthUnit(int unit) {
476  wxString ret;
477  if (unit == -1) unit = g_nDepthUnitDisplay;
478  switch (unit) {
479  case DEPTH_FT: // Feet
480  ret = _("ft");
481  break;
482  case DEPTH_M:// Meters
483  ret = _("m");
484  break;
485  case DEPTH_FA: // Fathoms
486  ret = _("fa");
487  break;
488  }
489  return ret;
490 }
491 
492 //---------------------------------------------------------------------------------
493 // Vector Stuff for Hit Test Algorithm
494 //---------------------------------------------------------------------------------
495 double vGetLengthOfNormal(pVector2D a, pVector2D b, pVector2D n) {
496  vector2D c, vNormal;
497  vNormal.x = 0;
498  vNormal.y = 0;
499  //
500  // Obtain projection vector.
501  //
502  // c = ((a * b)/(|b|^2))*b
503  //
504  c.x = b->x * (vDotProduct(a, b) / vDotProduct(b, b));
505  c.y = b->y * (vDotProduct(a, b) / vDotProduct(b, b));
506  //
507  // Obtain perpendicular projection : e = a - c
508  //
509  vSubtractVectors(a, &c, &vNormal);
510  //
511  // Fill PROJECTION structure with appropriate values.
512  //
513  *n = vNormal;
514 
515  return (vVectorMagnitude(&vNormal));
516 }
517 
518 double vDotProduct(pVector2D v0, pVector2D v1) {
519  double dotprod;
520 
521  dotprod =
522  (v0 == NULL || v1 == NULL) ? 0.0 : (v0->x * v1->x) + (v0->y * v1->y);
523 
524  return (dotprod);
525 }
526 
527 pVector2D vAddVectors(pVector2D v0, pVector2D v1, pVector2D v) {
528  if (v0 == NULL || v1 == NULL)
529  v = (pVector2D)NULL;
530  else {
531  v->x = v0->x + v1->x;
532  v->y = v0->y + v1->y;
533  }
534  return (v);
535 }
536 
537 pVector2D vSubtractVectors(pVector2D v0, pVector2D v1, pVector2D v) {
538  if (v0 == NULL || v1 == NULL)
539  v = (pVector2D)NULL;
540  else {
541  v->x = v0->x - v1->x;
542  v->y = v0->y - v1->y;
543  }
544  return (v);
545 }
546 
547 double vVectorSquared(pVector2D v0) {
548  double dS;
549 
550  if (v0 == NULL)
551  dS = 0.0;
552  else
553  dS = ((v0->x * v0->x) + (v0->y * v0->y));
554  return (dS);
555 }
556 
557 double vVectorMagnitude(pVector2D v0) {
558  double dMagnitude;
559 
560  if (v0 == NULL)
561  dMagnitude = 0.0;
562  else
563  dMagnitude = sqrt(vVectorSquared(v0));
564  return (dMagnitude);
565 }
566 
567 
568 // This function parses a string containing a GPX time representation
569 // and returns a wxDateTime containing the UTC corresponding to the
570 // input. The function return value is a pointer past the last valid
571 // character parsed (if successful) or NULL (if the string is invalid).
572 //
573 // Valid GPX time strings are in ISO 8601 format as follows:
574 //
575 // [-]<YYYY>-<MM>-<DD>T<hh>:<mm>:<ss>Z|(+|-<hh>:<mm>)
576 //
577 // For example, 2010-10-30T14:34:56Z and 2010-10-30T14:34:56-04:00
578 // are the same time. The first is UTC and the second is EDT.
579 
580 const wxChar *ParseGPXDateTime(wxDateTime &dt, const wxChar *datetime) {
581  long sign, hrs_west, mins_west;
582  const wxChar *end;
583 
584  // Skip any leading whitespace
585  while (isspace(*datetime)) datetime++;
586 
587  // Skip (and ignore) leading hyphen
588  if (*datetime == wxT('-')) datetime++;
589 
590  // Parse and validate ISO 8601 date/time string
591  if ((end = dt.ParseFormat(datetime, wxT("%Y-%m-%dT%T"))) != NULL) {
592  // Invalid date/time
593  if (*end == 0) return NULL;
594 
595  // ParseFormat outputs in UTC if the controlling
596  // wxDateTime class instance has not been initialized.
597 
598  // Date/time followed by UTC time zone flag, so we are done
599  else if (*end == wxT('Z')) {
600  end++;
601  return end;
602  }
603 
604  // Date/time followed by given number of hrs/mins west of UTC
605  else if (*end == wxT('+') || *end == wxT('-')) {
606  // Save direction from UTC
607  if (*end == wxT('+'))
608  sign = 1;
609  else
610  sign = -1;
611  end++;
612 
613  // Parse hrs west of UTC
614  if (isdigit(*end) && isdigit(*(end + 1)) && *(end + 2) == wxT(':')) {
615  // Extract and validate hrs west of UTC
616  wxString(end).ToLong(&hrs_west);
617  if (hrs_west > 12) return NULL;
618  end += 3;
619 
620  // Parse mins west of UTC
621  if (isdigit(*end) && isdigit(*(end + 1))) {
622  // Extract and validate mins west of UTC
623  wxChar mins[3];
624  mins[0] = *end;
625  mins[1] = *(end + 1);
626  mins[2] = 0;
627  wxString(mins).ToLong(&mins_west);
628  if (mins_west > 59) return NULL;
629 
630  // Apply correction
631  dt -= sign * wxTimeSpan(hrs_west, mins_west, 0, 0);
632  return end + 2;
633  } else
634  // Missing mins digits
635  return NULL;
636  } else
637  // Missing hrs digits or colon
638  return NULL;
639  } else
640  // Unknown field after date/time (not UTC, not hrs/mins
641  // west of UTC)
642  return NULL;
643  } else
644  // Invalid ISO 8601 date/time
645  return NULL;
646 }
647 
648 wxString formatTimeDelta(wxTimeSpan span) {
649  using namespace std;
650  // wxTimeSpan is returns complete span in different units.
651  // FIXME: (leamas) Replace with sane std::chrono.
652  stringstream ss;
653  ss << setfill(' ');
654  if (span.GetHours() > 0) span = RoundToMinutes(span);
655  if (span.GetDays() > 0) ss << setw(2) << span.GetDays() << "d ";
656  if (span.GetHours() > 0) {
657  ss << setw(2) << span.GetHours() % 24 << _("H ");
658  ss << setw(2) << span.GetMinutes() % 60 << _("M");
659  } else {
660  ss << setw(2) << span.GetMinutes() % 60 << _("M ");
661  ss << setw(2) << span.GetSeconds() % 60 << _("S");
662  }
663  return ss.str();
664 }
665 
666 wxString formatTimeDelta(wxDateTime startTime, wxDateTime endTime) {
667  wxString timeStr;
668  if (startTime.IsValid() && endTime.IsValid()) {
669  wxTimeSpan span = endTime - startTime;
670  return formatTimeDelta(span);
671  } else {
672  return _("N/A");
673  }
674 }
675 
676 wxString formatTimeDelta(wxLongLong secs) {
677  wxString timeStr;
678 
679  wxTimeSpan span(0, 0, secs);
680  return formatTimeDelta(span);
681 }
682 
683 // RFC4122 version 4 compliant random UUIDs generator.
684 wxString GpxDocument::GetUUID(void) {
685  wxString str;
686  struct {
687  int time_low;
688  int time_mid;
689  int time_hi_and_version;
690  int clock_seq_hi_and_rsv;
691  int clock_seq_low;
692  int node_hi;
693  int node_low;
694  } uuid;
695 
696  uuid.time_low = GetRandomNumber(
697  0, 2147483647); // FIXME: the max should be set to something like
698  // MAXINT32, but it doesn't compile un gcc...
699  uuid.time_mid = GetRandomNumber(0, 65535);
700  uuid.time_hi_and_version = GetRandomNumber(0, 65535);
701  uuid.clock_seq_hi_and_rsv = GetRandomNumber(0, 255);
702  uuid.clock_seq_low = GetRandomNumber(0, 255);
703  uuid.node_hi = GetRandomNumber(0, 65535);
704  uuid.node_low = GetRandomNumber(0, 2147483647);
705 
706  /* Set the two most significant bits (bits 6 and 7) of the
707  * clock_seq_hi_and_rsv to zero and one, respectively. */
708  uuid.clock_seq_hi_and_rsv = (uuid.clock_seq_hi_and_rsv & 0x3F) | 0x80;
709 
710  /* Set the four most significant bits (bits 12 through 15) of the
711  * time_hi_and_version field to 4 */
712  uuid.time_hi_and_version = (uuid.time_hi_and_version & 0x0fff) | 0x4000;
713 
714  str.Printf(_T("%08x-%04x-%04x-%02x%02x-%04x%08x"), uuid.time_low,
715  uuid.time_mid, uuid.time_hi_and_version, uuid.clock_seq_hi_and_rsv,
716  uuid.clock_seq_low, uuid.node_hi, uuid.node_low);
717 
718  return str;
719 }
720 
721 int GpxDocument::GetRandomNumber(int range_min, int range_max) {
722  long u = (long)wxRound(
723  ((double)rand() / ((double)(RAND_MAX) + 1) * (range_max - range_min)) +
724  range_min);
725  return (int)u;
726 }
727 
728 /****************************************************************************/
729 // Modified from the code posted by Andy Ross at
730 // http://www.mail-archive.com/flightgear-devel@flightgear.org/msg06702.html
731 // Basically, it looks for a list of decimal numbers embedded in the
732 // string and uses the first three as degree, minutes and seconds. The
733 // presence of a "S" or "W character indicates that the result is in a
734 // hemisphere where the final answer must be negated. Non-number
735 // characters are treated as whitespace separating numbers.
736 //
737 // So there are lots of bogus strings you can feed it to get a bogus
738 // answer, but that's not surprising. It does, however, correctly parse
739 // all the well-formed strings I can thing of to feed it. I've tried all
740 // the following:
741 //
742 // 37°54.204' N
743 // N37 54 12
744 // 37°54'12"
745 // 37.9034
746 // 122°18.621' W
747 // 122w 18 37
748 // -122.31035
749 /****************************************************************************/
750 double fromDMM(wxString sdms) {
751  wchar_t buf[64];
752  char narrowbuf[64];
753  int i, len, top = 0;
754  double stk[32], sign = 1;
755 
756  // First round of string modifications to accomodate some known strange
757  // formats
758  wxString replhelper;
759  replhelper = wxString::FromUTF8("´·"); // UKHO PDFs
760  sdms.Replace(replhelper, _T("."));
761  replhelper =
762  wxString::FromUTF8("\"·"); // Don't know if used, but to make sure
763  sdms.Replace(replhelper, _T("."));
764  replhelper = wxString::FromUTF8("·");
765  sdms.Replace(replhelper, _T("."));
766 
767  replhelper =
768  wxString::FromUTF8("s. š."); // Another example: cs.wikipedia.org
769  // (someone was too active translating...)
770  sdms.Replace(replhelper, _T("N"));
771  replhelper = wxString::FromUTF8("j. š.");
772  sdms.Replace(replhelper, _T("S"));
773  sdms.Replace(_T("v. d."), _T("E"));
774  sdms.Replace(_T("z. d."), _T("W"));
775 
776  // If the string contains hemisphere specified by a letter, then '-' is for
777  // sure a separator...
778  sdms.UpperCase();
779  if (sdms.Contains(_T("N")) || sdms.Contains(_T("S")) ||
780  sdms.Contains(_T("E")) || sdms.Contains(_T("W")))
781  sdms.Replace(_T("-"), _T(" "));
782 
783  wcsncpy(buf, sdms.wc_str(wxConvUTF8), 63);
784  buf[63] = 0;
785  len = wxMin(wcslen(buf), sizeof(narrowbuf) - 1);
786  ;
787 
788  for (i = 0; i < len; i++) {
789  wchar_t c = buf[i];
790  if ((c >= '0' && c <= '9') || c == '-' || c == '.' || c == '+') {
791  narrowbuf[i] = c;
792  continue; /* Digit characters are cool as is */
793  }
794  if (c == ',') {
795  narrowbuf[i] = '.'; /* convert to decimal dot */
796  continue;
797  }
798  if ((c | 32) == 'w' || (c | 32) == 's')
799  sign = -1; /* These mean "negate" (note case insensitivity) */
800  narrowbuf[i] = 0; /* Replace everything else with nuls */
801  }
802 
803  /* Build a stack of doubles */
804  stk[0] = stk[1] = stk[2] = 0;
805  for (i = 0; i < len; i++) {
806  while (i < len && narrowbuf[i] == 0) i++;
807  if (i != len) {
808  stk[top++] = atof(narrowbuf + i);
809  i += strlen(narrowbuf + i);
810  }
811  }
812 
813  return sign * (stk[0] + (stk[1] + stk[2] / 60) / 60);
814 }
815 
816 double toMagnetic(double deg_true) {
817  if (!std::isnan(gVar)) {
818  if ((deg_true - gVar) > 360.)
819  return (deg_true - gVar - 360.);
820  else
821  return ((deg_true - gVar) >= 0.) ? (deg_true - gVar) : (deg_true - gVar + 360.);
822  } else {
823  if ((deg_true - g_UserVar) > 360.)
824  return (deg_true - g_UserVar - 360.);
825  else
826  return ((deg_true - g_UserVar) >= 0.) ? (deg_true - g_UserVar) : (deg_true - g_UserVar + 360.);
827  }
828 }
829 
830 double toMagnetic(double deg_true, double variation) {
831  double degm = deg_true - variation;
832  if (degm >= 360.)
833  return degm - 360.;
834  else
835  return degm >= 0. ? degm : degm + 360.;
836 }