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 */
35 #define GPX_TIME_FORMAT "%Y-%m-%dT%H:%M:%SZ"
47 tt_wpt_link
, /* New in GPX 1.1 */
55 tt_trk_trkseg_trkpt_ele
,
56 tt_trk_trkseg_trkpt_time
,
63 typedef struct tag_mapping
{
64 tag_type tag_type
; /* enum from above for this tag */
65 const char *tag_name
; /* xpath-ish tag name */
69 * xpath(ish) mappings between full tag paths and internal identifers.
70 * These appear in the order they appear in the GPX specification.
71 * If it's not a tag we explictly handle, it doesn't go here.
74 tag_mapping tag_path_map
[] = {
76 { tt_wpt
, "/gpx/wpt" },
78 { tt_waypoint
, "/loc/waypoint" },
79 { tt_waypoint_coord
, "/loc/waypoint/coord" },
80 { tt_waypoint_name
, "/loc/waypoint/name" },
82 { tt_wpt_ele
, "/gpx/wpt/ele" },
83 { tt_wpt_name
, "/gpx/wpt/name" },
84 { tt_wpt_desc
, "/gpx/wpt/desc" },
85 { tt_wpt_sym
, "/gpx/wpt/sym" },
86 { tt_wpt_sym
, "/loc/waypoint/type" },
87 { tt_wpt_link
, "/gpx/wpt/link" }, /* GPX 1.1 */
89 { tt_trk
, "/gpx/trk" },
90 { tt_trk
, "/gpx/rte" },
91 { tt_trk_name
, "/gpx/trk/name" },
92 { tt_trk_desc
, "/gpx/trk/desc" },
93 { tt_trk_trkseg
, "/gpx/trk/trkseg" },
94 { tt_trk_trkseg_trkpt
, "/gpx/trk/trkseg/trkpt" },
95 { tt_trk_trkseg_trkpt
, "/gpx/rte/rtept" },
96 { tt_trk_trkseg_trkpt_ele
, "/gpx/trk/trkseg/trkpt/ele" },
97 { tt_trk_trkseg_trkpt_time
, "/gpx/trk/trkseg/trkpt/time" },
102 static tag_type
get_tag(const char *t
)
105 for (tm
= tag_path_map
; tm
->tag_type
!= 0; tm
++)
106 if (0 == strcmp(tm
->tag_name
, t
))
111 /******************************************/
113 tag_type current_tag
= tt_unknown
;
114 GString
*xpath
= NULL
;
115 GString
*c_cdata
= NULL
;
117 /* current ("c_") objects */
118 VikTrackpoint
*c_tp
= NULL
;
119 VikWaypoint
*c_wp
= NULL
;
120 VikTrack
*c_tr
= NULL
;
122 gchar
*c_wp_name
= NULL
;
123 gchar
*c_tr_name
= NULL
;
125 /* temporary things so we don't have to create them lots of times */
126 const gchar
*c_slat
, *c_slon
;
129 /* specialty flags / etc */
130 gboolean f_tr_newseg
;
131 guint unnamed_waypoints
= 0;
132 guint unnamed_tracks
= 0;
135 static const char *get_attr ( const char **attr
, const char *key
)
138 if ( strcmp(*attr
,key
) == 0 )
145 static gboolean
set_c_ll ( const char **attr
)
147 if ( (c_slat
= get_attr ( attr
, "lat" )) && (c_slon
= get_attr ( attr
, "lon" )) ) {
148 c_ll
.lat
= g_strtod(c_slat
, NULL
);
149 c_ll
.lon
= g_strtod(c_slon
, NULL
);
155 static void gpx_start(VikTrwLayer
*vtl
, const char *el
, const char **attr
)
157 static const gchar
*tmp
;
159 g_string_append_c ( xpath
, '/' );
160 g_string_append ( xpath
, el
);
161 current_tag
= get_tag ( xpath
->str
);
163 switch ( current_tag
) {
166 if ( set_c_ll( attr
) ) {
167 c_wp
= vik_waypoint_new ();
168 c_wp
->altitude
= VIK_DEFAULT_ALTITUDE
;
169 if ( ! get_attr ( attr
, "hidden" ) )
170 c_wp
->visible
= TRUE
;
172 vik_coord_load_from_latlon ( &(c_wp
->coord
), vik_trw_layer_get_coord_mode ( vtl
), &c_ll
);
177 c_tr
= vik_track_new ();
178 if ( ! get_attr ( attr
, "hidden" ) )
179 c_tr
->visible
= TRUE
;
186 case tt_trk_trkseg_trkpt
:
187 if ( set_c_ll( attr
) ) {
188 c_tp
= vik_trackpoint_new ();
189 c_tp
->altitude
= VIK_DEFAULT_ALTITUDE
;
190 vik_coord_load_from_latlon ( &(c_tp
->coord
), vik_trw_layer_get_coord_mode ( vtl
), &c_ll
);
192 c_tp
->newsegment
= TRUE
;
195 c_tr
->trackpoints
= g_list_append ( c_tr
->trackpoints
, c_tp
);
199 case tt_trk_trkseg_trkpt_ele
:
200 case tt_trk_trkseg_trkpt_time
:
207 g_string_erase ( c_cdata
, 0, -1 ); /* clear the cdata buffer */
211 c_wp
= vik_waypoint_new ();
212 c_wp
->altitude
= VIK_DEFAULT_ALTITUDE
;
213 c_wp
->visible
= TRUE
;
216 case tt_waypoint_coord
:
217 if ( set_c_ll( attr
) )
218 vik_coord_load_from_latlon ( &(c_wp
->coord
), vik_trw_layer_get_coord_mode ( vtl
), &c_ll
);
221 case tt_waypoint_name
:
222 if ( ( tmp
= get_attr(attr
, "id") ) ) {
224 g_free ( c_wp_name
);
225 c_wp_name
= g_strdup ( tmp
);
227 g_string_erase ( c_cdata
, 0, -1 ); /* clear the cdata buffer for description */
234 static void gpx_end(VikTrwLayer
*vtl
, const char *el
)
237 g_string_truncate ( xpath
, xpath
->len
- strlen(el
) - 1 );
239 switch ( current_tag
) {
244 c_wp_name
= g_strdup_printf("VIKING_WP%d", unnamed_waypoints
++);
245 vik_trw_layer_filein_add_waypoint ( vtl
, c_wp_name
, c_wp
);
246 g_free ( c_wp_name
);
253 c_tr_name
= g_strdup_printf("VIKING_TR%d", unnamed_waypoints
++);
254 vik_trw_layer_filein_add_track ( vtl
, c_tr_name
, c_tr
);
255 g_free ( c_tr_name
);
262 g_free ( c_wp_name
);
263 c_wp_name
= g_strdup ( c_cdata
->str
);
264 g_string_erase ( c_cdata
, 0, -1 );
269 g_free ( c_tr_name
);
270 c_tr_name
= g_strdup ( c_cdata
->str
);
271 g_string_erase ( c_cdata
, 0, -1 );
275 c_wp
->altitude
= g_strtod ( c_cdata
->str
, NULL
);
276 g_string_erase ( c_cdata
, 0, -1 );
279 case tt_trk_trkseg_trkpt_ele
:
280 c_tp
->altitude
= g_strtod ( c_cdata
->str
, NULL
);
281 g_string_erase ( c_cdata
, 0, -1 );
284 case tt_waypoint_name
: /* .loc name is really description. */
286 vik_waypoint_set_comment ( c_wp
, c_cdata
->str
);
287 g_string_erase ( c_cdata
, 0, -1 );
291 vik_waypoint_set_image ( c_wp
, c_cdata
->str
);
292 g_string_erase ( c_cdata
, 0, -1 );
296 gchar
*tmp_lower
= g_utf8_strdown(c_cdata
->str
, -1); /* for things like <type>Geocache</type> */
297 vik_waypoint_set_symbol ( c_wp
, tmp_lower
);
298 g_free ( tmp_lower
);
299 g_string_erase ( c_cdata
, 0, -1 );
304 vik_track_set_comment ( c_tr
, c_cdata
->str
);
305 g_string_erase ( c_cdata
, 0, -1 );
308 case tt_trk_trkseg_trkpt_time
:
310 if ( strptime(c_cdata
->str
, GPX_TIME_FORMAT
, &tm
) != c_cdata
->str
) { /* it read at least one char */
311 c_tp
->timestamp
= mktime(&tm
);
312 c_tp
->has_timestamp
= TRUE
;
314 g_string_erase ( c_cdata
, 0, -1 );
320 current_tag
= get_tag ( xpath
->str
);
323 static void gpx_cdata(void *dta
, const XML_Char
*s
, int len
)
325 switch ( current_tag
) {
329 case tt_trk_trkseg_trkpt_ele
:
334 case tt_trk_trkseg_trkpt_time
:
335 case tt_waypoint_name
: /* .loc name is really description. */
336 g_string_append_len ( c_cdata
, s
, len
);
339 default: break; /* ignore cdata from other things */
343 // make like a "stack" of tag names
344 // like gpspoint's separated like /gpx/wpt/whatever
346 void a_gpx_read_file( VikTrwLayer
*vtl
, FILE *f
) {
347 XML_Parser parser
= XML_ParserCreate(NULL
);
350 XML_SetElementHandler(parser
, (XML_StartElementHandler
) gpx_start
, (XML_EndElementHandler
) gpx_end
);
351 XML_SetUserData(parser
, vtl
); /* in the future we could remove all global variables */
352 XML_SetCharacterDataHandler(parser
, (XML_CharacterDataHandler
) gpx_cdata
);
356 g_assert ( f
!= NULL
&& vtl
!= NULL
);
358 xpath
= g_string_new ( "" );
359 c_cdata
= g_string_new ( "" );
361 unnamed_waypoints
= 0;
365 len
= fread(buf
, 1, sizeof(buf
)-7, f
);
366 done
= feof(f
) || !len
;
367 XML_Parse(parser
, buf
, len
, done
);
370 g_string_free ( xpath
, TRUE
);
371 g_string_free ( c_cdata
, TRUE
);
374 /**** entitize from GPSBabel ****/
382 entity_types stdentities
[] = {
384 { "'", "'", 1 },
387 { "\"", """, 0 },
391 void utf8_to_int( const char *cp
, int *bytes
, int *value
)
393 if ( (*cp
& 0xe0) == 0xc0 ) {
394 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
396 *value
= ((*cp
& 0x1f) << 6) |
399 else if ( (*cp
& 0xf0) == 0xe0 ) {
400 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
401 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
403 *value
= ((*cp
& 0x0f) << 12) |
404 ((*(cp
+1) & 0x3f) << 6) |
407 else if ( (*cp
& 0xf8) == 0xf0 ) {
408 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
409 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
410 if ( (*(cp
+3) & 0xc0) != 0x80 ) goto dodefault
;
412 *value
= ((*cp
& 0x07) << 18) |
413 ((*(cp
+1) & 0x3f) << 12) |
414 ((*(cp
+2) & 0x3f) << 6) |
417 else if ( (*cp
& 0xfc) == 0xf8 ) {
418 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
419 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
420 if ( (*(cp
+3) & 0xc0) != 0x80 ) goto dodefault
;
421 if ( (*(cp
+4) & 0xc0) != 0x80 ) goto dodefault
;
423 *value
= ((*cp
& 0x03) << 24) |
424 ((*(cp
+1) & 0x3f) << 18) |
425 ((*(cp
+2) & 0x3f) << 12) |
426 ((*(cp
+3) & 0x3f) << 6) |
429 else if ( (*cp
& 0xfe) == 0xfc ) {
430 if ( (*(cp
+1) & 0xc0) != 0x80 ) goto dodefault
;
431 if ( (*(cp
+2) & 0xc0) != 0x80 ) goto dodefault
;
432 if ( (*(cp
+3) & 0xc0) != 0x80 ) goto dodefault
;
433 if ( (*(cp
+4) & 0xc0) != 0x80 ) goto dodefault
;
434 if ( (*(cp
+5) & 0xc0) != 0x80 ) goto dodefault
;
436 *value
= ((*cp
& 0x01) << 30) |
437 ((*(cp
+1) & 0x3f) << 24) |
438 ((*(cp
+2) & 0x3f) << 18) |
439 ((*(cp
+3) & 0x3f) << 12) |
440 ((*(cp
+4) & 0x3f) << 6) |
446 *value
= (unsigned char)*cp
;
452 entitize(const char * str
)
454 int elen
, ecount
, nsecount
;
457 char * p
, * tmp
, * xstr
;
463 elen
= ecount
= nsecount
= 0;
465 /* figure # of entity replacements and additional size. */
468 while ((cp
= strstr(cp
, ep
->text
)) != NULL
) {
469 elen
+= strlen(ep
->entity
) - strlen(ep
->text
);
471 cp
+= strlen(ep
->text
);
476 /* figure the same for other than standard entities (i.e. anything
477 * that isn't in the range U+0000 to U+007F */
478 for ( cp
= str
; *cp
; cp
++ ) {
481 utf8_to_int( cp
, &bytes
, &value
);
483 elen
+= sprintf( tmpsub
, "&#x%x;", value
) - bytes
;
488 /* enough space for the whole string plus entity replacements, if any */
489 tmp
= g_malloc((strlen(str
) + elen
+ 1));
492 /* no entity replacements */
493 if (ecount
== 0 && nsecount
== 0)
497 for (ep
= stdentities
; ep
->text
; ep
++) {
499 while ((p
= strstr(p
, ep
->text
)) != NULL
) {
500 elen
= strlen(ep
->entity
);
502 xstr
= g_strdup(p
+ strlen(ep
->text
));
504 strcpy(p
, ep
->entity
);
505 strcpy(p
+ elen
, xstr
);
514 if ( nsecount
!= 0 ) {
518 utf8_to_int( p
, &bytes
, &value
);
520 xstr
= g_strdup( p
+ bytes
);
525 sprintf( p
, "&#x%x;", value
);
539 /**** end GPSBabel code ****/
543 static void gpx_write_waypoint ( const gchar
*name
, VikWaypoint
*wp
, FILE *f
)
545 static struct LatLon ll
;
548 vik_coord_to_latlon ( &(wp
->coord
), &ll
);
549 s_lat
= a_coords_dtostr( ll
.lat
);
550 s_lon
= a_coords_dtostr( ll
.lon
);
551 fprintf ( f
, "<wpt lat=\"%s\" lon=\"%s\"%s>\n",
552 s_lat
, s_lon
, wp
->visible
? "" : " hidden=\"hidden\"" );
556 tmp
= entitize ( name
);
557 fprintf ( f
, " <name>%s</name>\n", tmp
);
560 if ( wp
->altitude
!= VIK_DEFAULT_ALTITUDE
)
562 tmp
= a_coords_dtostr ( wp
->altitude
);
563 fprintf ( f
, " <ele>%s</ele>\n", tmp
);
568 tmp
= entitize(wp
->comment
);
569 fprintf ( f
, " <desc>%s</desc>\n", tmp
);
574 tmp
= entitize(wp
->image
);
575 fprintf ( f
, " <link>%s</link>\n", tmp
);
580 tmp
= entitize(wp
->symbol
);
581 fprintf ( f
, " <sym>%s</sym>\n", tmp
);
585 fprintf ( f
, "</wpt>\n" );
588 static void gpx_write_trackpoint ( VikTrackpoint
*tp
, FILE *f
)
590 static struct LatLon ll
;
591 gchar
*s_lat
,*s_lon
, *s_alt
;
592 static gchar time_buf
[30];
593 vik_coord_to_latlon ( &(tp
->coord
), &ll
);
595 if ( tp
->newsegment
)
596 fprintf ( f
, " </trkseg>\n <trkseg>\n" );
598 s_lat
= a_coords_dtostr( ll
.lat
);
599 s_lon
= a_coords_dtostr( ll
.lon
);
600 fprintf ( f
, " <trkpt lat=\"%s\" lon=\"%s\">\n", s_lat
, s_lon
);
604 if ( tp
->altitude
!= VIK_DEFAULT_ALTITUDE
)
606 s_alt
= a_coords_dtostr ( tp
->altitude
);
607 fprintf ( f
, " <ele>%s</ele>\n", s_alt
);
610 if ( tp
->has_timestamp
) {
611 time_buf
[ strftime ( time_buf
, sizeof(time_buf
)-1, GPX_TIME_FORMAT
, localtime(&(tp
->timestamp
)) ) ] = '\0';
612 fprintf ( f
, " <time>%s</time>\n", time_buf
);
614 fprintf ( f
, " </trkpt>\n" );
618 static void gpx_write_track ( const gchar
*name
, VikTrack
*t
, FILE *f
)
621 gboolean first_tp_is_newsegment
= FALSE
; /* must temporarily make it not so, but we want to restore state. not that it matters. */
623 tmp
= entitize ( name
);
624 fprintf ( f
, "<trk%s>\n <name>%s</name>\n", t
->visible
? "" : " hidden=\"hidden\"", tmp
);
629 tmp
= entitize ( t
->comment
);
630 fprintf ( f
, " <desc>%s</desc>\n", tmp
);
634 fprintf ( f
, " <trkseg>\n" );
636 if ( t
->trackpoints
&& t
->trackpoints
->data
) {
637 first_tp_is_newsegment
= VIK_TRACKPOINT(t
->trackpoints
->data
)->newsegment
;
638 VIK_TRACKPOINT(t
->trackpoints
->data
)->newsegment
= FALSE
; /* so we won't write </trkseg><trkseg> already */
639 g_list_foreach ( t
->trackpoints
, (GFunc
) gpx_write_trackpoint
, f
);
640 VIK_TRACKPOINT(t
->trackpoints
->data
)->newsegment
= first_tp_is_newsegment
; /* restore state */
643 fprintf ( f
, "</trkseg>\n</trk>\n" );
646 static void gpx_write_header( FILE *f
)
648 fprintf(f
, "<?xml version=\"1.0\"?>\n"
649 "<gpx version=\"1.0\" creator=\"Viking -- http://viking.sf.net/\"\n"
650 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
651 "xmlns=\"http://www.topografix.com/GPX/1/0\"\n"
652 "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
655 static void gpx_write_footer( FILE *f
)
657 fprintf(f
, "</gpx>\n");
660 void a_gpx_write_file( VikTrwLayer
*vtl
, FILE *f
)
662 gpx_write_header ( f
);
663 g_hash_table_foreach ( vik_trw_layer_get_waypoints ( vtl
), (GHFunc
) gpx_write_waypoint
, f
);
664 g_hash_table_foreach ( vik_trw_layer_get_tracks ( vtl
), (GHFunc
) gpx_write_track
, f
);
665 gpx_write_footer ( f
);
668 void a_gpx_write_track_file ( const gchar
*name
, VikTrack
*t
, FILE *f
)
670 gpx_write_header ( f
);
671 gpx_write_track ( name
, t
, f
);
672 gpx_write_footer ( f
);