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