2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4 * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
6 * Some of the code adapted from GPSBabel 1.2.7
7 * http://gpsbabel.sf.net/
8 * Copyright (C) 2002, 2003, 2004, 2005 Robert Lipe, robertlipe@usa.net
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #define _XOPEN_SOURCE /* glibc2 needs this */
33 #define GPX_TIME_FORMAT "%Y-%m-%dT%H:%M:%SZ"
45 tt_wpt_link
, /* New in GPX 1.1 */
53 tt_trk_trkseg_trkpt_ele
,
54 tt_trk_trkseg_trkpt_time
,
61 typedef struct tag_mapping
{
62 tag_type tag_type
; /* enum from above for this tag */
63 const char *tag_name
; /* xpath-ish tag name */
67 * xpath(ish) mappings between full tag paths and internal identifers.
68 * These appear in the order they appear in the GPX specification.
69 * If it's not a tag we explictly handle, it doesn't go here.
72 tag_mapping tag_path_map
[] = {
74 { tt_wpt
, "/gpx/wpt" },
76 { tt_waypoint
, "/loc/waypoint" },
77 { tt_waypoint_coord
, "/loc/waypoint/coord" },
78 { tt_waypoint_name
, "/loc/waypoint/name" },
80 { tt_wpt_ele
, "/gpx/wpt/ele" },
81 { tt_wpt_name
, "/gpx/wpt/name" },
82 { tt_wpt_desc
, "/gpx/wpt/desc" },
83 { tt_wpt_sym
, "/gpx/wpt/sym" },
84 { tt_wpt_sym
, "/loc/waypoint/type" },
85 { tt_wpt_link
, "/gpx/wpt/link" }, /* GPX 1.1 */
87 { tt_trk
, "/gpx/trk" },
88 { tt_trk
, "/gpx/rte" },
89 { tt_trk_name
, "/gpx/trk/name" },
90 { tt_trk_desc
, "/gpx/trk/desc" },
91 { tt_trk_trkseg
, "/gpx/trk/trkseg" },
92 { tt_trk_trkseg_trkpt
, "/gpx/trk/trkseg/trkpt" },
93 { tt_trk_trkseg_trkpt
, "/gpx/rte/rtept" },
94 { tt_trk_trkseg_trkpt_ele
, "/gpx/trk/trkseg/trkpt/ele" },
95 { tt_trk_trkseg_trkpt_time
, "/gpx/trk/trkseg/trkpt/time" },
100 static tag_type
get_tag(const char *t
)
103 for (tm
= tag_path_map
; tm
->tag_type
!= 0; tm
++)
104 if (0 == strcmp(tm
->tag_name
, t
))
109 /******************************************/
111 tag_type current_tag
= tt_unknown
;
112 GString
*xpath
= NULL
;
113 GString
*c_cdata
= NULL
;
115 /* current ("c_") objects */
116 VikTrackpoint
*c_tp
= NULL
;
117 VikWaypoint
*c_wp
= NULL
;
118 VikTrack
*c_tr
= NULL
;
120 gchar
*c_wp_name
= NULL
;
121 gchar
*c_tr_name
= NULL
;
123 /* temporary things so we don't have to create them lots of times */
124 const gchar
*c_slat
, *c_slon
;
127 /* specialty flags / etc */
128 gboolean f_tr_newseg
;
129 guint unnamed_waypoints
= 0;
130 guint unnamed_tracks
= 0;
133 static const char *get_attr ( const char **attr
, const char *key
)
136 if ( strcmp(*attr
,key
) == 0 )
143 static gboolean
set_c_ll ( const char **attr
)
145 if ( (c_slat
= get_attr ( attr
, "lat" )) && (c_slon
= get_attr ( attr
, "lon" )) ) {
146 c_ll
.lat
= g_strtod(c_slat
, NULL
);
147 c_ll
.lon
= g_strtod(c_slon
, NULL
);
153 static void gpx_start(VikTrwLayer
*vtl
, const char *el
, const char **attr
)
155 static const gchar
*tmp
;
157 g_string_append_c ( xpath
, '/' );
158 g_string_append ( xpath
, el
);
159 current_tag
= get_tag ( xpath
->str
);
161 switch ( current_tag
) {
164 if ( set_c_ll( attr
) ) {
165 c_wp
= vik_waypoint_new ();
166 c_wp
->altitude
= VIK_DEFAULT_ALTITUDE
;
167 if ( ! get_attr ( attr
, "hidden" ) )
168 c_wp
->visible
= TRUE
;
170 vik_coord_load_from_latlon ( &(c_wp
->coord
), vik_trw_layer_get_coord_mode ( vtl
), &c_ll
);
175 c_tr
= vik_track_new ();
176 if ( ! get_attr ( attr
, "hidden" ) )
177 c_tr
->visible
= TRUE
;
184 case tt_trk_trkseg_trkpt
:
185 if ( set_c_ll( attr
) ) {
186 c_tp
= vik_trackpoint_new ();
187 c_tp
->altitude
= VIK_DEFAULT_ALTITUDE
;
188 vik_coord_load_from_latlon ( &(c_tp
->coord
), vik_trw_layer_get_coord_mode ( vtl
), &c_ll
);
190 c_tp
->newsegment
= TRUE
;
193 c_tr
->trackpoints
= g_list_append ( c_tr
->trackpoints
, c_tp
);
197 case tt_trk_trkseg_trkpt_ele
:
198 case tt_trk_trkseg_trkpt_time
:
205 g_string_erase ( c_cdata
, 0, -1 ); /* clear the cdata buffer */
209 c_wp
= vik_waypoint_new ();
210 c_wp
->altitude
= VIK_DEFAULT_ALTITUDE
;
211 c_wp
->visible
= TRUE
;
214 case tt_waypoint_coord
:
215 if ( set_c_ll( attr
) )
216 vik_coord_load_from_latlon ( &(c_wp
->coord
), vik_trw_layer_get_coord_mode ( vtl
), &c_ll
);
219 case tt_waypoint_name
:
220 if ( ( tmp
= get_attr(attr
, "id") ) ) {
222 g_free ( c_wp_name
);
223 c_wp_name
= g_strdup ( tmp
);
225 g_string_erase ( c_cdata
, 0, -1 ); /* clear the cdata buffer for description */
232 static void gpx_end(VikTrwLayer
*vtl
, const char *el
)
235 g_string_truncate ( xpath
, xpath
->len
- strlen(el
) - 1 );
237 switch ( current_tag
) {
242 c_wp_name
= g_strdup_printf("VIKING_WP%d", unnamed_waypoints
++);
243 g_hash_table_insert ( vik_trw_layer_get_waypoints ( vtl
), c_wp_name
, c_wp
);
250 c_tr_name
= g_strdup_printf("VIKING_TR%d", unnamed_waypoints
++);
251 g_hash_table_insert ( vik_trw_layer_get_tracks ( vtl
), c_tr_name
, c_tr
);
258 g_free ( c_wp_name
);
259 c_wp_name
= g_strdup ( c_cdata
->str
);
260 g_string_erase ( c_cdata
, 0, -1 );
265 g_free ( c_tr_name
);
266 c_tr_name
= g_strdup ( c_cdata
->str
);
267 g_string_erase ( c_cdata
, 0, -1 );
271 c_wp
->altitude
= g_strtod ( c_cdata
->str
, NULL
);
272 g_string_erase ( c_cdata
, 0, -1 );
275 case tt_trk_trkseg_trkpt_ele
:
276 c_tp
->altitude
= g_strtod ( c_cdata
->str
, NULL
);
277 g_string_erase ( c_cdata
, 0, -1 );
280 case tt_waypoint_name
: /* .loc name is really description. */
282 vik_waypoint_set_comment ( c_wp
, c_cdata
->str
);
283 g_string_erase ( c_cdata
, 0, -1 );
287 vik_waypoint_set_image ( c_wp
, c_cdata
->str
);
288 g_string_erase ( c_cdata
, 0, -1 );
292 gchar
*tmp_lower
= g_utf8_strdown(c_cdata
->str
, -1); /* for things like <type>Geocache</type> */
293 vik_waypoint_set_symbol ( c_wp
, tmp_lower
);
294 g_free ( tmp_lower
);
295 g_string_erase ( c_cdata
, 0, -1 );
300 vik_track_set_comment ( c_tr
, c_cdata
->str
);
301 g_string_erase ( c_cdata
, 0, -1 );
304 case tt_trk_trkseg_trkpt_time
:
305 if ( strptime(c_cdata
->str
, GPX_TIME_FORMAT
, &tm
) != c_cdata
->str
) { /* it read at least one char */
306 c_tp
->timestamp
= mktime(&tm
);
307 c_tp
->has_timestamp
= TRUE
;
309 g_string_erase ( c_cdata
, 0, -1 );
315 current_tag
= get_tag ( xpath
->str
);
318 static void gpx_cdata(void *dta
, const XML_Char
*s
, int len
)
320 switch ( current_tag
) {
324 case tt_trk_trkseg_trkpt_ele
:
329 case tt_trk_trkseg_trkpt_time
:
330 case tt_waypoint_name
: /* .loc name is really description. */
331 g_string_append_len ( c_cdata
, s
, len
);
334 default: break; /* ignore cdata from other things */
338 // make like a "stack" of tag names
339 // like gpspoint's separated like /gpx/wpt/whatever
341 void a_gpx_read_file( VikTrwLayer
*vtl
, FILE *f
) {
342 XML_Parser parser
= XML_ParserCreate(NULL
);
345 XML_SetElementHandler(parser
, (XML_StartElementHandler
) gpx_start
, (XML_EndElementHandler
) gpx_end
);
346 XML_SetUserData(parser
, vtl
); /* in the future we could remove all global variables */
347 XML_SetCharacterDataHandler(parser
, (XML_CharacterDataHandler
) gpx_cdata
);
351 g_assert ( f
!= NULL
&& vtl
!= NULL
);
353 xpath
= g_string_new ( "" );
354 c_cdata
= g_string_new ( "" );
356 unnamed_waypoints
= 0;
360 len
= fread(buf
, 1, sizeof(buf
)-7, f
);
361 done
= feof(f
) || !len
;
362 XML_Parse(parser
, buf
, len
, done
);
365 g_string_free ( xpath
, TRUE
);
366 g_string_free ( c_cdata
, TRUE
);
369 /**** entitize from GPSBabel ****/
377 entity_types stdentities
[] = {
379 { "'", "'", 1 },
382 { "\"", """, 0 },
386 void utf8_to_int( const char *cp
, int *bytes
, int *value
)
388 if ( (*cp
& 0xe0) == 0xc0 ) {
389 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
391 *value
= ((*cp
& 0x1f) << 6) |
394 else if ( (*cp
& 0xf0) == 0xe0 ) {
395 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
396 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
398 *value
= ((*cp
& 0x0f) << 12) |
399 ((*(cp
+1) & 0x3f) << 6) |
402 else if ( (*cp
& 0xf8) == 0xf0 ) {
403 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
404 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
405 if ( (*(cp
+3) & 0xc0) != 0x80 ) goto dodefault
;
407 *value
= ((*cp
& 0x07) << 18) |
408 ((*(cp
+1) & 0x3f) << 12) |
409 ((*(cp
+2) & 0x3f) << 6) |
412 else if ( (*cp
& 0xfc) == 0xf8 ) {
413 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
414 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
415 if ( (*(cp
+3) & 0xc0) != 0x80 ) goto dodefault
;
416 if ( (*(cp
+4) & 0xc0) != 0x80 ) goto dodefault
;
418 *value
= ((*cp
& 0x03) << 24) |
419 ((*(cp
+1) & 0x3f) << 18) |
420 ((*(cp
+2) & 0x3f) << 12) |
421 ((*(cp
+3) & 0x3f) << 6) |
424 else if ( (*cp
& 0xfe) == 0xfc ) {
425 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
426 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
427 if ( (*(cp
+3) & 0xc0) != 0x80 ) goto dodefault
;
428 if ( (*(cp
+4) & 0xc0) != 0x80 ) goto dodefault
;
429 if ( (*(cp
+5) & 0xc0) != 0x80 ) goto dodefault
;
431 *value
= ((*cp
& 0x01) << 30) |
432 ((*(cp
+1) & 0x3f) << 24) |
433 ((*(cp
+2) & 0x3f) << 18) |
434 ((*(cp
+3) & 0x3f) << 12) |
435 ((*(cp
+4) & 0x3f) << 6) |
441 *value
= (unsigned char)*cp
;
447 entitize(const char * str
)
449 int elen
, ecount
, nsecount
;
452 char * p
, * tmp
, * xstr
;
458 elen
= ecount
= nsecount
= 0;
460 /* figure # of entity replacements and additional size. */
463 while ((cp
= strstr(cp
, ep
->text
)) != NULL
) {
464 elen
+= strlen(ep
->entity
) - strlen(ep
->text
);
466 cp
+= strlen(ep
->text
);
471 /* figure the same for other than standard entities (i.e. anything
472 * that isn't in the range U+0000 to U+007F */
473 for ( cp
= str
; *cp
; cp
++ ) {
476 utf8_to_int( cp
, &bytes
, &value
);
478 elen
+= sprintf( tmpsub
, "&#x%x;", value
) - bytes
;
483 /* enough space for the whole string plus entity replacements, if any */
484 tmp
= g_malloc((strlen(str
) + elen
+ 1));
487 /* no entity replacements */
488 if (ecount
== 0 && nsecount
== 0)
492 for (ep
= stdentities
; ep
->text
; ep
++) {
494 while ((p
= strstr(p
, ep
->text
)) != NULL
) {
495 elen
= strlen(ep
->entity
);
497 xstr
= g_strdup(p
+ strlen(ep
->text
));
499 strcpy(p
, ep
->entity
);
500 strcpy(p
+ elen
, xstr
);
509 if ( nsecount
!= 0 ) {
513 utf8_to_int( p
, &bytes
, &value
);
515 xstr
= g_strdup( p
+ bytes
);
520 sprintf( p
, "&#x%x;", value
);
534 /**** end GPSBabel code ****/
537 static void gpx_write_waypoint ( const gchar
*name
, VikWaypoint
*wp
, FILE *f
)
539 static struct LatLon ll
;
541 vik_coord_to_latlon ( &(wp
->coord
), &ll
);
542 fprintf ( f
, "<wpt lat=\"%f\" lon=\"%f\"%s>\n",
543 ll
.lat
, ll
.lon
, wp
->visible
? "" : " hidden=\"hidden\"" );
545 tmp
= entitize ( name
);
546 fprintf ( f
, " <name>%s</name>\n", tmp
);
549 if ( wp
->altitude
!= VIK_DEFAULT_ALTITUDE
)
550 fprintf ( f
, " <ele>%f</ele>\n", wp
->altitude
);
553 tmp
= entitize(wp
->comment
);
554 fprintf ( f
, " <desc>%s</desc>\n", tmp
);
559 tmp
= entitize(wp
->image
);
560 fprintf ( f
, " <link>%s</link>\n", tmp
);
565 tmp
= entitize(wp
->symbol
);
566 fprintf ( f
, " <sym>%s</sym>\n", tmp
);
570 fprintf ( f
, "</wpt>\n" );
573 static void gpx_write_trackpoint ( VikTrackpoint
*tp
, FILE *f
)
575 static struct LatLon ll
;
576 static gchar time_buf
[30];
577 vik_coord_to_latlon ( &(tp
->coord
), &ll
);
579 if ( tp
->newsegment
)
580 fprintf ( f
, " </trkseg>\n <trkseg>\n" );
582 fprintf ( f
, " <trkpt lat=\"%f\" lon=\"%f\">\n", ll
.lat
, ll
.lon
);
584 if ( tp
->altitude
!= VIK_DEFAULT_ALTITUDE
)
585 fprintf ( f
, " <ele>%f</ele>\n", tp
->altitude
);
586 if ( tp
->has_timestamp
) {
587 time_buf
[ strftime ( time_buf
, sizeof(time_buf
)-1, GPX_TIME_FORMAT
, localtime(&(tp
->timestamp
)) ) ] = '\0';
588 fprintf ( f
, " <time>%s</time>\n", time_buf
);
590 fprintf ( f
, " </trkpt>\n" );
594 static void gpx_write_track ( const gchar
*name
, VikTrack
*t
, FILE *f
)
597 gboolean first_tp_is_newsegment
; /* must temporarily make it not so, but we want to restore state. not that it matters. */
599 tmp
= entitize ( name
);
600 fprintf ( f
, "<trk%s>\n <name>%s</name>\n", t
->visible
? "" : " hidden=\"hidden\"", tmp
);
605 tmp
= entitize ( t
->comment
);
606 fprintf ( f
, " <desc>%s</desc>\n", tmp
);
610 fprintf ( f
, " <trkseg>\n" );
612 if ( t
->trackpoints
&& t
->trackpoints
->data
) {
613 first_tp_is_newsegment
= VIK_TRACKPOINT(t
->trackpoints
->data
)->newsegment
;
614 VIK_TRACKPOINT(t
->trackpoints
->data
)->newsegment
= FALSE
; /* so we won't write </trkseg><trkseg> already */
616 g_list_foreach ( t
->trackpoints
, (GFunc
) gpx_write_trackpoint
, f
);
617 if ( t
->trackpoints
&& t
->trackpoints
->data
)
618 VIK_TRACKPOINT(t
->trackpoints
->data
)->newsegment
= first_tp_is_newsegment
; /* restore state */
620 fprintf ( f
, "</trkseg>\n</trk>\n" );
623 void a_gpx_write_file( VikTrwLayer
*vtl
, FILE *f
)
625 fprintf(f
, "<?xml version=\"1.0\"?>\n"
626 "<gpx version=\"1.0\" creator=\"Viking -- http://viking.sf.net/\"\n"
627 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
628 "xmlns=\"http://www.topografix.com/GPX/1/0\"\n"
629 "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
630 g_hash_table_foreach ( vik_trw_layer_get_waypoints ( vtl
), (GHFunc
) gpx_write_waypoint
, f
);
631 g_hash_table_foreach ( vik_trw_layer_get_tracks ( vtl
), (GHFunc
) gpx_write_track
, f
);
632 fprintf(f
, "</gpx>\n");