2 * This file is part of mapper
4 * Copyright (C) 2006-2007 John Costigan.
6 * POI and GPS-Info code originally written by Cezary Jackiewicz.
8 * Default map data provided by http://www.openstreetmap.org/
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 along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
35 #include <glib/gstdio.h>
36 #include <glib/gi18n.h>
39 #include <libgnomevfs/gnome-vfs.h>
40 #include <curl/multi.h>
41 #include <gconf/gconf-client.h>
42 #include <libxml/parser.h>
49 #include "ui-common.h"
52 #include "mapper-types.h"
58 #define XML_DATE_FORMAT "%FT%T"
60 #define WRITE_STRING(string) { \
61 GnomeVFSResult vfs_result; \
62 GnomeVFSFileSize size; \
63 if(GNOME_VFS_OK != (vfs_result = gnome_vfs_write(handle, (string), strlen((string)), &size))) \
65 gchar buffer[BUFFER_SIZE]; \
66 g_snprintf(buffer, sizeof(buffer), \
67 "%s:\n%s\n%s", _("Error while writing to file"), _("File is incomplete."), \
68 gnome_vfs_result_to_string(vfs_result)); \
69 popup_error(_window, buffer); \
74 /** This enum defines the states of the SAX parsing state machine. */
82 INSIDE_PATH_POINT_ELE,
83 INSIDE_PATH_POINT_TIME,
84 INSIDE_PATH_POINT_DESC,
85 INSIDE_PATH_POINT_NAME,
91 /** Data used during the SAX parsing operation. */
92 typedef struct _SaxData SaxData;
98 gboolean at_least_one_trkpt;
102 static gchar XML_TZONE[7];
110 localtime_r(&time1, &time2);
111 g_snprintf(XML_TZONE, sizeof(XML_TZONE), "%+03ld:%02ld", (time2.tm_gmtoff / 60 / 60), (time2.tm_gmtoff / 60) % 60);
115 gpx_write(Path *path, GnomeVFSHandle *handle)
118 WayPoint *wcurr = NULL;
119 gboolean trkseg_break = FALSE;
122 gpx_time = time(NULL);
124 /* Find first non-zero point. */
125 for (curr = path->head - 1, wcurr = path->whead; curr++ != path->tail;) {
128 else if (wcurr && curr == wcurr->point)
132 /* Write the header. */
133 WRITE_STRING("<?xml version=\"1.0\"?>\n"
134 "<gpx version=\"1.0\" creator=\"mapper\" xmlns=\"http://www.topografix.com/GPX/1/0\">\n");
136 /* Write any metadata */
137 WRITE_STRING("<metadata>\n");
139 WRITE_STRING("<time>");
140 strftime(buffer, sizeof(buffer), XML_DATE_FORMAT, localtime(&gpx_time));
141 WRITE_STRING(buffer);
142 WRITE_STRING(XML_TZONE);
143 WRITE_STRING("</time>\n");
145 WRITE_STRING("</metadata>\n");
147 /* Write track(s) and waypoint(s) */
148 WRITE_STRING("<trk>\n<trkseg>\n");
150 /* Curr points to first non-zero point. */
151 for (curr--; curr++ != path->tail;) {
154 gboolean first_sub = TRUE;
156 /* First trkpt of the segment - write trkseg header. */
157 WRITE_STRING("</trkseg>\n<trkseg>\n");
158 trkseg_break = FALSE;
160 unit2latlon(curr->unitx, curr->unity, &lat, &lon);
161 WRITE_STRING("<trkpt lat=\"");
162 g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lat);
163 WRITE_STRING(buffer);
164 WRITE_STRING("\" lon=\"");
165 g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lon);
166 WRITE_STRING(buffer);
169 /* write the elevation */
170 if (!isnan(curr->altitude)) {
175 WRITE_STRING("<ele>");
176 g_ascii_formatd(buffer, sizeof(buffer), "%.2f", curr->altitude);
177 WRITE_STRING(buffer);
178 WRITE_STRING("</ele>\n");
187 WRITE_STRING("<time>");
188 strftime(buffer, sizeof(buffer), XML_DATE_FORMAT, localtime(&curr->time));
189 WRITE_STRING(buffer);
190 WRITE_STRING(XML_TZONE);
191 WRITE_STRING("</time>\n");
194 if (wcurr && curr == wcurr->point) {
201 desc=g_markup_printf_escaped("<desc>%s</desc>\n", wcurr->desc);
208 WRITE_STRING("/>\n");
210 WRITE_STRING("</trkpt>\n");
216 /* Write the footer. */
217 WRITE_STRING("</trkseg>\n</trk>\n</gpx>\n");
223 * Handle a start tag in the parsing of a GPX file.
225 #define MACRO_SET_UNKNOWN(utag) { \
226 data->prev_state = data->state; \
227 data->state = UNKNOWN; \
228 data->unknown_depth = 1; \
229 g_debug("GPX: unknown tag [%s]", (gchar *)utag); }
232 gpx_start_element(SaxData *data, const xmlChar *name, const xmlChar **attrs)
234 switch (data->state) {
238 if (!strcmp((gchar *) name, "gpx"))
239 data->state = INSIDE_GPX;
241 MACRO_SET_UNKNOWN(name);
244 if (!strcmp((gchar *) name, "trk"))
245 data->state = INSIDE_PATH;
246 else if (!strcmp((gchar *) name, "metadata"))
247 data->state = INSIDE_METADATA;
249 MACRO_SET_UNKNOWN(name);
251 case INSIDE_METADATA:
254 if (!strcmp((gchar *) name, "trkseg")) {
255 data->state = INSIDE_PATH_SEGMENT;
256 data->at_least_one_trkpt = FALSE;
258 MACRO_SET_UNKNOWN(name);
260 case INSIDE_PATH_SEGMENT:
261 if (!strcmp((gchar *) name, "trkpt")) {
262 const xmlChar **curr_attr;
264 gdouble lat = 0.f, lon = 0.f;
265 gboolean has_lat, has_lon;
268 for (curr_attr = attrs; *curr_attr != NULL;) {
269 const gchar *attr_name = *curr_attr++;
270 const gchar *attr_val = *curr_attr++;
271 if (!strcmp(attr_name, "lat")) {
272 lat = g_ascii_strtod(attr_val, &error_check);
273 if (error_check != attr_val)
275 } else if (!strcmp(attr_name, "lon")) {
276 lon = g_ascii_strtod(attr_val, &error_check);
277 if (error_check != attr_val)
281 if (has_lat && has_lon) {
282 MACRO_PATH_INCREMENT_TAIL(data->path);
283 latlon2unit(lat, lon, &data->path.tail->unitx, &data->path.tail->unity);
284 data->path.tail->time = 0;
285 data->path.tail->altitude = NAN;
286 data->state = INSIDE_PATH_POINT;
290 MACRO_SET_UNKNOWN(name);
292 case INSIDE_PATH_POINT:
293 if (!strcmp((gchar *) name, "time"))
294 data->state = INSIDE_PATH_POINT_TIME;
295 else if (!strcmp((gchar *) name, "ele"))
296 data->state = INSIDE_PATH_POINT_ELE;
297 else if (!strcmp((gchar *) name, "desc"))
298 data->state = INSIDE_PATH_POINT_DESC;
299 else if (!strcmp((gchar *) name, "name"))
300 data->state = INSIDE_PATH_POINT_NAME;
302 MACRO_SET_UNKNOWN(name);
305 data->unknown_depth++;
313 * Handle an end tag in the parsing of a GPX file.
316 gpx_end_element(SaxData * data, const xmlChar * name)
318 switch (data->state) {
326 if (!strcmp((gchar *) name, "gpx"))
327 data->state = FINISH;
331 case INSIDE_METADATA:
332 if (!strcmp((gchar *) name, "metadata"))
333 data->state = INSIDE_GPX;
336 if (!strcmp((gchar *) name, "trk"))
337 data->state = INSIDE_GPX;
341 case INSIDE_PATH_SEGMENT:
342 if (!strcmp((gchar *) name, "trkseg")) {
343 if (data->at_least_one_trkpt) {
344 MACRO_PATH_INCREMENT_TAIL(data->path);
345 *data->path.tail = _point_null;
347 data->state = INSIDE_PATH;
351 case INSIDE_PATH_POINT:
352 if (!strcmp((gchar *) name, "trkpt")) {
353 data->state = INSIDE_PATH_SEGMENT;
354 data->at_least_one_trkpt = TRUE;
358 case INSIDE_PATH_POINT_ELE:
359 if (!strcmp((gchar *) name, "ele")) {
361 data->path.tail->altitude = g_ascii_strtod(data->chars->str, &error_check);
362 if (error_check == data->chars->str)
363 data->path.tail->altitude = NAN;
364 data->state = INSIDE_PATH_POINT;
365 g_string_free(data->chars, TRUE);
366 data->chars = g_string_new("");
370 case INSIDE_PATH_POINT_TIME:
371 if (!strcmp((gchar *) name, "time")) {
375 if (NULL == (ptr = strptime(data->chars->str, XML_DATE_FORMAT, &time)))
376 /* Failed to parse dateTime format. */
379 /* Parse was successful. Now we have to parse timezone.
380 * From here on, if there is an error, I just assume local
381 * timezone. Yes, this is not proper XML, but I don't
385 /* First, set time in "local" time zone. */
386 data->path.tail->time = (mktime(&time));
388 /* Now, skip inconsequential characters */
389 while (*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
392 /* Check if we ran to the end of the string. */
394 /* Next character is either 'Z', '-', or '+' */
396 /* Zulu (UTC) time. Undo the local time zone's offset. */
397 data->path.tail->time += time.tm_gmtoff;
399 /* Not Zulu (UTC). Must parse hours and minutes. */
400 gint offhours = strtol(ptr, &error_check, 10);
401 if (error_check != ptr && *(ptr = error_check) == ':') {
402 /* Parse of hours worked. Check minutes. */
403 gint offmins = strtol(ptr + 1, &error_check, 10);
404 if (error_check != (ptr + 1)) {
405 /* Parse of minutes worked. Calculate. */
406 data->path.tail->time += (time.tm_gmtoff - (offhours * 60 * 60 + offmins * 60));
411 /* Successfully parsed dateTime. */
412 data->state = INSIDE_PATH_POINT;
415 g_string_free(data->chars, TRUE);
416 data->chars = g_string_new("");
420 case INSIDE_PATH_POINT_DESC:
421 /* only parse description for routes */
422 if (!strcmp((gchar *) name, "desc")) {
423 MACRO_PATH_INCREMENT_WTAIL(data->path);
424 data->path.wtail->point = data->path.tail;
425 data->path.wtail->desc = g_string_free(data->chars, FALSE);
426 data->chars = g_string_new("");
427 data->state = INSIDE_PATH_POINT;
431 case INSIDE_PATH_POINT_NAME:
432 /* Just ignore these for now */
433 g_string_free(data->chars, FALSE);
434 data->chars = g_string_new("");
435 data->state = INSIDE_PATH_POINT;
438 if (!--data->unknown_depth)
439 data->state = data->prev_state;
449 * Handle char data in the parsing of a GPX file.
452 gpx_chars(SaxData *data, const xmlChar *ch, int len)
456 switch (data->state) {
460 case INSIDE_PATH_POINT_ELE:
461 case INSIDE_PATH_POINT_TIME:
462 case INSIDE_PATH_POINT_DESC:
463 case INSIDE_PATH_POINT_NAME:
464 for (i = 0; i < len; i++)
465 data->chars = g_string_append_c(data->chars, ch[i]);
466 /* g_debug("GPXC: %s", data->chars->str); */
475 * Handle an entity in the parsing of a GPX file. We don't do anything
479 gpx_get_entity(SaxData *data, const xmlChar *name)
481 return xmlGetPredefinedEntity(name);
485 * Handle an error in the parsing of a GPX file.
488 gpx_error(SaxData *data, const gchar *msg, ...)
493 g_logv("GPX", G_LOG_LEVEL_WARNING, msg, args);
500 * Parse a buffer of GPX data.
502 * XXX: Add support for parsing directly from file if the file is local.
506 gpx_parse(Path *path, gchar *buffer, gint size, gpx_path_policy policy)
509 xmlSAXHandler sax_handler;
511 MACRO_PATH_INIT(data.path);
513 data.chars = g_string_new("");
515 memset(&sax_handler, 0, sizeof(sax_handler));
516 sax_handler.characters = (charactersSAXFunc) gpx_chars;
517 sax_handler.startElement = (startElementSAXFunc) gpx_start_element;
518 sax_handler.endElement = (endElementSAXFunc) gpx_end_element;
519 sax_handler.entityDecl = (entityDeclSAXFunc) gpx_get_entity;
520 sax_handler.warning = (warningSAXFunc) gpx_error;
521 sax_handler.error = (errorSAXFunc) gpx_error;
522 sax_handler.fatalError = (fatalErrorSAXFunc) gpx_error;
524 xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
526 g_string_free(data.chars, TRUE);
528 if (data.state != FINISH) {
529 g_warning("GPX: Parser stopped in error state %d", data.state);
534 case GPX_PATH_APPEND:
535 case GPX_PATH_PREPEND:
540 if (policy==GPX_PATH_APPEND) {
541 /* Append to current path. Make sure last path point is zero. */
542 if (path->tail->unity != 0) {
543 MACRO_PATH_INCREMENT_TAIL((*path));
544 *path->tail = _point_null;
549 /* Prepend to current route. */
554 /* Find src_first non-zero point. */
555 for (src_first = src->head - 1; src_first++ != src->tail;)
556 if (src_first->unity)
559 /* Append route points from src to dest. */
560 if (src->tail >= src_first) {
562 guint num_dest_points = dest->tail - dest->head + 1;
563 guint num_src_points = src->tail - src_first + 1;
565 /* Adjust dest->tail to be able to fit src route data
566 * plus room for more route data. */
567 path_resize(dest, num_dest_points + num_src_points);
569 memcpy(dest->tail + 1, src_first, num_src_points * sizeof(Point));
571 dest->tail += num_src_points;
573 /* Append waypoints from src to dest->. */
574 path_wresize(dest, (dest->wtail - dest->whead) + (src->wtail - src->whead) + 2);
575 for (curr = src->whead - 1; curr++ != src->wtail;) {
576 (++(dest->wtail))->point = dest->head + num_dest_points + (curr->point - src_first);
577 dest->wtail->desc = curr->desc;
581 /* Kill old route - don't use MACRO_PATH_FREE(), because that
582 * would free the string desc's that we just moved to data.route. */
585 if (policy==GPX_PATH_PREPEND)
590 /* Overwrite with data.route. */
591 MACRO_PATH_FREE((*path));
593 path_resize(path, path->tail - path->head + 1);
594 path_wresize(path, path->wtail - path->whead + 1);
597 g_assert_not_reached();