]> err.no Git - mapper/blob - src/gpx.c
Move macros and some functions to better places
[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 #define _GNU_SOURCE
26
27 #include <config.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <strings.h>
32 #include <stddef.h>
33 #include <locale.h>
34 #include <math.h>
35 #include <errno.h>
36 #include <sys/wait.h>
37 #include <glib/gstdio.h>
38 #include <glib/gi18n.h>
39 #include <gtk/gtk.h>
40 #include <fcntl.h>
41 #include <libgnomevfs/gnome-vfs.h>
42 #include <curl/multi.h>
43 #include <gconf/gconf-client.h>
44 #include <libxml/parser.h>
45
46 #include <libintl.h>
47 #include <locale.h>
48
49 #include <sqlite3.h>
50
51 #include "ui-common.h"
52 #include "path.h"
53 #include "utils.h"
54 #include "gps.h"
55 #include "mapper-types.h"
56 #include "latlon.h"
57 #include "gpx.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( \
65                     handle, (string), strlen((string)), &size))) \
66     { \
67         gchar buffer[BUFFER_SIZE]; \
68         snprintf(buffer, sizeof(buffer), \
69                 "%s:\n%s\n%s", _("Error while writing to file"), \
70                 _("File is incomplete."), \
71                 gnome_vfs_result_to_string(vfs_result)); \
72         popup_error(_window, buffer); \
73         return FALSE; \
74     } \
75 }
76
77 /** This enum defines the states of the SAX parsing state machine. */
78 typedef enum {
79         START,
80         INSIDE_GPX,
81         INSIDE_PATH,
82         INSIDE_PATH_SEGMENT,
83         INSIDE_PATH_POINT,
84         INSIDE_PATH_POINT_ELE,
85         INSIDE_PATH_POINT_TIME,
86         INSIDE_PATH_POINT_DESC,
87         FINISH,
88         UNKNOWN,
89         ERROR,
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 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 }
220
221 static void
222 gpx_start_element(SaxData * data, const xmlChar * name, const xmlChar ** attrs)
223 {
224
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
238                         MACRO_SET_UNKNOWN();
239                 break;
240         case INSIDE_PATH:
241                 if (!strcmp((gchar *) name, "trkseg")) {
242                         data->state = INSIDE_PATH_SEGMENT;
243                         data->at_least_one_trkpt = FALSE;
244                 } else
245                         MACRO_SET_UNKNOWN();
246                 break;
247         case INSIDE_PATH_SEGMENT:
248                 if (!strcmp((gchar *) name, "trkpt")) {
249                         const xmlChar **curr_attr;
250                         gchar *error_check;
251                         gdouble lat = 0.f, lon = 0.f;
252                         gboolean has_lat, has_lon;
253                         has_lat = FALSE;
254                         has_lon = FALSE;
255                         for (curr_attr = attrs; *curr_attr != NULL;) {
256                                 const gchar *attr_name = *curr_attr++;
257                                 const gchar *attr_val = *curr_attr++;
258                                 if (!strcmp(attr_name, "lat")) {
259                                         lat = g_ascii_strtod(attr_val, &error_check);
260                                         if (error_check != attr_val)
261                                                 has_lat = TRUE;
262                                 } else if (!strcmp(attr_name, "lon")) {
263                                         lon = g_ascii_strtod(attr_val, &error_check);
264                                         if (error_check != attr_val)
265                                                 has_lon = TRUE;
266                                 }
267                         }
268                         if (has_lat && has_lon) {
269                                 MACRO_PATH_INCREMENT_TAIL(data->path);
270                                 latlon2unit(lat, lon, data->path.tail->unitx, data->path.tail->unity);
271                                 data->path.tail->time = 0;
272                                 data->path.tail->altitude = NAN;
273                                 data->state = INSIDE_PATH_POINT;
274                         } else
275                                 data->state = ERROR;
276                 } else
277                         MACRO_SET_UNKNOWN();
278                 break;
279         case INSIDE_PATH_POINT:
280                 if (!strcmp((gchar *) name, "time"))
281                         data->state = INSIDE_PATH_POINT_TIME;
282                 else if (!strcmp((gchar *) name, "ele"))
283                         data->state = INSIDE_PATH_POINT_ELE;
284                 else if (!strcmp((gchar *) name, "desc"))
285                         data->state = INSIDE_PATH_POINT_DESC;
286
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
306         switch (data->state) {
307         case ERROR:
308                 break;
309         case START:
310                 data->state = ERROR;
311                 break;
312         case INSIDE_GPX:
313                 if (!strcmp((gchar *) name, "gpx"))
314                         data->state = FINISH;
315                 else
316                         data->state = ERROR;
317                 break;
318         case INSIDE_PATH:
319                 if (!strcmp((gchar *) name, "trk"))
320                         data->state = INSIDE_GPX;
321                 else
322                         data->state = ERROR;
323                 break;
324         case INSIDE_PATH_SEGMENT:
325                 if (!strcmp((gchar *) name, "trkseg")) {
326                         if (data->at_least_one_trkpt) {
327                                 MACRO_PATH_INCREMENT_TAIL(data->path);
328                                 *data->path.tail = _point_null;
329                         }
330                         data->state = INSIDE_PATH;
331                 } else
332                         data->state = ERROR;
333                 break;
334         case INSIDE_PATH_POINT:
335                 if (!strcmp((gchar *) name, "trkpt")) {
336                         data->state = INSIDE_PATH_SEGMENT;
337                         data->at_least_one_trkpt = TRUE;
338                 } else
339                         data->state = ERROR;
340                 break;
341         case INSIDE_PATH_POINT_ELE:
342                 if (!strcmp((gchar *) name, "ele")) {
343                         gchar *error_check;
344                         data->path.tail->altitude = g_ascii_strtod(data->chars->str, &error_check);
345                         if (error_check == data->chars->str)
346                                 data->path.tail->altitude = NAN;
347                         data->state = INSIDE_PATH_POINT;
348                         g_string_free(data->chars, TRUE);
349                         data->chars = g_string_new("");
350                 } else
351                         data->state = ERROR;
352                 break;
353         case INSIDE_PATH_POINT_TIME:
354                 if (!strcmp((gchar *) name, "time")) {
355                         struct tm time;
356                         gchar *ptr;
357
358                         if (NULL == (ptr = strptime(data->chars->str,
359                                                     XML_DATE_FORMAT, &time)))
360                                 /* Failed to parse dateTime format. */
361                                 data->state = ERROR;
362                         else {
363                                 /* Parse was successful. Now we have to parse timezone.
364                                  * From here on, if there is an error, I just assume local
365                                  * timezone.  Yes, this is not proper XML, but I don't
366                                  * care. */
367                                 gchar *error_check;
368
369                                 /* First, set time in "local" time zone. */
370                                 data->path.tail->time = (mktime(&time));
371
372                                 /* Now, skip inconsequential characters */
373                                 while (*ptr && *ptr != 'Z' && *ptr != '-'
374                                        && *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
382                                                  * offset. */
383                                                 data->path.tail->time += time.tm_gmtoff;
384                                         else {
385                                                 /* Not Zulu (UTC). Must parse hours and minutes. */
386                                                 gint offhours =
387                                                     strtol(ptr, &error_check,
388                                                            10);
389                                                 if (error_check != ptr
390                                                     && *(ptr =
391                                                          error_check) == ':') {
392                                                         /* Parse of hours worked. Check minutes. */
393                                                         gint offmins =
394                                                             strtol(ptr + 1,
395                                                                    &error_check,
396                                                                    10);
397                                                         if (error_check !=
398                                                             (ptr + 1)) {
399                                                                 /* Parse of minutes worked. Calculate. */
400                                                                 data->path.tail->time +=
401                                                                     (time.tm_gmtoff -
402                                                                      (offhours * 60 * 60 + offmins * 60));
403                                                         }
404                                                 }
405                                         }
406                                 }
407                                 /* Successfully parsed dateTime. */
408                                 data->state = INSIDE_PATH_POINT;
409                         }
410
411                         g_string_free(data->chars, TRUE);
412                         data->chars = g_string_new("");
413                 } else
414                         data->state = ERROR;
415                 break;
416         case INSIDE_PATH_POINT_DESC:
417                 /* only parse description for routes */
418                 if (!strcmp((gchar *) name, "desc")) {
419                         MACRO_PATH_INCREMENT_WTAIL(data->path);
420                         data->path.wtail->point = data->path.tail;
421                         data->path.wtail->desc
422                             = g_string_free(data->chars, FALSE);
423                         data->chars = g_string_new("");
424                         data->state = INSIDE_PATH_POINT;
425                 } else
426                         data->state = ERROR;
427                 break;
428         case UNKNOWN:
429                 if (!--data->unknown_depth)
430                         data->state = data->prev_state;
431                 else
432                         data->state = ERROR;
433                 break;
434         default:
435                 ;
436         }
437
438 }
439
440 /**
441  * Handle char data in the parsing of a GPX file.
442  */
443 static void 
444 gpx_chars(SaxData * data, const xmlChar * ch, int len)
445 {
446         guint i;
447
448         switch (data->state) {
449         case ERROR:
450         case UNKNOWN:
451                 break;
452         case INSIDE_PATH_POINT_ELE:
453         case INSIDE_PATH_POINT_TIME:
454         case INSIDE_PATH_POINT_DESC:
455                 for (i = 0; i < len; i++)
456                         data->chars = g_string_append_c(data->chars, ch[i]);
457                 vprintf("%s\n", data->chars->str);
458                 break;
459         default:
460                 break;
461         }
462
463 }
464
465 /**
466  * Handle an entity in the parsing of a GPX file.  We don't do anything
467  * special here.
468  */
469 static xmlEntityPtr 
470 gpx_get_entity(SaxData * data, const xmlChar * name)
471 {
472         return xmlGetPredefinedEntity(name);
473 }
474
475 /**
476  * Handle an error in the parsing of a GPX file.
477  */
478 static void 
479 gpx_error(SaxData * data, const gchar * msg, ...)
480 {
481         data->state = ERROR;
482 }
483
484 gboolean
485 parse_gpx(Path * to_replace, gchar * buffer, gint size, gint policy_old)
486 {
487         SaxData data;
488         xmlSAXHandler sax_handler;
489
490         MACRO_PATH_INIT(data.path);
491         data.state = START;
492         data.chars = g_string_new("");
493
494         memset(&sax_handler, 0, sizeof(sax_handler));
495         sax_handler.characters = (charactersSAXFunc) gpx_chars;
496         sax_handler.startElement = (startElementSAXFunc) gpx_start_element;
497         sax_handler.endElement = (endElementSAXFunc) gpx_end_element;
498         sax_handler.entityDecl = (entityDeclSAXFunc) gpx_get_entity;
499         sax_handler.warning = (warningSAXFunc) gpx_error;
500         sax_handler.error = (errorSAXFunc) gpx_error;
501         sax_handler.fatalError = (fatalErrorSAXFunc) gpx_error;
502
503         xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
504         g_string_free(data.chars, TRUE);
505
506         if (data.state != FINISH) {
507                 return FALSE;
508         }
509
510         if (policy_old && to_replace->head != to_replace->tail) {
511                 Point *src_first;
512                 Path *src, *dest;
513
514                 if (policy_old > 0) {
515                         /* Append to current path. Make sure last path point is zero. */
516                         if (to_replace->tail->unity != 0) {
517                                 MACRO_PATH_INCREMENT_TAIL((*to_replace));
518                                 *to_replace->tail = _point_null;
519                         }
520                         src = &data.path;
521                         dest = to_replace;
522                 } else {
523                         /* Prepend to current route. */
524                         src = to_replace;
525                         dest = &data.path;
526                 }
527
528                 /* Find src_first non-zero point. */
529                 for (src_first = src->head - 1; src_first++ != src->tail;)
530                         if (src_first->unity)
531                                 break;
532
533                 /* Append route points from src to dest. */
534                 if (src->tail >= src_first) {
535                         WayPoint *curr;
536                         guint num_dest_points = dest->tail - dest->head + 1;
537                         guint num_src_points = src->tail - src_first + 1;
538
539                         /* Adjust dest->tail to be able to fit src route data
540                          * plus room for more route data. */
541                         path_resize(dest, num_dest_points + num_src_points);
542
543                         memcpy(dest->tail + 1, src_first,
544                                num_src_points * sizeof(Point));
545
546                         dest->tail += num_src_points;
547
548                         /* Append waypoints from src to dest->. */
549                         path_wresize(dest, (dest->wtail - dest->whead)
550                                      + (src->wtail - src->whead) + 2);
551                         for (curr = src->whead - 1; curr++ != src->wtail;) {
552                                 (++(dest->wtail))->point =
553                                     dest->head + num_dest_points +
554                                     (curr->point - src_first);
555                                 dest->wtail->desc = curr->desc;
556                         }
557
558                 }
559
560                 /* Kill old route - don't use MACRO_PATH_FREE(), because that
561                  * would free the string desc's that we just moved to data.route. */
562                 g_free(src->head);
563                 g_free(src->whead);
564                 if (policy_old < 0)
565                         (*to_replace) = *dest;
566         } else {
567                 MACRO_PATH_FREE((*to_replace));
568                 /* Overwrite with data.route. */
569                 (*to_replace) = data.path;
570                 path_resize(to_replace,
571                             to_replace->tail - to_replace->head + 1);
572                 path_wresize(to_replace,
573                              to_replace->wtail - to_replace->whead + 1);
574         }
575
576         return TRUE;
577 }