1 /*
2 This file is part of darktable,
3 Copyright (C) 2011-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "common/gpx.h"
19 #include "common/geo.h"
20 #include "common/darktable.h"
21 #include <glib.h>
22 #include <inttypes.h>
23
24 /* GPX XML parser */
25 typedef enum _gpx_parser_element_t
26 {
27 GPX_PARSER_ELEMENT_NONE = 0,
28 GPX_PARSER_ELEMENT_TRKPT = 1 << 0,
29 GPX_PARSER_ELEMENT_TIME = 1 << 1,
30 GPX_PARSER_ELEMENT_ELE = 1 << 2,
31 GPX_PARSER_ELEMENT_NAME = 1 << 3
32 } _gpx_parser_element_t;
33
34 typedef struct dt_gpx_t
35 {
36 /* the list of track records parsed */
37 GList *trkpts;
38 GList *trksegs;
39
40 /* currently parsed track point */
41 dt_gpx_track_point_t *current_track_point;
42 _gpx_parser_element_t current_parser_element;
43 gboolean invalid_track_point;
44 gboolean parsing_trk;
45 uint32_t segid;
46 char *seg_name;
47 } dt_gpx_t;
48
49 static void _gpx_parser_start_element(GMarkupParseContext *ctx, const gchar *element_name,
50 const gchar **attribute_names, const gchar **attribute_values,
51 gpointer ueer_data, GError **error);
52 static void _gpx_parser_end_element(GMarkupParseContext *context, const gchar *element_name,
53 gpointer user_data, GError **error);
54 static void _gpx_parser_text(GMarkupParseContext *context, const gchar *text, gsize text_len,
55 gpointer user_data, GError **error);
56
57 static GMarkupParser _gpx_parser
58 = { _gpx_parser_start_element, _gpx_parser_end_element, _gpx_parser_text, NULL, NULL };
59
60
_sort_track(gconstpointer a,gconstpointer b)61 static gint _sort_track(gconstpointer a, gconstpointer b)
62 {
63 const dt_gpx_track_point_t *pa = (const dt_gpx_track_point_t *)a;
64 const dt_gpx_track_point_t *pb = (const dt_gpx_track_point_t *)b;
65 return g_date_time_compare(pa->time, pb->time);
66 }
67
_sort_segment(gconstpointer a,gconstpointer b)68 static gint _sort_segment(gconstpointer a, gconstpointer b)
69 {
70 const dt_gpx_track_segment_t *pa = (const dt_gpx_track_segment_t *)a;
71 const dt_gpx_track_segment_t *pb = (const dt_gpx_track_segment_t *)b;
72 return g_date_time_compare(pa->start_dt, pb->start_dt);
73 }
74
dt_gpx_new(const gchar * filename)75 dt_gpx_t *dt_gpx_new(const gchar *filename)
76 {
77 dt_gpx_t *gpx = NULL;
78 GMarkupParseContext *ctx = NULL;
79 GError *err = NULL;
80 GMappedFile *gpxmf = NULL;
81 gchar *gpxmf_content = NULL;
82 gint gpxmf_size = 0;
83 gint bom_offset = 0;
84
85
86 /* map gpx file to parse into memory */
87 gpxmf = g_mapped_file_new(filename, FALSE, &err);
88 if(err) goto error;
89
90 gpxmf_content = g_mapped_file_get_contents(gpxmf);
91 gpxmf_size = g_mapped_file_get_length(gpxmf);
92 if(!gpxmf_content || gpxmf_size < 10) goto error;
93
94 /* allocate new dt_gpx_t context */
95 gpx = g_malloc0(sizeof(dt_gpx_t));
96
97 /* skip UTF-8 BOM */
98 if(gpxmf_size > 3 && gpxmf_content[0] == '\xef' && gpxmf_content[1] == '\xbb' && gpxmf_content[2] == '\xbf')
99 bom_offset = 3;
100
101 /* initialize the parser and start parse gpx xml data */
102 ctx = g_markup_parse_context_new(&_gpx_parser, 0, gpx, NULL);
103 g_markup_parse_context_parse(ctx, gpxmf_content + bom_offset, gpxmf_size - bom_offset, &err);
104 if(err) goto error;
105
106
107 /* cleanup and return gpx context */
108 g_markup_parse_context_free(ctx);
109 g_mapped_file_unref(gpxmf);
110
111 gpx->trkpts = g_list_sort(gpx->trkpts, _sort_track);
112 gpx->trksegs = g_list_sort(gpx->trksegs, _sort_segment);
113
114 return gpx;
115
116 error:
117 if(err)
118 {
119 fprintf(stderr, "dt_gpx_new: %s\n", err->message);
120 g_error_free(err);
121 }
122
123 if(ctx) g_markup_parse_context_free(ctx);
124
125 g_free(gpx);
126
127 if(gpxmf) g_mapped_file_unref(gpxmf);
128
129 return NULL;
130 }
131
_track_seg_free(dt_gpx_track_segment_t * trkseg)132 void _track_seg_free(dt_gpx_track_segment_t *trkseg)
133 {
134 g_free(trkseg->name);
135 g_free(trkseg);
136 }
137
_track_pts_free(dt_gpx_track_point_t * trkpt)138 void _track_pts_free(dt_gpx_track_point_t *trkpt)
139 {
140 g_date_time_unref(trkpt->time);
141 g_free(trkpt);
142 }
143
dt_gpx_destroy(struct dt_gpx_t * gpx)144 void dt_gpx_destroy(struct dt_gpx_t *gpx)
145 {
146 g_assert(gpx != NULL);
147
148 if(gpx->trkpts) g_list_free_full(gpx->trkpts, (GDestroyNotify)_track_pts_free);
149 if(gpx->trksegs) g_list_free_full(gpx->trksegs, (GDestroyNotify)_track_seg_free);
150
151 g_free(gpx);
152 }
153
dt_gpx_get_location(struct dt_gpx_t * gpx,GDateTime * timestamp,dt_image_geoloc_t * geoloc)154 gboolean dt_gpx_get_location(struct dt_gpx_t *gpx, GDateTime *timestamp, dt_image_geoloc_t *geoloc)
155 {
156 g_assert(gpx != NULL);
157
158 /* verify that we got at least 2 trackpoints */
159 if(g_list_shorter_than(gpx->trkpts,2)) return FALSE;
160
161 for(GList *item = gpx->trkpts; item; item = g_list_next(item))
162 {
163 dt_gpx_track_point_t *tp = (dt_gpx_track_point_t *)item->data;
164
165 /* if timestamp is out of time range return false but fill
166 closest location value start or end point */
167 const gint cmp = g_date_time_compare(timestamp, tp->time);
168 if((!item->next && cmp >= 0) || (cmp <= 0))
169 {
170 geoloc->longitude = tp->longitude;
171 geoloc->latitude = tp->latitude;
172 geoloc->elevation = tp->elevation;
173 return FALSE;
174 }
175
176 /* check if timestamp is within current and next trackpoint */
177 const gint cmp_n = g_date_time_compare(timestamp, ((dt_gpx_track_point_t *)item->next->data)->time);
178 if((cmp >= 0) && (item->next && cmp_n <= 0))
179 {
180 geoloc->longitude = tp->longitude;
181 geoloc->latitude = tp->latitude;
182 geoloc->elevation = tp->elevation;
183 return TRUE;
184 }
185 }
186
187 /* should not reach this point */
188 return FALSE;
189 }
190
191 /*
192 * GPX XML parser code
193 */
_gpx_parser_start_element(GMarkupParseContext * ctx,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)194 void _gpx_parser_start_element(GMarkupParseContext *ctx, const gchar *element_name,
195 const gchar **attribute_names, const gchar **attribute_values,
196 gpointer user_data, GError **error)
197 {
198 dt_gpx_t *gpx = (dt_gpx_t *)user_data;
199
200 if(gpx->parsing_trk == FALSE)
201 {
202 // we only parse tracks and its points, nothing else
203 if(strcmp(element_name, "trk") == 0)
204 {
205 gpx->parsing_trk = TRUE;
206 }
207 goto end;
208 }
209
210 /* from here on, parse wpType data from track points */
211 if(strcmp(element_name, "trkpt") == 0)
212 {
213 if(gpx->current_track_point)
214 {
215 fprintf(stderr, "broken gpx file, new trkpt element before the previous ended.\n");
216 g_free(gpx->current_track_point);
217 }
218
219 const gchar **attribute_name = attribute_names;
220 const gchar **attribute_value = attribute_values;
221
222 gpx->invalid_track_point = FALSE;
223
224 if(*attribute_name)
225 {
226 gpx->current_track_point = g_malloc0(sizeof(dt_gpx_track_point_t));
227 gpx->current_track_point->segid = gpx->segid;
228
229 /* initialize with NAN for validation check */
230 gpx->current_track_point->longitude = NAN;
231 gpx->current_track_point->latitude = NAN;
232 gpx->current_track_point->elevation = NAN;
233
234 /* go thru the attributes to find and get values of lon / lat*/
235 while(*attribute_name)
236 {
237 if(strcmp(*attribute_name, "lon") == 0)
238 gpx->current_track_point->longitude = g_ascii_strtod(*attribute_value, NULL);
239 else if(strcmp(*attribute_name, "lat") == 0)
240 gpx->current_track_point->latitude = g_ascii_strtod(*attribute_value, NULL);
241
242 attribute_name++;
243 attribute_value++;
244 }
245
246 /* validate that we actually got lon / lat attribute values */
247 if(isnan(gpx->current_track_point->longitude) || isnan(gpx->current_track_point->latitude))
248 {
249 fprintf(stderr, "broken gpx file, failed to get lon/lat attribute values for trkpt\n");
250 gpx->invalid_track_point = TRUE;
251 }
252 }
253 else
254 fprintf(stderr, "broken gpx file, trkpt element doesn't have lon/lat attributes\n");
255
256 gpx->current_parser_element = GPX_PARSER_ELEMENT_TRKPT;
257 }
258 else if(strcmp(element_name, "time") == 0)
259 {
260 if(!gpx->current_track_point) goto element_error;
261
262 gpx->current_parser_element = GPX_PARSER_ELEMENT_TIME;
263 }
264 else if(strcmp(element_name, "ele") == 0)
265 {
266 if(!gpx->current_track_point) goto element_error;
267
268 gpx->current_parser_element = GPX_PARSER_ELEMENT_ELE;
269 }
270 else if(strcmp(element_name, "name") == 0)
271 {
272 gpx->current_parser_element = GPX_PARSER_ELEMENT_NAME;
273 }
274 else if(strcmp(element_name, "trkseg") == 0)
275 {
276 dt_gpx_track_segment_t *ts = g_malloc0(sizeof(dt_gpx_track_segment_t));
277 ts->name = gpx->seg_name;
278 ts->id = gpx->segid;
279 gpx->seg_name = NULL;
280 gpx->trksegs = g_list_prepend(gpx->trksegs, ts);
281 }
282
283 end:
284
285 return;
286
287 element_error:
288 fprintf(stderr, "broken gpx file, element '%s' found outside of trkpt.\n", element_name);
289 }
290
_gpx_parser_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)291 void _gpx_parser_end_element(GMarkupParseContext *context, const gchar *element_name, gpointer user_data,
292 GError **error)
293 {
294 dt_gpx_t *gpx = (dt_gpx_t *)user_data;
295
296 /* closing trackpoint lets take care of data parsed */
297 if(gpx->parsing_trk == TRUE)
298 {
299 if(strcmp(element_name, "trk") == 0)
300 {
301 gpx->parsing_trk = FALSE;
302 }
303 else if(strcmp(element_name, "trkpt") == 0)
304 {
305 if(!gpx->invalid_track_point)
306 gpx->trkpts = g_list_prepend(gpx->trkpts, gpx->current_track_point);
307 else
308 g_free(gpx->current_track_point);
309
310 gpx->current_track_point = NULL;
311 }
312 else if(strcmp(element_name, "trkseg") == 0)
313 {
314 gpx->segid++;
315 }
316
317 /* clear current parser element */
318 gpx->current_parser_element = GPX_PARSER_ELEMENT_NONE;
319 }
320 }
321
_gpx_parser_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)322 void _gpx_parser_text(GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data,
323 GError **error)
324 {
325 dt_gpx_t *gpx = (dt_gpx_t *)user_data;
326
327 if(gpx->current_parser_element == GPX_PARSER_ELEMENT_NAME)
328 {
329 if(gpx->seg_name) g_free(gpx->seg_name);
330 gpx->seg_name = g_strdup(text);
331 }
332
333 if(!gpx->current_track_point) return;
334
335 if(gpx->current_parser_element == GPX_PARSER_ELEMENT_TIME)
336 {
337 gpx->current_track_point->time = g_date_time_new_from_iso8601(text, NULL);
338 if(!gpx->current_track_point->time)
339 {
340 gpx->invalid_track_point = TRUE;
341 fprintf(stderr, "broken gpx file, failed to pars is8601 time '%s' for trackpoint\n", text);
342 }
343 dt_gpx_track_segment_t *ts = (dt_gpx_track_segment_t *)gpx->trksegs->data;
344 if(ts)
345 {
346 ts->nb_trkpt++;
347 if(!ts->start_dt)
348 {
349 ts->start_dt = gpx->current_track_point->time;
350 ts->trkpt = gpx->current_track_point;
351 }
352 ts->end_dt = gpx->current_track_point->time;
353 }
354 }
355 else if(gpx->current_parser_element == GPX_PARSER_ELEMENT_ELE)
356 gpx->current_track_point->elevation = g_ascii_strtod(text, NULL);
357 }
358
dt_gpx_get_trkseg(struct dt_gpx_t * gpx)359 GList *dt_gpx_get_trkseg(struct dt_gpx_t *gpx)
360 {
361 return gpx->trksegs;
362 }
363
dt_gpx_get_trkpts(struct dt_gpx_t * gpx,const guint segid)364 GList *dt_gpx_get_trkpts(struct dt_gpx_t *gpx, const guint segid)
365 {
366 GList *pts = NULL;
367 GList *ts = g_list_nth(gpx->trksegs, segid);
368 if(!ts) return pts;
369 dt_gpx_track_segment_t *tsd = (dt_gpx_track_segment_t *)ts->data;
370 GList *tps = g_list_find(gpx->trkpts, tsd->trkpt);
371 if(!tps) return pts;
372 for(GList *tp = tps; tp; tp = g_list_next(tp))
373 {
374 dt_gpx_track_point_t *tpd = (dt_gpx_track_point_t *)tp->data;
375 if(tpd->segid != segid) return pts;
376 dt_geo_map_display_point_t *p = g_malloc0(sizeof(dt_geo_map_display_point_t));
377 p->lat = tpd->latitude;
378 p->lon = tpd->longitude;
379 pts = g_list_prepend(pts, p);
380 }
381 return pts;
382 }
383
384 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
385 // vim: shiftwidth=2 expandtab tabstop=2 cindent
386 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
387