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