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