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"
53 #include "mapper-types.h"
59 #define XML_DATE_FORMAT "%FT%T"
61 #define WRITE_STRING(string) { \
62 GnomeVFSResult vfs_result; \
63 GnomeVFSFileSize size; \
64 if(GNOME_VFS_OK != (vfs_result = gnome_vfs_write(handle, (string), strlen((string)), &size))) \
66 gchar buffer[BUFFER_SIZE]; \
67 g_snprintf(buffer, sizeof(buffer), \
68 "%s:\n%s\n%s", _("Error while writing to file"), _("File is incomplete."), \
69 gnome_vfs_result_to_string(vfs_result)); \
70 popup_error(_window, buffer); \
75 /** This enum defines the states of the SAX parsing state machine. */
83 INSIDE_PATH_POINT_ELE,
84 INSIDE_PATH_POINT_TIME,
85 INSIDE_PATH_POINT_DESC,
86 INSIDE_PATH_POINT_NAME,
92 /** Data used during the SAX parsing operation. */
93 typedef struct _SaxData SaxData;
99 gboolean at_least_one_trkpt;
111 localtime_r(&time1, &time2);
112 g_snprintf(XML_TZONE, sizeof(XML_TZONE), "%+03ld:%02ld",
113 (time2.tm_gmtoff / 60 / 60), (time2.tm_gmtoff / 60) % 60);
117 write_gpx(Path * path, GnomeVFSHandle * handle)
120 WayPoint *wcurr = NULL;
121 gboolean trkseg_break = FALSE;
123 /* Find first non-zero point. */
124 for (curr = path->head - 1, wcurr = path->whead; curr++ != path->tail;) {
127 else if (wcurr && curr == wcurr->point)
131 /* Write the header. */
132 WRITE_STRING("<?xml version=\"1.0\"?>\n"
133 "<gpx version=\"1.0\" creator=\"mapper\" "
134 "xmlns=\"http://www.topografix.com/GPX/1/0\">\n"
135 " <trk>\n" " <trkseg>\n");
137 /* Curr points to first non-zero point. */
138 for (curr--; curr++ != path->tail;) {
142 gboolean first_sub = TRUE;
144 /* First trkpt of the segment - write trkseg header. */
145 WRITE_STRING(" </trkseg>\n"
147 trkseg_break = FALSE;
149 unit2latlon(curr->unitx, curr->unity, lat, lon);
150 WRITE_STRING(" <trkpt lat=\"");
151 g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lat);
152 WRITE_STRING(buffer);
153 WRITE_STRING("\" lon=\"");
154 g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lon);
155 WRITE_STRING(buffer);
158 /* write the elevation */
159 if (!isnan(curr->altitude)) {
164 WRITE_STRING(" <ele>");
166 g_ascii_formatd(buffer, 80, "%.2f",
168 WRITE_STRING(buffer);
170 WRITE_STRING("</ele>\n");
179 WRITE_STRING(" <time>");
180 strftime(buffer, 80, XML_DATE_FORMAT,
181 localtime(&curr->time));
182 WRITE_STRING(buffer);
183 WRITE_STRING(XML_TZONE);
184 WRITE_STRING("</time>\n");
187 if (wcurr && curr == wcurr->point) {
192 WRITE_STRING(" <desc>");
193 WRITE_STRING(wcurr->desc);
194 WRITE_STRING("</desc>\n");
198 WRITE_STRING("/>\n");
200 WRITE_STRING(" </trkpt>\n");
206 /* Write the footer. */
207 WRITE_STRING(" </trkseg>\n" " </trk>\n" "</gpx>\n");
213 * Handle a start tag in the parsing of a GPX file.
215 #define MACRO_SET_UNKNOWN() { \
216 data->prev_state = data->state; \
217 data->state = UNKNOWN; \
218 data->unknown_depth = 1; \
219 g_debug("GPX: unknown tag"); }
222 gpx_start_element(SaxData * data, const xmlChar * name, const xmlChar ** attrs)
224 g_debug("GPX-START: %s (%d)", name, data->state);
225 switch (data->state) {
229 if (!strcmp((gchar *) name, "gpx"))
230 data->state = INSIDE_GPX;
235 if (!strcmp((gchar *) name, "trk"))
236 data->state = INSIDE_PATH;
237 else if (!strcmp((gchar *) name, "metadata"))
238 data->state = INSIDE_METADATA;
242 case INSIDE_METADATA:
246 if (!strcmp((gchar *) name, "trkseg")) {
247 data->state = INSIDE_PATH_SEGMENT;
248 data->at_least_one_trkpt = FALSE;
252 case INSIDE_PATH_SEGMENT:
253 if (!strcmp((gchar *) name, "trkpt")) {
254 const xmlChar **curr_attr;
256 gdouble lat = 0.f, lon = 0.f;
257 gboolean has_lat, has_lon;
260 for (curr_attr = attrs; *curr_attr != NULL;) {
261 const gchar *attr_name = *curr_attr++;
262 const gchar *attr_val = *curr_attr++;
263 if (!strcmp(attr_name, "lat")) {
264 lat = g_ascii_strtod(attr_val, &error_check);
265 if (error_check != attr_val)
267 } else if (!strcmp(attr_name, "lon")) {
268 lon = g_ascii_strtod(attr_val, &error_check);
269 if (error_check != attr_val)
273 if (has_lat && has_lon) {
274 MACRO_PATH_INCREMENT_TAIL(data->path);
275 latlon2unit(lat, lon, data->path.tail->unitx, data->path.tail->unity);
276 data->path.tail->time = 0;
277 data->path.tail->altitude = NAN;
278 data->state = INSIDE_PATH_POINT;
284 case INSIDE_PATH_POINT:
285 if (!strcmp((gchar *) name, "time"))
286 data->state = INSIDE_PATH_POINT_TIME;
287 else if (!strcmp((gchar *) name, "ele"))
288 data->state = INSIDE_PATH_POINT_ELE;
289 else if (!strcmp((gchar *) name, "desc"))
290 data->state = INSIDE_PATH_POINT_DESC;
291 else if (!strcmp((gchar *) name, "name"))
292 data->state = INSIDE_PATH_POINT_NAME;
297 data->unknown_depth++;
306 * Handle an end tag in the parsing of a GPX file.
309 gpx_end_element(SaxData * data, const xmlChar * name)
311 g_debug("GPX-END: %s", name);
313 switch (data->state) {
321 if (!strcmp((gchar *) name, "gpx"))
322 data->state = FINISH;
326 case INSIDE_METADATA:
327 if (!strcmp((gchar *) name, "metadata"))
328 data->state = INSIDE_GPX;
331 if (!strcmp((gchar *) name, "trk"))
332 data->state = INSIDE_GPX;
336 case INSIDE_PATH_SEGMENT:
337 if (!strcmp((gchar *) name, "trkseg")) {
338 if (data->at_least_one_trkpt) {
339 MACRO_PATH_INCREMENT_TAIL(data->path);
340 *data->path.tail = _point_null;
342 data->state = INSIDE_PATH;
346 case INSIDE_PATH_POINT:
347 if (!strcmp((gchar *) name, "trkpt")) {
348 data->state = INSIDE_PATH_SEGMENT;
349 data->at_least_one_trkpt = TRUE;
353 case INSIDE_PATH_POINT_ELE:
354 if (!strcmp((gchar *) name, "ele")) {
356 data->path.tail->altitude = g_ascii_strtod(data->chars->str, &error_check);
357 if (error_check == data->chars->str)
358 data->path.tail->altitude = NAN;
359 data->state = INSIDE_PATH_POINT;
360 g_string_free(data->chars, TRUE);
361 data->chars = g_string_new("");
365 case INSIDE_PATH_POINT_TIME:
366 if (!strcmp((gchar *) name, "time")) {
370 if (NULL == (ptr = strptime(data->chars->str, XML_DATE_FORMAT, &time)))
371 /* Failed to parse dateTime format. */
374 /* Parse was successful. Now we have to parse timezone.
375 * From here on, if there is an error, I just assume local
376 * timezone. Yes, this is not proper XML, but I don't
380 /* First, set time in "local" time zone. */
381 data->path.tail->time = (mktime(&time));
383 /* Now, skip inconsequential characters */
384 while (*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
387 /* Check if we ran to the end of the string. */
389 /* Next character is either 'Z', '-', or '+' */
391 /* Zulu (UTC) time. Undo the local time zone's offset. */
392 data->path.tail->time += time.tm_gmtoff;
394 /* Not Zulu (UTC). Must parse hours and minutes. */
395 gint offhours = strtol(ptr, &error_check, 10);
396 if (error_check != ptr && *(ptr = error_check) == ':') {
397 /* Parse of hours worked. Check minutes. */
398 gint offmins = strtol(ptr + 1, &error_check, 10);
399 if (error_check != (ptr + 1)) {
400 /* Parse of minutes worked. Calculate. */
401 data->path.tail->time += (time.tm_gmtoff - (offhours * 60 * 60 + offmins * 60));
406 /* Successfully parsed dateTime. */
407 data->state = INSIDE_PATH_POINT;
410 g_string_free(data->chars, TRUE);
411 data->chars = g_string_new("");
415 case INSIDE_PATH_POINT_DESC:
416 /* only parse description for routes */
417 if (!strcmp((gchar *) name, "desc")) {
418 MACRO_PATH_INCREMENT_WTAIL(data->path);
419 data->path.wtail->point = data->path.tail;
420 data->path.wtail->desc = g_string_free(data->chars, FALSE);
421 data->chars = g_string_new("");
422 data->state = INSIDE_PATH_POINT;
426 case INSIDE_PATH_POINT_NAME:
427 /* Just ignore these for now */
428 g_string_free(data->chars, FALSE);
429 data->chars = g_string_new("");
430 data->state = INSIDE_PATH_POINT;
433 if (!--data->unknown_depth)
434 data->state = data->prev_state;
445 * Handle char data in the parsing of a GPX file.
448 gpx_chars(SaxData * data, const xmlChar * ch, int len)
452 switch (data->state) {
456 case INSIDE_PATH_POINT_ELE:
457 case INSIDE_PATH_POINT_TIME:
458 case INSIDE_PATH_POINT_DESC:
459 case INSIDE_PATH_POINT_NAME:
460 for (i = 0; i < len; i++)
461 data->chars = g_string_append_c(data->chars, ch[i]);
462 g_debug("GPXC: %s", data->chars->str);
471 * Handle an entity in the parsing of a GPX file. We don't do anything
475 gpx_get_entity(SaxData * data, const xmlChar * name)
477 return xmlGetPredefinedEntity(name);
481 * Handle an error in the parsing of a GPX file.
484 gpx_error(SaxData * data, const gchar * msg, ...)
490 parse_gpx(Path * to_replace, gchar * buffer, gint size, gint policy_old)
493 xmlSAXHandler sax_handler;
495 MACRO_PATH_INIT(data.path);
497 data.chars = g_string_new("");
499 memset(&sax_handler, 0, sizeof(sax_handler));
500 sax_handler.characters = (charactersSAXFunc) gpx_chars;
501 sax_handler.startElement = (startElementSAXFunc) gpx_start_element;
502 sax_handler.endElement = (endElementSAXFunc) gpx_end_element;
503 sax_handler.entityDecl = (entityDeclSAXFunc) gpx_get_entity;
504 sax_handler.warning = (warningSAXFunc) gpx_error;
505 sax_handler.error = (errorSAXFunc) gpx_error;
506 sax_handler.fatalError = (fatalErrorSAXFunc) gpx_error;
508 xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
509 g_string_free(data.chars, TRUE);
511 if (data.state != FINISH) {
512 g_debug("GPX: Parser stopped in error state %d", data.state);
516 if (policy_old && to_replace->head != to_replace->tail) {
520 if (policy_old > 0) {
521 /* Append to current path. Make sure last path point is zero. */
522 if (to_replace->tail->unity != 0) {
523 MACRO_PATH_INCREMENT_TAIL((*to_replace));
524 *to_replace->tail = _point_null;
529 /* Prepend to current route. */
534 /* Find src_first non-zero point. */
535 for (src_first = src->head - 1; src_first++ != src->tail;)
536 if (src_first->unity)
539 /* Append route points from src to dest. */
540 if (src->tail >= src_first) {
542 guint num_dest_points = dest->tail - dest->head + 1;
543 guint num_src_points = src->tail - src_first + 1;
545 /* Adjust dest->tail to be able to fit src route data
546 * plus room for more route data. */
547 path_resize(dest, num_dest_points + num_src_points);
549 memcpy(dest->tail + 1, src_first, num_src_points * sizeof(Point));
551 dest->tail += num_src_points;
553 /* Append waypoints from src to dest->. */
554 path_wresize(dest, (dest->wtail - dest->whead) + (src->wtail - src->whead) + 2);
555 for (curr = src->whead - 1; curr++ != src->wtail;) {
556 (++(dest->wtail))->point = dest->head + num_dest_points + (curr->point - src_first);
557 dest->wtail->desc = curr->desc;
562 /* Kill old route - don't use MACRO_PATH_FREE(), because that
563 * would free the string desc's that we just moved to data.route. */
567 (*to_replace) = *dest;
569 MACRO_PATH_FREE((*to_replace));
570 /* Overwrite with data.route. */
571 (*to_replace) = data.path;
572 path_resize(to_replace, to_replace->tail - to_replace->head + 1);
573 path_wresize(to_replace, to_replace->wtail - to_replace->whead + 1);