]> git.street.me.uk Git - andy/viking.git/blob - src/gpx.c
Automatically remove suspicious first points of GPX Tracks.
[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-2014, 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 #include <glib/gstdio.h>
43 #include <glib/gi18n.h>
44 #ifdef HAVE_MATH_H
45 #include <math.h>
46 #endif
47 #include <time.h>
48
49 typedef enum {
50         tt_unknown = 0,
51
52         tt_gpx,
53         tt_gpx_name,
54         tt_gpx_desc,
55         tt_gpx_author,
56         tt_gpx_time,
57         tt_gpx_keywords,
58
59         tt_wpt,
60         tt_wpt_cmt,
61         tt_wpt_desc,
62         tt_wpt_src,
63         tt_wpt_type,
64         tt_wpt_name,
65         tt_wpt_ele,
66         tt_wpt_sym,
67         tt_wpt_time,
68         tt_wpt_url,
69         tt_wpt_link,            /* New in GPX 1.1 */
70
71         tt_trk,
72         tt_trk_cmt,
73         tt_trk_desc,
74         tt_trk_src,
75         tt_trk_type,
76         tt_trk_name,
77
78         tt_rte,
79
80         tt_trk_trkseg,
81         tt_trk_trkseg_trkpt,
82         tt_trk_trkseg_trkpt_ele,
83         tt_trk_trkseg_trkpt_time,
84         tt_trk_trkseg_trkpt_name,
85         /* extended */
86         tt_trk_trkseg_trkpt_course,
87         tt_trk_trkseg_trkpt_speed,
88         tt_trk_trkseg_trkpt_fix,
89         tt_trk_trkseg_trkpt_sat,
90
91         tt_trk_trkseg_trkpt_hdop,
92         tt_trk_trkseg_trkpt_vdop,
93         tt_trk_trkseg_trkpt_pdop,
94
95         tt_waypoint,
96         tt_waypoint_coord,
97         tt_waypoint_name,
98 } tag_type;
99
100 typedef struct tag_mapping {
101         tag_type tag_type;              /* enum from above for this tag */
102         const char *tag_name;           /* xpath-ish tag name */
103 } tag_mapping;
104
105 typedef struct {
106         GpxWritingOptions *options;
107         FILE *file;
108 } GpxWritingContext;
109
110 /*
111  * xpath(ish) mappings between full tag paths and internal identifiers.
112  * These appear in the order they appear in the GPX specification.
113  * If it's not a tag we explicitly handle, it doesn't go here.
114  */
115
116 tag_mapping tag_path_map[] = {
117
118         { tt_gpx, "/gpx" },
119         { tt_gpx_name, "/gpx/name" },
120         { tt_gpx_desc, "/gpx/desc" },
121         { tt_gpx_time, "/gpx/time" },
122         { tt_gpx_author, "/gpx/author" },
123         { tt_gpx_keywords, "/gpx/keywords" },
124
125         // GPX 1.1 variant - basic properties moved into metadata namespace
126         { tt_gpx_name, "/gpx/metadata/name" },
127         { tt_gpx_desc, "/gpx/metadata/desc" },
128         { tt_gpx_time, "/gpx/metadata/time" },
129         { tt_gpx_author, "/gpx/metadata/author" },
130         { tt_gpx_keywords, "/gpx/metadata/keywords" },
131
132         { tt_wpt, "/gpx/wpt" },
133
134         { tt_waypoint, "/loc/waypoint" },
135         { tt_waypoint_coord, "/loc/waypoint/coord" },
136         { tt_waypoint_name, "/loc/waypoint/name" },
137
138         { tt_wpt_ele, "/gpx/wpt/ele" },
139         { tt_wpt_time, "/gpx/wpt/time" },
140         { tt_wpt_name, "/gpx/wpt/name" },
141         { tt_wpt_cmt, "/gpx/wpt/cmt" },
142         { tt_wpt_desc, "/gpx/wpt/desc" },
143         { tt_wpt_src, "/gpx/wpt/src" },
144         { tt_wpt_type, "/gpx/wpt/type" },
145         { tt_wpt_sym, "/gpx/wpt/sym" },
146         { tt_wpt_sym, "/loc/waypoint/type" },
147         { tt_wpt_url, "/gpx/wpt/url" },
148         { tt_wpt_link, "/gpx/wpt/link" },                    /* GPX 1.1 */
149
150         { tt_trk, "/gpx/trk" },
151         { tt_trk_name, "/gpx/trk/name" },
152         { tt_trk_cmt, "/gpx/trk/cmt" },
153         { tt_trk_desc, "/gpx/trk/desc" },
154         { tt_trk_src, "/gpx/trk/src" },
155         { tt_trk_type, "/gpx/trk/type" },
156         { tt_trk_trkseg, "/gpx/trk/trkseg" },
157         { tt_trk_trkseg_trkpt, "/gpx/trk/trkseg/trkpt" },
158         { tt_trk_trkseg_trkpt_ele, "/gpx/trk/trkseg/trkpt/ele" },
159         { tt_trk_trkseg_trkpt_time, "/gpx/trk/trkseg/trkpt/time" },
160         { tt_trk_trkseg_trkpt_name, "/gpx/trk/trkseg/trkpt/name" },
161         /* extended */
162         { tt_trk_trkseg_trkpt_course, "/gpx/trk/trkseg/trkpt/course" },
163         { tt_trk_trkseg_trkpt_speed, "/gpx/trk/trkseg/trkpt/speed" },
164         { tt_trk_trkseg_trkpt_fix, "/gpx/trk/trkseg/trkpt/fix" },
165         { tt_trk_trkseg_trkpt_sat, "/gpx/trk/trkseg/trkpt/sat" },
166
167         { tt_trk_trkseg_trkpt_hdop, "/gpx/trk/trkseg/trkpt/hdop" },
168         { tt_trk_trkseg_trkpt_vdop, "/gpx/trk/trkseg/trkpt/vdop" },
169         { tt_trk_trkseg_trkpt_pdop, "/gpx/trk/trkseg/trkpt/pdop" },
170
171         { tt_rte, "/gpx/rte" },
172         // NB Route reuses track point feature tags
173         { tt_trk_name, "/gpx/rte/name" },
174         { tt_trk_cmt, "/gpx/rte/cmt" },
175         { tt_trk_desc, "/gpx/rte/desc" },
176         { tt_trk_src, "/gpx/rte/src" },
177         { tt_trk_trkseg_trkpt, "/gpx/rte/rtept" },
178         { tt_trk_trkseg_trkpt_name, "/gpx/rte/rtept/name" },
179         { tt_trk_trkseg_trkpt_ele, "/gpx/rte/rtept/ele" },
180
181         {0}
182 };
183
184 static tag_type get_tag(const char *t)
185 {
186         tag_mapping *tm;
187         for (tm = tag_path_map; tm->tag_type != 0; tm++)
188                 if (0 == strcmp(tm->tag_name, t))
189                         return tm->tag_type;
190         return tt_unknown;
191 }
192
193 /******************************************/
194
195 tag_type current_tag = tt_unknown;
196 GString *xpath = NULL;
197 GString *c_cdata = NULL;
198
199 /* current ("c_") objects */
200 VikTrackpoint *c_tp = NULL;
201 VikWaypoint *c_wp = NULL;
202 VikTrack *c_tr = NULL;
203 VikTRWMetadata *c_md = NULL;
204
205 gchar *c_wp_name = NULL;
206 gchar *c_tr_name = NULL;
207
208 /* temporary things so we don't have to create them lots of times */
209 const gchar *c_slat, *c_slon;
210 struct LatLon c_ll;
211
212 /* specialty flags / etc */
213 gboolean f_tr_newseg;
214 guint unnamed_waypoints = 0;
215 guint unnamed_tracks = 0;
216 guint unnamed_routes = 0;
217
218 static const char *get_attr ( const char **attr, const char *key )
219 {
220   while ( *attr ) {
221     if ( strcmp(*attr,key) == 0 )
222       return *(attr + 1);
223     attr += 2;
224   }
225   return NULL;
226 }
227
228 static gboolean set_c_ll ( const char **attr )
229 {
230   if ( (c_slat = get_attr ( attr, "lat" )) && (c_slon = get_attr ( attr, "lon" )) ) {
231     c_ll.lat = g_ascii_strtod(c_slat, NULL);
232     c_ll.lon = g_ascii_strtod(c_slon, NULL);
233     return TRUE;
234   }
235   return FALSE;
236 }
237
238 static void gpx_start(VikTrwLayer *vtl, const char *el, const char **attr)
239 {
240   static const gchar *tmp;
241
242   g_string_append_c ( xpath, '/' );
243   g_string_append ( xpath, el );
244   current_tag = get_tag ( xpath->str );
245
246   switch ( current_tag ) {
247
248      case tt_gpx:
249        c_md = vik_trw_metadata_new();
250        break;
251
252      case tt_wpt:
253        if ( set_c_ll( attr ) ) {
254          c_wp = vik_waypoint_new ();
255          c_wp->visible = TRUE;
256          if ( get_attr ( attr, "hidden" ) )
257            c_wp->visible = FALSE;
258
259          vik_coord_load_from_latlon ( &(c_wp->coord), vik_trw_layer_get_coord_mode ( vtl ), &c_ll );
260        }
261        break;
262
263      case tt_trk:
264      case tt_rte:
265        c_tr = vik_track_new ();
266        vik_track_set_defaults ( c_tr );
267        c_tr->is_route = (current_tag == tt_rte) ? TRUE : FALSE;
268        c_tr->visible = TRUE;
269        if ( get_attr ( attr, "hidden" ) )
270          c_tr->visible = FALSE;
271        break;
272
273      case tt_trk_trkseg:
274        f_tr_newseg = TRUE;
275        break;
276
277      case tt_trk_trkseg_trkpt:
278        if ( set_c_ll( attr ) ) {
279          c_tp = vik_trackpoint_new ();
280          vik_coord_load_from_latlon ( &(c_tp->coord), vik_trw_layer_get_coord_mode ( vtl ), &c_ll );
281          if ( f_tr_newseg ) {
282            c_tp->newsegment = TRUE;
283            f_tr_newseg = FALSE;
284          }
285          c_tr->trackpoints = g_list_append ( c_tr->trackpoints, c_tp );
286        }
287        break;
288
289      case tt_gpx_name:
290      case tt_gpx_author:
291      case tt_gpx_desc:
292      case tt_gpx_keywords:
293      case tt_gpx_time:
294      case tt_trk_trkseg_trkpt_name:
295      case tt_trk_trkseg_trkpt_ele:
296      case tt_trk_trkseg_trkpt_time:
297      case tt_wpt_cmt:
298      case tt_wpt_desc:
299      case tt_wpt_src:
300      case tt_wpt_type:
301      case tt_wpt_name:
302      case tt_wpt_ele:
303      case tt_wpt_time:
304      case tt_wpt_url:
305      case tt_wpt_link:
306      case tt_trk_cmt:
307      case tt_trk_desc:
308      case tt_trk_src:
309      case tt_trk_type:
310      case tt_trk_name:
311        g_string_erase ( c_cdata, 0, -1 ); /* clear the cdata buffer */
312        break;
313
314      case tt_waypoint:
315        c_wp = vik_waypoint_new ();
316        c_wp->visible = TRUE;
317        break;
318
319      case tt_waypoint_coord:
320        if ( set_c_ll( attr ) )
321          vik_coord_load_from_latlon ( &(c_wp->coord), vik_trw_layer_get_coord_mode ( vtl ), &c_ll );
322        break;
323
324      case tt_waypoint_name:
325        if ( ( tmp = get_attr(attr, "id") ) ) {
326          if ( c_wp_name )
327            g_free ( c_wp_name );
328          c_wp_name = g_strdup ( tmp );
329        }
330        g_string_erase ( c_cdata, 0, -1 ); /* clear the cdata buffer for description */
331        break;
332         
333      default: break;
334   }
335 }
336
337 // Allow user override / refinement of GPX tidying
338 #define VIK_SETTINGS_GPX_TIDY "gpx_tidy_points"
339 #define VIK_SETTINGS_GPX_TIDY_SPEED "gpx_tidy_points_max_speed"
340
341 /**
342  *
343  */
344 static void track_tidy_processing ( VikTrwLayer *vtl )
345 {
346   // Default to automatically attempt tiding
347   gboolean do_tidy = TRUE;
348   gboolean btmp = TRUE;
349   if ( a_settings_get_boolean ( VIK_SETTINGS_GPX_TIDY, &btmp ) )
350      do_tidy = btmp;
351
352   if ( do_tidy ) {
353     // highly unlikely to be going faster than this, especially for the first point
354     guint speed = 340; // Speed of Sound
355
356     gint itmp = 0;
357     if ( a_settings_get_integer ( VIK_SETTINGS_GPX_TIDY_SPEED, &itmp ) )
358       speed = (guint)itmp;
359
360     // NB bounds calculated in subsequent layer post read,
361     //  so no need to do it now
362     vik_trw_layer_tidy_tracks ( vtl, speed, FALSE );
363   }
364 }
365
366 static void gpx_end(VikTrwLayer *vtl, const char *el)
367 {
368   static GTimeVal tp_time;
369   static GTimeVal wp_time;
370
371   g_string_truncate ( xpath, xpath->len - strlen(el) - 1 );
372
373   switch ( current_tag ) {
374
375      case tt_gpx:
376        vik_trw_layer_set_metadata ( vtl, c_md );
377        c_md = NULL;
378
379        // Essentially the end for a TrackWaypoint layer,
380        //  so any specific GPX post processing can occur here
381        track_tidy_processing ( vtl );
382
383        break;
384
385      case tt_gpx_name:
386        vik_layer_rename ( VIK_LAYER(vtl), c_cdata->str );
387        g_string_erase ( c_cdata, 0, -1 );
388        break;
389
390      case tt_gpx_author:
391        if ( c_md->author )
392          g_free ( c_md->author );
393        c_md->author = g_strdup ( c_cdata->str );
394        g_string_erase ( c_cdata, 0, -1 );
395        break;
396
397      case tt_gpx_desc:
398        if ( c_md->description )
399          g_free ( c_md->description );
400        c_md->description = g_strdup ( c_cdata->str );
401        g_string_erase ( c_cdata, 0, -1 );
402        break;
403
404      case tt_gpx_keywords:
405        if ( c_md->keywords )
406          g_free ( c_md->keywords );
407        c_md->keywords = g_strdup ( c_cdata->str );
408        g_string_erase ( c_cdata, 0, -1 );
409        break;
410
411      case tt_gpx_time:
412        if ( c_md->timestamp )
413          g_free ( c_md->timestamp );
414        c_md->timestamp = g_strdup ( c_cdata->str );
415        g_string_erase ( c_cdata, 0, -1 );
416        break;
417
418      case tt_waypoint:
419      case tt_wpt:
420        if ( ! c_wp_name )
421          c_wp_name = g_strdup_printf("VIKING_WP%04d", unnamed_waypoints++);
422        vik_trw_layer_filein_add_waypoint ( vtl, c_wp_name, c_wp );
423        g_free ( c_wp_name );
424        c_wp = NULL;
425        c_wp_name = NULL;
426        break;
427
428      case tt_trk:
429        if ( ! c_tr_name )
430          c_tr_name = g_strdup_printf("VIKING_TR%03d", unnamed_tracks++);
431        // Delibrate fall through
432      case tt_rte:
433        if ( ! c_tr_name )
434          c_tr_name = g_strdup_printf("VIKING_RT%03d", unnamed_routes++);
435        vik_trw_layer_filein_add_track ( vtl, c_tr_name, c_tr );
436        g_free ( c_tr_name );
437        c_tr = NULL;
438        c_tr_name = NULL;
439        break;
440
441      case tt_wpt_name:
442        if ( c_wp_name )
443          g_free ( c_wp_name );
444        c_wp_name = g_strdup ( c_cdata->str );
445        g_string_erase ( c_cdata, 0, -1 );
446        break;
447
448      case tt_trk_name:
449        if ( c_tr_name )
450          g_free ( c_tr_name );
451        c_tr_name = g_strdup ( c_cdata->str );
452        g_string_erase ( c_cdata, 0, -1 );
453        break;
454
455      case tt_wpt_ele:
456        c_wp->altitude = g_ascii_strtod ( c_cdata->str, NULL );
457        g_string_erase ( c_cdata, 0, -1 );
458        break;
459
460      case tt_trk_trkseg_trkpt_ele:
461        c_tp->altitude = g_ascii_strtod ( c_cdata->str, NULL );
462        g_string_erase ( c_cdata, 0, -1 );
463        break;
464
465      case tt_waypoint_name: /* .loc name is really description. */
466      case tt_wpt_desc:
467        vik_waypoint_set_description ( c_wp, c_cdata->str );
468        g_string_erase ( c_cdata, 0, -1 );
469        break;
470
471      case tt_wpt_cmt:
472        vik_waypoint_set_comment ( c_wp, c_cdata->str );
473        g_string_erase ( c_cdata, 0, -1 );
474        break;
475
476      case tt_wpt_src:
477        vik_waypoint_set_source ( c_wp, c_cdata->str );
478        g_string_erase ( c_cdata, 0, -1 );
479        break;
480
481      case tt_wpt_type:
482        vik_waypoint_set_type ( c_wp, c_cdata->str );
483        g_string_erase ( c_cdata, 0, -1 );
484        break;
485
486      case tt_wpt_url:
487        vik_waypoint_set_url ( c_wp, c_cdata->str );
488        g_string_erase ( c_cdata, 0, -1 );
489        break;
490
491      case tt_wpt_link:
492        vik_waypoint_set_image ( c_wp, c_cdata->str );
493        g_string_erase ( c_cdata, 0, -1 );
494        break;
495
496      case tt_wpt_sym: {
497        vik_waypoint_set_symbol ( c_wp, c_cdata->str );
498        g_string_erase ( c_cdata, 0, -1 );
499        break;
500        }
501
502      case tt_trk_desc:
503        vik_track_set_description ( c_tr, c_cdata->str );
504        g_string_erase ( c_cdata, 0, -1 );
505        break;
506
507      case tt_trk_src:
508        vik_track_set_source ( c_tr, c_cdata->str );
509        g_string_erase ( c_cdata, 0, -1 );
510        break;
511
512      case tt_trk_type:
513        vik_track_set_type ( c_tr, c_cdata->str );
514        g_string_erase ( c_cdata, 0, -1 );
515        break;
516
517      case tt_trk_cmt:
518        vik_track_set_comment ( c_tr, c_cdata->str );
519        g_string_erase ( c_cdata, 0, -1 );
520        break;
521
522      case tt_wpt_time:
523        if ( g_time_val_from_iso8601(c_cdata->str, &wp_time) ) {
524          c_wp->timestamp = wp_time.tv_sec;
525          c_wp->has_timestamp = TRUE;
526        }
527        g_string_erase ( c_cdata, 0, -1 );
528        break;
529
530      case tt_trk_trkseg_trkpt_name:
531        vik_trackpoint_set_name ( c_tp, c_cdata->str );
532        g_string_erase ( c_cdata, 0, -1 );
533        break;
534
535      case tt_trk_trkseg_trkpt_time:
536        if ( g_time_val_from_iso8601(c_cdata->str, &tp_time) ) {
537          c_tp->timestamp = tp_time.tv_sec;
538          c_tp->has_timestamp = TRUE;
539        }
540        g_string_erase ( c_cdata, 0, -1 );
541        break;
542
543      case tt_trk_trkseg_trkpt_course:
544        c_tp->course = g_ascii_strtod ( c_cdata->str, NULL );
545        g_string_erase ( c_cdata, 0, -1 );
546        break;
547
548      case tt_trk_trkseg_trkpt_speed:
549        c_tp->speed = g_ascii_strtod ( c_cdata->str, NULL );
550        g_string_erase ( c_cdata, 0, -1 );
551        break;
552
553      case tt_trk_trkseg_trkpt_fix:
554        if (!strcmp("2d", c_cdata->str))
555          c_tp->fix_mode = VIK_GPS_MODE_2D;
556        else if (!strcmp("3d", c_cdata->str))
557          c_tp->fix_mode = VIK_GPS_MODE_3D;
558        else if (!strcmp("dgps", c_cdata->str))
559          c_tp->fix_mode = VIK_GPS_MODE_DGPS;
560        else if (!strcmp("pps", c_cdata->str))
561          c_tp->fix_mode = VIK_GPS_MODE_PPS;
562        else
563          c_tp->fix_mode = VIK_GPS_MODE_NOT_SEEN;
564        g_string_erase ( c_cdata, 0, -1 );
565        break;
566
567      case tt_trk_trkseg_trkpt_sat:
568        c_tp->nsats = atoi ( c_cdata->str );
569        g_string_erase ( c_cdata, 0, -1 );
570        break;
571
572      case tt_trk_trkseg_trkpt_hdop:
573        c_tp->hdop = g_strtod ( c_cdata->str, NULL );
574        g_string_erase ( c_cdata, 0, -1 );
575        break;
576
577      case tt_trk_trkseg_trkpt_vdop:
578        c_tp->vdop = g_strtod ( c_cdata->str, NULL );
579        g_string_erase ( c_cdata, 0, -1 );
580        break;
581
582      case tt_trk_trkseg_trkpt_pdop:
583        c_tp->pdop = g_strtod ( c_cdata->str, NULL );
584        g_string_erase ( c_cdata, 0, -1 );
585        break;
586
587      default: break;
588   }
589
590   current_tag = get_tag ( xpath->str );
591 }
592
593 static void gpx_cdata(void *dta, const XML_Char *s, int len)
594 {
595   switch ( current_tag ) {
596     case tt_gpx_name:
597     case tt_gpx_author:
598     case tt_gpx_desc:
599     case tt_gpx_keywords:
600     case tt_gpx_time:
601     case tt_wpt_name:
602     case tt_trk_name:
603     case tt_wpt_ele:
604     case tt_trk_trkseg_trkpt_ele:
605     case tt_wpt_cmt:
606     case tt_wpt_desc:
607     case tt_wpt_src:
608     case tt_wpt_type:
609     case tt_wpt_sym:
610     case tt_wpt_url:
611     case tt_wpt_link:
612     case tt_trk_cmt:
613     case tt_trk_desc:
614     case tt_trk_src:
615     case tt_trk_type:
616     case tt_trk_trkseg_trkpt_time:
617     case tt_wpt_time:
618     case tt_trk_trkseg_trkpt_name:
619     case tt_trk_trkseg_trkpt_course:
620     case tt_trk_trkseg_trkpt_speed:
621     case tt_trk_trkseg_trkpt_fix:
622     case tt_trk_trkseg_trkpt_sat:
623     case tt_trk_trkseg_trkpt_hdop:
624     case tt_trk_trkseg_trkpt_vdop:
625     case tt_trk_trkseg_trkpt_pdop:
626     case tt_waypoint_name: /* .loc name is really description. */
627       g_string_append_len ( c_cdata, s, len );
628       break;
629
630     default: break;  /* ignore cdata from other things */
631   }
632 }
633
634 // make like a "stack" of tag names
635 // like gpspoint's separated like /gpx/wpt/whatever
636
637 gboolean a_gpx_read_file( VikTrwLayer *vtl, FILE *f ) {
638   XML_Parser parser = XML_ParserCreate(NULL);
639   int done=0, len;
640   enum XML_Status status = XML_STATUS_ERROR;
641
642   XML_SetElementHandler(parser, (XML_StartElementHandler) gpx_start, (XML_EndElementHandler) gpx_end);
643   XML_SetUserData(parser, vtl); /* in the future we could remove all global variables */
644   XML_SetCharacterDataHandler(parser, (XML_CharacterDataHandler) gpx_cdata);
645
646   gchar buf[4096];
647
648   g_assert ( f != NULL && vtl != NULL );
649
650   xpath = g_string_new ( "" );
651   c_cdata = g_string_new ( "" );
652
653   unnamed_waypoints = 1;
654   unnamed_tracks = 1;
655   unnamed_routes = 1;
656
657   while (!done) {
658     len = fread(buf, 1, sizeof(buf)-7, f);
659     done = feof(f) || !len;
660     status = XML_Parse(parser, buf, len, done);
661   }
662  
663   XML_ParserFree (parser);
664   g_string_free ( xpath, TRUE );
665   g_string_free ( c_cdata, TRUE );
666
667   return status != XML_STATUS_ERROR;
668 }
669
670 /**** entitize from GPSBabel ****/
671 typedef struct {
672         const char * text;
673         const char * entity;
674         int  not_html;
675 } entity_types;
676
677 static
678 entity_types stdentities[] =  {
679         { "&",  "&amp;", 0 },
680         { "'",  "&apos;", 1 },
681         { "<",  "&lt;", 0 },
682         { ">",  "&gt;", 0 },
683         { "\"", "&quot;", 0 },
684         { NULL, NULL, 0 }
685 };
686
687 void utf8_to_int( const char *cp, int *bytes, int *value )
688 {
689         if ( (*cp & 0xe0) == 0xc0 ) {
690                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
691                 *bytes = 2;
692                 *value = ((*cp & 0x1f) << 6) |
693                         (*(cp+1) & 0x3f);
694         }
695         else if ( (*cp & 0xf0) == 0xe0 ) {
696                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
697                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
698                 *bytes = 3;
699                 *value = ((*cp & 0x0f) << 12) |
700                         ((*(cp+1) & 0x3f) << 6) |
701                         (*(cp+2) & 0x3f);
702         }
703         else if ( (*cp & 0xf8) == 0xf0 ) {
704                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
705                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
706                 if ( (*(cp+3) & 0xc0) != 0x80 ) goto dodefault;
707                 *bytes = 4;
708                 *value = ((*cp & 0x07) << 18) |
709                         ((*(cp+1) & 0x3f) << 12) |
710                         ((*(cp+2) & 0x3f) << 6) |
711                         (*(cp+3) & 0x3f);
712         }
713         else if ( (*cp & 0xfc) == 0xf8 ) {
714                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
715                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
716                 if ( (*(cp+3) & 0xc0) != 0x80 ) goto dodefault;
717                 if ( (*(cp+4) & 0xc0) != 0x80 ) goto dodefault;
718                 *bytes = 5;
719                 *value = ((*cp & 0x03) << 24) |
720                         ((*(cp+1) & 0x3f) << 18) |
721                         ((*(cp+2) & 0x3f) << 12) |
722                         ((*(cp+3) & 0x3f) << 6) |
723                         (*(cp+4) & 0x3f);
724         }
725         else if ( (*cp & 0xfe) == 0xfc ) {
726                 if ( (*(cp+1) & 0xc0) != 0x80 ) goto dodefault;
727                 if ( (*(cp+2) & 0xc0) != 0x80 ) goto dodefault;
728                 if ( (*(cp+3) & 0xc0) != 0x80 ) goto dodefault;
729                 if ( (*(cp+4) & 0xc0) != 0x80 ) goto dodefault;
730                 if ( (*(cp+5) & 0xc0) != 0x80 ) goto dodefault;
731                 *bytes = 6;
732                 *value = ((*cp & 0x01) << 30) |
733                         ((*(cp+1) & 0x3f) << 24) |
734                         ((*(cp+2) & 0x3f) << 18) |
735                         ((*(cp+3) & 0x3f) << 12) |
736                         ((*(cp+4) & 0x3f) << 6) |
737                         (*(cp+5) & 0x3f);
738         }
739         else {
740 dodefault:
741                 *bytes = 1;
742                 *value = (unsigned char)*cp;
743         }
744 }
745
746 static
747 char *
748 entitize(const char * str)
749 {
750         int elen, ecount, nsecount;
751         entity_types *ep;
752         const char * cp;
753         char * p, * tmp, * xstr;
754
755         char tmpsub[20];
756         int bytes = 0;
757         int value = 0;
758         ep = stdentities;
759         elen = ecount = nsecount = 0;
760
761         /* figure # of entity replacements and additional size. */
762         while (ep->text) {
763                 cp = str;
764                 while ((cp = strstr(cp, ep->text)) != NULL) {
765                         elen += strlen(ep->entity) - strlen(ep->text);
766                         ecount++;
767                         cp += strlen(ep->text);
768                 }
769                 ep++;
770         }
771
772         /* figure the same for other than standard entities (i.e. anything
773          * that isn't in the range U+0000 to U+007F */
774         for ( cp = str; *cp; cp++ ) {
775                 if ( *cp & 0x80 ) {
776
777                         utf8_to_int( cp, &bytes, &value );
778                         cp += bytes-1;
779                         elen += sprintf( tmpsub, "&#x%x;", value ) - bytes;
780                         nsecount++;
781                 }
782         }
783
784         /* enough space for the whole string plus entity replacements, if any */
785         tmp = g_malloc((strlen(str) + elen + 1));
786         strcpy(tmp, str);
787
788         /* no entity replacements */
789         if (ecount == 0 && nsecount == 0)
790                 return (tmp);
791
792         if ( ecount != 0 ) {
793                 for (ep = stdentities; ep->text; ep++) {
794                         p = tmp;
795                         while ((p = strstr(p, ep->text)) != NULL) {
796                                 elen = strlen(ep->entity);
797
798                                 xstr = g_strdup(p + strlen(ep->text));
799
800                                 strcpy(p, ep->entity);
801                                 strcpy(p + elen, xstr);
802
803                                 g_free(xstr);
804
805                                 p += elen;
806                         }
807                 }
808         }
809
810         if ( nsecount != 0 ) {
811                 p = tmp;
812                 while (*p) {
813                         if ( *p & 0x80 ) {
814                                 utf8_to_int( p, &bytes, &value );
815                                 if ( p[bytes] ) {
816                                         xstr = g_strdup( p + bytes );
817                                 }
818                                 else {
819                                         xstr = NULL;
820                                 }
821                                 sprintf( p, "&#x%x;", value );
822                                 p = p+strlen(p);
823                                 if ( xstr ) {
824                                         strcpy( p, xstr );
825                                         g_free(xstr);
826                                 }
827                         }
828                         else {
829                                 p++;
830                         }
831                 }
832         }
833         return (tmp);
834 }
835 /**** end GPSBabel code ****/
836
837 /* export GPX */
838
839 static void gpx_write_waypoint ( VikWaypoint *wp, GpxWritingContext *context )
840 {
841   // Don't write invisible waypoints when specified
842   if (context->options && !context->options->hidden && !wp->visible)
843     return;
844
845   FILE *f = context->file;
846   struct LatLon ll;
847   gchar s_lat[COORDS_STR_BUFFER_SIZE];
848   gchar s_lon[COORDS_STR_BUFFER_SIZE];
849   gchar *tmp;
850   vik_coord_to_latlon ( &(wp->coord), &ll );
851   a_coords_dtostr_buffer ( ll.lat, s_lat );
852   a_coords_dtostr_buffer ( ll.lon, s_lon );
853   // NB 'hidden' is not part of any GPX standard - this appears to be a made up Viking 'extension'
854   //  luckily most other GPX processing software ignores things they don't understand
855   fprintf ( f, "<wpt lat=\"%s\" lon=\"%s\"%s>\n",
856                s_lat, s_lon, wp->visible ? "" : " hidden=\"hidden\"" );
857
858   // Sanity clause
859   if ( wp->name )
860     tmp = entitize ( wp->name );
861   else
862     tmp = g_strdup ("waypoint");
863
864   fprintf ( f, "  <name>%s</name>\n", tmp );
865   g_free ( tmp);
866
867   if ( wp->altitude != VIK_DEFAULT_ALTITUDE )
868   {
869     gchar s_alt[COORDS_STR_BUFFER_SIZE];
870     a_coords_dtostr_buffer ( wp->altitude, s_alt );
871     fprintf ( f, "  <ele>%s</ele>\n", s_alt );
872   }
873
874   if ( wp->has_timestamp ) {
875     GTimeVal timestamp;
876     timestamp.tv_sec = wp->timestamp;
877     timestamp.tv_usec = 0;
878
879     gchar *time_iso8601 = g_time_val_to_iso8601 ( &timestamp );
880     if ( time_iso8601 != NULL )
881       fprintf ( f, "  <time>%s</time>\n", time_iso8601 );
882     g_free ( time_iso8601 );
883   }
884
885   if ( wp->comment )
886   {
887     tmp = entitize(wp->comment);
888     fprintf ( f, "  <cmt>%s</cmt>\n", tmp );
889     g_free ( tmp );
890   }
891   if ( wp->description )
892   {
893     tmp = entitize(wp->description);
894     fprintf ( f, "  <desc>%s</desc>\n", tmp );
895     g_free ( tmp );
896   }
897   if ( wp->source )
898   {
899     tmp = entitize(wp->source);
900     fprintf ( f, "  <src>%s</src>\n", tmp );
901     g_free ( tmp );
902   }
903   if ( wp->type )
904   {
905     tmp = entitize(wp->type);
906     fprintf ( f, "  <type>%s</type>\n", tmp );
907     g_free ( tmp );
908   }
909   if ( wp->url )
910   {
911     tmp = entitize(wp->url);
912     fprintf ( f, "  <url>%s</url>\n", tmp );
913     g_free ( tmp );
914   }
915   if ( wp->image )
916   {
917     tmp = entitize(wp->image);
918     fprintf ( f, "  <link>%s</link>\n", tmp );
919     g_free ( tmp );
920   }
921   if ( wp->symbol ) 
922   {
923     tmp = entitize(wp->symbol);
924     if ( a_vik_gpx_export_wpt_sym_name ( ) ) {
925        // Lowercase the symbol name
926        gchar *tmp2 = g_utf8_strdown ( tmp, -1 );
927        fprintf ( f, "  <sym>%s</sym>\n",  tmp2 );
928        g_free ( tmp2 );
929     }
930     else
931       fprintf ( f, "  <sym>%s</sym>\n", tmp);
932     g_free ( tmp );
933   }
934
935   fprintf ( f, "</wpt>\n" );
936 }
937
938 static void gpx_write_trackpoint ( VikTrackpoint *tp, GpxWritingContext *context )
939 {
940   FILE *f = context->file;
941   struct LatLon ll;
942   gchar s_lat[COORDS_STR_BUFFER_SIZE];
943   gchar s_lon[COORDS_STR_BUFFER_SIZE];
944   gchar s_alt[COORDS_STR_BUFFER_SIZE];
945   gchar s_dop[COORDS_STR_BUFFER_SIZE];
946   gchar *time_iso8601;
947   vik_coord_to_latlon ( &(tp->coord), &ll );
948
949   // No such thing as a rteseg! So make sure we don't put them in
950   if ( context->options && !context->options->is_route && tp->newsegment )
951     fprintf ( f, "  </trkseg>\n  <trkseg>\n" );
952
953   a_coords_dtostr_buffer ( ll.lat, s_lat );
954   a_coords_dtostr_buffer ( ll.lon, s_lon );
955   fprintf ( f, "  <%spt lat=\"%s\" lon=\"%s\">\n", (context->options && context->options->is_route) ? "rte" : "trk", s_lat, s_lon );
956
957   if (tp->name) {
958     gchar *s_name = entitize(tp->name);
959     fprintf ( f, "    <name>%s</name>\n", s_name );
960     g_free(s_name);
961   }
962
963   if ( tp->altitude != VIK_DEFAULT_ALTITUDE )
964   {
965     a_coords_dtostr_buffer ( tp->altitude, s_alt );
966     fprintf ( f, "    <ele>%s</ele>\n", s_alt );
967   }
968   else if ( context->options != NULL && context->options->force_ele )
969   {
970     fprintf ( f, "    <ele>0</ele>\n" );
971   }
972   
973   time_iso8601 = NULL;
974   if ( tp->has_timestamp ) {
975     GTimeVal timestamp;
976     timestamp.tv_sec = tp->timestamp;
977     timestamp.tv_usec = 0;
978   
979     time_iso8601 = g_time_val_to_iso8601 ( &timestamp );
980   }
981   else if ( context->options != NULL && context->options->force_time )
982   {
983     GTimeVal current;
984     g_get_current_time ( &current );
985   
986     time_iso8601 = g_time_val_to_iso8601 ( &current );
987   }
988   if ( time_iso8601 != NULL )
989     fprintf ( f, "    <time>%s</time>\n", time_iso8601 );
990   g_free(time_iso8601);
991   time_iso8601 = NULL;
992   
993   if (!isnan(tp->course)) {
994     gchar s_course[COORDS_STR_BUFFER_SIZE];
995     a_coords_dtostr_buffer ( tp->course, s_course );
996     fprintf ( f, "    <course>%s</course>\n", s_course );
997   }
998   if (!isnan(tp->speed)) {
999     gchar s_speed[COORDS_STR_BUFFER_SIZE];
1000     a_coords_dtostr_buffer ( tp->speed, s_speed );
1001     fprintf ( f, "    <speed>%s</speed>\n", s_speed );
1002   }
1003   if (tp->fix_mode == VIK_GPS_MODE_2D)
1004     fprintf ( f, "    <fix>2d</fix>\n");
1005   if (tp->fix_mode == VIK_GPS_MODE_3D)
1006     fprintf ( f, "    <fix>3d</fix>\n");
1007   if (tp->fix_mode == VIK_GPS_MODE_DGPS)
1008     fprintf ( f, "    <fix>dgps</fix>\n");
1009   if (tp->fix_mode == VIK_GPS_MODE_PPS)
1010     fprintf ( f, "    <fix>pps</fix>\n");
1011   if (tp->nsats > 0)
1012     fprintf ( f, "    <sat>%d</sat>\n", tp->nsats );
1013
1014   if ( tp->hdop != VIK_DEFAULT_DOP ) {
1015     a_coords_dtostr_buffer ( tp->hdop, s_dop );
1016     fprintf ( f, "    <hdop>%s</hdop>\n", s_dop );
1017   }
1018
1019   if ( tp->vdop != VIK_DEFAULT_DOP ) {
1020     a_coords_dtostr_buffer ( tp->vdop, s_dop );
1021     fprintf ( f, "    <vdop>%s</vdop>\n", s_dop );
1022   }
1023
1024   if ( tp->pdop != VIK_DEFAULT_DOP ) {
1025     a_coords_dtostr_buffer ( tp->pdop, s_dop );
1026     fprintf ( f, "    <pdop>%s</pdop>\n", s_dop );
1027   }
1028
1029   fprintf ( f, "  </%spt>\n", (context->options && context->options->is_route) ? "rte" : "trk" );
1030 }
1031
1032
1033 static void gpx_write_track ( VikTrack *t, GpxWritingContext *context )
1034 {
1035   // Don't write invisible tracks when specified
1036   if (context->options && !context->options->hidden && !t->visible)
1037     return;
1038
1039   FILE *f = context->file;
1040   gchar *tmp;
1041   gboolean first_tp_is_newsegment = FALSE; /* must temporarily make it not so, but we want to restore state. not that it matters. */
1042
1043   // Sanity clause
1044   if ( t->name )
1045     tmp = entitize ( t->name );
1046   else
1047     tmp = g_strdup ("track");
1048
1049   // NB 'hidden' is not part of any GPX standard - this appears to be a made up Viking 'extension'
1050   //  luckily most other GPX processing software ignores things they don't understand
1051   fprintf ( f, "<%s%s>\n  <name>%s</name>\n",
1052             t->is_route ? "rte" : "trk",
1053             t->visible ? "" : " hidden=\"hidden\"",
1054             tmp );
1055   g_free ( tmp );
1056
1057   if ( t->comment )
1058   {
1059     tmp = entitize ( t->comment );
1060     fprintf ( f, "  <cmt>%s</cmt>\n", tmp );
1061     g_free ( tmp );
1062   }
1063
1064   if ( t->description )
1065   {
1066     tmp = entitize ( t->description );
1067     fprintf ( f, "  <desc>%s</desc>\n", tmp );
1068     g_free ( tmp );
1069   }
1070
1071   if ( t->source )
1072   {
1073     tmp = entitize ( t->source );
1074     fprintf ( f, "  <src>%s</src>\n", tmp );
1075     g_free ( tmp );
1076   }
1077
1078   if ( t->type )
1079   {
1080     tmp = entitize ( t->type );
1081     fprintf ( f, "  <type>%s</type>\n", tmp );
1082     g_free ( tmp );
1083   }
1084
1085   /* No such thing as a rteseg! */
1086   if ( !t->is_route )
1087     fprintf ( f, "  <trkseg>\n" );
1088
1089   if ( t->trackpoints && t->trackpoints->data ) {
1090     first_tp_is_newsegment = VIK_TRACKPOINT(t->trackpoints->data)->newsegment;
1091     VIK_TRACKPOINT(t->trackpoints->data)->newsegment = FALSE; /* so we won't write </trkseg><trkseg> already */
1092     g_list_foreach ( t->trackpoints, (GFunc) gpx_write_trackpoint, context );
1093     VIK_TRACKPOINT(t->trackpoints->data)->newsegment = first_tp_is_newsegment; /* restore state */
1094   }
1095
1096   /* NB apparently no such thing as a rteseg! */
1097   if (!t->is_route)
1098     fprintf ( f, "  </trkseg>\n");
1099
1100   fprintf ( f, "</%s>\n", t->is_route ? "rte" : "trk" );
1101 }
1102
1103 static void gpx_write_header( FILE *f )
1104 {
1105   fprintf(f, "<?xml version=\"1.0\"?>\n"
1106           "<gpx version=\"1.0\" creator=\"Viking -- http://viking.sf.net/\"\n"
1107           "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1108           "xmlns=\"http://www.topografix.com/GPX/1/0\"\n"
1109           "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
1110 }
1111
1112 static void gpx_write_footer( FILE *f )
1113 {
1114   fprintf(f, "</gpx>\n");
1115 }
1116
1117 static int gpx_waypoint_compare(const void *x, const void *y)
1118 {
1119   VikWaypoint *a = (VikWaypoint *)x;
1120   VikWaypoint *b = (VikWaypoint *)y;
1121   return strcmp(a->name,b->name);
1122 }
1123
1124 static int gpx_track_compare_name(const void *x, const void *y)
1125 {
1126   VikTrack *a = (VikTrack *)x;
1127   VikTrack *b = (VikTrack *)y;
1128   return strcmp(a->name,b->name);
1129 }
1130
1131 void a_gpx_write_file ( VikTrwLayer *vtl, FILE *f, GpxWritingOptions *options )
1132 {
1133   GpxWritingContext context = { options, f };
1134
1135   gpx_write_header ( f );
1136
1137   gchar *tmp;
1138   const gchar *name = vik_layer_get_name(VIK_LAYER(vtl));
1139   if ( name ) {
1140     tmp = entitize ( name );
1141     fprintf ( f, "  <name>%s</name>\n", tmp );
1142     g_free ( tmp );
1143   }
1144
1145   VikTRWMetadata *md = vik_trw_layer_get_metadata (vtl);
1146   if ( md ) {
1147     if ( md->author && strlen(md->author) > 0 ) {
1148       tmp = entitize ( md->author );
1149       fprintf ( f, "  <author>%s</author>\n", tmp );
1150       g_free ( tmp );
1151     }
1152     if ( md->description && strlen(md->description) > 0) {
1153       tmp = entitize ( md->description );
1154       fprintf ( f, "  <desc>%s</desc>\n", tmp );
1155       g_free ( tmp );
1156     }
1157     if ( md->timestamp ) {
1158       tmp = entitize ( md->timestamp );
1159       fprintf ( f, "  <time>%s</time>\n", tmp );
1160       g_free ( tmp );
1161     }
1162     if ( md->keywords && strlen(md->keywords) > 0) {
1163       tmp = entitize ( md->keywords );
1164       fprintf ( f, "  <keywords>%s</keywords>\n", tmp );
1165       g_free ( tmp );
1166     }
1167   }
1168
1169   if ( vik_trw_layer_get_waypoints_visibility(vtl) || (options && options->hidden) ) {
1170     // gather waypoints in a list, then sort
1171     GList *gl = g_hash_table_get_values ( vik_trw_layer_get_waypoints ( vtl ) );
1172     gl = g_list_sort ( gl, gpx_waypoint_compare );
1173
1174     for (GList *iter = g_list_first (gl); iter != NULL; iter = g_list_next (iter)) {
1175       gpx_write_waypoint ( (VikWaypoint*)iter->data, &context );
1176     }
1177     g_list_free ( gl );
1178   }
1179
1180   GList *gl = NULL;
1181   if ( vik_trw_layer_get_tracks_visibility(vtl) || (options && options->hidden) ) {
1182     //gl = g_hash_table_get_values ( vik_trw_layer_get_tracks ( vtl ) );
1183     // Forming the list manually seems to produce one that is more likely to be nearer to the creation order
1184     gpointer key, value;
1185     GHashTableIter ght_iter;
1186     g_hash_table_iter_init ( &ght_iter, vik_trw_layer_get_tracks ( vtl ) );
1187     while ( g_hash_table_iter_next (&ght_iter, &key, &value) ) {
1188       gl = g_list_prepend ( gl ,value );
1189     }
1190     gl = g_list_reverse ( gl );
1191
1192     // Sort method determined by preference
1193     if ( a_vik_get_gpx_export_trk_sort() == VIK_GPX_EXPORT_TRK_SORT_TIME )
1194       gl = g_list_sort ( gl, vik_track_compare_timestamp );
1195     else if ( a_vik_get_gpx_export_trk_sort() == VIK_GPX_EXPORT_TRK_SORT_ALPHA )
1196       gl = g_list_sort ( gl, gpx_track_compare_name );
1197   }
1198
1199   GList *glrte = NULL;
1200   // Routes sorted by name
1201   if ( vik_trw_layer_get_routes_visibility(vtl) || (options && options->hidden) ) {
1202     glrte = g_hash_table_get_values ( vik_trw_layer_get_routes ( vtl ) );
1203     glrte = g_list_sort ( glrte, gpx_track_compare_name );
1204   }
1205
1206   // g_list_concat doesn't copy memory properly
1207   // so process each list separately
1208
1209   GpxWritingContext context_tmp = context;
1210   GpxWritingOptions opt_tmp = { FALSE, FALSE, FALSE, FALSE };
1211   // Force trackpoints on tracks
1212   if ( !context.options )
1213     context_tmp.options = &opt_tmp;
1214   context_tmp.options->is_route = FALSE;
1215
1216   // Loop around each list and write each one
1217   for (GList *iter = g_list_first (gl); iter != NULL; iter = g_list_next (iter)) {
1218     gpx_write_track ( (VikTrack*)iter->data, &context_tmp );
1219   }
1220
1221   // Routes (to get routepoints)
1222   context_tmp.options->is_route = TRUE;
1223   for (GList *iter = g_list_first (glrte); iter != NULL; iter = g_list_next (iter)) {
1224     gpx_write_track ( (VikTrack*)iter->data, &context_tmp );
1225   }
1226
1227   g_list_free ( gl );
1228   g_list_free ( glrte );
1229
1230   gpx_write_footer ( f );
1231 }
1232
1233 void a_gpx_write_track_file ( VikTrack *trk, FILE *f, GpxWritingOptions *options )
1234 {
1235   GpxWritingContext context = {options, f};
1236   gpx_write_header ( f );
1237   gpx_write_track ( trk, &context );
1238   gpx_write_footer ( f );
1239 }
1240
1241 /**
1242  * Common write of a temporary GPX file
1243  */
1244 static gchar* write_tmp_file ( VikTrwLayer *vtl, VikTrack *trk, GpxWritingOptions *options )
1245 {
1246         gchar *tmp_filename = NULL;
1247         GError *error = NULL;
1248         // Opening temporary file
1249         int fd = g_file_open_tmp("viking_XXXXXX.gpx", &tmp_filename, &error);
1250         if (fd < 0) {
1251                 g_warning ( _("failed to open temporary file: %s"), error->message );
1252                 g_clear_error ( &error );
1253                 return NULL;
1254         }
1255         g_debug ("%s: temporary file = %s", __FUNCTION__, tmp_filename);
1256
1257         FILE *ff = fdopen (fd, "w");
1258
1259         if ( trk )
1260                 a_gpx_write_track_file ( trk, ff, options );
1261         else
1262                 a_gpx_write_file ( vtl, ff, options );
1263
1264         fclose (ff);
1265
1266         return tmp_filename;
1267 }
1268
1269 /*
1270  * a_gpx_write_tmp_file:
1271  * @vtl:     The #VikTrwLayer to write
1272  * @options: Possible ways of writing the file data (can be NULL)
1273  *
1274  * Returns: The name of newly created temporary GPX file
1275  *          This file should be removed once used and the string freed.
1276  *          If NULL then the process failed.
1277  */
1278 gchar* a_gpx_write_tmp_file ( VikTrwLayer *vtl, GpxWritingOptions *options )
1279 {
1280         return write_tmp_file ( vtl, NULL, options );
1281 }
1282
1283 /*
1284  * a_gpx_write_track_tmp_file:
1285  * @trk:     The #VikTrack to write
1286  * @options: Possible ways of writing the file data (can be NULL)
1287  *
1288  * Returns: The name of newly created temporary GPX file
1289  *          This file should be removed once used and the string freed.
1290  *          If NULL then the process failed.
1291  */
1292 gchar* a_gpx_write_track_tmp_file ( VikTrack *trk, GpxWritingOptions *options )
1293 {
1294         return write_tmp_file ( NULL, trk, options );
1295 }