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