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