1 /*
2
3 Support for CompeGPS waypoint (.wpt), route (.rte) and track (.trk) files,
4
5 Copyright (C) 2005 Olaf Klein, o.b.klein@gpsbabel.org
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 /*
23 History:
24 10/23/2005: First release; only a reader
25 10/25/2005: becomes a writer too
26 10/26/2005: received documentation from CompeGPS team
27 added fatals for "G" and "U" if not WGS84 and lat/lon
28 08/13/2006: switch to gbfile api
29 */
30
31 /*
32
33 the meaning of leading characters in CompeGPS data lines (enhanced PCX):
34
35 header lines:
36
37 "G": WGS 84 - Datum of the map
38 "N": Anybody - Name of the user
39 "L": -02:00:00 - Difference to UTC
40 "M": ... - Any comments
41 "R": 16711680 , xxxx , 1 - Route header
42 "U": 1 - System of coordinates (0=UTM 1=Latitude/Longitude)
43
44 "C": 0 0 255 2 -1.000000 - ???
45 "V": 0.0 0.0 0 0 0 0 0.0 - ???
46 "E": 0|1|00-NUL-00 00:00:00|00:00:00|0 - ???
47
48 data lines:
49
50 "W": if(route) routepoint; else waypoint
51 "T": trackpoint
52 "t": if(track) additionally track info
53 if(!track) additionally trackpoint info
54 "a": link to ...
55 "w": waypoint additional info
56
57 */
58
59 #include "defs.h"
60 #include "cet_util.h"
61 #include "csv_util.h"
62
63 #if CSVFMTS_ENABLED
64 #include <cmath>
65 #include "jeeps/gpsmath.h"
66 #include <cstdlib>
67 #include <cstdio>
68
69 #define MYNAME "CompeGPS"
70
71 #define SHORT_NAME_LENGTH 16
72
73 static gbfile* fin, *fout;
74 static int target_index, curr_index;
75 static int track_info_flag;
76 static short_handle sh;
77 static int snlen;
78 static int radius;
79 static int input_datum;
80
81 static const route_head* curr_track;
82
83 /* placeholders for options */
84
85 static char* option_icon;
86 static char* option_index;
87 static char* option_radius;
88 static char* option_snlen;
89
90 static
91 QVector<arglist_t> compegps_args = {
92 {
93 "deficon", &option_icon, "Default icon name",
94 nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr
95 },
96 {
97 "index", &option_index, "Index of route/track to write (if more than one in source)",
98 nullptr, ARGTYPE_INT, "1", nullptr, nullptr
99 },
100 {
101 "radius", &option_radius, "Give points (waypoints/route points) a default radius (proximity)",
102 nullptr, ARGTYPE_FLOAT, "0", nullptr, nullptr
103 },
104 {
105 "snlen", &option_snlen, "Length of generated shortnames (default 16)",
106 "16", ARGTYPE_INT, "1", nullptr, nullptr
107 },
108 };
109
110 static
fix_datum(double * lat,double * lon)111 void fix_datum(double* lat, double* lon)
112 {
113 double amt;
114
115 /*
116 * Avoid FP jitter in the common case.
117 */
118 if (input_datum != DATUM_WGS84) {
119 GPS_Math_Known_Datum_To_WGS84_M(*lat, *lon, 0.0, lat, lon,
120 &amt, input_datum);
121 }
122 }
123
124 static void
compegps_parse_date(const char * c,struct tm * tm)125 compegps_parse_date(const char* c, struct tm* tm)
126 {
127 char month[4];
128 tm->tm_mday = atoi(c);
129 strncpy(month, c+3, 3);
130 month[3] = 0;
131 tm->tm_mon = month_lookup(month);
132 int year = atoi(c + 7);
133 if (year < 70) {
134 year += 100;
135 }
136 if (year > 1900) {
137 year -= 1900;
138 }
139 tm->tm_year = year;
140 // if (tm->tm_year < 70) tm->tm_year += 100;
141 }
142
143 static void
compegps_parse_time(const char * c,struct tm * tm)144 compegps_parse_time(const char* c, struct tm* tm)
145 {
146 tm->tm_hour = atoi(c);
147 tm->tm_min = atoi(c+3);
148 tm->tm_sec = atoi(c+6);
149 }
150
151 /* specialized readers */
152
153 static Waypoint*
parse_wpt(char * buff)154 parse_wpt(char* buff)
155 {
156 int col = -1;
157 char* cx;
158 auto* wpt = new Waypoint;
159 struct tm tm;
160 int has_time = 0;
161 memset(&tm, 0, sizeof(tm));
162
163 char* c = strstr(buff, "A ");
164 if (c == buff) {
165 col++;
166 }
167
168 c = csv_lineparse(buff, " ", "", col++);
169 while (c != nullptr) {
170 c = lrtrim(c);
171 if (*c != '\0') {
172 #if 0
173 printf(MYNAME "_read_wpt: col(%d)=%s\n", col, c);
174 #endif
175 switch (col) {
176 case 0:
177
178 cx = c + strlen(c) - 1; /* trim trailing underscores */
179 while ((cx >= c) && (*cx == '_')) {
180 *cx-- = '\0';
181 }
182 if (*c != '\0') {
183 wpt->shortname = c;
184 }
185 break;
186 case 2:
187 human_to_dec(c, &wpt->latitude, nullptr, 1);
188 break;
189 case 3:
190 human_to_dec(c, nullptr, &wpt->longitude, 2);
191 break;
192 // Older compegps used a dumb constant.
193 // Report are that 2010-era writes a sensible
194 // value here.
195 /* always "27-MAR-62 00:00:00" */
196 case 4:
197 if (strcmp(c, "27-MAR-62")) {
198 has_time = 1;
199 compegps_parse_date(c, &tm);
200 }
201 break;
202 case 5:
203 if (has_time) {
204 compegps_parse_time(c, &tm);
205 wpt->SetCreationTime(mkgmtime(&tm));
206 }
207 break;
208 case 6:
209 wpt->altitude = atof(c);
210 break;
211 case 7:
212 wpt->description = c;
213 break;
214 default:
215 if (col > 7) {
216 wpt->description += " ";
217 wpt->description += c;
218 }
219 }
220 }
221 c = csv_lineparse(nullptr, " ", "", col++);
222 }
223 fix_datum(&wpt->latitude, &wpt->longitude);
224 return wpt;
225 }
226
227 static void
parse_wpt_info(const char * buff,Waypoint * wpt)228 parse_wpt_info(const char* buff, Waypoint* wpt) /* "w" */
229 {
230 int col = -1;
231 double fx;
232
233 char* c = csv_lineparse(buff, ",", "", col++);
234 while (c != nullptr) {
235 c = lrtrim(c);
236 if (*c != '\0') {
237 #if 0
238 printf(MYNAME "_read_wpt_info: col(%d)=%s\n", col, c);
239 #endif
240 switch (col) {
241 case 0:
242 wpt->icon_descr = c;
243 break;
244 case 1:
245 break; /* Text postion */
246 case 2:
247 break; /* Lens zoom level */
248 case 3:
249 break; /* Text colour */
250 case 4:
251 break; /* Background colour */
252 case 5:
253 break; /* Transparent text (0=transparent, 1=no transparent) */
254 case 6:
255 break; /* ??? */
256 case 7:
257 break; /* ??? */
258 case 8: /* radius */
259 fx = atof(c);
260 if (fx > 0) {
261 WAYPT_SET(wpt, proximity, fx);
262 }
263 break;
264 }
265 }
266 c = csv_lineparse(nullptr, ",", "", col++);
267 }
268 }
269
270 static Waypoint*
parse_trkpt(char * buff)271 parse_trkpt(char* buff)
272 {
273 int col = -1;
274 struct tm tm;
275 auto* wpt = new Waypoint;
276
277 char* c = strstr(buff, "A ");
278 if (c == buff) {
279 col++;
280 }
281
282 memset(&tm, 0, sizeof(tm));
283 c = csv_lineparse(buff, " ", "", col++);
284 while (c != nullptr) {
285 c = lrtrim(c);
286 if (*c != '\0') {
287 #if 0
288 printf(MYNAME "_read_trkpt: col(%d)=%s\n", col, c);
289 #endif
290 switch (col) {
291 case 2:
292 human_to_dec(c, &wpt->latitude, nullptr, 1);
293 break;
294 case 3:
295 human_to_dec(c, nullptr, &wpt->longitude, 2);
296 break;
297 case 4:
298 compegps_parse_date(c, &tm);
299 break;
300 case 5:
301 compegps_parse_time(c, &tm);
302 wpt->SetCreationTime(mkgmtime(&tm));
303 break;
304 case 7:
305 wpt->altitude = atof(c);
306 break;
307 }
308 }
309 c = csv_lineparse(nullptr, " ", "", col++);
310 }
311 fix_datum(&wpt->latitude, &wpt->longitude);
312 return wpt;
313 }
314
315 static void
parse_track_info(const char * buff,route_head * track)316 parse_track_info(const char* buff, route_head* track) /* "t" */
317 {
318 int col = -1;
319
320 char* c = csv_lineparse(buff, "|", "", col++);
321 while (c != nullptr) {
322 c = lrtrim(c);
323 if (*c != '\0') {
324 #if 0
325 printf(MYNAME "_read_track_info: col(%d)=%s\n", col, c);
326 #endif
327 switch (col) {
328 case 0:
329 break; /* unknown field */
330 case 1:
331 track->rte_name = c;
332 break;
333 case 2:
334 break; /* unknown field */
335 case 3:
336 break; /* unknown field */
337 }
338 }
339 c = csv_lineparse(nullptr, "|", "", col++);
340 }
341 }
342
343 static void
parse_rte_info(const char * buff,route_head * route)344 parse_rte_info(const char* buff, route_head* route) /* "R" */
345 {
346 int col = -1;
347
348 char* c = csv_lineparse(buff, ",", "", col++);
349 while (c != nullptr) {
350 c = lrtrim(c);
351 if (*c != '\0') {
352 #if 0
353 printf(MYNAME "_read_rte_info: col(%d)=%s\n", col, c);
354 #endif
355 switch (col) {
356 case 0:
357 break; /* unknown field (colour?) */
358 case 1:
359 route->rte_name = c;
360 break;
361 case 2:
362 break; /* unknown field */
363
364 }
365 }
366 c = csv_lineparse(nullptr, ",", "", col++);
367 }
368 }
369
370 /* main functions */
371
372 static void
compegps_rd_init(const QString & fname)373 compegps_rd_init(const QString& fname)
374 {
375 fin = gbfopen(fname, "rb", MYNAME);
376 input_datum = DATUM_WGS84;
377 }
378
379 static void
compegps_rd_deinit()380 compegps_rd_deinit()
381 {
382 gbfclose(fin);
383 }
384
385 static void
compegps_data_read()386 compegps_data_read()
387 {
388 char* buff;
389 int line = 0;
390 Waypoint* wpt = nullptr;
391 route_head* route = nullptr;
392 route_head* track = nullptr;
393
394 while ((buff = gbfgetstr(fin))) {
395 if ((line++ == 0) && fin->unicode) {
396 cet_convert_init(CET_CHARSET_UTF8, 1);
397 }
398 char* cin = lrtrim(buff);
399 if (strlen(cin) == 0) {
400 continue;
401 }
402
403 char* ctail = strchr(cin, ' ');
404 if (ctail == nullptr) {
405 continue;
406 }
407 ctail = lrtrim(ctail);
408
409 switch (*cin) {
410 case 'G':
411 input_datum = GPS_Lookup_Datum_Index(ctail);
412 if (input_datum < 0) {
413 fatal(MYNAME ": Unsupported datum \"%s\"!", ctail);
414 }
415 break;
416 case 'U':
417 switch (*ctail) {
418 case '1': /* lat/lon, that's we want to see */
419 break;
420 case '0': /* UTM not supported yet */
421 fatal(MYNAME "Sorry, UTM is not supported yet!\n");
422 default:
423 fatal(MYNAME "Invalid system of coordinates (%s)!\n", cin);
424 }
425 break;
426 case 'R':
427 route = new route_head;
428 route_add_head(route);
429 parse_rte_info(ctail, route);
430 break;
431 case 'M':
432 break;
433 case 'W':
434 wpt = parse_wpt(ctail);
435 if (wpt != nullptr) {
436 if (route != nullptr) {
437 route_add_wpt(route, wpt);
438 } else {
439 waypt_add(wpt);
440 }
441 }
442 break;
443 case 'w':
444 is_fatal((wpt == nullptr), MYNAME ": No waypoint data before \"%s\"!", cin);
445 parse_wpt_info(ctail, wpt);
446 break;
447 case 'T':
448 wpt = parse_trkpt(ctail);
449 if (wpt != nullptr) {
450 if (track == nullptr) {
451 track = new route_head;
452 track_add_head(track);
453 }
454 track_add_wpt(track, wpt);
455 }
456 break;
457 case 't':
458 if (track != nullptr) {
459 parse_track_info(ctail, track);
460 }
461 break;
462 }
463 }
464 }
465
466 /* ----------------------------------------------------------- */
467
468 static void
write_waypt_cb(const Waypoint * wpt)469 write_waypt_cb(const Waypoint* wpt)
470 {
471 if (curr_index != target_index) {
472 return;
473 }
474
475 // Our only output cleansing is to replace
476 QString cleaned_name(wpt->shortname);
477 cleaned_name.replace(' ', '_');
478
479 QString name = (snlen > 0) ? mkshort(sh, cleaned_name) : cleaned_name;
480
481 gbfprintf(fout, "W %s A ", CSTR(name));
482 gbfprintf(fout, "%.10f%c%c ",
483 fabs(wpt->latitude), 0xBA, (wpt->latitude >= 0) ? 'N' : 'S');
484 gbfprintf(fout, "%.10f%c%c ",
485 fabs(wpt->longitude), 0xBA, (wpt->longitude >= 0) ? 'E' : 'W');
486 gbfprintf(fout, "27-MAR-62 00:00:00 %.6f",
487 (wpt->altitude != unknown_alt) ? wpt->altitude : 0.0);
488 if (wpt->description != nullptr) {
489 gbfprintf(fout, " %s", CSTRc(wpt->description));
490 }
491 gbfprintf(fout, "\n");
492
493 if ((!wpt->icon_descr.isNull()) || (wpt->wpt_flags.proximity) || \
494 (option_icon != nullptr)) {
495 gbfprintf(fout, "w %s,0,0.0,16777215,255,1,7,,%.1f\n",
496 wpt->icon_descr.isNull() ? "Waypoint" : CSTR(wpt->icon_descr),
497 WAYPT_GET(wpt, proximity, 0));
498 }
499 }
500
501 static void
write_route_hdr_cb(const route_head * rte)502 write_route_hdr_cb(const route_head* rte)
503 {
504 curr_index++;
505 if (curr_index != target_index) {
506 return;
507 }
508
509 QString name = rte->rte_name;
510 if (name != nullptr) {
511 name = csv_stringclean(name, ",");
512 } else {
513 name = " ";
514 }
515 gbfprintf(fout, "R 16711680,%s,1,-1\n", CSTR(name));
516 }
517
518 static void
write_route()519 write_route()
520 {
521 curr_index = 0;
522 route_disp_all(write_route_hdr_cb, nullptr, write_waypt_cb);
523 }
524
525 static void
write_track_hdr_cb(const route_head * trk)526 write_track_hdr_cb(const route_head* trk)
527 {
528 track_info_flag = 0;
529 curr_track = trk;
530
531 curr_index++;
532 if (curr_index != target_index) {
533 return;
534 }
535
536 track_info_flag = 1;
537 }
538
539 static void
write_trkpt_cb(const Waypoint * wpt)540 write_trkpt_cb(const Waypoint* wpt)
541 {
542 char buff[128];
543
544 if ((curr_index != target_index) || (wpt == nullptr)) {
545 return;
546 }
547
548 buff[0] = '\0';
549
550 // TOOD: This should probably attempt a gmtime and then fall back to the 1-1-1970
551 // case or bypass the time_t completely and build string representations directly.
552 if (wpt->creation_time.isValid()) {
553 const time_t tt = wpt->GetCreationTime().toTime_t();
554 struct tm tm = *gmtime(&tt);
555
556 strftime(buff, sizeof(buff), "%d-%b-%y %H:%M:%S", &tm);
557 strupper(buff);
558 } else {
559 strncpy(buff, "01-JAN-70 00:00:00", sizeof(buff));
560 }
561
562 gbfprintf(fout, "T A %.10f%c%c %.10f%c%c ",
563 fabs(wpt->latitude), 0xBA, (wpt->latitude >= 0) ? 'N' : 'S',
564 fabs(wpt->longitude), 0xBA, (wpt->longitude >= 0) ? 'E' : 'W');
565 gbfprintf(fout, "%s s %.1f %.1f %.1f %.1f %d ",
566 buff,
567 wpt->altitude,
568 0.0,
569 0.0,
570 0.0,
571 0);
572 gbfprintf(fout, "%.1f %.1f %.1f %.1f %.1f\n",
573 -1000.0,
574 -1.0,
575 -1.0,
576 -1.0,
577 -1.0);
578 if (track_info_flag != 0) {
579 track_info_flag = 0;
580 if (curr_track->rte_name != nullptr) {
581 QString name = csv_stringclean(curr_track->rte_name, "|");
582 gbfprintf(fout, "t 4294967295|%s|-1|-1\n", CSTR(name));
583 }
584 }
585 }
586
587 static void
write_track()588 write_track()
589 {
590 curr_index = 0;
591
592 // gbfprintf(fout, "L +02:00:00\n");
593 track_disp_all(write_track_hdr_cb, nullptr, write_trkpt_cb);
594 gbfprintf(fout, "F 1234\n");
595 }
596
597 static void
write_waypoints()598 write_waypoints()
599 {
600 waypt_disp_all(write_waypt_cb);
601 }
602
603 /* --------------------------------------------------------------------------- */
604
605 static void
compegps_wr_init(const QString & fname)606 compegps_wr_init(const QString& fname)
607 {
608 fout = gbfopen(fname, "w", MYNAME);
609 sh = mkshort_new_handle();
610 }
611
612 static void
compegps_wr_deinit()613 compegps_wr_deinit()
614 {
615 mkshort_del_handle(&sh);
616 gbfclose(fout);
617 }
618
619 static void
compegps_data_write()620 compegps_data_write()
621 {
622 /* because of different file extensions we can only write one GPS data type at time */
623
624 gbfprintf(fout, "G WGS 84\n");
625 gbfprintf(fout, "U 1\n");
626
627 /* process options */
628
629 target_index = 1;
630 if (option_index != nullptr) {
631 target_index = atoi(option_index);
632 }
633
634 snlen = 0;
635 if (global_opts.synthesize_shortnames != 0) {
636 if (option_snlen != nullptr) {
637 snlen = atoi(option_snlen);
638 } else {
639 snlen = SHORT_NAME_LENGTH;
640 }
641
642 is_fatal((snlen < 1), MYNAME "Invalid length for generated shortnames!");
643
644 setshort_whitespace_ok(sh, 0);
645 setshort_length(sh, snlen);
646 }
647
648 radius = -1;
649 if (option_radius != nullptr) {
650 radius = atof(option_radius);
651 is_fatal((radius <= 0.0), MYNAME "Invalid value for radius!");
652 }
653
654 if (option_icon != nullptr) {
655 if (*option_icon == '\0') {
656 option_icon = nullptr;
657 } else if (case_ignore_strcmp(option_icon, "deficon") == 0) {
658 option_icon = nullptr;
659 }
660 }
661
662 switch (global_opts.objective) {
663 case wptdata:
664 case unknown_gpsdata:
665 curr_index = target_index = 0;
666 write_waypoints();
667 break;
668 case trkdata:
669 write_track();
670 break;
671 case rtedata:
672 write_route();
673 break;
674 case posndata:
675 fatal(MYNAME ": Realtime positioning not supported.\n");
676 break;
677 }
678 }
679
680 /* --------------------------------------------------------------------------- */
681
682 ff_vecs_t compegps_vecs = {
683 ff_type_file,
684 FF_CAP_RW_ALL,
685 compegps_rd_init,
686 compegps_wr_init,
687 compegps_rd_deinit,
688 compegps_wr_deinit,
689 compegps_data_read,
690 compegps_data_write,
691 nullptr,
692 &compegps_args,
693 CET_CHARSET_MS_ANSI, 1
694 , NULL_POS_OPS,
695 nullptr
696 };
697 #endif /* CSVFMTS_ENABLED */
698