]> err.no Git - mapper/blob - src/gpx.c
MapWidgetIntegration:
[mapper] / src / gpx.c
1 /*
2  * This file is part of mapper
3  *
4  * Copyright (C) 2006-2007 John Costigan.
5  *
6  * POI and GPS-Info code originally written by Cezary Jackiewicz.
7  *
8  * Default map data provided by http://www.openstreetmap.org/
9  *
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.
14  *
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.
19  *
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.
23  */
24
25 #include <config.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <strings.h>
30 #include <stddef.h>
31 #include <locale.h>
32 #include <math.h>
33 #include <errno.h>
34 #include <sys/wait.h>
35 #include <glib/gstdio.h>
36 #include <glib/gi18n.h>
37 #include <gtk/gtk.h>
38 #include <fcntl.h>
39 #include <libgnomevfs/gnome-vfs.h>
40 #include <curl/multi.h>
41 #include <gconf/gconf-client.h>
42 #include <libxml/parser.h>
43
44 #include <libintl.h>
45 #include <locale.h>
46
47 #include <sqlite3.h>
48
49 #include "ui-common.h"
50 #include "path.h"
51 #include "utils.h"
52 #include "mapper-types.h"
53 #include "latlon.h"
54 #include "gpx.h"
55 #include "dialogs.h"
56 #include "gtkmap.h"
57
58 #define XML_DATE_FORMAT "%FT%T"
59
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))) \
64     { \
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); \
70         return FALSE; \
71     } \
72 }
73
74 /** This enum defines the states of the SAX parsing state machine. */
75 typedef enum {
76         START=1,
77         INSIDE_GPX=100,
78         INSIDE_METADATA,
79         INSIDE_PATH,
80         INSIDE_PATH_SEGMENT,
81         INSIDE_PATH_POINT,
82         INSIDE_PATH_POINT_ELE,
83         INSIDE_PATH_POINT_TIME,
84         INSIDE_PATH_POINT_DESC,
85         INSIDE_PATH_POINT_NAME,
86         FINISH=5000,
87         UNKNOWN=6666,
88         ERROR=9999,
89 } SaxState;
90
91 /** Data used during the SAX parsing operation. */
92 typedef struct _SaxData SaxData;
93 struct _SaxData {
94         Path path;
95         SaxState state;
96         SaxState prev_state;
97         guint unknown_depth;
98         gboolean at_least_one_trkpt;
99         GString *chars;
100 };
101
102 static gchar XML_TZONE[7];
103
104 void 
105 gpx_init(void)
106 {
107 time_t time1;
108 struct tm time2;
109 time1 = time(NULL);
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);
112 }
113
114 gboolean 
115 gpx_write(Path *path, GnomeVFSHandle *handle)
116 {
117 Point *curr = NULL;
118 WayPoint *wcurr = NULL;
119 gboolean trkseg_break = FALSE;
120 gchar buffer[80];
121 time_t gpx_time;
122 gpx_time = time(NULL);
123
124 /* Find first non-zero point. */
125 for (curr = path->head - 1, wcurr = path->whead; curr++ != path->tail;) {
126         if (curr->unity)
127                 break;
128         else if (wcurr && curr == wcurr->point)
129                 wcurr++;
130 }
131
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");
135
136 /* Write any metadata */
137 WRITE_STRING("<metadata>\n");
138
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");
144
145 WRITE_STRING("</metadata>\n");
146
147 /* Write track(s) and waypoint(s) */
148 WRITE_STRING("<trk>\n<trkseg>\n");
149
150 /* Curr points to first non-zero point. */
151 for (curr--; curr++ != path->tail;) {
152         gdouble lat, lon;
153         if (curr->unity) {
154                 gboolean first_sub = TRUE;
155                 if (trkseg_break) {
156                         /* First trkpt of the segment - write trkseg header. */
157                         WRITE_STRING("</trkseg>\n<trkseg>\n");
158                         trkseg_break = FALSE;
159                 }
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);
167                 WRITE_STRING("\"");
168
169                 /* write the elevation */
170                 if (!isnan(curr->altitude)) {
171                         if (first_sub) {
172                                 WRITE_STRING(">\n");
173                                 first_sub = FALSE;
174                         }
175                         WRITE_STRING("<ele>");
176                         g_ascii_formatd(buffer, sizeof(buffer), "%.2f", curr->altitude);
177                         WRITE_STRING(buffer);
178                         WRITE_STRING("</ele>\n");
179                 }
180
181                 /* write the time */
182                 if (curr->time) {
183                         if (first_sub) {
184                                 WRITE_STRING(">\n");
185                                 first_sub = FALSE;
186                         }
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");
192                 }
193
194                 if (wcurr && curr == wcurr->point) {
195                         gchar *desc;
196                         if (first_sub) {
197                                 WRITE_STRING(">\n");
198                                 first_sub = FALSE;
199                         }
200                         if (wcurr->desc) {
201                                 desc=g_markup_printf_escaped("<desc>%s</desc>\n", wcurr->desc);
202                                 WRITE_STRING(desc);
203                                 g_free(desc);
204                         }
205                         wcurr++;
206                 }
207                 if (first_sub) {
208                         WRITE_STRING("/>\n");
209                 } else {
210                         WRITE_STRING("</trkpt>\n");
211                 }
212         } else
213                 trkseg_break = TRUE;
214 }
215
216 /* Write the footer. */
217 WRITE_STRING("</trkseg>\n</trk>\n</gpx>\n");
218
219 return TRUE;
220 }
221
222 /**
223  * Handle a start tag in the parsing of a GPX file.
224  */
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); }
230
231 static void
232 gpx_start_element(SaxData *data, const xmlChar *name, const xmlChar **attrs)
233 {
234 switch (data->state) {
235 case ERROR:
236 break;
237 case START:
238         if (!strcmp((gchar *) name, "gpx"))
239                 data->state = INSIDE_GPX;
240         else
241                 MACRO_SET_UNKNOWN(name);
242 break;
243 case INSIDE_GPX:
244         if (!strcmp((gchar *) name, "trk"))
245                 data->state = INSIDE_PATH;
246         else if (!strcmp((gchar *) name, "metadata"))
247                 data->state = INSIDE_METADATA;
248         else
249                 MACRO_SET_UNKNOWN(name);
250 break;
251 case INSIDE_METADATA:
252 break;
253 case INSIDE_PATH:
254         if (!strcmp((gchar *) name, "trkseg")) {
255                 data->state = INSIDE_PATH_SEGMENT;
256                 data->at_least_one_trkpt = FALSE;
257         } else
258                 MACRO_SET_UNKNOWN(name);
259 break;
260 case INSIDE_PATH_SEGMENT:
261         if (!strcmp((gchar *) name, "trkpt")) {
262                 const xmlChar **curr_attr;
263                 gchar *error_check;
264                 gdouble lat = 0.f, lon = 0.f;
265                 gboolean has_lat, has_lon;
266                 has_lat = FALSE;
267                 has_lon = FALSE;
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)
274                                         has_lat = TRUE;
275                         } else if (!strcmp(attr_name, "lon")) {
276                                 lon = g_ascii_strtod(attr_val, &error_check);
277                                 if (error_check != attr_val)
278                                         has_lon = TRUE;
279                         }
280                 }
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;
287                 } else
288                         data->state = ERROR;
289         } else
290                 MACRO_SET_UNKNOWN(name);
291 break;
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;
301         else
302                 MACRO_SET_UNKNOWN(name);
303 break;
304 case UNKNOWN:
305         data->unknown_depth++;
306 break;
307 default: ;
308 }
309
310 }
311
312 /**
313  * Handle an end tag in the parsing of a GPX file.
314  */
315 static void 
316 gpx_end_element(SaxData * data, const xmlChar * name)
317 {
318 switch (data->state) {
319 case ERROR:
320
321 break;
322 case START:
323         data->state = ERROR;
324 break;
325 case INSIDE_GPX:
326         if (!strcmp((gchar *) name, "gpx"))
327                 data->state = FINISH;
328         else
329                 data->state = ERROR;
330 break;
331 case INSIDE_METADATA:
332         if (!strcmp((gchar *) name, "metadata"))
333                 data->state = INSIDE_GPX;
334 break;
335 case INSIDE_PATH:
336         if (!strcmp((gchar *) name, "trk"))
337                 data->state = INSIDE_GPX;
338         else
339                 data->state = ERROR;
340 break;
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;
346                 }
347                 data->state = INSIDE_PATH;
348         } else
349                 data->state = ERROR;
350 break;
351 case INSIDE_PATH_POINT:
352         if (!strcmp((gchar *) name, "trkpt")) {
353                 data->state = INSIDE_PATH_SEGMENT;
354                 data->at_least_one_trkpt = TRUE;
355         } else
356                 data->state = ERROR;
357 break;
358 case INSIDE_PATH_POINT_ELE:
359         if (!strcmp((gchar *) name, "ele")) {
360                 gchar *error_check;
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("");
367         } else
368                 data->state = ERROR;
369 break;
370 case INSIDE_PATH_POINT_TIME:
371         if (!strcmp((gchar *) name, "time")) {
372                 struct tm time;
373                 gchar *ptr;
374
375                 if (NULL == (ptr = strptime(data->chars->str, XML_DATE_FORMAT, &time)))
376                         /* Failed to parse dateTime format. */
377                         data->state = ERROR;
378                 else {
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
382                          * care. */
383                         gchar *error_check;
384
385                         /* First, set time in "local" time zone. */
386                         data->path.tail->time = (mktime(&time));
387
388                         /* Now, skip inconsequential characters */
389                         while (*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
390                                 ptr++;
391
392                         /* Check if we ran to the end of the string. */
393                         if (*ptr) {
394                                 /* Next character is either 'Z', '-', or '+' */
395                                 if (*ptr == 'Z')
396                                         /* Zulu (UTC) time. Undo the local time zone's offset. */
397                                         data->path.tail->time += time.tm_gmtoff;
398                                 else {
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));
407                                                 }
408                                         }
409                                 }
410                         }
411                         /* Successfully parsed dateTime. */
412                         data->state = INSIDE_PATH_POINT;
413                 }
414
415                 g_string_free(data->chars, TRUE);
416                 data->chars = g_string_new("");
417         } else
418                 data->state = ERROR;
419 break;
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;
428         } else
429                 data->state = ERROR;
430 break;
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;
436 break;
437 case UNKNOWN:
438         if (!--data->unknown_depth)
439                 data->state = data->prev_state;
440         else
441                 data->state = ERROR;
442 break;
443 default: ;
444 }
445
446 }
447
448 /**
449  * Handle char data in the parsing of a GPX file.
450  */
451 static void 
452 gpx_chars(SaxData *data, const xmlChar *ch, int len)
453 {
454 guint i;
455
456 switch (data->state) {
457 case ERROR:
458 case UNKNOWN:
459         break;
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); */
467         break;
468 default:
469 break;
470 }
471
472 }
473
474 /**
475  * Handle an entity in the parsing of a GPX file.  We don't do anything
476  * special here.
477  */
478 static xmlEntityPtr 
479 gpx_get_entity(SaxData *data, const xmlChar *name)
480 {
481 return xmlGetPredefinedEntity(name);
482 }
483
484 /**
485  * Handle an error in the parsing of a GPX file.
486  */
487 static void 
488 gpx_error(SaxData *data, const gchar *msg, ...)
489 {
490 va_list args;
491
492 va_start(args, msg);
493 g_logv("GPX", G_LOG_LEVEL_WARNING, msg, args);
494 va_end(args);
495
496 data->state = ERROR;
497 }
498
499 /**
500  * Parse a buffer of GPX data.
501  *
502  * XXX: Add support for parsing directly from file if the file is local.
503  *
504  */
505 gboolean
506 gpx_parse(Path *path, gchar *buffer, gint size, gpx_path_policy policy)
507 {
508 SaxData data;
509 xmlSAXHandler sax_handler;
510
511 MACRO_PATH_INIT(data.path);
512 data.state = START;
513 data.chars = g_string_new("");
514
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;
523
524 xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
525
526 g_string_free(data.chars, TRUE);
527
528 if (data.state != FINISH) {
529         g_warning("GPX: Parser stopped in error state %d", data.state);
530         return FALSE;
531 }
532
533 switch (policy) {
534 case GPX_PATH_APPEND:
535 case GPX_PATH_PREPEND:
536         {
537         Point *src_first;
538         Path *src, *dest;
539
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;
545                 }
546                 src = &data.path;
547                 dest = path;
548         } else {
549                 /* Prepend to current route. */
550                 src = path;
551                 dest = &data.path;
552         }
553
554         /* Find src_first non-zero point. */
555         for (src_first = src->head - 1; src_first++ != src->tail;)
556                 if (src_first->unity)
557                         break;
558
559         /* Append route points from src to dest. */
560         if (src->tail >= src_first) {
561                 WayPoint *curr;
562                 guint num_dest_points = dest->tail - dest->head + 1;
563                 guint num_src_points = src->tail - src_first + 1;
564
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);
568
569                 memcpy(dest->tail + 1, src_first, num_src_points * sizeof(Point));
570
571                 dest->tail += num_src_points;
572
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;
578                 }
579         }
580
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. */
583         g_free(src->head);
584         g_free(src->whead);
585         if (policy==GPX_PATH_PREPEND)
586                 (*path) = *dest;
587         }
588 break;
589 case GPX_PATH_NEW:
590         /* Overwrite with data.route. */
591         MACRO_PATH_FREE((*path));
592         (*path) = data.path;
593         path_resize(path, path->tail - path->head + 1);
594         path_wresize(path, path->wtail - path->whead + 1);
595 break;
596 default:
597         g_assert_not_reached();
598 break;
599 }
600
601 return TRUE;
602 }