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