1 /*
2 
3     Support for MapSource Text Export (Tab delimited) files.
4 
5     Copyright (C) 2006 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., 59 Temple Place - Suite 330, Boston, MA 02111 USA
20 
21  */
22 
23 #include "defs.h"
24 
25 #if CSVFMTS_ENABLED
26 #include <math.h>
27 #include <time.h>
28 #include <ctype.h>
29 #include <errno.h>
30 #include <stdarg.h>
31 #include "cet_util.h"
32 #include "csv_util.h"
33 #include "garmin_fs.h"
34 #include "garmin_tables.h"
35 #include "grtcirc.h"
36 #include "inifile.h"
37 #include "jeeps/gpsmath.h"
38 #include "strptime.h"
39 
40 #define MYNAME "garmin_txt"
41 
42 typedef struct gtxt_flags_s {
43   unsigned int metric:1;
44   unsigned int celsius:1;
45   unsigned int utc:1;
46   unsigned int enum_waypoints:1;
47   unsigned int route_header_written:1;
48   unsigned int track_header_written:1;
49 } gtxt_flags_t;
50 
51 static gbfile* fin, *fout;
52 static route_head* current_trk, *current_rte;
53 static int waypoints;
54 static int routepoints;
55 static waypoint** wpt_a;
56 static int wpt_a_ct;
57 static grid_type grid_index;
58 static int datum_index;
59 static char* datum_str;
60 static int current_line;
61 static char* date_time_format = NULL;
62 static int precision = 3;
63 static time_t utc_offs = 0;
64 
65 static gtxt_flags_t gtxt_flags;
66 
67 typedef enum {
68   waypt_header = 0,
69   rtept_header,
70   trkpt_header,
71   route_header,
72   track_header,
73   unknown_header
74 } header_type;
75 
76 #if __cplusplus
77 inline header_type operator++(header_type& rs, int)
78 {
79   return rs = (header_type)((int)rs + 1);
80 }
81 
82 inline gt_display_modes_e  operator++(gt_display_modes_e& rs, int)
83 {
84   return rs = (gt_display_modes_e)((int)rs + 1);
85 }
86 #endif
87 
88 #define MAX_HEADER_FIELDS 36
89 
90 static char* header_lines[unknown_header + 1][MAX_HEADER_FIELDS];
91 static int header_fields[unknown_header + 1][MAX_HEADER_FIELDS];
92 static int header_ct[unknown_header + 1];
93 
94 #define GARMIN_UNKNOWN_ALT 1.0e25f
95 #define DEFAULT_DISPLAY garmin_display_symbol_and_name
96 #define DEFAULT_DATE_FORMAT "dd/mm/yyyy"
97 #define DEFAULT_TIME_FORMAT "HH:mm:ss"
98 
99 /* macros */
100 
101 #define IS_VALID_ALT(a) (((a) != unknown_alt) && ((a) < GARMIN_UNKNOWN_ALT))
102 #define DUPSTR(a) (((a) != NULL) && ((a)[0] != 0)) ? xstrdup((a)) : NULL
103 
104 static char* opt_datum = NULL;
105 static char* opt_dist = NULL;
106 static char* opt_temp = NULL;
107 static char* opt_date_format = NULL;
108 static char* opt_time_format = NULL;
109 static char* opt_precision = NULL;
110 static char* opt_utc = NULL;
111 static char* opt_grid = NULL;
112 
113 static
114 arglist_t garmin_txt_args[] = {
115   {"date",  &opt_date_format, "Read/Write date format (i.e. yyyy/mm/dd)", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
116   {"datum", &opt_datum, 	    "GPS datum (def. WGS 84)", "WGS 84", ARGTYPE_STRING, ARG_NOMINMAX},
117   {"dist",  &opt_dist,        "Distance unit [m=metric, s=statute]", "m", ARGTYPE_STRING, ARG_NOMINMAX},
118   {"grid",  &opt_grid,        "Write position using this grid.", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
119   {"prec",  &opt_precision,   "Precision of coordinates", "3", ARGTYPE_INT, ARG_NOMINMAX},
120   {"temp",  &opt_temp,        "Temperature unit [c=Celsius, f=Fahrenheit]", "c", ARGTYPE_STRING, ARG_NOMINMAX},
121   {"time",  &opt_time_format, "Read/Write time format (i.e. HH:mm:ss xx)", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
122   {"utc",   &opt_utc,         "Write timestamps with offset x to UTC time", NULL, ARGTYPE_INT, "-23", "+23"},
123   ARG_TERMINATOR
124 };
125 
126 typedef struct info_s {
127   double length;
128   time_t start;
129   time_t time;
130   double speed;
131   double total;
132   int count;
133   waypoint* prev_wpt;
134   waypoint* first_wpt;
135   waypoint* last_wpt;
136 } info_t;
137 
138 static info_t* route_info;
139 static int route_idx;
140 static info_t* cur_info;
141 
142 static char* headers[] = {
143   "Name\tDescription\tType\tPosition\tAltitude\tDepth\tProximity\tTemperature\t"
144   "Display Mode\tColor\tSymbol\tFacility\tCity\tState\tCountry\t"
145   "Date Modified\tLink\tCategories",
146   "Waypoint Name\tDistance\tLeg Length\tCourse",
147   "Position\tTime\tAltitude\tDepth\tTemperature\tLeg Length\tLeg Time\tLeg Speed\tLeg Course",
148   "Name\tLength\tCourse\tWaypoints\tLink",
149   "Name\tStart Time\tElapsed Time\tLength\tAverage Speed\tLink",
150   NULL
151 };
152 
153 /* helpers */
154 
155 static char*
get_option_val(char * option,char * def)156 get_option_val(char* option, char* def)
157 {
158   char* c = (option != NULL) ? option : def;
159   return c;
160 }
161 
162 static void
init_date_and_time_format(void)163 init_date_and_time_format(void)
164 {
165   char* f, *c;
166 
167   f = get_option_val(opt_date_format, DEFAULT_DATE_FORMAT);
168   date_time_format = convert_human_date_format(f);
169 
170   date_time_format = xstrappend(date_time_format, " ");
171 
172   f = get_option_val(opt_time_format, DEFAULT_TIME_FORMAT);
173   c = convert_human_time_format(f);
174   date_time_format = xstrappend(date_time_format, c);
175   xfree(c);
176 }
177 
178 static void
convert_datum(const waypoint * wpt,double * dest_lat,double * dest_lon)179 convert_datum(const waypoint* wpt, double* dest_lat, double* dest_lon)
180 {
181   double alt;
182 
183   if (datum_index == DATUM_WGS84) {
184     *dest_lat = wpt->latitude;
185     *dest_lon = wpt->longitude;
186   } else GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0.0,
187                                            dest_lat, dest_lon, &alt, datum_index);
188 }
189 
190 /* WRITER *****************************************************************/
191 
192 /* Waypoint preparation */
193 
194 static void
enum_waypt_cb(const waypoint * wpt)195 enum_waypt_cb(const waypoint* wpt)
196 {
197   garmin_fs_p gmsd;
198   int wpt_class;
199 
200   gmsd = GMSD_FIND(wpt);
201   wpt_class = GMSD_GET(wpt_class, 0);
202   if (wpt_class < 0x80) {
203     int i;
204 
205     if (gtxt_flags.enum_waypoints) {		/* enumerate only */
206       waypoints++;
207       return;
208     }
209     for (i = 0; i < wpt_a_ct; i++) {		/* check for duplicates */
210       waypoint* tmp = wpt_a[i];
211       if (case_ignore_strcmp(tmp->shortname, wpt->shortname) == 0) {
212         wpt_a[i] = (waypoint*)wpt;
213         waypoints--;
214         return;
215 
216       }
217     }
218     wpt_a[wpt_a_ct++] = (waypoint*)wpt;
219   }
220 
221 }
222 
223 static int
sort_waypt_cb(const void * a,const void * b)224 sort_waypt_cb(const void* a, const void* b)
225 {
226   const waypoint* wa = *(waypoint**)a;
227   const waypoint* wb = *(waypoint**)b;
228 
229   return case_ignore_strcmp(wa->shortname, wb->shortname);
230 }
231 
232 
233 /* common route and track pre-work */
234 
235 static void
prework_hdr_cb(const route_head * rte)236 prework_hdr_cb(const route_head* rte)
237 {
238   cur_info = &route_info[route_idx];
239   cur_info->prev_wpt = NULL;
240   cur_info->length = 0;
241   cur_info->time = 0;
242 }
243 
244 static void
prework_tlr_cb(const route_head * rte)245 prework_tlr_cb(const route_head* rte)
246 {
247   cur_info->last_wpt = cur_info->prev_wpt;
248   route_idx++;
249 }
250 
251 static void
prework_wpt_cb(const waypoint * wpt)252 prework_wpt_cb(const waypoint* wpt)
253 {
254   waypoint* prev = cur_info->prev_wpt;
255 
256   if (prev != NULL) {
257     cur_info->time += (wpt->creation_time - prev->creation_time);
258     cur_info->length += waypt_distance_ex(prev, wpt);
259   } else {
260     cur_info->first_wpt = (waypoint*)wpt;
261     cur_info->start = wpt->creation_time;
262   }
263   cur_info->prev_wpt = (waypoint*)wpt;
264   cur_info->count++;
265   routepoints++;
266 }
267 
268 
269 /* output helpers */
270 
271 static void
print_position(const waypoint * wpt)272 print_position(const waypoint* wpt)
273 {
274   int valid = 1;
275   double lat, lon, north, east;
276   char latsig, lonsig;
277   double  latmin, lonmin, latsec, lonsec;
278   int     latint, lonint, zone;
279   char map[3], zonec;
280 
281   convert_datum(wpt, &lat, &lon);
282 
283   /* ----------------------------------------------------------------------------*/
284   /*            the following code is from pretty_deg_format (util.c)            */
285   /* ----------------------------------------------------------------------------*/
286   /* !ToDo! generate common code for calculating of degrees, minutes and seconds */
287   /* ----------------------------------------------------------------------------*/
288 
289   latsig = lat < 0 ? 'S':'N';
290   lonsig = lon < 0 ? 'W':'E';
291   latint = abs((int) lat);
292   lonint = abs((int) lon);
293   latmin = 60.0 * (fabs(lat) - latint);
294   lonmin = 60.0 * (fabs(lon) - lonint);
295   latsec = 60.0 * (latmin - floor(latmin));
296   lonsec = 60.0 * (lonmin - floor(lonmin));
297 
298   switch (grid_index) {
299 
300   case grid_lat_lon_ddd:
301 
302     gbfprintf(fout, "%c%0.*f %c%0.*f\t",
303               latsig, precision, fabs(lat),
304               lonsig, precision, fabs(lon));
305     break;
306 
307   case grid_lat_lon_dmm:
308 
309     gbfprintf(fout, "%c%d %0*.*f %c%d %0*.*f\t",
310               latsig, latint, precision + 3, precision, latmin,
311               lonsig, lonint, precision + 3, precision, lonmin);
312     break;
313 
314   case grid_lat_lon_dms:
315 
316     gbfprintf(fout, "%c%d %d %.*f %c%d %d %.*f\t",
317               latsig, latint, (int)latmin, precision, latsec,
318               lonsig, lonint, (int)lonmin, precision, lonsec);
319     break;
320 
321   case grid_bng:
322 
323     valid = GPS_Math_WGS84_To_UKOSMap_M(wpt->latitude, wpt->longitude, &east, &north, map);
324     if (valid) {
325       gbfprintf(fout, "%s %5.0f %5.0f\t", map, east, north);
326     }
327     break;
328 
329   case grid_utm:
330 
331     valid = GPS_Math_Known_Datum_To_UTM_EN(lat, lon,
332                                            &east, &north, &zone, &zonec, datum_index);
333     if (valid) {
334       gbfprintf(fout, "%02d %c %.0f %.0f\t", zone, zonec, east, north);
335     }
336     break;
337 
338   case grid_swiss:
339 
340     valid = GPS_Math_WGS84_To_Swiss_EN(wpt->latitude, wpt->longitude, &east, &north);
341     if (valid) {
342       gbfprintf(fout, "%.f %.f\t", east, north);
343     }
344     break;
345 
346   default:
347     fatal("ToDo\n");
348   }
349 
350   if (! valid) {
351     gbfprintf(fout, "#####\n");
352     fatal(MYNAME ": %s (%s) is outside of convertable area \"%s\"!\n",
353           wpt->shortname ? wpt->shortname : "Waypoint",
354           pretty_deg_format(wpt->latitude, wpt->longitude, 'd', NULL, 0),
355           gt_get_mps_grid_longname(grid_index, MYNAME));
356   }
357 }
358 
359 static void
print_date_and_time(const time_t time,const int time_only)360 print_date_and_time(const time_t time, const int time_only)
361 {
362   struct tm tm;
363   char tbuf[32];
364 
365   if (time < 0) {
366     gbfprintf(fout, "\t");
367     return;
368   }
369   if (time_only) {
370     tm = *gmtime(&time);
371     snprintf(tbuf, sizeof(tbuf), "%d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
372     gbfprintf(fout, "%s", tbuf);
373   } else if (time != 0) {
374     if (gtxt_flags.utc) {
375       time_t t = time + utc_offs;
376       tm = *gmtime(&t);
377     } else {
378       tm = *localtime(&time);
379     }
380     strftime(tbuf, sizeof(tbuf), date_time_format, &tm);
381     gbfprintf(fout, "%s ", tbuf);
382   }
383   gbfprintf(fout, "\t");
384 }
385 
386 static void
print_categories(gbuint16 categories)387 print_categories(gbuint16 categories)
388 {
389   int i, count;
390   char* c;
391 
392   if (categories == 0) {
393     return;
394   }
395 
396   count = 0;
397   for (i = 0; i < 16; i++) {
398     if ((categories & 1) != 0) {
399       if (global_opts.inifile != NULL) {
400         char key[3];
401         snprintf(key, sizeof(key), "%d", i + 1);
402         c = inifile_readstr(global_opts.inifile, GMSD_SECTION_CATEGORIES, key);
403       } else {
404         c = NULL;
405       }
406 
407       gbfprintf(fout, "%s", (count++ > 0) ? "," : "");
408       if (c == NULL) {
409         gbfprintf(fout, "Category %d", i+1);
410       }
411 //				gbfprintf(fout, "%s", gps_categories[i]);
412       else {
413         gbfprintf(fout, "%s", c);
414       }
415 
416     }
417     categories = categories >> 1;
418   }
419 }
420 
421 static void
print_course(const waypoint * A,const waypoint * B)422 print_course(const waypoint* A, const waypoint* B)		/* seems to be okay */
423 {
424   if ((A != NULL) && (B != NULL) && (A != B)) {
425     int course;
426     course = si_round(waypt_course(A, B));
427     cet_gbfprintf(fout, &cet_cs_vec_cp1252, "%d%c true", course, 0xB0);
428   }
429 }
430 
431 static void
print_distance(const double distance,const int no_scale,const int with_tab,const int decis)432 print_distance(const double distance, const int no_scale, const int with_tab, const int decis)
433 {
434   double dist = distance;
435 
436   if (gtxt_flags.metric == 0) {
437     dist = METERS_TO_FEET(dist);
438 
439     if ((dist < 5280) || no_scale) {
440       gbfprintf(fout, "%.*f ft", decis, dist);
441     } else {
442       dist = METERS_TO_MILES(distance);
443       if (dist < (double)100) {
444         gbfprintf(fout, "%.1f mi", dist);
445       } else {
446         gbfprintf(fout, "%d mi", si_round(dist));
447       }
448     }
449   } else {
450     if ((dist < 1000) || no_scale) {
451       gbfprintf(fout, "%.*f m", decis, dist);
452     } else {
453       dist = dist / (double)1000.0;
454       if (dist < (double)100) {
455         gbfprintf(fout, "%.1f km", dist);
456       } else {
457         gbfprintf(fout, "%d km", si_round(dist));
458       }
459     }
460   }
461   if (with_tab) {
462     gbfprintf(fout, "\t");
463   }
464 }
465 
466 static void
print_speed(double * distance,time_t * time)467 print_speed(double* distance, time_t* time)
468 {
469   int idist;
470   double dist = *distance;
471   char* unit;
472 
473   if (!gtxt_flags.metric) {
474     dist = METERS_TO_MILES(dist) * 1000.0;
475     unit = "mph";
476   } else {
477     unit = "kph";
478   }
479   idist = si_round(dist);
480 
481   if ((*time != 0) && (idist > 0)) {
482     double speed = MPS_TO_KPH(dist / (double)*time);
483     int ispeed = si_round(speed);
484 
485     if (speed < (double)0.01) {
486       gbfprintf(fout, "0 %s", unit);
487     } else if (ispeed < 2) {
488       gbfprintf(fout, "%.1f %s", speed, unit);
489     } else {
490       gbfprintf(fout, "%d %s", ispeed, unit);
491     }
492   } else {
493     gbfprintf(fout, "0 %s", unit);
494   }
495   gbfprintf(fout, "\t");
496 }
497 
498 static void
print_temperature(const float temperature)499 print_temperature(const float temperature)
500 {
501   if (gtxt_flags.celsius) {
502     gbfprintf(fout, "%.f C", temperature);
503   } else {
504     gbfprintf(fout, "%.f F", (temperature * 1.8) + 32);
505   }
506 }
507 
508 static void
print_string(const char * fmt,const char * string)509 print_string(const char* fmt, const char* string)
510 {
511   char* c;
512   char* buff;
513 
514   buff = xstrdup(string);
515   /* remove unwanted characters from source string */
516   for (c = buff; *c; c++) {
517     if (iscntrl(*c)) {
518       *c = ' ';
519     }
520   }
521   gbfprintf(fout, fmt, buff);
522   xfree(buff);
523 }
524 
525 
526 /* main cb's */
527 
528 static void
write_waypt(const waypoint * wpt)529 write_waypt(const waypoint* wpt)
530 {
531   unsigned char wpt_class;
532   garmin_fs_p gmsd;
533   char* wpt_type;
534   char* dspl_mode;
535   const char* country;
536   double x;
537   int i, icon, dynamic;
538   char* icon_descr;
539 
540   gmsd = GMSD_FIND(wpt);
541 
542   i = GMSD_GET(display, 0);
543   if (i > GT_DISPLAY_MODE_MAX) {
544     i = 0;
545   }
546   dspl_mode = gt_display_mode_names[i];
547 
548   wpt_class = GMSD_GET(wpt_class, 0);
549   if (wpt_class <= gt_waypt_class_map_line) {
550     wpt_type = gt_waypt_class_names[wpt_class];
551   } else {
552     wpt_type = gt_waypt_class_names[0];
553   }
554 
555   gbfprintf(fout, "Waypoint\t%s\t", (wpt->shortname) ? wpt->shortname : "");
556   if (wpt_class <= gt_waypt_class_airport_ndb) {
557     char* temp = wpt->notes;
558     if (temp == NULL) {
559       if (wpt->description && (strcmp(wpt->description, wpt->shortname) != 0)) {
560         temp = wpt->description;
561       } else {
562         temp = "";
563       }
564     }
565     print_string("%s\t", temp);
566   } else {
567     gbfprintf(fout, "\t");
568   }
569   gbfprintf(fout, "%s\t", wpt_type);
570 
571   print_position(wpt);
572 
573   if IS_VALID_ALT(wpt->altitude) {
574     print_distance(wpt->altitude, 1, 0, 0);
575   }
576   gbfprintf(fout, "\t");
577 
578   x = WAYPT_GET(wpt, depth, unknown_alt);
579   if (x != unknown_alt) {
580     print_distance(x, 1, 0, 1);
581   }
582   gbfprintf(fout, "\t");
583 
584   x = WAYPT_GET(wpt, proximity, unknown_alt);
585   if (x != unknown_alt) {
586     print_distance(x, 0, 0, 0);
587   }
588   gbfprintf(fout, "\t");
589 
590   x = WAYPT_GET(wpt, temperature, -999);
591   if (x != -999) {
592     print_temperature(x);
593   }
594   gbfprintf(fout, "\t%s\t", dspl_mode);
595 
596   gbfprintf(fout, "Unknown\t"); 				/* Color is fixed: Unknown */
597 
598   icon = GMSD_GET(icon, -1);
599   if (icon == -1) {
600     icon = gt_find_icon_number_from_desc(wpt->icon_descr, GDB);
601   }
602   icon_descr = gt_find_desc_from_icon_number(icon, GDB, &dynamic);
603   print_string("%s\t", icon_descr);
604   if (dynamic) {
605     xfree(icon_descr);
606   }
607 
608   print_string("%s\t", GMSD_GET(facility, ""));
609   print_string("%s\t", GMSD_GET(city, ""));
610   print_string("%s\t", GMSD_GET(state, ""));
611   country = gt_get_icao_country(GMSD_GET(cc, ""));
612   print_string("%s\t", (country != NULL) ? country : "");
613   print_date_and_time(wpt->creation_time, 0);
614   print_string("%s\t", wpt->url ? wpt->url : "");
615   print_categories(GMSD_GET(category, 0));
616 
617   gbfprintf(fout, "\r\n");
618 }
619 
620 static void
route_disp_hdr_cb(const route_head * rte)621 route_disp_hdr_cb(const route_head* rte)
622 {
623   current_trk = (route_head*)rte;
624   cur_info = &route_info[route_idx];
625   cur_info->prev_wpt = NULL;
626   cur_info->total = 0;
627   if (rte->rte_waypt_ct <= 0) {
628     return;
629   }
630 
631   if (!gtxt_flags.route_header_written) {
632     gtxt_flags.route_header_written = 1;
633     gbfprintf(fout, "\r\n\r\nHeader\t%s\r\n", headers[route_header]);
634   }
635 
636   print_string("\r\nRoute\t%s\t", current_trk->rte_name ? current_trk->rte_name : "");
637   print_distance(cur_info->length, 0, 1, 0);
638   print_course(cur_info->first_wpt, cur_info->last_wpt);
639   gbfprintf(fout, "\t%d waypoints\t", cur_info->count);
640   print_string("%s\r\n", rte->rte_url ? rte->rte_url : "");
641   gbfprintf(fout, "\r\nHeader\t%s\r\n\r\n", headers[rtept_header]);
642 }
643 
644 static void
route_disp_tlr_cb(const route_head * rte)645 route_disp_tlr_cb(const route_head* rte)
646 {
647   route_idx++;
648 }
649 
650 static void
route_disp_wpt_cb(const waypoint * wpt)651 route_disp_wpt_cb(const waypoint* wpt)
652 {
653   waypoint* prev = cur_info->prev_wpt;
654 
655   gbfprintf(fout, "Route Waypoint\t");
656   gbfprintf(fout, "%s\t", wpt->shortname);
657 
658   if (prev != NULL) {
659     double dist = waypt_distance_ex(prev, wpt);
660     cur_info->total += dist;
661     print_distance(cur_info->total, 0, 1, 0);
662     print_distance(dist, 0, 1, 0);
663     print_course(prev, wpt);
664   } else {
665     print_distance(0, 1, 0, 0);
666   }
667 
668   gbfprintf(fout, "\r\n");
669 
670   cur_info->prev_wpt = (waypoint*)wpt;
671 }
672 
673 static void
track_disp_hdr_cb(const route_head * track)674 track_disp_hdr_cb(const route_head* track)
675 {
676   cur_info = &route_info[route_idx];
677   cur_info->prev_wpt = NULL;
678   cur_info->total = 0;
679   current_trk = (route_head*)track;
680   if (track->rte_waypt_ct <= 0) {
681     return;
682   }
683 
684   if (!gtxt_flags.track_header_written) {
685     gtxt_flags.track_header_written = 1;
686     gbfprintf(fout, "\r\n\r\nHeader\t%s\r\n", headers[track_header]);
687   }
688 
689   print_string("\r\nTrack\t%s\t", current_trk->rte_name ? current_trk->rte_name : "");
690   print_date_and_time(cur_info->start, 0);
691   print_date_and_time(cur_info->time, 1);
692   print_distance(cur_info->length, 0, 1, 0);
693   print_speed(&cur_info->length, &cur_info->time);
694   print_string("%s", (track->rte_url != NULL) ? track->rte_url : "");
695   gbfprintf(fout, "\r\n\r\nHeader\t%s\r\n\r\n", headers[trkpt_header]);
696 }
697 
698 static void
track_disp_tlr_cb(const route_head * track)699 track_disp_tlr_cb(const route_head* track)
700 {
701   route_idx++;
702 }
703 
704 static void
track_disp_wpt_cb(const waypoint * wpt)705 track_disp_wpt_cb(const waypoint* wpt)
706 {
707   waypoint* prev = cur_info->prev_wpt;
708   time_t delta;
709   double dist, depth;
710 
711   gbfprintf(fout, "Trackpoint\t");
712 
713   print_position(wpt);
714   print_date_and_time(wpt->creation_time, 0);
715   if IS_VALID_ALT(wpt->altitude) {
716     print_distance(wpt->altitude, 1, 0, 0);
717   }
718 
719   gbfprintf(fout, "\t");
720   depth = WAYPT_GET(wpt, depth, unknown_alt);
721   if (depth != unknown_alt) {
722     print_distance(depth, 1, 0, 1);
723   }
724 
725   if (prev != NULL) {
726     float temp;
727     gbfprintf(fout, "\t");
728     delta = wpt->creation_time - prev->creation_time;
729     temp = WAYPT_GET(wpt, temperature, -999);
730     if (temp != -999) {
731       print_temperature(temp);
732     }
733     gbfprintf(fout, "\t");
734     dist = waypt_distance_ex(prev, wpt);
735     print_distance(dist, 0, 1, 0);
736     print_date_and_time(delta, 1);
737     print_speed(&dist, &delta);
738     print_course(prev, wpt);
739   }
740   gbfprintf(fout, "\r\n");
741 
742   cur_info->prev_wpt = (waypoint*)wpt;
743 }
744 
745 /*******************************************************************************
746 * %%%        global callbacks called by gpsbabel main process              %%% *
747 *******************************************************************************/
748 
749 static void
garmin_txt_wr_init(const char * fname)750 garmin_txt_wr_init(const char* fname)
751 {
752   char* grid_str;
753 
754   memset(&gtxt_flags, 0, sizeof(gtxt_flags));
755 
756   fout = gbfopen(fname, "wb", MYNAME);
757 
758   gtxt_flags.metric = (toupper(*get_option_val(opt_dist, "m")) == 'M');
759   gtxt_flags.celsius = (toupper(*get_option_val(opt_temp, "c")) == 'C');
760   init_date_and_time_format();
761   if (opt_precision) {
762     precision = atoi(opt_precision);
763     is_fatal(precision < 0, MYNAME ": Invalid precision (%s)!", opt_precision);
764   }
765 
766   datum_str = get_option_val(opt_datum, NULL);
767   grid_str = get_option_val(opt_grid, NULL);
768 
769   grid_index = grid_lat_lon_dmm;
770   if (grid_str != NULL) {
771     int i;
772 
773     if (sscanf(grid_str, "%d", &i)) {
774       grid_index = (grid_type) i;
775       if ((grid_index < GRID_INDEX_MIN) || (grid_index > GRID_INDEX_MAX))
776         fatal(MYNAME ": Grid index out of range (%d..%d)!",
777               (int)GRID_INDEX_MIN, (int)GRID_INDEX_MAX);
778     } else {
779       grid_index = gt_lookup_grid_type(grid_str, MYNAME);
780     }
781   }
782 
783   switch (grid_index) {
784   case grid_bng: /* force datum to "Ord Srvy Grt Britn" */
785     datum_index = DATUM_OSGB36;
786     break;
787   case grid_swiss: /* force datum to "Ord Srvy Grt Britn" */
788     datum_index = DATUM_WGS84;
789     break;
790   default:
791     datum_index = gt_lookup_datum_index(datum_str, MYNAME);
792   }
793 
794   if (opt_utc != NULL) {
795     if (case_ignore_strcmp(opt_utc, "utc") == 0) {
796       utc_offs = 0;
797     } else {
798       utc_offs = atoi(opt_utc);
799     }
800     utc_offs *= (60 * 60);
801     gtxt_flags.utc = 1;
802   }
803 }
804 
805 static void
garmin_txt_wr_deinit(void)806 garmin_txt_wr_deinit(void)
807 {
808   gbfclose(fout);
809   xfree(date_time_format);
810 }
811 
812 static void
garmin_txt_write(void)813 garmin_txt_write(void)
814 {
815   char* grid_str, *c;
816   const char* datum_str;
817 
818   grid_str = xstrdup(gt_get_mps_grid_longname(grid_index, MYNAME));
819   while ((c = strchr(grid_str, '*'))) {
820     *c = 0xB0;  /* degree sign */
821   }
822   cet_gbfprintf(fout, &cet_cs_vec_cp1252, "Grid\t%s\r\n", grid_str);
823   xfree(grid_str);
824 
825   datum_str = gt_get_mps_datum_name(datum_index);
826   gbfprintf(fout, "Datum\t%s\r\n\r\n", datum_str);
827 
828   waypoints = 0;
829   gtxt_flags.enum_waypoints = 1;			/* enum all waypoints */
830   waypt_disp_all(enum_waypt_cb);
831   route_disp_all(NULL, NULL, enum_waypt_cb);
832   gtxt_flags.enum_waypoints = 0;
833 
834   if (waypoints > 0) {
835     int i;
836 
837     wpt_a_ct = 0;
838     wpt_a = (waypoint**)xcalloc(waypoints, sizeof(*wpt_a));
839     waypt_disp_all(enum_waypt_cb);
840     route_disp_all(NULL, NULL, enum_waypt_cb);
841     qsort(wpt_a, waypoints, sizeof(*wpt_a), sort_waypt_cb);
842 
843     gbfprintf(fout, "Header\t%s\r\n\r\n", headers[waypt_header]);
844     for (i = 0; i < waypoints; i++) {
845       waypoint* wpt = wpt_a[i];
846       write_waypt(wpt);
847     }
848     xfree(wpt_a);
849 
850     route_idx = 0;
851     route_info = (info_t*) xcalloc(route_count(), sizeof(struct info_s));
852     routepoints = 0;
853     route_disp_all(prework_hdr_cb, prework_tlr_cb, prework_wpt_cb);
854     if (routepoints > 0) {
855       route_idx = 0;
856       route_disp_all(route_disp_hdr_cb, route_disp_tlr_cb, route_disp_wpt_cb);
857     }
858     xfree(route_info);
859   }
860 
861   route_idx = 0;
862   route_info = (info_t*) xcalloc(track_count(), sizeof(struct info_s));
863   routepoints = 0;
864   track_disp_all(prework_hdr_cb, prework_tlr_cb, prework_wpt_cb);
865 
866   if (routepoints > 0) {
867     route_idx = 0;
868     track_disp_all(track_disp_hdr_cb, track_disp_tlr_cb, track_disp_wpt_cb);
869   }
870   xfree(route_info);
871 }
872 
873 /* READER *****************************************************************/
874 
875 /* helpers */
876 
877 static void
free_header(const header_type ht)878 free_header(const header_type ht)
879 {
880   int i;
881 
882   for (i = 0; i < MAX_HEADER_FIELDS; i++) {
883     char* c = header_lines[ht][i];
884     if (c != NULL) {
885       xfree(c);
886       header_lines[ht][i] = NULL;
887     }
888   }
889   header_ct[ht] = 0;
890   memset(header_fields[ht], 0, sizeof(header_fields[ht]));
891 }
892 
893 /* data parsers */
894 
895 static int
parse_date_and_time(char * str,time_t * value)896 parse_date_and_time(char* str, time_t* value)
897 {
898   struct tm tm;
899   char* cerr, *cin;
900 
901   memset(&tm, 0, sizeof(tm));
902   cin = lrtrim(str);
903   if (*cin == '\0') {
904     return 0;
905   }
906 
907   cerr = strptime(cin, date_time_format, &tm);
908   if (cerr == NULL) {
909     cerr = strptime(cin, "%m/%d/%Y %I:%M:%S %p", &tm);
910     is_fatal(cerr == NULL, MYNAME ": Invalid date or/and time \"%s\" at line %d!", cin, current_line);
911   }
912 
913 //	printf(MYNAME "_parse_date_and_time: %02d.%02d.%04d, %02d:%02d:%02d\n",
914 //		tm.tm_mday, tm.tm_mon+1, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
915 
916   *value = mklocaltime(&tm);
917   return 1;
918 }
919 
920 static gbuint16
parse_categories(const char * str)921 parse_categories(const char* str)
922 {
923   char buff[256];
924   gbuint16 val;
925   gbuint16 res = 0;
926   char* cin, *cx;
927 
928   if (*str == '\0') {
929     return 0;
930   }
931 
932   strncpy(buff, str, sizeof(buff));
933   cin = lrtrim(buff);
934   if (*cin == '\0') {
935     return 0;
936   }
937 
938   strcat(cin, ",");
939 
940   while ((cx = strchr(cin, ','))) {
941     *cx++ = '\0';
942     cin = lrtrim(cin);
943     if (*cin != '\0') {
944       if (!garmin_fs_convert_category(cin, &val)) {
945         warning(MYNAME ": Unable to convert category \"%s\" at line %d!\n", cin, current_line);
946       } else {
947         res = res | val;
948       }
949     }
950     cin = cx;
951   }
952   return res;
953 }
954 
955 static int
parse_temperature(const char * str,double * temperature)956 parse_temperature(const char* str, double* temperature)
957 {
958   double value;
959   unsigned char unit;
960 
961   if ((str == NULL) || (*str == '\0')) {
962     return 0;
963   }
964 
965   if (sscanf(str, "%lf %c", &value, &unit) == 2) {
966     unit = toupper(unit);
967     switch (unit) {
968     case 'C':
969       *temperature = value;
970       break;
971     case 'F':
972       *temperature = FAHRENHEIT_TO_CELSIUS(value);
973       break;
974     default:
975       fatal(MYNAME ": Unknown temperature unit \"%c\" at line %d!\n", unit, current_line);
976     }
977     return 1;
978   } else {
979     fatal(MYNAME ": Invalid temperature \"%s\" at line %d!\n", str, current_line);
980   }
981   return 0;
982 }
983 
984 static void
parse_header(void)985 parse_header(void)
986 {
987   char* str;
988   int column = -1;
989 
990   free_header(unknown_header);
991 
992   while ((str = csv_lineparse(NULL, "\t", "", column++))) {
993     header_lines[unknown_header][column] = strupper(xstrdup(str));
994     header_ct[unknown_header]++;
995     if (header_ct[unknown_header] >= MAX_HEADER_FIELDS) {
996       break;
997     }
998   }
999 }
1000 
1001 static int
parse_display(const char * str,int * val)1002 parse_display(const char* str, int* val)
1003 {
1004   gt_display_modes_e i;
1005 
1006   if ((str == NULL) || (*str == '\0')) {
1007     return 0;
1008   }
1009 
1010   for (i = GT_DISPLAY_MODE_MIN; i <= GT_DISPLAY_MODE_MAX; i++) {
1011     if (case_ignore_strcmp(str, gt_display_mode_names[i]) == 0) {
1012       *val = i;
1013       return 1;
1014     }
1015   }
1016   warning(MYNAME ": Unknown display mode \"%s\" at line %d.\n", str, current_line);
1017   return 0;
1018 }
1019 
1020 static void
bind_fields(const header_type ht)1021 bind_fields(const header_type ht)
1022 {
1023   int i;
1024   char* fields, *c;
1025 
1026   is_fatal((grid_index < 0) || (datum_index < 0), MYNAME ": Incomplete or invalid file header!");
1027 
1028   if (header_ct[unknown_header] <= 0) {
1029     return;
1030   }
1031   free_header(ht);
1032 
1033   /* make a copy of headers[ht], uppercase, replace "\t" with "\0" */
1034 
1035   i = strlen(headers[ht]);
1036   fields = (char*) xmalloc(i + 2);
1037   strcpy(fields, headers[ht]);
1038   strcat(fields, "\t");
1039   c = strupper(fields);
1040   while ((c = strchr(c, '\t'))) {
1041     *c++ = '\0';
1042   }
1043 
1044   for (i = 0; i < header_ct[unknown_header]; i++) {
1045     char* name;
1046     int field_no;
1047     name = header_lines[ht][i] = header_lines[unknown_header][i];
1048     header_lines[unknown_header][i] = NULL;
1049 
1050     c = fields;
1051     field_no = 1;
1052     while (*c) {
1053       if (strcmp(c, name) == 0) {
1054         header_fields[ht][i] = field_no;
1055 #if 0
1056         printf("Binding field \"%s\" to internal number %d (%d,%d)\n", name, field_no, ht, i);
1057 #endif
1058         break;
1059       }
1060       field_no++;
1061       c = c + strlen(c) + 1;
1062     }
1063   }
1064   header_ct[unknown_header] = 0;
1065   xfree(fields);
1066 }
1067 
1068 static void
parse_grid(void)1069 parse_grid(void)
1070 {
1071   char* str = csv_lineparse(NULL, "\t", "", 1);
1072 
1073   if (str != NULL) {
1074     if (strstr(str, "dd.ddddd") != 0) {
1075       grid_index = grid_lat_lon_ddd;
1076     } else if (strstr(str, "mm.mmm") != 0) {
1077       grid_index = grid_lat_lon_dmm;
1078     } else if (strstr(str, "mm'ss.s") != 0) {
1079       grid_index = grid_lat_lon_dms;
1080     } else {
1081       grid_index = gt_lookup_grid_type(str, MYNAME);
1082     }
1083   } else {
1084     fatal(MYNAME ": Missing grid headline!\n");
1085   }
1086 }
1087 
1088 static void
parse_datum(void)1089 parse_datum(void)
1090 {
1091   char* str = csv_lineparse(NULL, "\t", "", 1);
1092 
1093   if (str != NULL) {
1094     datum_index = gt_lookup_datum_index(str, MYNAME);
1095   } else {
1096     fatal(MYNAME ": Missing GPS datum headline!\n");
1097   }
1098 }
1099 
1100 static void
parse_waypoint(void)1101 parse_waypoint(void)
1102 {
1103   char* str;
1104   int column = -1;
1105   waypoint* wpt;
1106   garmin_fs_p gmsd = NULL;
1107 
1108   bind_fields(waypt_header);
1109 
1110   wpt = waypt_new();
1111   gmsd = garmin_fs_alloc(-1);
1112   fs_chain_add(&wpt->fs, (format_specific_data*) gmsd);
1113 
1114   while ((str = csv_lineparse(NULL, "\t", "", column++))) {
1115     int i, dynamic;
1116     double d;
1117     int field_no = header_fields[waypt_header][column];
1118 
1119     switch (field_no) {
1120     case  1:
1121       wpt->shortname = DUPSTR(str);
1122       break;
1123     case  2:
1124       wpt->notes = DUPSTR(str);
1125       break;
1126     case  3:
1127       for (i = 0; i <= gt_waypt_class_map_line; i++) {
1128         if (case_ignore_strcmp(str, gt_waypt_class_names[i]) == 0) {
1129           GMSD_SET(wpt_class, i);
1130           break;
1131         }
1132       }
1133       break;
1134     case  4:
1135       parse_coordinates(str, datum_index, grid_index,
1136                         &wpt->latitude, &wpt->longitude, MYNAME);
1137       break;
1138     case  5:
1139       if (parse_distance(str, &d, 1, MYNAME)) {
1140         wpt->altitude = d;
1141       }
1142       break;
1143     case  6:
1144       if (parse_distance(str, &d, 1, MYNAME)) {
1145         WAYPT_SET(wpt, depth, d);
1146       }
1147       break;
1148     case  7:
1149       if (parse_distance(str, &d, 1, MYNAME)) {
1150         WAYPT_SET(wpt, proximity, d);
1151       }
1152       break;
1153     case  8:
1154       if (parse_temperature(str, &d)) {
1155         WAYPT_SET(wpt, temperature, d);
1156       }
1157       break;
1158     case  9:
1159       if (parse_display(str, &i)) {
1160         GMSD_SET(display, i);
1161       }
1162       break;
1163     case 10:
1164       break;	/* skip color */
1165     case 11:
1166       i = gt_find_icon_number_from_desc(str, GDB);
1167       GMSD_SET(icon, i);
1168       wpt->icon_descr = gt_find_desc_from_icon_number(i, GDB, &dynamic);
1169       wpt->wpt_flags.icon_descr_is_dynamic = dynamic;
1170       break;
1171     case 12:
1172       GMSD_SETSTR(facility, str);
1173       break;
1174     case 13:
1175       GMSD_SETSTR(city, str);
1176       break;
1177     case 14:
1178       GMSD_SETSTR(state, str);
1179       break;
1180     case 15:
1181       GMSD_SETSTR(country, str);
1182       GMSD_SETSTR(cc, gt_get_icao_cc(str, wpt->shortname));
1183       break;
1184     case 16:
1185       parse_date_and_time(str, &wpt->creation_time);
1186       break;
1187     case 17:
1188       wpt->url = DUPSTR(str);
1189       break;
1190     case 18:
1191       GMSD_SET(category, parse_categories(str));
1192       break;
1193     default:
1194       break;
1195     }
1196   }
1197   waypt_add(wpt);
1198 }
1199 
1200 static void
parse_route_header(void)1201 parse_route_header(void)
1202 {
1203   char* str;
1204   int column = -1;
1205   route_head* rte;
1206 
1207   rte = route_head_alloc();
1208 
1209   bind_fields(route_header);
1210   while ((str = csv_lineparse(NULL, "\t", "", column++))) {
1211     int field_no = header_fields[route_header][column];
1212     switch (field_no) {
1213     case 1:
1214       rte->rte_name = DUPSTR(str);
1215       break;
1216     case 5:
1217       rte->rte_url = DUPSTR(str);
1218       break;
1219     }
1220   }
1221   route_add_head(rte);
1222   current_rte = rte;
1223 }
1224 
1225 static void
parse_track_header(void)1226 parse_track_header(void)
1227 {
1228   char* str;
1229   int column = -1;
1230   route_head* trk;
1231 
1232   bind_fields(track_header);
1233   trk = route_head_alloc();
1234   while ((str = csv_lineparse(NULL, "\t", "", column++))) {
1235     int field_no = header_fields[track_header][column];
1236     switch (field_no) {
1237     case 1:
1238       trk->rte_name = DUPSTR(str);
1239       break;
1240     case 6:
1241       trk->rte_url = DUPSTR(str);
1242       break;
1243     }
1244   }
1245   track_add_head(trk);
1246   current_trk = trk;
1247 }
1248 
1249 static void
parse_route_waypoint(void)1250 parse_route_waypoint(void)
1251 {
1252   char* str;
1253   int column = -1;
1254   waypoint* wpt = NULL;
1255 
1256   bind_fields(rtept_header);
1257 
1258   while ((str = csv_lineparse(NULL, "\t", "", column++))) {
1259     int field_no = header_fields[rtept_header][column];
1260     switch (field_no) {
1261     case 1:
1262       is_fatal((*str == '\0'), MYNAME ": Route waypoint without name at line %d!\n", current_line);
1263       wpt = find_waypt_by_name(str);
1264       is_fatal((wpt == NULL), MYNAME ": Route waypoint \"%s\" not in waypoint list (line %d)!\n", str, current_line);
1265       wpt = waypt_dupe(wpt);
1266       break;
1267     }
1268   }
1269   if (wpt != NULL) {
1270     route_add_wpt(current_rte, wpt);
1271   }
1272 }
1273 
1274 static void
parse_track_waypoint(void)1275 parse_track_waypoint(void)
1276 {
1277   char* str;
1278   int column = -1;
1279   waypoint* wpt;
1280 
1281   bind_fields(trkpt_header);
1282   wpt = waypt_new();
1283 
1284   while ((str = csv_lineparse(NULL, "\t", "", column++))) {
1285     int field_no;
1286     double x;
1287 
1288     if (! *str) {
1289       continue;
1290     }
1291 
1292     field_no = header_fields[trkpt_header][column];
1293     switch (field_no) {
1294     case 1:
1295       parse_coordinates(str, datum_index, grid_index,
1296                         &wpt->latitude, &wpt->longitude, MYNAME);
1297       break;
1298     case 2:
1299       parse_date_and_time(str, &wpt->creation_time);
1300       break;
1301     case 3:
1302       if (parse_distance(str, &x, 1, MYNAME)) {
1303         wpt->altitude = x;
1304       }
1305       break;
1306     case 4:
1307       if (parse_distance(str, &x, 1, MYNAME)) {
1308         WAYPT_SET(wpt, depth, x);
1309       }
1310       break;
1311     case 5:
1312       if (parse_temperature(str, &x)) {
1313         WAYPT_SET(wpt, temperature, x);
1314       }
1315       break;
1316     case 8:
1317       if (parse_speed(str, &x, 1, MYNAME)) {
1318         WAYPT_SET(wpt, speed, x);
1319       }
1320       break;
1321     case 9:
1322       WAYPT_SET(wpt, course, atoi(str));
1323       break;
1324     }
1325   }
1326   track_add_wpt(current_trk, wpt);
1327 }
1328 
1329 /***************************************************************/
1330 
1331 static void
garmin_txt_rd_init(const char * fname)1332 garmin_txt_rd_init(const char* fname)
1333 {
1334   memset(&gtxt_flags, 0, sizeof(gtxt_flags));
1335 
1336   fin = gbfopen(fname, "rb", MYNAME);
1337   memset(&header_ct, 0, sizeof(header_ct));
1338 
1339   datum_index = -1;
1340   grid_index = (grid_type) -1;
1341 
1342   init_date_and_time_format();
1343 }
1344 
1345 static void
garmin_txt_rd_deinit(void)1346 garmin_txt_rd_deinit(void)
1347 {
1348   header_type h;
1349 
1350   for (h = waypt_header; h <= unknown_header; h++) {
1351     free_header(h);
1352   }
1353   gbfclose(fin);
1354   xfree(date_time_format);
1355 }
1356 
1357 static void
garmin_txt_read(void)1358 garmin_txt_read(void)
1359 {
1360   char* buff;
1361 
1362   current_line = 0;
1363 
1364   while ((buff = gbfgetstr(fin))) {
1365     char* cin;
1366 
1367     if ((current_line++ == 0) && fin->unicode) {
1368       cet_convert_init(CET_CHARSET_UTF8, 1);
1369     }
1370 
1371     cin = lrtrim(buff);
1372     if (*cin == '\0') {
1373       continue;
1374     }
1375 
1376     cin = csv_lineparse(cin, "\t", "", 0);
1377 
1378     if (cin == NULL) {
1379       continue;
1380     }
1381 
1382     if (case_ignore_strcmp(cin, "Header") == 0) {
1383       parse_header();
1384     } else if (case_ignore_strcmp(cin, "Grid") == 0) {
1385       parse_grid();
1386     } else if (case_ignore_strcmp(cin, "Datum") == 0) {
1387       parse_datum();
1388     } else if (case_ignore_strcmp(cin, "Waypoint") == 0) {
1389       parse_waypoint();
1390     } else if (case_ignore_strcmp(cin, "Route Waypoint") == 0) {
1391       parse_route_waypoint();
1392     } else if (case_ignore_strcmp(cin, "Trackpoint") == 0) {
1393       parse_track_waypoint();
1394     } else if (case_ignore_strcmp(cin, "Route") == 0) {
1395       parse_route_header();
1396     } else if (case_ignore_strcmp(cin, "Track") == 0) {
1397       parse_track_header();
1398     } else if (case_ignore_strcmp(cin, "Map") == 0) /* do nothing */ ;
1399     else {
1400       fatal(MYNAME ": Unknwon identifier (%s) at line %d!\n", cin, current_line);
1401     }
1402 
1403     /* flush pending data */
1404     while (csv_lineparse(NULL, "\t", "", 0));
1405   }
1406 }
1407 
1408 ff_vecs_t garmin_txt_vecs = {
1409   ff_type_file,
1410   FF_CAP_RW_ALL,
1411   garmin_txt_rd_init,
1412   garmin_txt_wr_init,
1413   garmin_txt_rd_deinit,
1414   garmin_txt_wr_deinit,
1415   garmin_txt_read,
1416   garmin_txt_write,
1417   NULL,
1418   garmin_txt_args,
1419   CET_CHARSET_MS_ANSI, 0
1420 };
1421 
1422 #endif // CSVFMTS_ENABLED
1423