1 /*
2 
3     Support for G7ToWin data files (.g7t),
4     Copyright (C) 2007 Olaf Klein, o.b.klein@gpsbabel.org
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20 
21 /*
22     History:
23     		04/07/2007: start programming
24     		04/15/2007: added to gpsbabel
25 */
26 
27 #include "defs.h"
28 #include "cet_util.h"
29 #include "csv_util.h"
30 #include "garmin_fs.h"
31 #include "garmin_tables.h"
32 #include "jeeps/gpsmath.h"
33 #include "strptime.h"
34 
35 #include <cstdio>
36 #include <cstdlib>
37 #include <ctime>
38 
39 #if CSVFMTS_ENABLED
40 
41 #define MYNAME "g7towin"
42 
43 #define G7T_HEADER	"Version 2:G7T"
44 
45 static gbfile* fin;
46 static grid_type grid;
47 static int datum;
48 static gpsdata_type mode;
49 static double altf;
50 static int gardown;
51 static int event_ct;
52 
53 static
54 QVector<arglist_t> g7towin_args = {
55 };
56 
57 #define WAYPT__OFS	0x00000
58 #define TRKPT__OFS	0x01000
59 
60 #define WPT_c0_OFS	0x0c000
61 #define WPT_c1_OFS	0x0c100
62 #define WPT_c2_OFS	0x0c200
63 #define WPT_c3_OFS	0x0c300
64 #define WPT_c4_OFS	0x0c400
65 #define WPT_c5_OFS	0x0c500
66 #define WPT_c6_OFS	0x0c600
67 #define WPT_c7_OFS	0x0c700
68 #define WPT_c8_OFS	0x0c800
69 #define WPT_cA_OFS	0x0cA00
70 #define WPT_cB_OFS	0x0cB00
71 #define WPT_cC_OFS	0x0cC00
72 #define WPT_cD_OFS	0x0cD00
73 
74 static void
parse_line(char * buff,int index,const char * delimiter,Waypoint * wpt)75 parse_line(char* buff, int index, const char* delimiter, Waypoint* wpt)
76 {
77   char* cin;
78   garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
79 
80   while ((cin = csv_lineparse(buff, delimiter, "", index++))) {
81 
82     buff = nullptr;
83     cin = lrtrim(cin);
84 
85     if ((*cin == '\0') ||
86         (strcmp(cin, "INF") == 0) ||
87         (strcmp(cin, "1e25") == 0) ||
88         (strcmp(cin, "1.0e25") == 0)) {
89       continue;
90     }
91 
92     switch (index) {
93 
94       int categories;
95       struct tm tm;
96       char* cerr;
97 
98     case TRKPT__OFS + 1:
99       cin += parse_coordinates(cin, datum, grid,
100                                &wpt->latitude, &wpt->longitude, MYNAME);
101       while (isspace(*cin)) {
102         cin++;
103       }
104 
105       memset(&tm, 0, sizeof(tm));
106       cerr = strptime(cin, "%a %b %d %H:%M:%S %Y", &tm);
107       if (cerr == nullptr) {
108         fatal(MYNAME ": Unable to convert date (%s)!\n", cin);
109       }
110       wpt->SetCreationTime(mkgmtime(&tm));
111       break;
112 
113     case WAYPT__OFS + 1:
114       wpt->description = (cin);
115       break;
116 
117     case WAYPT__OFS + 2:
118       wpt->icon_descr = gt_find_desc_from_icon_number(
119                           atoi(cin), PCX);
120       break;
121 
122     case WAYPT__OFS + 4:
123       if (strcmp(cin, "S+C") == 0) {
124         garmin_fs_t::set_display(gmsd, gt_display_mode_symbol_and_comment);
125       } else if (strcmp(cin, "S") == 0) {
126         garmin_fs_t::set_display(gmsd, gt_display_mode_symbol);
127       } else if (strcmp(cin, "S+N") == 0) {
128         garmin_fs_t::set_display(gmsd, gt_display_mode_symbol_and_name);
129       }
130       break;
131 
132     case WPT_cA_OFS + 1:
133     case WPT_c1_OFS + 1:
134       wpt->shortname = cin;
135       break;
136 
137     case WPT_cA_OFS + 4:
138     case WPT_c4_OFS + 2:
139       garmin_fs_t::set_city(gmsd, cin);
140       break;
141 
142     case WPT_cA_OFS + 5:
143     case WPT_c4_OFS + 3:
144       garmin_fs_t::set_state(gmsd, cin);
145       break;
146 
147     case WPT_cA_OFS + 6:
148     case WPT_c4_OFS + 4:
149       garmin_fs_t::set_cc(gmsd, cin);
150       break;
151 
152     case WPT_cB_OFS + 1:
153     case WPT_c6_OFS + 2:
154       garmin_fs_t::set_facility(gmsd, cin);
155       break;
156 
157     case WPT_cB_OFS + 2:
158     case WPT_c6_OFS + 3:
159       garmin_fs_t::set_addr(gmsd, cin);
160       break;
161 
162     case WPT_cB_OFS + 3: /*cross road */
163     case WPT_c6_OFS + 4:
164       garmin_fs_t::set_cross_road(gmsd, cin);
165       break;
166 
167     case TRKPT__OFS + 2: /* altitude */
168     case WPT_cC_OFS + 1:
169     case WPT_c5_OFS + 1:
170     case WPT_c8_OFS + 1:
171       wpt->altitude = altf * atof(cin);
172       break;
173 
174     case TRKPT__OFS + 3: /* depth */
175     case WPT_cC_OFS + 2:
176     case WPT_c5_OFS + 2:
177     case WPT_c8_OFS + 2:
178       WAYPT_SET(wpt, depth, altf * atof(cin));
179       break;
180 
181     case TRKPT__OFS + 10: /* temperature */
182       if (*cin == '|') {
183         cin++;  /* in track points */
184       }
185       if (strcmp(cin, "1e25") == 0) {
186         break;
187       }
188       if (strcmp(cin, "1.0e25") == 0) {
189         break;
190       }
191       /* fallthrough */
192     case WPT_cD_OFS + 1:
193     case WPT_cB_OFS + 6:
194       WAYPT_SET(wpt, temperature, atof(cin));
195       break;
196 
197     case WAYPT__OFS + 6: /* proximity */
198     case WPT_cD_OFS + 2:
199       WAYPT_SET(wpt, proximity, atof(cin));
200       break;
201 
202     case WPT_cB_OFS + 5:
203     case WPT_cD_OFS + 3:
204       categories = atoi(cin);
205       if (categories != 0) {
206         garmin_fs_t::set_category(gmsd, atoi(cin));
207       }
208       break;
209 
210 #if 0
211 
212       /* currently unused */
213 
214     case TRKPT__OFS + 5: /* distance from previous point */
215     case TRKPT__OFS + 6: /* distance from segment start */
216     case TRKPT__OFS + 7: /* distance from start */
217     case TRKPT__OFS + 8: /* velocity from previous point */
218     case TRKPT__OFS + 9: /* time (in seconds) from previous point */
219       break;
220 
221     case WAYPT__OFS + 3: /* ignore color */
222       break;
223 
224     case WAYPT__OFS + 5: /* always '0' */
225       break;
226 
227     case TRKPT__OFS + 4:
228       if (case_ignore_strcmp(cin, "FT") == 0) ;
229       else if (case_ignore_strcmp(cin, "M") == 0) ;
230       else if (case_ignore_strcmp(cin, "SM") == 0) ;
231       else if (case_ignore_strcmp(cin, "NM") == 0) ;
232       else if (case_ignore_strcmp(cin, "KM") == 0) ;
233       break;
234 
235     case WPT_cB_OFS + 4: /* unknown (datatype) */
236       break;
237 
238     case WPT_cC_OFS + 3: /* waypt_class (always FF) */
239       break;
240 
241     case WPT_cC_OFS + 4: /* class & subclass */
242     case WPT_cC_OFS + 5:
243     case WPT_cC_OFS + 6:
244     case WPT_cC_OFS + 7:
245     case WPT_cC_OFS + 8:
246     case WPT_cC_OFS + 9:
247     case WPT_cC_OFS + 10:
248     case WPT_cC_OFS + 11:
249     case WPT_cC_OFS + 12:
250     case WPT_cC_OFS + 13:
251     case WPT_cC_OFS + 14:
252     case WPT_cC_OFS + 15:
253     case WPT_cC_OFS + 16:
254     case WPT_cC_OFS + 17:
255     case WPT_cC_OFS + 18:
256     case WPT_cC_OFS + 19:
257     case WPT_cC_OFS + 20:
258     case WPT_cC_OFS + 21:
259       break;
260 
261     case WPT_cC_OFS + 22:
262       /* distance */
263       break;
264 #endif
265     }
266   }
267 }
268 
269 static Waypoint*
parse_waypt(char * buff)270 parse_waypt(char* buff)
271 {
272   char* cin;
273   struct tm tm;
274 
275   auto* wpt = new Waypoint;
276   garmin_fs_t* gmsd = garmin_fs_alloc(-1);
277   wpt->fs.FsChainAdd(gmsd);
278 
279   if (gardown) {
280     cin = buff + 6;
281   } else {
282     /* We've seen waypoints with length of 14 and 15 !!! */
283     cin = buff + 15;
284     while ((cin > buff) && (! isspace(*cin))) {
285       cin--;
286     }
287   }
288 
289   while (isspace(*cin)) {
290     cin--;
291   }
292   if (cin >= buff) {
293     char*s = xstrndup(buff, cin - buff + 1);
294     wpt->shortname = s;
295     xfree(s);
296   }
297 
298   if (gardown) {
299     buff += 6;
300   } else {
301     buff += 15;
302   }
303   while (isspace(*buff)) {
304     buff++;
305   }
306 
307   buff += parse_coordinates(buff, datum, grid,
308                             &wpt->latitude, &wpt->longitude, MYNAME);
309   while (isspace(*buff)) {
310     buff++;
311   }
312 
313   memset(&tm, 0, sizeof(tm));
314   char* cerr = strptime(buff, "%a %b %d %H:%M:%S %Y", &tm);
315   if (cerr == nullptr) {
316     fatal(MYNAME ": Unable to convert date (%s)!\n", buff);
317   }
318   wpt->SetCreationTime(mkgmtime(&tm));
319 
320   /* go over time stamp */
321   int i = 5;
322   while (buff && i) {
323     i--;
324     buff = strchr(buff, ' ');
325     if (buff) {
326       buff++;
327     }
328   }
329   if (gardown && (buff == nullptr)) {
330     return wpt;
331   }
332   is_fatal((buff == nullptr), MYNAME ": Incomplete waypoint line!");
333 
334   while (isspace(*buff)) {
335     buff++;
336   }
337 
338   parse_line(buff, WAYPT__OFS, "^", wpt);
339 
340   return wpt;
341 }
342 
343 static Waypoint*
parse_trkpt(char * buff)344 parse_trkpt(char* buff)
345 {
346   auto* wpt = new Waypoint;
347   garmin_fs_t* gmsd = garmin_fs_alloc(-1);
348   wpt->fs.FsChainAdd(gmsd);
349 
350   parse_line(buff, TRKPT__OFS, ";", wpt);
351 
352   return wpt;
353 }
354 
355 /*
356  * parse_categories is currently only a dummy procedure.
357  * w'll need a central storage with binding to the module
358  * which has established a list of category names.
359  */
360 
361 static void
parse_categories(char * buff)362 parse_categories(char* buff)
363 {
364   char* cin;
365   int cat = 0;
366 
367   while ((cin = csv_lineparse(buff, ",", "", cat++))) {
368     uint16_t cx;
369 
370     buff = nullptr;
371 
372     cin = lrtrim(cin);
373     if (*cin == 0) {
374       continue;
375     }
376 
377     garmin_fs_convert_category(cin, &cx);
378   }
379 }
380 
381 
382 /* main functions */
383 
384 static void
rd_init(const QString & fname)385 rd_init(const QString& fname)
386 {
387   fin = gbfopen(fname, "rb", MYNAME);
388 
389   gardown = 1;
390   mode = wptdata;
391   grid = grid_lat_lon_dmm;
392   datum = DATUM_WGS84;
393   altf = 1;
394   event_ct = 0;
395 }
396 
397 static void
rd_deinit()398 rd_deinit()
399 {
400   gbfclose(fin);
401 }
402 
403 static void
data_read()404 data_read()
405 {
406   char* buff;
407   int line = 0;
408   Waypoint* wpt = nullptr;
409   route_head* head = nullptr;
410 
411   while ((buff = gbfgetstr(fin))) {
412     if ((line++ == 0) && fin->unicode) {
413       cet_convert_init(CET_CHARSET_UTF8, 1);
414     }
415 
416     char* cin = lrtrim(buff);
417     if (!*cin) {
418       continue;
419     }
420 
421     char* cdata = cin+1;
422     while (! isspace(*cdata)) {
423       cdata++;
424     }
425     while (isspace(*cdata)) {
426       cdata++;
427     }
428     if (! *cdata) {
429       continue;
430     }
431 
432     switch (*cin) {
433 
434     case '#': /* comment */
435       break;
436 
437     case 'A':
438       if (case_ignore_strncmp(cdata, "Meter", 5) == 0) {
439         altf = 1.0;
440       } else if (case_ignore_strncmp(cdata, "Feet", 4) == 0) {
441         altf = FEET_TO_METERS(1.0);
442       }
443       break;
444 
445     case 'C': /* categories */
446       parse_categories(cdata);
447       break;
448 
449     case 'D':
450       datum = gt_lookup_datum_index(cdata, MYNAME);
451       break;
452 
453     case 'I': /* event point */
454       wpt = new Waypoint;
455       cdata += parse_coordinates(cdata, datum, grid,
456                                  &wpt->latitude, &wpt->longitude, MYNAME);
457       wpt->shortname = QString("Event%1").arg(++event_ct);
458       while (isspace(*cdata)) {
459         cdata++;
460       }
461       if (*cdata == ';') {
462         cdata++;
463         wpt->icon_descr = gt_find_desc_from_icon_number(
464                             atoi(cdata), PCX);
465       }
466       waypt_add(wpt);
467       break;
468 
469     case 'M':
470       grid = gt_lookup_grid_type(cdata, MYNAME);
471       break;
472 
473     case 'P': /* proximity waypoint */
474     case 'W': /* normal waypoint */
475       wpt = parse_waypt(cin + 3);
476       if (wpt) {
477         if (mode == rtedata) {
478           route_add_wpt(head, wpt);
479         } else {
480           waypt_add(wpt);
481         }
482       }
483       break;
484 
485     case 'c': /* additional lines */
486       switch (*(cin+1)) {
487         int index;
488 
489       case 'A':
490       case 'B':
491       case 'C':
492       case 'D':
493 
494         index = WPT_cA_OFS + ((*(cin+1) - 'A') * 256);
495         parse_line(cdata, index, "|", wpt);
496         break;
497 
498       case '1':
499       case '2':
500       case '3':
501       case '4':
502       case '5':
503       case '6':
504       case '7':
505       case '8':
506 
507         index = WPT_c0_OFS + ((*(cin+1) - '0') * 256);
508         parse_line(cdata, index, ";", wpt);
509         break;
510 
511       case 'L':
512         waypt_add_url(wpt, cdata, nullptr);
513         break;
514 
515       default:
516         break;
517       }
518       break;
519 
520     case 'N':	/* track log header */
521       mode = trkdata;
522       head = new route_head;
523       cdata = strchr(cdata, '-');
524       if (cdata) {
525         while (isspace(*cdata)) {
526           cdata++;
527         }
528         if (*cdata) {
529           char* s = strrchr(cdata, ',');
530           if (s) {
531             *s = '\0';
532             s = strrchr(cdata, ',');
533             if (s) {
534               *s = '\0';
535               head->rte_name = cdata;
536             }
537           }
538         }
539       }
540       track_add_head(head);
541       break;
542 
543     case 'R':	/* route header */
544       mode = rtedata;
545       head = new route_head;
546       cdata += 3; /*skip route number */
547       if (*cdata) {
548         head->rte_name = cdata;
549       }
550       route_add_head(head);
551       break;
552 
553     case 'T':
554       wpt = parse_trkpt(cdata);
555       if (wpt) {
556         track_add_wpt(head, wpt);
557       }
558       break;
559 
560     case 'V':
561       if (strcmp(cin, G7T_HEADER) != 0) {
562         fatal(MYNAME ": Invalid version or invalid file!\n");
563       }
564       gardown = 0;
565       break;
566 
567     default:
568       break;
569     }
570   }
571 }
572 
573 /* --------------------------------------------------------------------------- */
574 
575 ff_vecs_t g7towin_vecs = {
576   ff_type_file,
577   { ff_cap_read, ff_cap_read, ff_cap_read },
578   rd_init,
579   nullptr,
580   rd_deinit,
581   nullptr,
582   data_read,
583   nullptr,
584   nullptr,
585   &g7towin_args,
586   CET_CHARSET_MS_ANSI, 0
587   , NULL_POS_OPS,
588   nullptr
589 };
590 
591 #endif /* CSVFMTS_ENABLED */
592