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