]> git.street.me.uk Git - andy/viking.git/blob - src/gpx.c
[QA] Update year values.
[andy/viking.git] / src / gpx.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
4  * Copyright (C) 2003-2007, Evan Battaglia <gtoevan@gmx.net>
5  * Copyright (C) 2007, Quy Tonthat <qtonthat@gmail.com>
6  * Copyright (C) 2008, Hein Ragas <viking@ragas.nl>
7  * Copyright (C) 2009, Tal B <tal.bav@gmail.com>
8  * Copyright (c) 2012, Rob Norris <rw_norris@hotmail.com>
9  *
10  * Some of the code adapted from GPSBabel 1.2.7
11  * http://gpsbabel.sf.net/
12  * Copyright (C) 2002, 2003, 2004, 2005 Robert Lipe, robertlipe@usa.net
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27  *
28  */
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #define _XOPEN_SOURCE /* glibc2 needs this */
34
35 #include "gpx.h"
36 #include "viking.h"
37 #include <expat.h>
38 #ifdef HAVE_STRING_H
39 #include <string.h>
40 #endif
41 #include <glib.h>
42 #ifdef HAVE_MATH_H
43 #include <math.h>
44 #endif
45 #include <time.h>
46
47 typedef enum {
48         tt_unknown = 0,
49
50         tt_gpx,
51
52         tt_wpt,
53         tt_wpt_cmt,
54         tt_wpt_desc,
55         tt_wpt_name,
56         tt_wpt_ele,
57         tt_wpt_sym,
58         tt_wpt_time,
59         tt_wpt_link,            /* New in GPX 1.1 */
60
61         tt_trk,
62         tt_trk_cmt,
63         tt_trk_desc,
64         tt_trk_name,
65
66         tt_rte,
67
68         tt_trk_trkseg,
69         tt_trk_trkseg_trkpt,
70         tt_trk_trkseg_trkpt_ele,
71         tt_trk_trkseg_trkpt_time,
72         tt_trk_trkseg_trkpt_name,
73         /* extended */
74         tt_trk_trkseg_trkpt_course,
75         tt_trk_trkseg_trkpt_speed,
76         tt_trk_trkseg_trkpt_fix,
77         tt_trk_trkseg_trkpt_sat,
78
79         tt_trk_trkseg_trkpt_hdop,
80         tt_trk_trkseg_trkpt_vdop,
81         tt_trk_trkseg_trkpt_pdop,
82
83         tt_waypoint,
84         tt_waypoint_coord,
85         tt_waypoint_name,
86 } tag_type;
87
88 typedef struct tag_mapping {
89         tag_type tag_type;              /* enum from above for this tag */
90         const char *tag_name;           /* xpath-ish tag name */
91 } tag_mapping;
92
93 typedef struct {
94         GpxWritingOptions *options;
95         FILE *file;
96 } GpxWritingContext;
97
98 /*
99  * xpath(ish) mappings between full tag paths and internal identifers.
100  * These appear in the order they appear in the GPX specification.
101  * If it's not a tag we explictly handle, it doesn't go here.
102  */
103
104 tag_mapping tag_path_map[] = {
105
106         { tt_wpt, "/gpx/wpt" },
107
108         { tt_waypoint, "/loc/waypoint" },
109         { tt_waypoint_coord, "/loc/waypoint/coord" },
110         { tt_waypoint_name, "/loc/waypoint/name" },
111
112         { tt_wpt_ele, "/gpx/wpt/ele" },
113         { tt_wpt_time, "/gpx/wpt/time" },
114         { tt_wpt_name, "/gpx/wpt/name" },
115         { tt_wpt_cmt, "/gpx/wpt/cmt" },
116         { tt_wpt_desc, "/gpx/wpt/desc" },
117         { tt_wpt_sym, "/gpx/wpt/sym" },
118         { tt_wpt_sym, "/loc/waypoint/type" },
119         { tt_wpt_link, "/gpx/wpt/link" },                    /* GPX 1.1 */
120
121         { tt_trk, "/gpx/trk" },
122         { tt_trk_name, "/gpx/trk/name" },
123         { tt_trk_cmt, "/gpx/trk/cmt" },
124         { tt_trk_desc, "/gpx/trk/desc" },
125         { tt_trk_trkseg, "/gpx/trk/trkseg" },
126         { tt_trk_trkseg_trkpt, "/gpx/trk/trkseg/trkpt" },
127         { tt_trk_trkseg_trkpt_ele, "/gpx/trk/trkseg/trkpt/ele" },
128         { tt_trk_trkseg_trkpt_time, "/gpx/trk/trkseg/trkpt/time" },
129         { tt_trk_trkseg_trkpt_name, "/gpx/trk/trkseg/trkpt/name" },
130         /* extended */
131         { tt_trk_trkseg_trkpt_course, "/gpx/trk/trkseg/trkpt/course" },
132         { tt_trk_trkseg_trkpt_speed, "/gpx/trk/trkseg/trkpt/speed" },
133         { tt_trk_trkseg_trkpt_fix, "/gpx/trk/trkseg/trkpt/fix" },
134         { tt_trk_trkseg_trkpt_sat, "/gpx/trk/trkseg/trkpt/sat" },
135
136         { tt_trk_trkseg_trkpt_hdop, "/gpx/trk/trkseg/trkpt/hdop" },
137         { tt_trk_trkseg_trkpt_vdop, "/gpx/trk/trkseg/trkpt/vdop" },
138         { tt_trk_trkseg_trkpt_pdop, "/gpx/trk/trkseg/trkpt/pdop" },
139
140         { tt_rte, "/gpx/rte" },
141         // NB Route reuses track point feature tags
142         { tt_trk_name, "/gpx/rte/name" },
143         { tt_trk_cmt, "/gpx/rte/cmt" },
144         { tt_trk_desc, "/gpx/rte/desc" },
145         { tt_trk_trkseg_trkpt, "/gpx/rte/rtept" },
146         { tt_trk_trkseg_trkpt_name, "/gpx/rte/rtept/name" },
147         { tt_trk_trkseg_trkpt_ele, "/gpx/rte/rtept/ele" },
148
149         {0}
150 };
151
152 static tag_type get_tag(const char *t)
153 {
154         tag_mapping *tm;
155         for (tm = tag_path_map; tm->tag_type != 0; tm++)
156                 if (0 == strcmp(tm->tag_name, t))
157                         return tm->tag_type;
158         return tt_unknown;
159 }
160
161 /******************************************/
162
163 tag_type current_tag = tt_unknown;
164 GString *xpath = NULL;
165 GString *c_cdata = NULL;
166
167 /* current ("c_") objects */
168 VikTrackpoint *c_tp = NULL;
169 VikWaypoint *c_wp = NULL;
170 VikTrack *c_tr = NULL;
171
172 gchar *c_wp_name = NULL;
173 gchar *c_tr_name = NULL;
174
175 /* temporary things so we don't have to create them lots of times */
176 const gchar *c_slat, *c_slon;
177 struct LatLon c_ll;
178
179 /* specialty flags / etc */
180 gboolean f_tr_newseg;
181 guint unnamed_waypoints = 0;
182 guint unnamed_tracks = 0;
183 guint unnamed_routes = 0;
184
185 static const char *get_attr ( const char **attr, const char *key )
186 {
187   while ( *attr ) {
188     if ( strcmp(*attr,key) == 0 )
189       return *(attr + 1);
190     attr += 2;
191   }
192   return NULL;
193 }
194
195 static gboolean set_c_ll ( const char **attr )
196 {
197   if ( (c_slat = get_attr ( attr, "lat" )) && (c_slon = get_attr ( attr, "lon" )) ) {
198     c_ll.lat = g_ascii_strtod(c_slat, NULL);
199     c_ll.lon = g_ascii_strtod(c_slon, NULL);
200     return TRUE;
201   }
202   return FALSE;
203 }
204
205 static void gpx_start(VikTrwLayer *vtl, const char *el, const char **attr)
206 {
207   static const gchar *tmp;
208
209   g_string_append_c ( xpath, '/' );
210   g_string_append ( xpath, el );
211   current_tag = get_tag ( xpath->str );
212
213   switch ( current_tag ) {
214
215      case tt_wpt:
216        if ( set_c_ll( attr ) ) {
217          c_wp = vik_waypoint_new ();
218          c_wp->visible = TRUE;
219          if ( get_attr ( attr, "hidden" ) )
220            c_wp->visible = FALSE;
221
222          vik_coord_load_from_latlon ( &(c_wp->coord), vik_trw_layer_get_coord_mode ( vtl ), &c_ll );
223        }
224        break;
225
226      case tt_trk:
227      case tt_rte:
228        c_tr = vik_track_new ();
229        vik_track_set_defaults ( c_tr );
230        c_tr->is_route = (current_tag == tt_rte) ? TRUE : FALSE;
231        c_tr->visible = TRUE;
232        if ( get_attr ( attr, "hidden" ) )
233          c_tr->visible = FALSE;
234        break;
235
236      case tt_trk_trkseg:
237        f_tr_newseg = TRUE;
238        break;
239
240      case tt_trk_trkseg_trkpt:
241        if ( set_c_ll( attr ) ) {
242          c_tp = vik_trackpoint_new ();
243          vik_coord_load_from_latlon ( &(c_tp->coord), vik_trw_layer_get_coord_mode ( vtl ), &c_ll );
244          if ( f_tr_newseg ) {
245            c_tp->newsegment = TRUE;
246            f_tr_newseg = FALSE;
247          }
248          c_tr->trackpoints = g_list_append ( c_tr->trackpoints, c_tp );
249        }
250        break;
251
252      case tt_trk_trkseg_trkpt_name:
253      case tt_trk_trkseg_trkpt_ele:
254      case tt_trk_trkseg_trkpt_time:
255      case tt_wpt_cmt:
256      case tt_wpt_desc:
257      case tt_wpt_name:
258      case tt_wpt_ele:
259      case tt_wpt_time:
260      case tt_wpt_link:
261      case tt_trk_cmt:
262      case tt_trk_desc:
263      case tt_trk_name:
264        g_string_erase ( c_cdata, 0, -1 ); /* clear the cdata buffer */
265        break;
266
267      case tt_waypoint:
268        c_wp = vik_waypoint_new ();
269        c_wp->visible = TRUE;
270        break;
271
272      case tt_waypoint_coord:
273        if ( set_c_ll( attr ) )
274          vik_coord_load_from_latlon ( &(c_wp->coord), vik_trw_layer_get_coord_mode ( vtl ), &c_ll );
275        break;
276
277      case tt_waypoint_name:
278        if ( ( tmp = get_attr(attr, "id") ) ) {
279          if ( c_wp_name )
280            g_free ( c_wp_name );
281          c_wp_name = g_strdup ( tmp );
282        }
283        g_string_erase ( c_cdata, 0, -1 ); /* clear the cdata buffer for description */
284        break;
285         
286      default: break;
287   }
288 }
289
290 static void gpx_end(VikTrwLayer *vtl, const char *el)
291 {
292   static GTimeVal tp_time;
293   static GTimeVal wp_time;
294
295   g_string_truncate ( xpath, xpath->len - strlen(el) - 1 );
296
297   switch ( current_tag ) {
298
299      case tt_waypoint:
300      case tt_wpt:
301        if ( ! c_wp_name )
302          c_wp_name = g_strdup_printf("VIKING_WP%04d", unnamed_waypoints++);
303        vik_trw_layer_filein_add_waypoint ( vtl, c_wp_name, c_wp );
304        g_free ( c_wp_name );
305        c_wp = NULL;
306        c_wp_name = NULL;
307        break;
308
309      case tt_trk:
310        if ( ! c_tr_name )
311          c_tr_name = g_strdup_printf("VIKING_TR%03d", unnamed_tracks++);
312        // Delibrate fall through
313      case tt_rte:
314        if ( ! c_tr_name )
315          c_tr_name = g_strdup_printf("VIKING_RT%03d", unnamed_routes++);
316        vik_trw_layer_filein_add_track ( vtl, c_tr_name, c_tr );
317        g_free ( c_tr_name );
318        c_tr = NULL;
319        c_tr_name = NULL;
320        break;
321
322      case tt_wpt_name:
323        if ( c_wp_name )
324          g_free ( c_wp_name );
325        c_wp_name = g_strdup ( c_cdata->str );
326        g_string_erase ( c_cdata, 0, -1 );
327        break;
328
329      case tt_trk_name:
330        if ( c_tr_name )
331          g_free ( c_tr_name );
332        c_tr_name = g_strdup ( c_cdata->str );
333        g_string_erase ( c_cdata, 0, -1 );
334        break;
335
336      case tt_wpt_ele:
337        c_wp->altitude = g_ascii_strtod ( c_cdata->str, NULL );
338        g_string_erase ( c_cdata, 0, -1 );
339        break;
340
341      case tt_trk_trkseg_trkpt_ele:
342        c_tp->altitude = g_ascii_strtod ( c_cdata->str, NULL );
343        g_string_erase ( c_cdata, 0, -1 );
344        break;
345
346      case tt_waypoint_name: /* .loc name is really description. */
347      case tt_wpt_desc:
348        vik_waypoint_set_description ( c_wp, c_cdata->str );
349        g_string_erase ( c_cdata, 0, -1 );
350        break;
351
352      case tt_wpt_cmt:
353        vik_waypoint_set_comment ( c_wp, c_cdata->str );
354        g_string_erase ( c_cdata, 0, -1 );
355        break;
356
357      case tt_wpt_link:
358        vik_waypoint_set_image ( c_wp, c_cdata->str );
359        g_string_erase ( c_cdata, 0, -1 );
360        break;
361
362      case tt_wpt_sym: {
363        vik_waypoint_set_symbol ( c_wp, c_cdata->str );
364        g_string_erase ( c_cdata, 0, -1 );
365        break;
366        }
367
368      case tt_trk_desc:
369        vik_track_set_description ( c_tr, c_cdata->str );
370        g_string_erase ( c_cdata, 0, -1 );
371        break;
372
373      case tt_trk_cmt:
374        vik_track_set_comment ( c_tr, c_cdata->str );
375        g_string_erase ( c_cdata, 0, -1 );
376        break;
377
378      case tt_wpt_time:
379        if ( g_time_val_from_iso8601(c_cdata->str, &wp_time) ) {
380          c_wp->timestamp = wp_time.tv_sec;
381          c_wp->has_timestamp = TRUE;
382        }
383        g_string_erase ( c_cdata, 0, -1 );
384        break;
385
386      case tt_trk_trkseg_trkpt_name:
387        vik_trackpoint_set_name ( c_tp, c_cdata->str );
388        g_string_erase ( c_cdata, 0, -1 );
389        break;
390
391      case tt_trk_trkseg_trkpt_time:
392        if ( g_time_val_from_iso8601(c_cdata->str, &tp_time) ) {
393          c_tp->timestamp = tp_time.tv_sec;
394          c_tp->has_timestamp = TRUE;
395        }
396        g_string_erase ( c_cdata, 0, -1 );
397        break;
398
399      case tt_trk_trkseg_trkpt_course:
400        c_tp->course = g_ascii_strtod ( c_cdata->str, NULL );
401        g_string_erase ( c_cdata, 0, -1 );
402        break;
403
404      case tt_trk_trkseg_trkpt_speed:
405        c_tp->speed = g_ascii_strtod ( c_cdata->str, NULL );
406        g_string_erase ( c_cdata, 0, -1 );
407        break;
408
409      case tt_trk_trkseg_trkpt_fix:
410        if (!strcmp("2d", c_cdata->str))
411          c_tp->fix_mode = VIK_GPS_MODE_2D;
412        else if (!strcmp("3d", c_cdata->str))
413          c_tp->fix_mode = VIK_GPS_MODE_3D;
414        else  /* TODO: more fix modes here */
415          c_tp->fix_mode = VIK_GPS_MODE_NOT_SEEN;
416        g_string_erase ( c_cdata, 0, -1 );
417        break;
418
419      case tt_trk_trkseg_trkpt_sat:
420        c_tp->nsats = atoi ( c_cdata->str );
421        g_string_erase ( c_cdata, 0, -1 );
422        break;
423
424      case tt_trk_trkseg_trkpt_hdop:
425        c_tp->hdop = g_strtod ( c_cdata->str, NULL );
426        g_string_erase ( c_cdata, 0, -1 );
427        break;
428
429      case tt_trk_trkseg_trkpt_vdop:
430        c_tp->vdop = g_strtod ( c_cdata->str, NULL );
431        g_string_erase ( c_cdata, 0, -1 );
432        break;
433
434      case tt_trk_trkseg_trkpt_pdop:
435        c_tp->pdop = g_strtod ( c_cdata->str, NULL );
436        g_string_erase ( c_cdata, 0, -1 );
437        break;
438
439      default: break;
440   }
441
442   current_tag = get_tag ( xpath->str );
443 }
444
445 static void gpx_cdata(void *dta, const XML_Char *s, int len)
446 {
447   switch ( current_tag ) {
448     case tt_wpt_name:
449     case tt_trk_name:
450     case tt_wpt_ele:
451     case tt_trk_trkseg_trkpt_ele:
452     case tt_wpt_cmt:
453     case tt_wpt_desc:
454     case tt_wpt_sym:
455     case tt_wpt_link:
456     case tt_trk_cmt:
457     case tt_trk_desc:
458     case tt_trk_trkseg_trkpt_time:
459     case tt_wpt_time:
460     case tt_trk_trkseg_trkpt_name:
461     case tt_trk_trkseg_trkpt_course:
462     case tt_trk_trkseg_trkpt_speed:
463     case tt_trk_trkseg_trkpt_fix:
464     case tt_trk_trkseg_trkpt_sat:
465     case tt_trk_trkseg_trkpt_hdop:
466     case tt_trk_trkseg_trkpt_vdop:
467     case tt_trk_trkseg_trkpt_pdop:
468     case tt_waypoint_name: /* .loc name is really description. */
469       g_string_append_len ( c_cdata, s, len );
470       break;
471
472     default: break;  /* ignore cdata from other things */
473   }
474 }
475
476 // make like a "stack" of tag names
477 // like gpspoint's separated like /gpx/wpt/whatever
478
479 gboolean a_gpx_read_file( VikTrwLayer *vtl, FILE *f ) {
480   XML_Parser parser = XML_ParserCreate(NULL);
481   int done=0, len;
482   enum XML_Status status = XML_STATUS_ERROR;
483
484   XML_SetElementHandler(parser, (XML_StartElementHandler) gpx_start, (XML_EndElementHandler) gpx_end);
485   XML_SetUserData(parser, vtl); /* in the future we could remove all global variables */
486   XML_SetCharacterDataHandler(parser, (XML_CharacterDataHandler) gpx_cdata);
487
488   gchar buf[4096];
489
490   g_assert ( f != NULL && vtl != NULL );
491
492   xpath = g_string_new ( "" );
493   c_cdata = g_string_new ( "" );
494
495   unnamed_waypoints = 1;
496   unnamed_tracks = 1;
497   unnamed_routes = 1;
498
499   while (!done) {
500     len = fread(buf, 1, sizeof(buf)-7, f);
501     done = feof(f) || !len;
502     status = XML_Parse(parser, buf, len, done);
503   }
504  
505   XML_ParserFree (parser);
506   g_string_free ( xpath, TRUE );
507   g_string_free ( c_cdata, TRUE );
508
509   return status != XML_STATUS_ERROR;
510 }
511
512 /**** entitize from GPSBabel ****/
513 typedef struct {
514         const char * text;
515         const char * entity;
516         int  not_html;
517 } entity_types;
518
519 static
520 entity_types stdentities[] =  {
521         { "&",  "&amp;", 0 },
522         { "'",  "&apos;", 1 },
523         { "<",  "&lt;", 0 },
524         { ">",  "&gt;", 0 },
525         { "\"", "&quot;", 0 },
526         { NULL, NULL, 0 }
527 };
528
529 void utf8_to_int( const char *cp, int *bytes, int *value )
530 {
531         if ( (*cp & 0xe0) == 0xc0 ) {
532                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
533                 *bytes = 2;
534                 *value = ((*cp & 0x1f) << 6) |
535                         (*(cp+1) & 0x3f);
536         }
537         else if ( (*cp & 0xf0) == 0xe0 ) {
538                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
539                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
540                 *bytes = 3;
541                 *value = ((*cp & 0x0f) << 12) |
542                         ((*(cp+1) & 0x3f) << 6) |
543                         (*(cp+2) & 0x3f);
544         }
545         else if ( (*cp & 0xf8) == 0xf0 ) {
546                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
547                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
548                 if ( (*(cp+3) & 0xc0) != 0x80 ) goto dodefault;
549                 *bytes = 4;
550                 *value = ((*cp & 0x07) << 18) |
551                         ((*(cp+1) & 0x3f) << 12) |
552                         ((*(cp+2) & 0x3f) << 6) |
553                         (*(cp+3) & 0x3f);
554         }
555         else if ( (*cp & 0xfc) == 0xf8 ) {
556                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
557                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
558                 if ( (*(cp+3) & 0xc0) != 0x80 ) goto dodefault;
559                 if ( (*(cp+4) & 0xc0) != 0x80 ) goto dodefault;
560                 *bytes = 5;
561                 *value = ((*cp & 0x03) << 24) |
562                         ((*(cp+1) & 0x3f) << 18) |
563                         ((*(cp+2) & 0x3f) << 12) |
564                         ((*(cp+3) & 0x3f) << 6) |
565                         (*(cp+4) & 0x3f);
566         }
567         else if ( (*cp & 0xfe) == 0xfc ) {
568                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
569                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
570                 if ( (*(cp+3) & 0xc0) != 0x80 ) goto dodefault;
571                 if ( (*(cp+4) & 0xc0) != 0x80 ) goto dodefault;
572                 if ( (*(cp+5) & 0xc0) != 0x80 ) goto dodefault;
573                 *bytes = 6;
574                 *value = ((*cp & 0x01) << 30) |
575                         ((*(cp+1) & 0x3f) << 24) |
576                         ((*(cp+2) & 0x3f) << 18) |
577                         ((*(cp+3) & 0x3f) << 12) |
578                         ((*(cp+4) & 0x3f) << 6) |
579                         (*(cp+5) & 0x3f);
580         }
581         else {
582 dodefault:
583                 *bytes = 1;
584                 *value = (unsigned char)*cp;
585         }
586 }
587
588 static
589 char *
590 entitize(const char * str)
591 {
592         int elen, ecount, nsecount;
593         entity_types *ep;
594         const char * cp;
595         char * p, * tmp, * xstr;
596
597         char tmpsub[20];
598         int bytes = 0;
599         int value = 0;
600         ep = stdentities;
601         elen = ecount = nsecount = 0;
602
603         /* figure # of entity replacements and additional size. */
604         while (ep->text) {
605                 cp = str;
606                 while ((cp = strstr(cp, ep->text)) != NULL) {
607                         elen += strlen(ep->entity) - strlen(ep->text);
608                         ecount++;
609                         cp += strlen(ep->text);
610                 }
611                 ep++;
612         }
613
614         /* figure the same for other than standard entities (i.e. anything
615          * that isn't in the range U+0000 to U+007F */
616         for ( cp = str; *cp; cp++ ) {
617                 if ( *cp & 0x80 ) {
618
619                         utf8_to_int( cp, &bytes, &value );
620                         cp += bytes-1;
621                         elen += sprintf( tmpsub, "&#x%x;", value ) - bytes;
622                         nsecount++;
623                 }
624         }
625
626         /* enough space for the whole string plus entity replacements, if any */
627         tmp = g_malloc((strlen(str) + elen + 1));
628         strcpy(tmp, str);
629
630         /* no entity replacements */
631         if (ecount == 0 && nsecount == 0)
632                 return (tmp);
633
634         if ( ecount != 0 ) {
635                 for (ep = stdentities; ep->text; ep++) {
636                         p = tmp;
637                         while ((p = strstr(p, ep->text)) != NULL) {
638                                 elen = strlen(ep->entity);
639
640                                 xstr = g_strdup(p + strlen(ep->text));
641
642                                 strcpy(p, ep->entity);
643                                 strcpy(p + elen, xstr);
644
645                                 g_free(xstr);
646
647                                 p += elen;
648                         }
649                 }
650         }
651
652         if ( nsecount != 0 ) {
653                 p = tmp;
654                 while (*p) {
655                         if ( *p & 0x80 ) {
656                                 utf8_to_int( p, &bytes, &value );
657                                 if ( p[bytes] ) {
658                                         xstr = g_strdup( p + bytes );
659                                 }
660                                 else {
661                                         xstr = NULL;
662                                 }
663                                 sprintf( p, "&#x%x;", value );
664                                 p = p+strlen(p);
665                                 if ( xstr ) {
666                                         strcpy( p, xstr );
667                                         g_free(xstr);
668                                 }
669                         }
670                         else {
671                                 p++;
672                         }
673                 }
674         }
675         return (tmp);
676 }
677 /**** end GPSBabel code ****/
678
679 /* export GPX */
680
681 static void gpx_write_waypoint ( VikWaypoint *wp, GpxWritingContext *context )
682 {
683   // Don't write invisible waypoints when specified
684   if (context->options && !context->options->hidden && !wp->visible)
685     return;
686
687   FILE *f = context->file;
688   static struct LatLon ll;
689   gchar *s_lat,*s_lon;
690   gchar *tmp;
691   vik_coord_to_latlon ( &(wp->coord), &ll );
692   s_lat = a_coords_dtostr( ll.lat );
693   s_lon = a_coords_dtostr( ll.lon );
694   // NB 'hidden' is not part of any GPX standard - this appears to be a made up Viking 'extension'
695   //  luckily most other GPX processing software ignores things they don't understand
696   fprintf ( f, "<wpt lat=\"%s\" lon=\"%s\"%s>\n",
697                s_lat, s_lon, wp->visible ? "" : " hidden=\"hidden\"" );
698   g_free ( s_lat );
699   g_free ( s_lon );
700
701   // Sanity clause
702   if ( wp->name )
703     tmp = entitize ( wp->name );
704   else
705     tmp = g_strdup ("waypoint");
706
707   fprintf ( f, "  <name>%s</name>\n", tmp );
708   g_free ( tmp);
709
710   if ( wp->altitude != VIK_DEFAULT_ALTITUDE )
711   {
712     tmp = a_coords_dtostr ( wp->altitude );
713     fprintf ( f, "  <ele>%s</ele>\n", tmp );
714     g_free ( tmp );
715   }
716
717   if ( wp->has_timestamp ) {
718     GTimeVal timestamp;
719     timestamp.tv_sec = wp->timestamp;
720     timestamp.tv_usec = 0;
721
722     gchar *time_iso8601 = g_time_val_to_iso8601 ( &timestamp );
723     if ( time_iso8601 != NULL )
724       fprintf ( f, "  <time>%s</time>\n", time_iso8601 );
725     g_free ( time_iso8601 );
726   }
727
728   if ( wp->comment )
729   {
730     tmp = entitize(wp->comment);
731     fprintf ( f, "  <cmt>%s</cmt>\n", tmp );
732     g_free ( tmp );
733   }
734   if ( wp->description )
735   {
736     tmp = entitize(wp->description);
737     fprintf ( f, "  <desc>%s</desc>\n", tmp );
738     g_free ( tmp );
739   }
740   if ( wp->image )
741   {
742     tmp = entitize(wp->image);
743     fprintf ( f, "  <link>%s</link>\n", tmp );
744     g_free ( tmp );
745   }
746   if ( wp->symbol ) 
747   {
748     tmp = entitize(wp->symbol);
749     if ( a_vik_gpx_export_wpt_sym_name ( ) ) {
750        // Lowercase the symbol name
751        gchar *tmp2 = g_utf8_strdown ( tmp, -1 );
752        fprintf ( f, "  <sym>%s</sym>\n",  tmp2 );
753        g_free ( tmp2 );
754     }
755     else
756       fprintf ( f, "  <sym>%s</sym>\n", tmp);
757     g_free ( tmp );
758   }
759
760   fprintf ( f, "</wpt>\n" );
761 }
762
763 static void gpx_write_trackpoint ( VikTrackpoint *tp, GpxWritingContext *context )
764 {
765   FILE *f = context->file;
766   static struct LatLon ll;
767   gchar *s_lat,*s_lon, *s_alt, *s_dop;
768   gchar *time_iso8601;
769   vik_coord_to_latlon ( &(tp->coord), &ll );
770
771   // No such thing as a rteseg! So make sure we don't put them in
772   if ( context->options && !context->options->is_route && tp->newsegment )
773     fprintf ( f, "  </trkseg>\n  <trkseg>\n" );
774
775   s_lat = a_coords_dtostr( ll.lat );
776   s_lon = a_coords_dtostr( ll.lon );
777   fprintf ( f, "  <%spt lat=\"%s\" lon=\"%s\">\n", (context->options && context->options->is_route) ? "rte" : "trk", s_lat, s_lon );
778   g_free ( s_lat ); s_lat = NULL;
779   g_free ( s_lon ); s_lon = NULL;
780
781   if (tp->name) {
782     gchar *s_name = entitize(tp->name);
783     fprintf ( f, "    <name>%s</name>\n", s_name );
784     g_free(s_name);
785   }
786
787   s_alt = NULL;
788   if ( tp->altitude != VIK_DEFAULT_ALTITUDE )
789   {
790     s_alt = a_coords_dtostr ( tp->altitude );
791   }
792   else if ( context->options != NULL && context->options->force_ele )
793   {
794     s_alt = a_coords_dtostr ( 0 );
795   }
796   if (s_alt != NULL)
797     fprintf ( f, "    <ele>%s</ele>\n", s_alt );
798   g_free ( s_alt ); s_alt = NULL;
799   
800   time_iso8601 = NULL;
801   if ( tp->has_timestamp ) {
802     GTimeVal timestamp;
803     timestamp.tv_sec = tp->timestamp;
804     timestamp.tv_usec = 0;
805   
806     time_iso8601 = g_time_val_to_iso8601 ( &timestamp );
807   }
808   else if ( context->options != NULL && context->options->force_time )
809   {
810     GTimeVal current;
811     g_get_current_time ( &current );
812   
813     time_iso8601 = g_time_val_to_iso8601 ( &current );
814   }
815   if ( time_iso8601 != NULL )
816     fprintf ( f, "    <time>%s</time>\n", time_iso8601 );
817   g_free(time_iso8601);
818   time_iso8601 = NULL;
819   
820   if (!isnan(tp->course)) {
821     gchar *s_course = a_coords_dtostr(tp->course);
822     fprintf ( f, "    <course>%s</course>\n", s_course );
823     g_free(s_course);
824   }
825   if (!isnan(tp->speed)) {
826     gchar *s_speed = a_coords_dtostr(tp->speed);
827     fprintf ( f, "    <speed>%s</speed>\n", s_speed );
828     g_free(s_speed);
829   }
830   if (tp->fix_mode == VIK_GPS_MODE_2D)
831     fprintf ( f, "    <fix>2d</fix>\n");
832   if (tp->fix_mode == VIK_GPS_MODE_3D)
833     fprintf ( f, "    <fix>3d</fix>\n");
834   if (tp->nsats > 0)
835     fprintf ( f, "    <sat>%d</sat>\n", tp->nsats );
836
837   s_dop = NULL;
838   if ( tp->hdop != VIK_DEFAULT_DOP )
839   {
840     s_dop = a_coords_dtostr ( tp->hdop );
841   }
842   if (s_dop != NULL)
843     fprintf ( f, "    <hdop>%s</hdop>\n", s_dop );
844   g_free ( s_dop ); s_dop = NULL;
845
846   if ( tp->vdop != VIK_DEFAULT_DOP )
847   {
848     s_dop = a_coords_dtostr ( tp->vdop );
849   }
850   if (s_dop != NULL)
851     fprintf ( f, "    <vdop>%s</vdop>\n", s_dop );
852   g_free ( s_dop ); s_dop = NULL;
853
854   if ( tp->pdop != VIK_DEFAULT_DOP )
855   {
856     s_dop = a_coords_dtostr ( tp->pdop );
857   }
858   if (s_dop != NULL)
859     fprintf ( f, "    <pdop>%s</pdop>\n", s_dop );
860   g_free ( s_dop ); s_dop = NULL;
861
862   fprintf ( f, "  </%spt>\n", (context->options && context->options->is_route) ? "rte" : "trk" );
863 }
864
865
866 static void gpx_write_track ( VikTrack *t, GpxWritingContext *context )
867 {
868   // Don't write invisible tracks when specified
869   if (context->options && !context->options->hidden && !t->visible)
870     return;
871
872   FILE *f = context->file;
873   gchar *tmp;
874   gboolean first_tp_is_newsegment = FALSE; /* must temporarily make it not so, but we want to restore state. not that it matters. */
875
876   // Sanity clause
877   if ( t->name )
878     tmp = entitize ( t->name );
879   else
880     tmp = g_strdup ("track");
881
882   // NB 'hidden' is not part of any GPX standard - this appears to be a made up Viking 'extension'
883   //  luckily most other GPX processing software ignores things they don't understand
884   fprintf ( f, "<%s%s>\n  <name>%s</name>\n",
885             t->is_route ? "rte" : "trk",
886             t->visible ? "" : " hidden=\"hidden\"",
887             tmp );
888   g_free ( tmp );
889
890   if ( t->comment )
891   {
892     tmp = entitize ( t->comment );
893     fprintf ( f, "  <cmt>%s</cmt>\n", tmp );
894     g_free ( tmp );
895   }
896
897   if ( t->description )
898   {
899     tmp = entitize ( t->description );
900     fprintf ( f, "  <desc>%s</desc>\n", tmp );
901     g_free ( tmp );
902   }
903
904   /* No such thing as a rteseg! */
905   if ( !t->is_route )
906     fprintf ( f, "  <trkseg>\n" );
907
908   if ( t->trackpoints && t->trackpoints->data ) {
909     first_tp_is_newsegment = VIK_TRACKPOINT(t->trackpoints->data)->newsegment;
910     VIK_TRACKPOINT(t->trackpoints->data)->newsegment = FALSE; /* so we won't write </trkseg><trkseg> already */
911     g_list_foreach ( t->trackpoints, (GFunc) gpx_write_trackpoint, context );
912     VIK_TRACKPOINT(t->trackpoints->data)->newsegment = first_tp_is_newsegment; /* restore state */
913   }
914
915   /* NB apparently no such thing as a rteseg! */
916   if (!t->is_route)
917     fprintf ( f, "  </trkseg>\n");
918
919   fprintf ( f, "</%s>\n", t->is_route ? "rte" : "trk" );
920 }
921
922 static void gpx_write_header( FILE *f )
923 {
924   fprintf(f, "<?xml version=\"1.0\"?>\n"
925           "<gpx version=\"1.0\" creator=\"Viking -- http://viking.sf.net/\"\n"
926           "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
927           "xmlns=\"http://www.topografix.com/GPX/1/0\"\n"
928           "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
929 }
930
931 static void gpx_write_footer( FILE *f )
932 {
933   fprintf(f, "</gpx>\n");
934 }
935
936 static int gpx_waypoint_compare(const void *x, const void *y)
937 {
938   VikWaypoint *a = (VikWaypoint *)x;
939   VikWaypoint *b = (VikWaypoint *)y;
940   return strcmp(a->name,b->name);
941 }
942
943 static int gpx_track_compare_name(const void *x, const void *y)
944 {
945   VikTrack *a = (VikTrack *)x;
946   VikTrack *b = (VikTrack *)y;
947   return strcmp(a->name,b->name);
948 }
949
950 /* Function to compare two tracks by their first timestamp */
951 static int gpx_track_compare_timestamp (const void *x, const void *y)
952 {
953   VikTrack *a = (VikTrack *)x;
954   VikTrack *b = (VikTrack *)y;
955
956   VikTrackpoint *tpa = NULL;
957   VikTrackpoint *tpb = NULL;
958
959   if ( a->trackpoints )
960     tpa = VIK_TRACKPOINT(g_list_first(a->trackpoints)->data);
961
962   if ( b->trackpoints )
963     tpb = VIK_TRACKPOINT(g_list_first(b->trackpoints)->data);
964
965   if ( tpa && tpb ) {
966     if ( tpa->timestamp < tpb->timestamp )
967       return -1;
968     if ( tpa->timestamp > tpb->timestamp )
969       return 1;
970   }
971
972   if ( tpa && !tpb )
973     return 1;
974
975   if ( !tpa && tpb )
976     return -1;
977
978   return 0;
979 }
980
981 void a_gpx_write_file ( VikTrwLayer *vtl, FILE *f, GpxWritingOptions *options )
982 {
983   GpxWritingContext context = { options, f };
984
985   gpx_write_header ( f );
986
987   // gather waypoints in a list, then sort
988   // g_hash_table_get_values: glib 2.14+
989   GList *gl = g_hash_table_get_values ( vik_trw_layer_get_waypoints ( vtl ) );
990   gl = g_list_sort ( gl, gpx_waypoint_compare );
991
992   GList *iter;
993   for (iter = g_list_first (gl); iter != NULL; iter = g_list_next (iter)) {
994     gpx_write_waypoint ( (VikWaypoint*)iter->data, &context );
995   }
996
997   g_list_free ( gl );
998
999   //gl = g_hash_table_get_values ( vik_trw_layer_get_tracks ( vtl ) );
1000   // Forming the list manually seems to produce one that is more likely to be nearer to the creation order
1001   gl = NULL;
1002   gpointer key, value;
1003   GHashTableIter ght_iter;
1004   g_hash_table_iter_init ( &ght_iter, vik_trw_layer_get_tracks ( vtl ) );
1005   while ( g_hash_table_iter_next (&ght_iter, &key, &value) ) {
1006     gl = g_list_prepend ( gl ,value );
1007   }
1008   gl = g_list_reverse ( gl );
1009
1010   // Sort method determined by preference
1011   if ( a_vik_get_gpx_export_trk_sort() == VIK_GPX_EXPORT_TRK_SORT_TIME )
1012     gl = g_list_sort ( gl, gpx_track_compare_timestamp );
1013   else if ( a_vik_get_gpx_export_trk_sort() == VIK_GPX_EXPORT_TRK_SORT_ALPHA )
1014     gl = g_list_sort ( gl, gpx_track_compare_name );
1015
1016   // Routes sorted by name
1017   GList *glrte = g_hash_table_get_values ( vik_trw_layer_get_routes ( vtl ) );
1018   glrte = g_list_sort ( glrte, gpx_track_compare_name );
1019
1020   // g_list_concat doesn't copy memory properly
1021   // so process each list separately
1022
1023   GpxWritingContext context_tmp = context;
1024   GpxWritingOptions opt_tmp = { FALSE, FALSE, FALSE };
1025   // Force trackpoints on tracks
1026   if ( !context.options )
1027     context_tmp.options = &opt_tmp;
1028   context_tmp.options->is_route = FALSE;
1029
1030   // Loop around each list and write each one
1031   for (iter = g_list_first (gl); iter != NULL; iter = g_list_next (iter)) {
1032     gpx_write_track ( (VikTrack*)iter->data, &context_tmp );
1033   }
1034
1035   // Routes (to get routepoints)
1036   context_tmp.options->is_route = TRUE;
1037   for (iter = g_list_first (glrte); iter != NULL; iter = g_list_next (iter)) {
1038     gpx_write_track ( (VikTrack*)iter->data, &context_tmp );
1039   }
1040
1041   g_list_free ( gl );
1042   g_list_free ( glrte );
1043
1044   gpx_write_footer ( f );
1045 }
1046
1047 void a_gpx_write_track_file ( VikTrack *trk, FILE *f, GpxWritingOptions *options )
1048 {
1049   GpxWritingContext context = {options, f};
1050   gpx_write_header ( f );
1051   gpx_write_track ( trk, &context );
1052   gpx_write_footer ( f );
1053 }