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
31#include <wx/datetime.h>
32#include <wx/math.h>
33#include <wx/string.h>
34#include <wx/translation.h>
35#include <wx/utils.h>
36
37#include "navutil_base.h"
38
39#include "navutil_base.h"
40#include "vector2D.h"
41
42extern int g_iSDMMFormat;
43extern int g_iSpeedFormat;
44extern int g_iDistanceFormat;
45
46wxString toSDMM(int NEflag, double a, bool hi_precision) {
47 wxString s;
48 double mpy;
49 short neg = 0;
50 int d;
51 long m;
52 double ang = a;
53 char c = 'N';
54
55 if (a < 0.0) {
56 a = -a;
57 neg = 1;
58 }
59 d = (int)a;
60 if (neg) d = -d;
61 if (NEflag) {
62 if (NEflag == 1) {
63 c = 'N';
64
65 if (neg) {
66 d = -d;
67 c = 'S';
68 }
69 } else if (NEflag == 2) {
70 c = 'E';
71
72 if (neg) {
73 d = -d;
74 c = 'W';
75 }
76 }
77 }
78
79 switch (g_iSDMMFormat) {
80 case 0:
81 mpy = 600.0;
82 if (hi_precision) mpy = mpy * 1000;
83
84 m = (long)wxRound((a - (double)d) * mpy);
85
86 if (!NEflag || NEflag < 1 || NEflag > 2) // Does it EVER happen?
87 {
88 if (hi_precision)
89 s.Printf(_T ( "%d%c %02ld.%04ld'" ), d, 0x00B0, m / 10000, m % 10000);
90 else
91 s.Printf(_T ( "%d%c %02ld.%01ld'" ), d, 0x00B0, m / 10, m % 10);
92 } else {
93 if (hi_precision)
94 if (NEflag == 1)
95 s.Printf(_T ( "%02d%c %02ld.%04ld' %c" ), d, 0x00B0, m / 10000,
96 (m % 10000), c);
97 else
98 s.Printf(_T ( "%03d%c %02ld.%04ld' %c" ), d, 0x00B0, m / 10000,
99 (m % 10000), c);
100 else if (NEflag == 1)
101 s.Printf(_T ( "%02d%c %02ld.%01ld' %c" ), d, 0x00B0, m / 10, (m % 10), c);
102 else
103 s.Printf(_T ( "%03d%c %02ld.%01ld' %c" ), d, 0x00B0, m / 10, (m % 10), c);
104 }
105 break;
106 case 1:
107 if (hi_precision)
108 s.Printf(_T ( "%03.6f" ),
109 ang); // cca 11 cm - the GPX precision is higher, but as we
110 // use hi_precision almost everywhere it would be a
111 // little too much....
112 else
113 s.Printf(_T ( "%03.4f" ), ang); // cca 11m
114 break;
115 case 2:
116 m = (long)((a - (double)d) * 60);
117 mpy = 10.0;
118 if (hi_precision) mpy = mpy * 100;
119 long sec = (long)((a - (double)d - (((double)m) / 60)) * 3600 * mpy);
120
121 if (!NEflag || NEflag < 1 || NEflag > 2) // Does it EVER happen?
122 {
123 if (hi_precision)
124 s.Printf(_T ( "%d%c %ld'%ld.%ld\"" ), d, 0x00B0, m, sec / 1000,
125 sec % 1000);
126 else
127 s.Printf(_T ( "%d%c %ld'%ld.%ld\"" ), d, 0x00B0, m, sec / 10, sec % 10);
128 } else {
129 if (hi_precision)
130 if (NEflag == 1)
131 s.Printf(_T ( "%02d%c %02ld' %02ld.%03ld\" %c" ), d, 0x00B0, m,
132 sec / 1000, sec % 1000, c);
133 else
134 s.Printf(_T ( "%03d%c %02ld' %02ld.%03ld\" %c" ), d, 0x00B0, m,
135 sec / 1000, sec % 1000, c);
136 else if (NEflag == 1)
137 s.Printf(_T ( "%02d%c %02ld' %02ld.%ld\" %c" ), d, 0x00B0, m, sec / 10,
138 sec % 10, c);
139 else
140 s.Printf(_T ( "%03d%c %02ld' %02ld.%ld\" %c" ), d, 0x00B0, m, sec / 10,
141 sec % 10, c);
142 }
143 break;
144 }
145 return s;
146}
147
148/**************************************************************************/
149/* Converts the speed to the units selected by user */
150/**************************************************************************/
151double toUsrSpeed(double kts_speed, int unit) {
152 double ret = NAN;
153 if (unit == -1) unit = g_iSpeedFormat;
154 switch (unit) {
155 case SPEED_KTS: // kts
156 ret = kts_speed;
157 break;
158 case SPEED_MPH: // mph
159 ret = kts_speed * 1.15078;
160 break;
161 case SPEED_KMH: // km/h
162 ret = kts_speed * 1.852;
163 break;
164 case SPEED_MS: // m/s
165 ret = kts_speed * 0.514444444;
166 break;
167 }
168 return ret;
169}
170
171/**************************************************************************/
172/* Converts the distance to the units selected by user */
173/**************************************************************************/
174double toUsrDistance(double nm_distance, int unit) {
175 double ret = NAN;
176 if (unit == -1) unit = g_iDistanceFormat;
177 switch (unit) {
178 case DISTANCE_NMI: // Nautical miles
179 ret = nm_distance;
180 break;
181 case DISTANCE_MI: // Statute miles
182 ret = nm_distance * 1.15078;
183 break;
184 case DISTANCE_KM:
185 ret = nm_distance * 1.852;
186 break;
187 case DISTANCE_M:
188 ret = nm_distance * 1852;
189 break;
190 case DISTANCE_FT:
191 ret = nm_distance * 6076.12;
192 break;
193 case DISTANCE_FA:
194 ret = nm_distance * 1012.68591;
195 break;
196 case DISTANCE_IN:
197 ret = nm_distance * 72913.4;
198 break;
199 case DISTANCE_CM:
200 ret = nm_distance * 185200;
201 break;
202 }
203 return ret;
204}
205
206
207/**************************************************************************/
208/* Returns the abbreviation of user selected distance unit */
209/**************************************************************************/
210wxString getUsrDistanceUnit(int unit) {
211 wxString ret;
212 if (unit == -1) unit = g_iDistanceFormat;
213 switch (unit) {
214 case DISTANCE_NMI: // Nautical miles
215 ret = _("NMi");
216 break;
217 case DISTANCE_MI: // Statute miles
218 ret = _("mi");
219 break;
220 case DISTANCE_KM:
221 ret = _("km");
222 break;
223 case DISTANCE_M:
224 ret = _("m");
225 break;
226 case DISTANCE_FT:
227 ret = _("ft");
228 break;
229 case DISTANCE_FA:
230 ret = _("fa");
231 break;
232 case DISTANCE_IN:
233 ret = _("in");
234 break;
235 case DISTANCE_CM:
236 ret = _("cm");
237 break;
238 }
239 return ret;
240}
241
242
243/**************************************************************************/
244/* Returns the abbreviation of user selected speed unit */
245/**************************************************************************/
246wxString getUsrSpeedUnit(int unit) {
247 wxString ret;
248 if (unit == -1) unit = g_iSpeedFormat;
249 switch (unit) {
250 case SPEED_KTS: // kts
251 ret = _("kts");
252 break;
253 case SPEED_MPH: // mph
254 ret = _("mph");
255 break;
256 case SPEED_KMH:
257 ret = _("km/h");
258 break;
259 case SPEED_MS:
260 ret = _("m/s");
261 break;
262 }
263 return ret;
264}
265
266wxString FormatDistanceAdaptive(double distance) {
267 wxString result;
268 int unit = g_iDistanceFormat;
269 double usrDistance = toUsrDistance(distance, unit);
270 if (usrDistance < 0.1 &&
271 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI)) {
272 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
273 usrDistance = toUsrDistance(distance, unit);
274 }
275 wxString format;
276 if (usrDistance < 5.0) {
277 format = _T("%1.2f ");
278 } else if (usrDistance < 100.0) {
279 format = _T("%2.1f ");
280 } else if (usrDistance < 1000.0) {
281 format = _T("%3.0f ");
282 } else {
283 format = _T("%4.0f ");
284 }
285 result << wxString::Format(format, usrDistance) << getUsrDistanceUnit(unit);
286 return result;
287}
288
289/**************************************************************************/
290/* Converts the speed from the units selected by user to knots */
291/**************************************************************************/
292double fromUsrSpeed(double usr_speed, int unit, int default_val) {
293 double ret = NAN;
294
295 if (unit == -1) unit = default_val;
296 switch (unit) {
297 case SPEED_KTS: // kts
298 ret = usr_speed;
299 break;
300 case SPEED_MPH: // mph
301 ret = usr_speed / 1.15078;
302 break;
303 case SPEED_KMH: // km/h
304 ret = usr_speed / 1.852;
305 break;
306 case SPEED_MS: // m/s
307 ret = usr_speed / 0.514444444;
308 break;
309 }
310 return ret;
311}
312
313/**************************************************************************/
314/* Converts the distance from the units selected by user to NMi */
315/**************************************************************************/
316double fromUsrDistance(double usr_distance, int unit, int default_val) {
317 double ret = NAN;
318 if (unit == -1) unit = default_val;
319 switch (unit) {
320 case DISTANCE_NMI: // Nautical miles
321 ret = usr_distance;
322 break;
323 case DISTANCE_MI: // Statute miles
324 ret = usr_distance / 1.15078;
325 break;
326 case DISTANCE_KM:
327 ret = usr_distance / 1.852;
328 break;
329 case DISTANCE_M:
330 ret = usr_distance / 1852;
331 break;
332 case DISTANCE_FT:
333 ret = usr_distance / 6076.12;
334 break;
335 }
336 return ret;
337}
338
339//---------------------------------------------------------------------------------
340// Vector Stuff for Hit Test Algorithm
341//---------------------------------------------------------------------------------
342double vGetLengthOfNormal(pVector2D a, pVector2D b, pVector2D n) {
343 vector2D c, vNormal;
344 vNormal.x = 0;
345 vNormal.y = 0;
346 //
347 // Obtain projection vector.
348 //
349 // c = ((a * b)/(|b|^2))*b
350 //
351 c.x = b->x * (vDotProduct(a, b) / vDotProduct(b, b));
352 c.y = b->y * (vDotProduct(a, b) / vDotProduct(b, b));
353 //
354 // Obtain perpendicular projection : e = a - c
355 //
356 vSubtractVectors(a, &c, &vNormal);
357 //
358 // Fill PROJECTION structure with appropriate values.
359 //
360 *n = vNormal;
361
362 return (vVectorMagnitude(&vNormal));
363}
364
365double vDotProduct(pVector2D v0, pVector2D v1) {
366 double dotprod;
367
368 dotprod =
369 (v0 == NULL || v1 == NULL) ? 0.0 : (v0->x * v1->x) + (v0->y * v1->y);
370
371 return (dotprod);
372}
373
374pVector2D vAddVectors(pVector2D v0, pVector2D v1, pVector2D v) {
375 if (v0 == NULL || v1 == NULL)
376 v = (pVector2D)NULL;
377 else {
378 v->x = v0->x + v1->x;
379 v->y = v0->y + v1->y;
380 }
381 return (v);
382}
383
384pVector2D vSubtractVectors(pVector2D v0, pVector2D v1, pVector2D v) {
385 if (v0 == NULL || v1 == NULL)
386 v = (pVector2D)NULL;
387 else {
388 v->x = v0->x - v1->x;
389 v->y = v0->y - v1->y;
390 }
391 return (v);
392}
393
394double vVectorSquared(pVector2D v0) {
395 double dS;
396
397 if (v0 == NULL)
398 dS = 0.0;
399 else
400 dS = ((v0->x * v0->x) + (v0->y * v0->y));
401 return (dS);
402}
403
404double vVectorMagnitude(pVector2D v0) {
405 double dMagnitude;
406
407 if (v0 == NULL)
408 dMagnitude = 0.0;
409 else
410 dMagnitude = sqrt(vVectorSquared(v0));
411 return (dMagnitude);
412}
413
414
415// This function parses a string containing a GPX time representation
416// and returns a wxDateTime containing the UTC corresponding to the
417// input. The function return value is a pointer past the last valid
418// character parsed (if successful) or NULL (if the string is invalid).
419//
420// Valid GPX time strings are in ISO 8601 format as follows:
421//
422// [-]<YYYY>-<MM>-<DD>T<hh>:<mm>:<ss>Z|(+|-<hh>:<mm>)
423//
424// For example, 2010-10-30T14:34:56Z and 2010-10-30T14:34:56-04:00
425// are the same time. The first is UTC and the second is EDT.
426
427const wxChar *ParseGPXDateTime(wxDateTime &dt, const wxChar *datetime) {
428 long sign, hrs_west, mins_west;
429 const wxChar *end;
430
431 // Skip any leading whitespace
432 while (isspace(*datetime)) datetime++;
433
434 // Skip (and ignore) leading hyphen
435 if (*datetime == wxT('-')) datetime++;
436
437 // Parse and validate ISO 8601 date/time string
438 if ((end = dt.ParseFormat(datetime, wxT("%Y-%m-%dT%T"))) != NULL) {
439 // Invalid date/time
440 if (*end == 0) return NULL;
441
442 // ParseFormat outputs in UTC if the controlling
443 // wxDateTime class instance has not been initialized.
444
445 // Date/time followed by UTC time zone flag, so we are done
446 else if (*end == wxT('Z')) {
447 end++;
448 return end;
449 }
450
451 // Date/time followed by given number of hrs/mins west of UTC
452 else if (*end == wxT('+') || *end == wxT('-')) {
453 // Save direction from UTC
454 if (*end == wxT('+'))
455 sign = 1;
456 else
457 sign = -1;
458 end++;
459
460 // Parse hrs west of UTC
461 if (isdigit(*end) && isdigit(*(end + 1)) && *(end + 2) == wxT(':')) {
462 // Extract and validate hrs west of UTC
463 wxString(end).ToLong(&hrs_west);
464 if (hrs_west > 12) return NULL;
465 end += 3;
466
467 // Parse mins west of UTC
468 if (isdigit(*end) && isdigit(*(end + 1))) {
469 // Extract and validate mins west of UTC
470 wxChar mins[3];
471 mins[0] = *end;
472 mins[1] = *(end + 1);
473 mins[2] = 0;
474 wxString(mins).ToLong(&mins_west);
475 if (mins_west > 59) return NULL;
476
477 // Apply correction
478 dt -= sign * wxTimeSpan(hrs_west, mins_west, 0, 0);
479 return end + 2;
480 } else
481 // Missing mins digits
482 return NULL;
483 } else
484 // Missing hrs digits or colon
485 return NULL;
486 } else
487 // Unknown field after date/time (not UTC, not hrs/mins
488 // west of UTC)
489 return NULL;
490 } else
491 // Invalid ISO 8601 date/time
492 return NULL;
493}
494
495wxString formatTimeDelta(wxTimeSpan span) {
496 wxString timeStr;
497 int days = span.GetDays();
498 span -= wxTimeSpan::Days(days);
499 int hours = span.GetHours();
500 span -= wxTimeSpan::Hours(hours);
501 double minutes = (double)span.GetSeconds().ToLong() / 60.0;
502 span -= wxTimeSpan::Minutes(span.GetMinutes());
503 int seconds = (double)span.GetSeconds().ToLong();
504
505 timeStr =
506 (days ? wxString::Format(_("%dd "), days) : _T("")) +
507 (hours || days
508 ? wxString::Format(_("%2dH %2dM"), hours, (int)round(minutes))
509 : wxString::Format(_("%2dM %2dS"), (int)floor(minutes), seconds));
510
511 return timeStr;
512}
513
514wxString formatTimeDelta(wxDateTime startTime, wxDateTime endTime) {
515 wxString timeStr;
516 if (startTime.IsValid() && endTime.IsValid()) {
517 wxTimeSpan span = endTime - startTime;
518 return formatTimeDelta(span);
519 } else {
520 return _("N/A");
521 }
522}
523
524wxString formatTimeDelta(wxLongLong secs) {
525 wxString timeStr;
526
527 wxTimeSpan span(0, 0, secs);
528 return formatTimeDelta(span);
529}
530
531// RFC4122 version 4 compliant random UUIDs generator.
532wxString GpxDocument::GetUUID(void) {
533 wxString str;
534 struct {
535 int time_low;
536 int time_mid;
537 int time_hi_and_version;
538 int clock_seq_hi_and_rsv;
539 int clock_seq_low;
540 int node_hi;
541 int node_low;
542 } uuid;
543
544 uuid.time_low = GetRandomNumber(
545 0, 2147483647); // FIXME: the max should be set to something like
546 // MAXINT32, but it doesn't compile un gcc...
547 uuid.time_mid = GetRandomNumber(0, 65535);
548 uuid.time_hi_and_version = GetRandomNumber(0, 65535);
549 uuid.clock_seq_hi_and_rsv = GetRandomNumber(0, 255);
550 uuid.clock_seq_low = GetRandomNumber(0, 255);
551 uuid.node_hi = GetRandomNumber(0, 65535);
552 uuid.node_low = GetRandomNumber(0, 2147483647);
553
554 /* Set the two most significant bits (bits 6 and 7) of the
555 * clock_seq_hi_and_rsv to zero and one, respectively. */
556 uuid.clock_seq_hi_and_rsv = (uuid.clock_seq_hi_and_rsv & 0x3F) | 0x80;
557
558 /* Set the four most significant bits (bits 12 through 15) of the
559 * time_hi_and_version field to 4 */
560 uuid.time_hi_and_version = (uuid.time_hi_and_version & 0x0fff) | 0x4000;
561
562 str.Printf(_T("%08x-%04x-%04x-%02x%02x-%04x%08x"), uuid.time_low,
563 uuid.time_mid, uuid.time_hi_and_version, uuid.clock_seq_hi_and_rsv,
564 uuid.clock_seq_low, uuid.node_hi, uuid.node_low);
565
566 return str;
567}
568
569int GpxDocument::GetRandomNumber(int range_min, int range_max) {
570 long u = (long)wxRound(
571 ((double)rand() / ((double)(RAND_MAX) + 1) * (range_max - range_min)) +
572 range_min);
573 return (int)u;
574}
575
576/****************************************************************************/
577// Modified from the code posted by Andy Ross at
578// http://www.mail-archive.com/flightgear-devel@flightgear.org/msg06702.html
579// Basically, it looks for a list of decimal numbers embedded in the
580// string and uses the first three as degree, minutes and seconds. The
581// presence of a "S" or "W character indicates that the result is in a
582// hemisphere where the final answer must be negated. Non-number
583// characters are treated as whitespace separating numbers.
584//
585// So there are lots of bogus strings you can feed it to get a bogus
586// answer, but that's not surprising. It does, however, correctly parse
587// all the well-formed strings I can thing of to feed it. I've tried all
588// the following:
589//
590// 37°54.204' N
591// N37 54 12
592// 37°54'12"
593// 37.9034
594// 122°18.621' W
595// 122w 18 37
596// -122.31035
597/****************************************************************************/
598double fromDMM(wxString sdms) {
599 wchar_t buf[64];
600 char narrowbuf[64];
601 int i, len, top = 0;
602 double stk[32], sign = 1;
603
604 // First round of string modifications to accomodate some known strange
605 // formats
606 wxString replhelper;
607 replhelper = wxString::FromUTF8("´·"); // UKHO PDFs
608 sdms.Replace(replhelper, _T("."));
609 replhelper =
610 wxString::FromUTF8("\"·"); // Don't know if used, but to make sure
611 sdms.Replace(replhelper, _T("."));
612 replhelper = wxString::FromUTF8("·");
613 sdms.Replace(replhelper, _T("."));
614
615 replhelper =
616 wxString::FromUTF8("s. š."); // Another example: cs.wikipedia.org
617 // (someone was too active translating...)
618 sdms.Replace(replhelper, _T("N"));
619 replhelper = wxString::FromUTF8("j. š.");
620 sdms.Replace(replhelper, _T("S"));
621 sdms.Replace(_T("v. d."), _T("E"));
622 sdms.Replace(_T("z. d."), _T("W"));
623
624 // If the string contains hemisphere specified by a letter, then '-' is for
625 // sure a separator...
626 sdms.UpperCase();
627 if (sdms.Contains(_T("N")) || sdms.Contains(_T("S")) ||
628 sdms.Contains(_T("E")) || sdms.Contains(_T("W")))
629 sdms.Replace(_T("-"), _T(" "));
630
631 wcsncpy(buf, sdms.wc_str(wxConvUTF8), 63);
632 buf[63] = 0;
633 len = wxMin(wcslen(buf), sizeof(narrowbuf) - 1);
634 ;
635
636 for (i = 0; i < len; i++) {
637 wchar_t c = buf[i];
638 if ((c >= '0' && c <= '9') || c == '-' || c == '.' || c == '+') {
639 narrowbuf[i] = c;
640 continue; /* Digit characters are cool as is */
641 }
642 if (c == ',') {
643 narrowbuf[i] = '.'; /* convert to decimal dot */
644 continue;
645 }
646 if ((c | 32) == 'w' || (c | 32) == 's')
647 sign = -1; /* These mean "negate" (note case insensitivity) */
648 narrowbuf[i] = 0; /* Replace everything else with nuls */
649 }
650
651 /* Build a stack of doubles */
652 stk[0] = stk[1] = stk[2] = 0;
653 for (i = 0; i < len; i++) {
654 while (i < len && narrowbuf[i] == 0) i++;
655 if (i != len) {
656 stk[top++] = atof(narrowbuf + i);
657 i += strlen(narrowbuf + i);
658 }
659 }
660
661 return sign * (stk[0] + (stk[1] + stk[2] / 60) / 60);
662}
663
664