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(>xt_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(>xt_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