Add routepoint <rtept> support to shut up users of RouteConverter
[gpx-import.git] / src / gpx.c
1 /* gpx-import/src/gpx.c
2  *
3  * GPX load and memory management
4  *
5  * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
6  *
7  * Written for the OpenStreetMap project.
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; version 2 of the License
12  * only.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  */
19
20 #include <sys/types.h>
21 #include <expat.h>
22 #include <stdlib.h>
23 #include <alloca.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <stdbool.h>
27 #include <string.h>
28 #include <strings.h>
29 #include <stdio.h>
30 #include <errno.h>
31 #include <stdarg.h>
32 #include <time.h>
33 #include <inttypes.h>
34
35 #include <archive.h>
36 #include <archive_entry.h>
37
38 #include <zlib.h>
39 #include <bzlib.h>
40
41 #include "gpx.h"
42
43 #define GPX_BUFLEN 4096
44 #define ERR_BUFFER_SIZE 1024
45
46 #define PARSE_ERROR(ctx,F...)                           \
47   do {                                                  \
48     if (ctx->err != NULL && *(ctx->err) == NULL)        \
49       gpx_record_error(ctx, F);                         \
50   } while (0)
51
52 void
53 gpx_free(GPX *gpx)
54 {
55   GPXTrackPoint *pt = gpx->points, *ptnext;
56   while (pt != NULL) {
57     ptnext = pt->next;
58     free(pt->timestamp);
59     free(pt);
60     pt = ptnext;
61   }
62   free(gpx);
63 }
64
65 typedef enum {
66   UNKNOWN,
67   TRACKPOINT,
68   ELEVATION,
69   TIMESTAMP,
70   WAYPOINT,
71   ROUTEPOINT,
72 } GPXParseState;
73
74 static const char *
75 gpx_state_name(GPXParseState p)
76 {
77   switch (p) {
78   case UNKNOWN:
79     return "UNKNOWN";
80   case TRACKPOINT:
81     return "TRACKPOINT";
82   case ELEVATION:
83     return "ELEVATION";
84   case TIMESTAMP:
85     return "TIMESTAMP";
86   case WAYPOINT:
87     return "WAYPOINT";
88   case ROUTEPOINT:
89     return "ROUTEPOINT";
90   }
91   return "INVALID";
92 }
93
94 typedef struct {
95   XML_Parser p;
96   GPX *gpx;
97   GPXTrackPoint *point;
98   GPXTrackPoint *lastpoint;
99   uint32_t curseg;
100   char *accumulator;
101   int accumulator_size;
102   GPXParseState state;
103   bool got_lat, got_long, got_ele, got_time;
104   char **err;
105   const char *subfile;
106 } GPXParseContext;
107
108 static void
109 gpx_record_error(GPXParseContext *ctx, char *fmt, ...)
110 {
111   va_list va;
112   int sz;
113   char msg[ERR_BUFFER_SIZE];
114   
115   va_start(va, fmt);
116   sz = vsnprintf(msg, ERR_BUFFER_SIZE, fmt, va);
117   va_end(va);
118   
119   *(ctx->err) = calloc(2, ERR_BUFFER_SIZE);
120   snprintf(*(ctx->err), 2 * ERR_BUFFER_SIZE, "%s%s%s%s\n  XML parser at line %ld column %ld",
121            (ctx->subfile == NULL) ? "" : "In file ",
122            (ctx->subfile == NULL) ? "" : ctx->subfile,
123            (ctx->subfile == NULL) ? "" : " inside your upload:\n  ",
124            msg,
125            ctx->p ? XML_GetCurrentLineNumber(ctx->p) : 0,
126            ctx->p ? XML_GetCurrentColumnNumber(ctx->p) : 0);
127 }
128
129 static GPXCoord
130 gpx_parse_coord(const XML_Char *str)
131 {
132   GPXCoord ret = 0;
133   bool is_neg = false;
134   bool is_afterdot = false;
135   int afterdot = 0;
136   
137   if (*str == '-') {
138     is_neg = true;
139     str++;
140   }
141   while (*str != '\0' && afterdot < 9) {
142     if (*str == '.')
143       is_afterdot = true;
144     if (*str != '.') {
145       ret *= 10;
146       ret += (*str - '0');
147       if (is_afterdot == true)
148         afterdot++;
149     }
150     str++;
151   }
152   
153   while (afterdot < 9) {
154     afterdot++;
155     ret *= 10;
156   }
157   
158   return (is_neg ? -ret : ret);
159 }
160
161 static void
162 gpx_clear_accumulator(GPXParseContext *ctx)
163 {
164   if (ctx->accumulator != NULL)
165     free(ctx->accumulator);
166   ctx->accumulator = NULL;
167   ctx->accumulator_size = 0;
168 }
169
170 #define REQUIRE_STATE(S)                                                \
171   if (ctx->state != (S)) {                                              \
172     PARSE_ERROR(ctx, "Expected state %s, got state %s", gpx_state_name(S), gpx_state_name(ctx->state)); \
173     XML_StopParser(ctx->p, 0);                                          \
174     return;                                                             \
175   }
176
177 static void
178 gpx_handle_start_element(void *_ctx, const XML_Char *name, const XML_Char **atts)
179 {
180   GPXParseContext *ctx = (GPXParseContext *)(_ctx);
181   if (strcmp(name, "trkpt") == 0) {
182     REQUIRE_STATE(UNKNOWN);
183     ctx->state = TRACKPOINT;
184     ctx->point = calloc(1, sizeof(GPXTrackPoint));
185     ctx->point->segment = ctx->curseg;
186     ctx->got_lat = ctx->got_long = ctx->got_ele = ctx->got_time = false;
187     while (*atts != NULL) {
188       if (strcmp(*atts, "lat") == 0) {
189         atts++;
190         ctx->point->latitude = gpx_parse_coord(*atts++);
191         ctx->got_lat = true;
192       } else if (strcmp(*atts, "lon") == 0) {
193         atts++;
194         ctx->point->longitude = gpx_parse_coord(*atts++);
195         ctx->got_long = true;
196       } else {
197         atts += 2; /* skip tag + value */
198       }
199     }
200   } else if (strcmp(name, "ele") == 0) {
201     if (ctx->state == WAYPOINT || ctx->state == ROUTEPOINT)
202       return;
203     REQUIRE_STATE(TRACKPOINT);
204     ctx->state = ELEVATION;
205     gpx_clear_accumulator(ctx);
206   } else if (strcmp(name, "time") == 0) {
207     if (ctx->state == TRACKPOINT) {
208       ctx->state = TIMESTAMP;
209       gpx_clear_accumulator(ctx);
210     }
211   } else if (strcmp(name, "wpt") == 0) {
212     REQUIRE_STATE(UNKNOWN);
213     ctx->state = WAYPOINT;
214   } else if (strcmp(name, "rtept") == 0) {
215     REQUIRE_STATE(UNKNOWN);
216     ctx->state = ROUTEPOINT;
217   }
218 }
219
220 #ifndef MIN
221 #define MIN(a,b) (((a) < (b)) ? (a) : (b))
222 #endif
223
224 #ifndef MAX
225 #define MAX(a,b) (((a) > (b)) ? (a) : (b))
226 #endif
227
228 static void
229 gpx_update_minmax(GPX *gpx, GPXTrackPoint *point)
230 {
231   gpx->minlatitude = MIN(gpx->minlatitude, point->latitude);
232   gpx->minlongitude = MIN(gpx->minlongitude, point->longitude);
233   gpx->maxlatitude = MAX(gpx->maxlatitude, point->latitude);
234   gpx->maxlongitude = MAX(gpx->maxlongitude, point->longitude);
235 }
236
237 static void
238 gpx_handle_end_element(void *_ctx, const XML_Char *name)
239 {
240   GPXParseContext *ctx = (GPXParseContext *)(_ctx);
241   if (strcmp(name, "trkpt") == 0) {
242     REQUIRE_STATE(TRACKPOINT);
243     /* Remove the || true if elevation is mandatory. */
244     if (ctx->got_time == false)
245       ctx->gpx->missed_time++;
246     
247     if ((ctx->got_lat == false) ||
248         (ctx->point->latitude < -90000000000) ||
249         (ctx->point->latitude > 90000000000))
250       ctx->gpx->bad_lat++;
251     
252     if ((ctx->got_long == false) ||
253         (ctx->point->longitude < -180000000000) ||
254         (ctx->point->longitude > 180000000000))
255       ctx->gpx->bad_long++;
256     
257     if ((ctx->got_lat && ctx->got_long && (ctx->got_ele || true) && ctx->got_time) &&
258         (ctx->point != NULL) && (ctx->point->longitude >= -180000000000) &&
259         (ctx->point->longitude <= 180000000000) &&
260         (ctx->point->latitude >= -90000000000) &&
261         (ctx->point->latitude <= 90000000000)) {
262       gpx_update_minmax(ctx->gpx, ctx->point);
263       
264       if (ctx->lastpoint == NULL) {
265         INFO("Attaching first point");
266         ctx->gpx->firstlatitude = ctx->point->latitude;
267         ctx->gpx->firstlongitude = ctx->point->longitude;
268         ctx->lastpoint = ctx->point;
269         ctx->gpx->points = ctx->point;
270         ctx->point = NULL;
271       } else {
272         ctx->lastpoint->next = ctx->point;
273         ctx->lastpoint = ctx->point;
274         ctx->point = NULL;
275       }
276       
277       ctx->gpx->goodpoints++;
278       
279     } else {
280       ctx->gpx->badpoints++;
281       if (ctx->point->timestamp != NULL)
282         free(ctx->point->timestamp);
283       free(ctx->point);
284       ctx->point = NULL;
285     }
286     ctx->state = UNKNOWN;
287   } else if (strcmp(name, "ele") == 0) {
288     if (ctx->state == WAYPOINT || ctx->state == ROUTEPOINT)
289       return;
290     REQUIRE_STATE(ELEVATION);
291     ctx->point->elevation = strtof(ctx->accumulator ? ctx->accumulator : "", NULL);
292     ctx->state = TRACKPOINT;
293     ctx->got_ele = true;
294   } else if (strcmp(name, "time") == 0) {
295     char *pnull = NULL;
296     struct tm ignored;
297     if (ctx->state == UNKNOWN || ctx->state == WAYPOINT || ctx->state == ROUTEPOINT)
298       return;
299     REQUIRE_STATE(TIMESTAMP);
300     pnull = strptime(ctx->accumulator ? ctx->accumulator : "",
301                      "%Y-%m-%dT%H:%M:%S",
302                      &ignored);
303     if (pnull != NULL && (*pnull == '\0' || *pnull == '.' || *pnull == 'Z')) {
304       ctx->point->timestamp = ctx->accumulator;
305       ctx->accumulator = NULL;
306       ctx->got_time = true;
307     }
308     ctx->state = TRACKPOINT;
309   } else if (strcmp(name, "trkseg") == 0) {
310     REQUIRE_STATE(UNKNOWN);
311     ctx->curseg++;
312   } else if (strcmp(name, "wpt") == 0) {
313     REQUIRE_STATE(WAYPOINT);
314     ctx->state = UNKNOWN;
315   } else if (strcmp(name, "rtept") == 0) {
316     REQUIRE_STATE(ROUTEPOINT);
317     ctx->state = UNKNOWN;
318   }
319 }
320
321 void
322 gpx_handle_string_data(void *_ctx, const XML_Char *str, int len)
323 {
324   GPXParseContext *ctx = (GPXParseContext *)(_ctx);
325   
326   if ((ctx->state != ELEVATION) &&
327       (ctx->state != TIMESTAMP))
328     /* We only accumulate during elevation or timestamp */
329     return;
330   
331   if (ctx->accumulator == NULL) {
332     ctx->accumulator = malloc(len + 1);
333     memcpy(ctx->accumulator, str, len);
334     ctx->accumulator_size = len + 1;
335   } else {
336     ctx->accumulator = realloc(ctx->accumulator, ctx->accumulator_size + len);
337     memcpy(ctx->accumulator + ctx->accumulator_size - 1, str, len);
338     ctx->accumulator_size += len;
339   }
340   
341   ctx->accumulator[ctx->accumulator_size - 1] = '\0';
342 }
343
344 static bool
345 gpx_create_parser(GPXParseContext *ctx)
346 {
347   ctx->p = XML_ParserCreate(NULL);
348   
349   if (ctx->p == NULL)
350     return false;
351   
352   XML_SetElementHandler(ctx->p, gpx_handle_start_element, gpx_handle_end_element);
353   XML_SetDefaultHandler(ctx->p, gpx_handle_string_data);
354   XML_SetUserData(ctx->p, ctx);
355   
356   return true;
357 }
358
359 static bool
360 gpx_parse_buffer(GPXParseContext *ctx, const char *buffer, ssize_t buflen)
361 {
362   if (XML_Parse(ctx->p, buffer, buflen, XML_FALSE) != XML_STATUS_OK) {
363     if (*(ctx->err) == NULL) {
364       PARSE_ERROR(ctx, "Generic XML parse error");
365     }
366     return false;
367   }
368   return true;
369 }
370
371 static void
372 gpx_free_parser(GPXParseContext *ctx)
373 {
374   if (ctx->p != NULL)
375     XML_ParserFree(ctx->p);
376   ctx->p = NULL;
377 }
378
379 static void
380 gpx_abort_context(GPXParseContext *ctx)
381 {
382   gpx_free(ctx->gpx);
383   if (ctx->point != NULL) {
384     if (ctx->point->timestamp)
385       free(ctx->point->timestamp);
386     free(ctx->point);
387   }
388   if (ctx->accumulator != NULL)
389     free(ctx->accumulator);
390   gpx_free_parser(ctx);
391 }
392
393 static bool
394 gpx_parse_plain_file(GPXParseContext *ctx, const char *gpxfile)
395 {
396   char *buffer = alloca(GPX_BUFLEN);
397   ssize_t bufread;
398   int fd = open(gpxfile, O_RDONLY);
399   
400   if (fd == -1) {
401     PARSE_ERROR(ctx, "Error opening file %s: %s", gpxfile, strerror(errno));
402     return false;
403   }
404   
405   if (gpx_create_parser(ctx) == false) {
406     PARSE_ERROR(ctx, "Unable to create XML parser");
407     close(fd);
408     return false;
409   }
410   
411   while ((bufread = read(fd, buffer, GPX_BUFLEN)) > 0) {
412     if (gpx_parse_buffer(ctx, buffer, bufread) == false) {
413       close(fd);
414       return false;
415     }
416   }
417   
418   close(fd);
419   
420   gpx_free_parser(ctx);
421   
422   return true;
423 }
424
425 static bool
426 gpx_parse_archive(GPXParseContext *ctx, const char *gpxfile)
427 {
428   struct archive *arch;
429   struct archive_entry *ent;
430   bool okay = false;
431   int64_t sublen;
432   char *buffer = alloca(GPX_BUFLEN);
433   int ret;
434   
435   arch = archive_read_new();
436   if (arch == NULL) {
437     return false;
438   }
439   
440   archive_read_support_compression_gzip(arch);
441   archive_read_support_compression_bzip2(arch);
442   archive_read_support_format_all(arch);
443   
444   if (archive_read_open_filename(arch, gpxfile, 1) < ARCHIVE_OK) {
445     goto out;
446   }
447   
448   while ((ret = archive_read_next_header(arch, &ent)) == ARCHIVE_OK) {
449     ctx->subfile = archive_entry_pathname(ent);
450     sublen = archive_entry_size(ent);
451     if (sublen > 0) {
452       INFO("Considering sub-entry %s in job", ctx->subfile);
453       /* There's data, let's try and parse it */
454       gpx_create_parser(ctx);
455       while (sublen > 0) {
456         if (archive_read_data(arch, buffer, MIN(GPX_BUFLEN, sublen)) < ARCHIVE_OK) {
457           PARSE_ERROR(ctx, "Unable to read data from archive.");
458           goto out;
459         }
460         if (gpx_parse_buffer(ctx, buffer, MIN(GPX_BUFLEN, sublen)) == false) {
461           goto out;
462         }
463         sublen -= MIN(GPX_BUFLEN, sublen);
464       }
465       gpx_free_parser(ctx);
466     }
467     ctx->subfile = NULL;
468   }
469   
470   
471   if ((ret == ARCHIVE_EOF) && ctx->gpx->goodpoints > 0)
472     okay = true;
473   
474   out:
475   archive_read_close(arch);
476   archive_read_finish(arch);
477   return okay;
478 }
479
480 static char gzip_magic[] = { 0x1f, 0x8b, 0x08 };
481 static char bzip2_magic[] = { 0x42, 0x5a, 0x68, 0x39, 0x31 };
482
483 static bool
484 gpx_try_gzip(GPXParseContext *ctx, const char *gpxfile)
485 {
486   int fd = open(gpxfile, O_RDONLY);
487   gzFile *f;
488   char *buffer = alloca(GPX_BUFLEN);
489   ssize_t bufread;
490   
491   if (fd == -1) {
492     PARSE_ERROR(ctx, "Unable to open %s (errno=%s)", gpxfile, strerror(errno));
493     return false;
494   }
495   
496   if (read(fd, buffer, sizeof(gzip_magic)) != sizeof(gzip_magic)) {
497     PARSE_ERROR(ctx, "Unable to read data from %s (errno=%s)", gpxfile, strerror(errno));
498     close(fd);
499     return false;
500   }
501   
502   if (memcmp(buffer, gzip_magic, sizeof(gzip_magic)) != 0) {
503     close(fd);
504     return false;
505   }
506   
507   lseek(fd, 0, SEEK_SET);
508   
509   f = gzdopen(fd, "rb"); /* Takes over the FD */
510   
511   INFO("Detected gzip encoded data");
512   
513   if (gpx_create_parser(ctx) == false) {
514     PARSE_ERROR(ctx, "Unable to create XML parser");
515     gzclose(f);
516     return false;
517   }
518   
519   while ((bufread = gzread(f, buffer, GPX_BUFLEN)) > 0) {
520     if (gpx_parse_buffer(ctx, buffer, bufread) == false) {
521       gzclose(f);
522       return false;
523     }
524   }
525   
526   gzclose(f);
527   
528   gpx_free_parser(ctx);
529   
530   return true;
531 }
532
533 static bool
534 gpx_try_bzip2(GPXParseContext *ctx, const char *gpxfile)
535 {
536   int fd = open(gpxfile, O_RDONLY);
537   BZFILE *f;
538   char *buffer = alloca(GPX_BUFLEN);
539   ssize_t bufread;
540   
541   if (fd == -1) {
542     PARSE_ERROR(ctx, "Unable to open %s (errno=%s)", gpxfile, strerror(errno));
543     return false;
544   }
545   
546   if (read(fd, buffer, sizeof(bzip2_magic)) != sizeof(bzip2_magic)) {
547     PARSE_ERROR(ctx, "Unable to read data from %s (errno=%s)", gpxfile, strerror(errno));
548     close(fd);
549     return false;
550   }
551   
552   if (memcmp(buffer, bzip2_magic, sizeof(bzip2_magic)) != 0) {
553     close(fd);
554     return false;
555   }
556   
557   close(fd);
558   
559   f = BZ2_bzopen(gpxfile, "rb");
560   
561   INFO("Detected bzip2 encoded data");
562   
563   if (gpx_create_parser(ctx) == false) {
564     PARSE_ERROR(ctx, "Unable to create XML parser");
565     BZ2_bzclose(f);
566     return false;
567   }
568   
569   while ((bufread = BZ2_bzread(f, buffer, GPX_BUFLEN)) > 0) {
570     if (gpx_parse_buffer(ctx, buffer, bufread) == false) {
571       BZ2_bzclose(f);
572       return false;
573     }
574   }
575   
576   BZ2_bzclose(f);
577   
578   gpx_free_parser(ctx);
579   
580   return true;
581 }
582
583 GPX*
584 gpx_parse_file(const char *gpxfile, char **err)
585 {
586   GPXParseContext ctx;
587   
588   ctx.err = err;
589   
590   ctx.gpx = calloc(1, sizeof(GPX));
591   ctx.gpx->minlatitude = 91000000000;
592   ctx.gpx->minlongitude = 181000000000;
593   ctx.gpx->maxlatitude = -91000000000;
594   ctx.gpx->maxlongitude = -181000000000;
595   ctx.point = NULL;
596   ctx.lastpoint = NULL;
597   ctx.curseg = 0;
598   ctx.accumulator = NULL;
599   ctx.state = UNKNOWN;
600   ctx.accumulator_size = 0;
601   ctx.subfile = NULL;
602   ctx.p = NULL;
603   
604   if (gpx_parse_archive(&ctx, gpxfile) == true) {
605     goto success;
606   } else {
607     if (*(ctx.err) != NULL) {
608       ERROR("Archive failure");
609       goto failure;
610     }
611   }
612   
613   if (gpx_try_gzip(&ctx, gpxfile) == true) {
614     goto success;
615   } else {
616     if (*(ctx.err) != NULL) {
617       ERROR("GZip failure");
618       goto failure;
619     }
620   }
621   
622   if (gpx_try_bzip2(&ctx, gpxfile) == true) {
623     goto success;
624   } else {
625     if (*(ctx.err) != NULL) {
626       ERROR("BZip2 failure");
627       goto failure;
628     }
629   }
630   
631   if (gpx_parse_plain_file(&ctx, gpxfile) == false) {
632     goto failure;
633   }
634   
635   if (ctx.gpx->goodpoints == 0) {
636     ERROR("Zero good points, %d bad (%d missed time, %d bad lat, %d bad long)",
637           ctx.gpx->badpoints,
638           ctx.gpx->missed_time,
639           ctx.gpx->bad_lat,
640           ctx.gpx->bad_long);
641     if (ctx.gpx->missed_time > ((ctx.gpx->badpoints * 3) >> 2)) {
642       *(ctx.err) = strdup("Found no good GPX points in the input data. At least 75% of the trackpoints lacked a <time> tag.");
643     } else {
644       *(ctx.err) = strdup("Found no good GPX points in the input data");
645     }
646     goto failure;
647   }
648   
649   success:
650   if (ctx.point)
651     free(ctx.point);
652   if (ctx.accumulator != NULL)
653     free(ctx.accumulator);
654   
655   return ctx.gpx;
656   
657   failure:
658   gpx_abort_context(&ctx);
659   return NULL;
660 }
661
662 void
663 gpx_print(GPX *gpx)
664 {
665   GPXTrackPoint *pt;
666   int pointnr;
667   
668   printf("minlat=%"PRId64" maxlat=%"PRId64" minlon=%"PRId64" maxlon=%"PRId64"\n",
669          gpx->minlatitude, gpx->maxlatitude,
670          gpx->minlongitude, gpx->maxlongitude);
671   
672   printf("goodpoints=%d badpoints=%d\n",
673          gpx->goodpoints,
674          gpx->badpoints);
675   return;
676   for (pt = gpx->points, pointnr = 1; pt != NULL; pt = pt->next, pointnr++)
677     printf("%4d: lat=%"PRId64" lon=%"PRId64" ele=%f time=%s seg=%d\n",
678            pointnr, pt->latitude, pt->longitude, pt->elevation,
679            pt->timestamp, pt->segment);
680   
681 }