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