]> err.no Git - mapper/blob - src/gpx.c
Add some debug output to gpx parser. Begin to handle more gpx tags.
[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 "gps.h"
53 #include "mapper-types.h"
54 #include "latlon.h"
55 #include "map.h"
56 #include "gpx.h"
57 #include "dialogs.h"
58
59 #define XML_DATE_FORMAT "%FT%T"
60
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))) \
65     { \
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); \
71         return FALSE; \
72     } \
73 }
74
75 /** This enum defines the states of the SAX parsing state machine. */
76 typedef enum {
77         START,
78         INSIDE_GPX,
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,
87         UNKNOWN,
88         ERROR,
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 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",
112          (time2.tm_gmtoff / 60 / 60), (time2.tm_gmtoff / 60) % 60);
113 }
114
115 gboolean 
116 write_gpx(Path * path, GnomeVFSHandle * handle)
117 {
118         Point *curr = NULL;
119         WayPoint *wcurr = NULL;
120         gboolean trkseg_break = FALSE;
121
122         /* Find first non-zero point. */
123         for (curr = path->head - 1, wcurr = path->whead; curr++ != path->tail;) {
124                 if (curr->unity)
125                         break;
126                 else if (wcurr && curr == wcurr->point)
127                         wcurr++;
128         }
129
130         /* Write the header. */
131         WRITE_STRING("<?xml version=\"1.0\"?>\n"
132                      "<gpx version=\"1.0\" creator=\"mapper\" "
133                      "xmlns=\"http://www.topografix.com/GPX/1/0\">\n"
134                      "  <trk>\n" "    <trkseg>\n");
135
136         /* Curr points to first non-zero point. */
137         for (curr--; curr++ != path->tail;) {
138                 gdouble lat, lon;
139                 if (curr->unity) {
140                         gchar buffer[80];
141                         gboolean first_sub = TRUE;
142                         if (trkseg_break) {
143                                 /* First trkpt of the segment - write trkseg header. */
144                                 WRITE_STRING("    </trkseg>\n"
145                                              "    <trkseg>\n");
146                                 trkseg_break = FALSE;
147                         }
148                         unit2latlon(curr->unitx, curr->unity, lat, lon);
149                         WRITE_STRING("      <trkpt lat=\"");
150                         g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lat);
151                         WRITE_STRING(buffer);
152                         WRITE_STRING("\" lon=\"");
153                         g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lon);
154                         WRITE_STRING(buffer);
155                         WRITE_STRING("\"");
156
157                         /* write the elevation */
158                         if (!isnan(curr->altitude)) {
159                                 if (first_sub) {
160                                         WRITE_STRING(">\n");
161                                         first_sub = FALSE;
162                                 }
163                                 WRITE_STRING("        <ele>");
164                                 {
165                                         g_ascii_formatd(buffer, 80, "%.2f",
166                                                         curr->altitude);
167                                         WRITE_STRING(buffer);
168                                 }
169                                 WRITE_STRING("</ele>\n");
170                         }
171
172                         /* write the time */
173                         if (curr->time) {
174                                 if (first_sub) {
175                                         WRITE_STRING(">\n");
176                                         first_sub = FALSE;
177                                 }
178                                 WRITE_STRING("        <time>");
179                                 strftime(buffer, 80, XML_DATE_FORMAT,
180                                          localtime(&curr->time));
181                                 WRITE_STRING(buffer);
182                                 WRITE_STRING(XML_TZONE);
183                                 WRITE_STRING("</time>\n");
184                         }
185
186                         if (wcurr && curr == wcurr->point) {
187                                 if (first_sub) {
188                                         WRITE_STRING(">\n");
189                                         first_sub = FALSE;
190                                 }
191                                 WRITE_STRING("        <desc>");
192                                 WRITE_STRING(wcurr->desc);
193                                 WRITE_STRING("</desc>\n");
194                                 wcurr++;
195                         }
196                         if (first_sub) {
197                                 WRITE_STRING("/>\n");
198                         } else {
199                                 WRITE_STRING("      </trkpt>\n");
200                         }
201                 } else
202                         trkseg_break = TRUE;
203         }
204
205         /* Write the footer. */
206         WRITE_STRING("    </trkseg>\n" "  </trk>\n" "</gpx>\n");
207
208         return TRUE;
209 }
210
211 /**
212  * Handle a start tag in the parsing of a GPX file.
213  */
214 #define MACRO_SET_UNKNOWN() { \
215     data->prev_state = data->state; \
216     data->state = UNKNOWN; \
217     data->unknown_depth = 1; \
218         g_debug("GPX: unknown tag"); }
219
220 static void
221 gpx_start_element(SaxData * data, const xmlChar * name, const xmlChar ** attrs)
222 {
223         g_debug("GPX-START: %s", name);
224         switch (data->state) {
225         case ERROR:
226                 break;
227         case START:
228                 if (!strcmp((gchar *) name, "gpx"))
229                         data->state = INSIDE_GPX;
230                 else
231                         MACRO_SET_UNKNOWN();
232                 break;
233         case INSIDE_GPX:
234                 if (!strcmp((gchar *) name, "trk"))
235                         data->state = INSIDE_PATH;
236                 else
237                         MACRO_SET_UNKNOWN();
238                 break;
239         case INSIDE_PATH:
240                 if (!strcmp((gchar *) name, "trkseg")) {
241                         data->state = INSIDE_PATH_SEGMENT;
242                         data->at_least_one_trkpt = FALSE;
243                 } else
244                         MACRO_SET_UNKNOWN();
245                 break;
246         case INSIDE_PATH_SEGMENT:
247                 if (!strcmp((gchar *) name, "trkpt")) {
248                         const xmlChar **curr_attr;
249                         gchar *error_check;
250                         gdouble lat = 0.f, lon = 0.f;
251                         gboolean has_lat, has_lon;
252                         has_lat = FALSE;
253                         has_lon = FALSE;
254                         for (curr_attr = attrs; *curr_attr != NULL;) {
255                                 const gchar *attr_name = *curr_attr++;
256                                 const gchar *attr_val = *curr_attr++;
257                                 if (!strcmp(attr_name, "lat")) {
258                                         lat = g_ascii_strtod(attr_val, &error_check);
259                                         if (error_check != attr_val)
260                                                 has_lat = TRUE;
261                                 } else if (!strcmp(attr_name, "lon")) {
262                                         lon = g_ascii_strtod(attr_val, &error_check);
263                                         if (error_check != attr_val)
264                                                 has_lon = TRUE;
265                                 }
266                         }
267                         if (has_lat && has_lon) {
268                                 MACRO_PATH_INCREMENT_TAIL(data->path);
269                                 latlon2unit(lat, lon, data->path.tail->unitx, data->path.tail->unity);
270                                 data->path.tail->time = 0;
271                                 data->path.tail->altitude = NAN;
272                                 data->state = INSIDE_PATH_POINT;
273                         } else
274                                 data->state = ERROR;
275                 } else
276                         MACRO_SET_UNKNOWN();
277                 break;
278         case INSIDE_PATH_POINT:
279                 if (!strcmp((gchar *) name, "time"))
280                         data->state = INSIDE_PATH_POINT_TIME;
281                 else if (!strcmp((gchar *) name, "ele"))
282                         data->state = INSIDE_PATH_POINT_ELE;
283                 else if (!strcmp((gchar *) name, "desc"))
284                         data->state = INSIDE_PATH_POINT_DESC;
285                 else if (!strcmp((gchar *) name, "name"))
286                         data->state = INSIDE_PATH_POINT_NAME;
287                 else
288                         MACRO_SET_UNKNOWN();
289                 break;
290         case UNKNOWN:
291                 data->unknown_depth++;
292                 break;
293         default:
294                 ;
295         }
296
297 }
298
299 /**
300  * Handle an end tag in the parsing of a GPX file.
301  */
302 static void 
303 gpx_end_element(SaxData * data, const xmlChar * name)
304 {
305         g_debug("GPX-END: %s", name);
306
307         switch (data->state) {
308         case ERROR:
309
310         break;
311         case START:
312                 data->state = ERROR;
313         break;
314         case INSIDE_GPX:
315                 if (!strcmp((gchar *) name, "gpx"))
316                         data->state = FINISH;
317                 else
318                         data->state = ERROR;
319         break;
320         case INSIDE_PATH:
321                 if (!strcmp((gchar *) name, "trk"))
322                         data->state = INSIDE_GPX;
323                 else
324                         data->state = ERROR;
325         break;
326         case INSIDE_PATH_SEGMENT:
327                 if (!strcmp((gchar *) name, "trkseg")) {
328                         if (data->at_least_one_trkpt) {
329                                 MACRO_PATH_INCREMENT_TAIL(data->path);
330                                 *data->path.tail = _point_null;
331                         }
332                         data->state = INSIDE_PATH;
333                 } else
334                         data->state = ERROR;
335         break;
336         case INSIDE_PATH_POINT:
337                 if (!strcmp((gchar *) name, "trkpt")) {
338                         data->state = INSIDE_PATH_SEGMENT;
339                         data->at_least_one_trkpt = TRUE;
340                 } else
341                         data->state = ERROR;
342         break;
343         case INSIDE_PATH_POINT_ELE:
344                 if (!strcmp((gchar *) name, "ele")) {
345                         gchar *error_check;
346                         data->path.tail->altitude = g_ascii_strtod(data->chars->str, &error_check);
347                         if (error_check == data->chars->str)
348                                 data->path.tail->altitude = NAN;
349                         data->state = INSIDE_PATH_POINT;
350                         g_string_free(data->chars, TRUE);
351                         data->chars = g_string_new("");
352                 } else
353                         data->state = ERROR;
354         break;
355         case INSIDE_PATH_POINT_TIME:
356                 if (!strcmp((gchar *) name, "time")) {
357                         struct tm time;
358                         gchar *ptr;
359
360                         if (NULL == (ptr = strptime(data->chars->str, XML_DATE_FORMAT, &time)))
361                                 /* Failed to parse dateTime format. */
362                                 data->state = ERROR;
363                         else {
364                                 /* Parse was successful. Now we have to parse timezone.
365                                  * From here on, if there is an error, I just assume local
366                                  * timezone.  Yes, this is not proper XML, but I don't
367                                  * care. */
368                                 gchar *error_check;
369
370                                 /* First, set time in "local" time zone. */
371                                 data->path.tail->time = (mktime(&time));
372
373                                 /* Now, skip inconsequential characters */
374                                 while (*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
375                                         ptr++;
376
377                                 /* Check if we ran to the end of the string. */
378                                 if (*ptr) {
379                                         /* Next character is either 'Z', '-', or '+' */
380                                         if (*ptr == 'Z')
381                                                 /* Zulu (UTC) time. Undo the local time zone's offset. */
382                                                 data->path.tail->time += time.tm_gmtoff;
383                                         else {
384                                                 /* Not Zulu (UTC). Must parse hours and minutes. */
385                                                 gint offhours = strtol(ptr, &error_check, 10);
386                                                 if (error_check != ptr && *(ptr = error_check) == ':') {
387                                                         /* Parse of hours worked. Check minutes. */
388                                                         gint offmins = strtol(ptr + 1, &error_check, 10);
389                                                         if (error_check != (ptr + 1)) {
390                                                                 /* Parse of minutes worked. Calculate. */
391                                                                 data->path.tail->time += (time.tm_gmtoff - (offhours * 60 * 60 + offmins * 60));
392                                                         }
393                                                 }
394                                         }
395                                 }
396                                 /* Successfully parsed dateTime. */
397                                 data->state = INSIDE_PATH_POINT;
398                         }
399
400                         g_string_free(data->chars, TRUE);
401                         data->chars = g_string_new("");
402                 } else
403                         data->state = ERROR;
404         break;
405         case INSIDE_PATH_POINT_DESC:
406                 /* only parse description for routes */
407                 if (!strcmp((gchar *) name, "desc")) {
408                         MACRO_PATH_INCREMENT_WTAIL(data->path);
409                         data->path.wtail->point = data->path.tail;
410                         data->path.wtail->desc = g_string_free(data->chars, FALSE);
411                         data->chars = g_string_new("");
412                         data->state = INSIDE_PATH_POINT;
413                 } else
414                         data->state = ERROR;
415         break;
416         case INSIDE_PATH_POINT_NAME:
417
418         break;
419         case UNKNOWN:
420                 if (!--data->unknown_depth)
421                         data->state = data->prev_state;
422                 else
423                         data->state = ERROR;
424         break;
425         default:
426                 ;
427         }
428
429 }
430
431 /**
432  * Handle char data in the parsing of a GPX file.
433  */
434 static void 
435 gpx_chars(SaxData * data, const xmlChar * ch, int len)
436 {
437         guint i;
438
439         switch (data->state) {
440         case ERROR:
441         case UNKNOWN:
442                 break;
443         case INSIDE_PATH_POINT_ELE:
444         case INSIDE_PATH_POINT_TIME:
445         case INSIDE_PATH_POINT_DESC:
446         case INSIDE_PATH_POINT_NAME:
447                 for (i = 0; i < len; i++)
448                         data->chars = g_string_append_c(data->chars, ch[i]);
449                 g_debug("GPXC: %s", data->chars->str);
450                 break;
451         default:
452                 break;
453         }
454
455 }
456
457 /**
458  * Handle an entity in the parsing of a GPX file.  We don't do anything
459  * special here.
460  */
461 static xmlEntityPtr 
462 gpx_get_entity(SaxData * data, const xmlChar * name)
463 {
464         return xmlGetPredefinedEntity(name);
465 }
466
467 /**
468  * Handle an error in the parsing of a GPX file.
469  */
470 static void 
471 gpx_error(SaxData * data, const gchar * msg, ...)
472 {
473         data->state = ERROR;
474 }
475
476 gboolean
477 parse_gpx(Path * to_replace, gchar * buffer, gint size, gint policy_old)
478 {
479         SaxData data;
480         xmlSAXHandler sax_handler;
481
482         MACRO_PATH_INIT(data.path);
483         data.state = START;
484         data.chars = g_string_new("");
485
486         memset(&sax_handler, 0, sizeof(sax_handler));
487         sax_handler.characters = (charactersSAXFunc) gpx_chars;
488         sax_handler.startElement = (startElementSAXFunc) gpx_start_element;
489         sax_handler.endElement = (endElementSAXFunc) gpx_end_element;
490         sax_handler.entityDecl = (entityDeclSAXFunc) gpx_get_entity;
491         sax_handler.warning = (warningSAXFunc) gpx_error;
492         sax_handler.error = (errorSAXFunc) gpx_error;
493         sax_handler.fatalError = (fatalErrorSAXFunc) gpx_error;
494
495         xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
496         g_string_free(data.chars, TRUE);
497
498         if (data.state != FINISH) {
499                 return FALSE;
500         }
501
502         if (policy_old && to_replace->head != to_replace->tail) {
503                 Point *src_first;
504                 Path *src, *dest;
505
506                 if (policy_old > 0) {
507                         /* Append to current path. Make sure last path point is zero. */
508                         if (to_replace->tail->unity != 0) {
509                                 MACRO_PATH_INCREMENT_TAIL((*to_replace));
510                                 *to_replace->tail = _point_null;
511                         }
512                         src = &data.path;
513                         dest = to_replace;
514                 } else {
515                         /* Prepend to current route. */
516                         src = to_replace;
517                         dest = &data.path;
518                 }
519
520                 /* Find src_first non-zero point. */
521                 for (src_first = src->head - 1; src_first++ != src->tail;)
522                         if (src_first->unity)
523                                 break;
524
525                 /* Append route points from src to dest. */
526                 if (src->tail >= src_first) {
527                         WayPoint *curr;
528                         guint num_dest_points = dest->tail - dest->head + 1;
529                         guint num_src_points = src->tail - src_first + 1;
530
531                         /* Adjust dest->tail to be able to fit src route data
532                          * plus room for more route data. */
533                         path_resize(dest, num_dest_points + num_src_points);
534
535                         memcpy(dest->tail + 1, src_first, num_src_points * sizeof(Point));
536
537                         dest->tail += num_src_points;
538
539                         /* Append waypoints from src to dest->. */
540                         path_wresize(dest, (dest->wtail - dest->whead) + (src->wtail - src->whead) + 2);
541                         for (curr = src->whead - 1; curr++ != src->wtail;) {
542                                 (++(dest->wtail))->point = dest->head + num_dest_points + (curr->point - src_first);
543                                 dest->wtail->desc = curr->desc;
544                         }
545
546                 }
547
548                 /* Kill old route - don't use MACRO_PATH_FREE(), because that
549                  * would free the string desc's that we just moved to data.route. */
550                 g_free(src->head);
551                 g_free(src->whead);
552                 if (policy_old < 0)
553                         (*to_replace) = *dest;
554         } else {
555                 MACRO_PATH_FREE((*to_replace));
556                 /* Overwrite with data.route. */
557                 (*to_replace) = data.path;
558                 path_resize(to_replace,
559                             to_replace->tail - to_replace->head + 1);
560                 path_wresize(to_replace,
561                              to_replace->wtail - to_replace->whead + 1);
562         }
563
564         return TRUE;
565 }