]> err.no Git - mapper/blob - src/gpx.c
Move some variables around
[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=1,
78         INSIDE_GPX=100,
79         INSIDE_METADATA,
80         INSIDE_PATH,
81         INSIDE_PATH_SEGMENT,
82         INSIDE_PATH_POINT,
83         INSIDE_PATH_POINT_ELE,
84         INSIDE_PATH_POINT_TIME,
85         INSIDE_PATH_POINT_DESC,
86         INSIDE_PATH_POINT_NAME,
87         FINISH=5000,
88         UNKNOWN=6666,
89         ERROR=9999,
90 } SaxState;
91
92 /** Data used during the SAX parsing operation. */
93 typedef struct _SaxData SaxData;
94 struct _SaxData {
95         Path path;
96         SaxState state;
97         SaxState prev_state;
98         guint unknown_depth;
99         gboolean at_least_one_trkpt;
100         GString *chars;
101 };
102
103 gchar XML_TZONE[7];
104
105 void 
106 gpx_init(void)
107 {
108 time_t time1;
109 struct tm time2;
110 time1 = time(NULL);
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);
114 }
115
116 gboolean 
117 write_gpx(Path * path, GnomeVFSHandle * handle)
118 {
119         Point *curr = NULL;
120         WayPoint *wcurr = NULL;
121         gboolean trkseg_break = FALSE;
122
123         /* Find first non-zero point. */
124         for (curr = path->head - 1, wcurr = path->whead; curr++ != path->tail;) {
125                 if (curr->unity)
126                         break;
127                 else if (wcurr && curr == wcurr->point)
128                         wcurr++;
129         }
130
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");
136
137         /* Curr points to first non-zero point. */
138         for (curr--; curr++ != path->tail;) {
139                 gdouble lat, lon;
140                 if (curr->unity) {
141                         gchar buffer[80];
142                         gboolean first_sub = TRUE;
143                         if (trkseg_break) {
144                                 /* First trkpt of the segment - write trkseg header. */
145                                 WRITE_STRING("    </trkseg>\n"
146                                              "    <trkseg>\n");
147                                 trkseg_break = FALSE;
148                         }
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);
156                         WRITE_STRING("\"");
157
158                         /* write the elevation */
159                         if (!isnan(curr->altitude)) {
160                                 if (first_sub) {
161                                         WRITE_STRING(">\n");
162                                         first_sub = FALSE;
163                                 }
164                                 WRITE_STRING("        <ele>");
165                                 {
166                                         g_ascii_formatd(buffer, 80, "%.2f",
167                                                         curr->altitude);
168                                         WRITE_STRING(buffer);
169                                 }
170                                 WRITE_STRING("</ele>\n");
171                         }
172
173                         /* write the time */
174                         if (curr->time) {
175                                 if (first_sub) {
176                                         WRITE_STRING(">\n");
177                                         first_sub = FALSE;
178                                 }
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");
185                         }
186
187                         if (wcurr && curr == wcurr->point) {
188                                 if (first_sub) {
189                                         WRITE_STRING(">\n");
190                                         first_sub = FALSE;
191                                 }
192                                 WRITE_STRING("        <desc>");
193                                 WRITE_STRING(wcurr->desc);
194                                 WRITE_STRING("</desc>\n");
195                                 wcurr++;
196                         }
197                         if (first_sub) {
198                                 WRITE_STRING("/>\n");
199                         } else {
200                                 WRITE_STRING("      </trkpt>\n");
201                         }
202                 } else
203                         trkseg_break = TRUE;
204         }
205
206         /* Write the footer. */
207         WRITE_STRING("    </trkseg>\n" "  </trk>\n" "</gpx>\n");
208
209         return TRUE;
210 }
211
212 /**
213  * Handle a start tag in the parsing of a GPX file.
214  */
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"); }
220
221 static void
222 gpx_start_element(SaxData * data, const xmlChar * name, const xmlChar ** attrs)
223 {
224         g_debug("GPX-START: %s (%d)", name, data->state);
225         switch (data->state) {
226         case ERROR:
227         break;
228         case START:
229                 if (!strcmp((gchar *) name, "gpx"))
230                         data->state = INSIDE_GPX;
231                 else
232                         MACRO_SET_UNKNOWN();
233         break;
234         case 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;
239                 else
240                         MACRO_SET_UNKNOWN();
241         break;
242         case INSIDE_METADATA:
243
244         break;
245         case INSIDE_PATH:
246                 if (!strcmp((gchar *) name, "trkseg")) {
247                         data->state = INSIDE_PATH_SEGMENT;
248                         data->at_least_one_trkpt = FALSE;
249                 } else
250                         MACRO_SET_UNKNOWN();
251         break;
252         case INSIDE_PATH_SEGMENT:
253                 if (!strcmp((gchar *) name, "trkpt")) {
254                         const xmlChar **curr_attr;
255                         gchar *error_check;
256                         gdouble lat = 0.f, lon = 0.f;
257                         gboolean has_lat, has_lon;
258                         has_lat = FALSE;
259                         has_lon = FALSE;
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)
266                                                 has_lat = TRUE;
267                                 } else if (!strcmp(attr_name, "lon")) {
268                                         lon = g_ascii_strtod(attr_val, &error_check);
269                                         if (error_check != attr_val)
270                                                 has_lon = TRUE;
271                                 }
272                         }
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;
279                         } else
280                                 data->state = ERROR;
281                 } else
282                         MACRO_SET_UNKNOWN();
283         break;
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;
293                 else
294                         MACRO_SET_UNKNOWN();
295         break;
296         case UNKNOWN:
297                 data->unknown_depth++;
298         break;
299         default:
300                 ;
301         }
302
303 }
304
305 /**
306  * Handle an end tag in the parsing of a GPX file.
307  */
308 static void 
309 gpx_end_element(SaxData * data, const xmlChar * name)
310 {
311         g_debug("GPX-END: %s", name);
312
313         switch (data->state) {
314         case ERROR:
315
316         break;
317         case START:
318                 data->state = ERROR;
319         break;
320         case INSIDE_GPX:
321                 if (!strcmp((gchar *) name, "gpx"))
322                         data->state = FINISH;
323                 else
324                         data->state = ERROR;
325         break;
326         case INSIDE_METADATA:
327                 if (!strcmp((gchar *) name, "metadata"))
328                         data->state = INSIDE_GPX;
329         break;
330         case INSIDE_PATH:
331                 if (!strcmp((gchar *) name, "trk"))
332                         data->state = INSIDE_GPX;
333                 else
334                         data->state = ERROR;
335         break;
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;
341                         }
342                         data->state = INSIDE_PATH;
343                 } else
344                         data->state = ERROR;
345         break;
346         case INSIDE_PATH_POINT:
347                 if (!strcmp((gchar *) name, "trkpt")) {
348                         data->state = INSIDE_PATH_SEGMENT;
349                         data->at_least_one_trkpt = TRUE;
350                 } else
351                         data->state = ERROR;
352         break;
353         case INSIDE_PATH_POINT_ELE:
354                 if (!strcmp((gchar *) name, "ele")) {
355                         gchar *error_check;
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("");
362                 } else
363                         data->state = ERROR;
364         break;
365         case INSIDE_PATH_POINT_TIME:
366                 if (!strcmp((gchar *) name, "time")) {
367                         struct tm time;
368                         gchar *ptr;
369
370                         if (NULL == (ptr = strptime(data->chars->str, XML_DATE_FORMAT, &time)))
371                                 /* Failed to parse dateTime format. */
372                                 data->state = ERROR;
373                         else {
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
377                                  * care. */
378                                 gchar *error_check;
379
380                                 /* First, set time in "local" time zone. */
381                                 data->path.tail->time = (mktime(&time));
382
383                                 /* Now, skip inconsequential characters */
384                                 while (*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
385                                         ptr++;
386
387                                 /* Check if we ran to the end of the string. */
388                                 if (*ptr) {
389                                         /* Next character is either 'Z', '-', or '+' */
390                                         if (*ptr == 'Z')
391                                                 /* Zulu (UTC) time. Undo the local time zone's offset. */
392                                                 data->path.tail->time += time.tm_gmtoff;
393                                         else {
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));
402                                                         }
403                                                 }
404                                         }
405                                 }
406                                 /* Successfully parsed dateTime. */
407                                 data->state = INSIDE_PATH_POINT;
408                         }
409
410                         g_string_free(data->chars, TRUE);
411                         data->chars = g_string_new("");
412                 } else
413                         data->state = ERROR;
414         break;
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;
423                 } else
424                         data->state = ERROR;
425         break;
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;
431         break;
432         case UNKNOWN:
433                 if (!--data->unknown_depth)
434                         data->state = data->prev_state;
435                 else
436                         data->state = ERROR;
437         break;
438         default:
439                 ;
440         }
441
442 }
443
444 /**
445  * Handle char data in the parsing of a GPX file.
446  */
447 static void 
448 gpx_chars(SaxData * data, const xmlChar * ch, int len)
449 {
450         guint i;
451
452         switch (data->state) {
453         case ERROR:
454         case UNKNOWN:
455                 break;
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);
463                 break;
464         default:
465                 break;
466         }
467
468 }
469
470 /**
471  * Handle an entity in the parsing of a GPX file.  We don't do anything
472  * special here.
473  */
474 static xmlEntityPtr 
475 gpx_get_entity(SaxData * data, const xmlChar * name)
476 {
477         return xmlGetPredefinedEntity(name);
478 }
479
480 /**
481  * Handle an error in the parsing of a GPX file.
482  */
483 static void 
484 gpx_error(SaxData * data, const gchar * msg, ...)
485 {
486         data->state = ERROR;
487 }
488
489 gboolean
490 parse_gpx(Path * to_replace, gchar * buffer, gint size, gint policy_old)
491 {
492         SaxData data;
493         xmlSAXHandler sax_handler;
494
495         MACRO_PATH_INIT(data.path);
496         data.state = START;
497         data.chars = g_string_new("");
498
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;
507
508         xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
509         g_string_free(data.chars, TRUE);
510
511         if (data.state != FINISH) {
512                 g_debug("GPX: Parser stopped in error state %d", data.state);
513                 return FALSE;
514         }
515
516         if (policy_old && to_replace->head != to_replace->tail) {
517                 Point *src_first;
518                 Path *src, *dest;
519
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;
525                         }
526                         src = &data.path;
527                         dest = to_replace;
528                 } else {
529                         /* Prepend to current route. */
530                         src = to_replace;
531                         dest = &data.path;
532                 }
533
534                 /* Find src_first non-zero point. */
535                 for (src_first = src->head - 1; src_first++ != src->tail;)
536                         if (src_first->unity)
537                                 break;
538
539                 /* Append route points from src to dest. */
540                 if (src->tail >= src_first) {
541                         WayPoint *curr;
542                         guint num_dest_points = dest->tail - dest->head + 1;
543                         guint num_src_points = src->tail - src_first + 1;
544
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);
548
549                         memcpy(dest->tail + 1, src_first, num_src_points * sizeof(Point));
550
551                         dest->tail += num_src_points;
552
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;
558                         }
559
560                 }
561
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. */
564                 g_free(src->head);
565                 g_free(src->whead);
566                 if (policy_old < 0)
567                         (*to_replace) = *dest;
568         } else {
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);
574         }
575
576         return TRUE;
577 }