Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
gpx.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Henrik Andersson.
4 Copyright (C) 2012 Jean-Sébastien Pédron.
5 Copyright (C) 2012, 2014, 2016 Tobias Ellinghaus.
6 Copyright (C) 2013-2016 Roman Lebedev.
7 Copyright (C) 2013 Simon Spannagel.
8 Copyright (C) 2015 Edouard Gomez.
9 Copyright (C) 2017 Stefan Schöfegger.
10 Copyright (C) 2019-2021 Pascal Obry.
11 Copyright (C) 2021 Paolo Benvenuto.
12 Copyright (C) 2021 Philippe Weyland.
13 Copyright (C) 2021 Ralf Brown.
14 Copyright (C) 2022 Martin Bařinka.
15 Copyright (C) 2022 Victor Forsiuk.
16
17 darktable is free software: you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 darktable is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with darktable. If not, see <http://www.gnu.org/licenses/>.
29*/
30#include "metadata/gpx.h"
31#include "metadata/geo.h"
32#include "system/macros.h"
33#include "system/mem_alloc.h"
34#include "common/glib_utils.h"
35#include <glib.h>
36#include <inttypes.h>
37
38/* GPX XML parser */
47
48typedef struct dt_gpx_t
49{
50 /* the list of track records parsed */
51 GList *trkpts;
52 GList *trksegs;
53
54 /* currently parsed track point */
58 gboolean parsing_trk;
59 uint32_t segid;
60 char *seg_name;
62
63static void _gpx_parser_start_element(GMarkupParseContext *ctx, const gchar *element_name,
64 const gchar **attribute_names, const gchar **attribute_values,
65 gpointer ueer_data, GError **error);
66static void _gpx_parser_end_element(GMarkupParseContext *context, const gchar *element_name,
67 gpointer user_data, GError **error);
68static void _gpx_parser_text(GMarkupParseContext *context, const gchar *text, gsize text_len,
69 gpointer user_data, GError **error);
70
73
74
75static gint _sort_track(gconstpointer a, gconstpointer b)
76{
77 const dt_gpx_track_point_t *pa = (const dt_gpx_track_point_t *)a;
78 const dt_gpx_track_point_t *pb = (const dt_gpx_track_point_t *)b;
79 return g_date_time_compare(pa->time, pb->time);
80}
81
82static gint _sort_segment(gconstpointer a, gconstpointer b)
83{
86 return g_date_time_compare(pa->start_dt, pb->start_dt);
87}
88
89dt_gpx_t *dt_gpx_new(const gchar *filename)
90{
91 GError *err = NULL;
92 gint bom_offset = 0;
93 GMarkupParseContext *ctx = NULL;
94 dt_gpx_t *gpx = NULL;
95
96 /* map gpx file to parse into memory */
97 GMappedFile *gpxmf = g_mapped_file_new(filename, FALSE, &err);
98 if(err) goto error;
99
100 gchar *gpxmf_content = g_mapped_file_get_contents(gpxmf);
101 const gint gpxmf_size = g_mapped_file_get_length(gpxmf);
102 if(IS_NULL_PTR(gpxmf_content) || gpxmf_size < 10) goto error;
103
104 /* allocate new dt_gpx_t context */
105 gpx = g_malloc0(sizeof(dt_gpx_t));
106
107 /* skip UTF-8 BOM */
108 if(gpxmf_content[0] == '\xef' && gpxmf_content[1] == '\xbb' && gpxmf_content[2] == '\xbf')
109 bom_offset = 3;
110
111 /* initialize the parser and start parse gpx xml data */
112 ctx = g_markup_parse_context_new(&_gpx_parser, 0, gpx, NULL);
113 g_markup_parse_context_parse(ctx, gpxmf_content + bom_offset, gpxmf_size - bom_offset, &err);
114 if(err) goto error;
115
116 /* cleanup and return gpx context */
117 g_markup_parse_context_free(ctx);
118 g_mapped_file_unref(gpxmf);
119
120 gpx->trkpts = g_list_sort(gpx->trkpts, _sort_track);
121 gpx->trksegs = g_list_sort(gpx->trksegs, _sort_segment);
122
123 return gpx;
124
125error:
126 if(err)
127 {
128 fprintf(stderr, "dt_gpx_new: %s\n", err->message);
129 g_error_free(err);
130 }
131
132 if(ctx) g_markup_parse_context_free(ctx);
133
134 dt_free(gpx);
135
136 if(gpxmf) g_mapped_file_unref(gpxmf);
137
138 return NULL;
139}
140
142{
143 dt_free(trkseg->name);
144 dt_free(trkseg);
145}
146
148{
149 g_date_time_unref(trkpt->time);
150 dt_free(trkpt);
151}
152
153void dt_gpx_destroy(struct dt_gpx_t *gpx)
154{
155 g_assert(!IS_NULL_PTR(gpx));
156
157 if(gpx->trkpts)
158 {
159 g_list_free_full(gpx->trkpts, (GDestroyNotify)_track_pts_free);
160 gpx->trkpts = NULL;
161 }
162 if(gpx->trksegs)
163 {
164 g_list_free_full(gpx->trksegs, (GDestroyNotify)_track_seg_free);
165 gpx->trksegs = NULL;
166 }
167
168 dt_free(gpx);
169}
170
171gboolean dt_gpx_get_location(struct dt_gpx_t *gpx, GDateTime *timestamp, dt_image_geoloc_t *geoloc)
172{
173 g_assert(!IS_NULL_PTR(gpx));
174
175 /* verify that we got at least 2 trackpoints */
176 if(g_list_shorter_than(gpx->trkpts,2)) return FALSE;
177
178 for(GList *item = gpx->trkpts; item; item = g_list_next(item))
179 {
180 dt_gpx_track_point_t *tp = (dt_gpx_track_point_t *)item->data;
181
182 /* if timestamp is out of time range return false but fill
183 closest location value start or end point */
184 const gint cmp = g_date_time_compare(timestamp, tp->time);
185 if((IS_NULL_PTR(item->next) && cmp >= 0) || (cmp <= 0))
186 {
187 geoloc->longitude = tp->longitude;
188 geoloc->latitude = tp->latitude;
189 geoloc->elevation = tp->elevation;
190 return FALSE;
191 }
192
193 dt_gpx_track_point_t *tp_next = (dt_gpx_track_point_t *)item->next->data;
194 /* check if timestamp is within current and next trackpoint */
195 const gint cmp_n = g_date_time_compare(timestamp, tp_next->time);
196 if(item->next && cmp_n <= 0)
197 {
198 GTimeSpan seg_diff = g_date_time_difference(tp_next->time, tp->time);
199 GTimeSpan diff = g_date_time_difference(timestamp, tp->time);
200 if(seg_diff == 0 || diff == 0)
201 {
202 geoloc->longitude = tp->longitude;
203 geoloc->latitude = tp->latitude;
204 geoloc->elevation = tp->elevation;
205 }
206 else
207 {
208 /* get the point by interpolation according to timestamp
209
210 We assume that the maximum difference in longitude is less or equal 180º:
211 since the bigger use case is that of an airplane, never an airplane flies more than 180º in longitude */
212
213 const double lat1 = tp->latitude;
214 const double lon1 = tp->longitude;
215 const double lat2 = tp_next->latitude;
216 const double lon2 = tp_next->longitude;
217
218 double lat, lon;
219
220 const double f = (double)diff / (double)seg_diff; /* the fraction of the distance */
221
222 if(fabs(lat2 - lat1) < DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC
223 && fabs(lon2 - lon1) < DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC)
224 {
225 /* short distance (< 10 km), no need for geodesic interpolation */
226 lon = lon1 + (lon2 - lon1) * f;
227 lat = lat1 + (lat2 - lat1) * f;
228 }
229 else
230 {
231 /* interpolation on the earth surface
232 formulas from http://www.movable-type.co.uk/scripts/latlong.html
233
234 the formulas are correct even if the two point are across the day line, e.g [(0, -179), (0,179)]
235 TO DO: in this case the line which is drawn is incorrect, but this should be a osm_gps issue
236 */
237
238 /* first, calculate the distance on the earth surface */
239 double d, delta;
240 dt_gpx_geodesic_distance(lat1, lon1,
241 lat2, lon2,
242 &d, &delta);
243 /* d is the distance on the surface in metres,
244 delta is the angle defined by the two points*/
245
246 /* then, calculate the intermediate point */
248 lat2, lon2,
249 delta,
250 TRUE,
251 f,
252 &lat, &lon);
253 }
254
255 geoloc->latitude = lat;
256 geoloc->longitude = lon;
257
258 /* make a simple linear interpolation on elevation */
259 if(isnan(tp_next->elevation) || isnan(tp->elevation))
260 geoloc->elevation = NAN;
261 else
262 geoloc->elevation = tp->elevation + (tp_next->elevation - tp->elevation) * f;
263 }
264 return TRUE;
265 }
266 }
267
268 /* should not reach this point */
269 return FALSE;
270}
271
272/*
273 * GPX XML parser code
274 */
275void _gpx_parser_start_element(GMarkupParseContext *ctx, const gchar *element_name,
276 const gchar **attribute_names, const gchar **attribute_values,
277 gpointer user_data, GError **error)
278{
279 dt_gpx_t *gpx = (dt_gpx_t *)user_data;
280
281 if(gpx->parsing_trk == FALSE)
282 {
283 // we only parse tracks and its points, nothing else
284 if(strcmp(element_name, "trk") == 0)
285 {
286 gpx->parsing_trk = TRUE;
287 }
288 goto end;
289 }
290
291 /* from here on, parse wpType data from track points */
292 if(strcmp(element_name, "trkpt") == 0)
293 {
294 if(gpx->current_track_point)
295 {
296 fprintf(stderr, "broken GPX file, new trkpt element before the previous ended.\n");
298 }
299
300 const gchar **attribute_name = attribute_names;
301 const gchar **attribute_value = attribute_values;
302
304
305 if(*attribute_name)
306 {
307 gpx->current_track_point = g_malloc0(sizeof(dt_gpx_track_point_t));
308 gpx->current_track_point->segid = gpx->segid;
309
310 /* initialize with NAN for validation check */
311 gpx->current_track_point->longitude = NAN;
312 gpx->current_track_point->latitude = NAN;
313 gpx->current_track_point->elevation = NAN;
314
315 /* go thru the attributes to find and get values of lon / lat*/
316 while(*attribute_name)
317 {
318 if(strcmp(*attribute_name, "lon") == 0)
319 gpx->current_track_point->longitude = g_ascii_strtod(*attribute_value, NULL);
320 else if(strcmp(*attribute_name, "lat") == 0)
321 gpx->current_track_point->latitude = g_ascii_strtod(*attribute_value, NULL);
322
323 attribute_name++;
324 attribute_value++;
325 }
326
327 /* validate that we actually got lon / lat attribute values */
328 if(isnan(gpx->current_track_point->longitude) || isnan(gpx->current_track_point->latitude))
329 {
330 fprintf(stderr, "broken GPX file, failed to get lon/lat attribute values for trkpt\n");
332 }
333 }
334 else
335 fprintf(stderr, "broken GPX file, trkpt element doesn't have lon/lat attributes\n");
336
338 }
339 else if(strcmp(element_name, "time") == 0)
340 {
341 if(IS_NULL_PTR(gpx->current_track_point)) goto element_error;
342
344 }
345 else if(strcmp(element_name, "ele") == 0)
346 {
347 if(IS_NULL_PTR(gpx->current_track_point)) goto element_error;
348
350 }
351 else if(strcmp(element_name, "name") == 0)
352 {
354 }
355 else if(strcmp(element_name, "trkseg") == 0)
356 {
357 dt_gpx_track_segment_t *ts = g_malloc0(sizeof(dt_gpx_track_segment_t));
358 ts->name = gpx->seg_name;
359 ts->id = gpx->segid;
360 gpx->seg_name = NULL;
361 gpx->trksegs = g_list_prepend(gpx->trksegs, ts);
362 }
363
364end:
365
366 return;
367
368element_error:
369 fprintf(stderr, "broken GPX file, element '%s' found outside of trkpt.\n", element_name);
370}
371
372void _gpx_parser_end_element(GMarkupParseContext *context, const gchar *element_name, gpointer user_data,
373 GError **error)
374{
375 dt_gpx_t *gpx = (dt_gpx_t *)user_data;
376
377 /* closing trackpoint lets take care of data parsed */
378 if(gpx->parsing_trk == TRUE)
379 {
380 if(strcmp(element_name, "trk") == 0)
381 {
382 gpx->parsing_trk = FALSE;
383 }
384 else if(strcmp(element_name, "trkpt") == 0)
385 {
386 if(!gpx->invalid_track_point) {
387 gpx->trkpts = g_list_prepend(gpx->trkpts, gpx->current_track_point);
388 gpx->current_track_point = NULL;
389 } else {
391 }
392
393 }
394 else if(strcmp(element_name, "trkseg") == 0)
395 {
396 gpx->segid++;
397 }
398
399 /* clear current parser element */
401 }
402}
403
404void _gpx_parser_text(GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data,
405 GError **error)
406{
407 dt_gpx_t *gpx = (dt_gpx_t *)user_data;
408
410 {
411 if(gpx->seg_name)
412 {
413 dt_free(gpx->seg_name);
414 }
415 gpx->seg_name = g_strdup(text);
416 }
417
418 if(IS_NULL_PTR(gpx->current_track_point)) return;
419
421 {
422 gpx->current_track_point->time = g_date_time_new_from_iso8601(text, NULL);
424 {
426 fprintf(stderr, "broken GPX file, failed to pars is8601 time '%s' for trackpoint\n", text);
427 }
429 if(ts)
430 {
431 ts->nb_trkpt++;
432 if(IS_NULL_PTR(ts->start_dt))
433 {
435 ts->trkpt = gpx->current_track_point;
436 }
437 ts->end_dt = gpx->current_track_point->time;
438 }
439 }
441 gpx->current_track_point->elevation = g_ascii_strtod(text, NULL);
442}
443
444GList *dt_gpx_get_trkseg(struct dt_gpx_t *gpx)
445{
446 return gpx->trksegs;
447}
448
449GList *dt_gpx_get_trkpts(struct dt_gpx_t *gpx, const guint segid)
450{
451 GList *pts = NULL;
452 GList *ts = g_list_nth(gpx->trksegs, segid);
453 if(IS_NULL_PTR(ts)) return pts;
455 GList *tps = g_list_find(gpx->trkpts, tsd->trkpt);
456 if(IS_NULL_PTR(tps)) return pts;
457 for(GList *tp = tps; tp; tp = g_list_next(tp))
458 {
460 if(tpd->segid != segid) return pts;
462 p->lat = tpd->latitude;
463 p->lon = tpd->longitude;
464 pts = g_list_prepend(pts, p);
465 }
466 return pts;
467}
468
469/* --------------------------------------------------------------------------
470 * Geodesic interpolation functions
471 * ------------------------------------------------------------------------*/
472
473void dt_gpx_geodesic_distance(double lat1, double lon1,
474 double lat2, double lon2,
475 double *d, double *delta)
476{
477 const double lat_rad_1 = lat1 * M_PI / 180;
478 const double lat_rad_2 = lat2 * M_PI / 180;
479 const double lon_rad_1 = lon1 * M_PI / 180;
480 const double lon_rad_2 = lon2 * M_PI / 180;
481 const double delta_lat_rad = lat_rad_2 - lat_rad_1;
482 const double delta_lon_rad = lon_rad_2 - lon_rad_1;
483 const double sin_delta_lat_rad = sin(delta_lat_rad / 2);
484 const double sin_delta_lon_rad = sin(delta_lon_rad / 2);
485
486 const double a = sin_delta_lat_rad * sin_delta_lat_rad +
487 cos(lat_rad_1) * cos(lat_rad_2) *
488 sin_delta_lon_rad * sin_delta_lon_rad;
489 *delta = 2 * atan2(sqrt(a), sqrt(1 - a)); /* angular distance between the points in radians */
490
491 *d = *delta * EARTH_RADIUS; /* distance on the surface in metres */
492}
493
494void dt_gpx_geodesic_intermediate_point(const double lat1, const double lon1,
495 const double lat2, const double lon2,
496 const double delta,
497 const gboolean first_time,
498 double f,
499 double *lat, double *lon)
500{
501 static double lat_rad_1;
502 static double sin_lat_rad_1;
503 static double cos_lat_rad_1;
504 static double lat_rad_2;
505 static double sin_lat_rad_2;
506 static double cos_lat_rad_2;
507 static double lon_rad_1;
508 static double sin_lon_rad_1;
509 static double cos_lon_rad_1;
510 static double lon_rad_2;
511 static double sin_lon_rad_2;
512 static double cos_lon_rad_2;
513 static double sin_delta;
514
515 if(first_time)
516 {
517 lat_rad_1 = lat1 * M_PI / 180;
518 sin_lat_rad_1 = sin(lat_rad_1);
519 cos_lat_rad_1 = cos(lat_rad_1);
520 lat_rad_2 = lat2 * M_PI / 180;
521 sin_lat_rad_2 = sin(lat_rad_2);
522 cos_lat_rad_2 = cos(lat_rad_2);
523 lon_rad_1 = lon1 * M_PI / 180;
524 sin_lon_rad_1 = sin(lon_rad_1);
525 cos_lon_rad_1 = cos(lon_rad_1);
526 lon_rad_2 = lon2 * M_PI / 180;
527 sin_lon_rad_2 = sin(lon_rad_2);
528 cos_lon_rad_2 = cos(lon_rad_2);
529 sin_delta = sin(delta);
530 }
531
532 const double a = sin((1 - f) * delta) / sin_delta;
533 const double b = sin(f * delta) / sin_delta;
534 const double x = a * cos_lat_rad_1 * cos_lon_rad_1 + b * cos_lat_rad_2 * cos_lon_rad_2;
535 const double y = a * cos_lat_rad_1 * sin_lon_rad_1 + b * cos_lat_rad_2 * sin_lon_rad_2;
536 const double z = a * sin_lat_rad_1 + b * sin_lat_rad_2;
537 const double lat_rad = atan2(z, sqrt(x * x + y * y)); /* latitude of intermediate point in radians */
538 const double lon_rad = atan2(y, x); /* longitude of intermediate point in radians */
539
540 *lat = lat_rad / M_PI * 180;
541 *lon = lon_rad / M_PI * 180;
542}
543/* -------- end of Geodesic interpolation functions -----------------------*/
544
545
546// clang-format off
547// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
548// vim: shiftwidth=2 expandtab tabstop=2 cindent
549// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
550// clang-format on
551
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const float x
const float f
const float delta
static gboolean g_list_shorter_than(const GList *list, unsigned len)
Definition glib_utils.h:34
void dt_gpx_destroy(struct dt_gpx_t *gpx)
Definition gpx.c:153
GList * dt_gpx_get_trkpts(struct dt_gpx_t *gpx, const guint segid)
Definition gpx.c:449
static gint _sort_track(gconstpointer a, gconstpointer b)
Definition gpx.c:75
static void _gpx_parser_text(GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, GError **error)
Definition gpx.c:404
static GMarkupParser _gpx_parser
Definition gpx.c:72
void _track_seg_free(dt_gpx_track_segment_t *trkseg)
Definition gpx.c:141
static void _gpx_parser_end_element(GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error)
Definition gpx.c:372
gboolean dt_gpx_get_location(struct dt_gpx_t *gpx, GDateTime *timestamp, dt_image_geoloc_t *geoloc)
Definition gpx.c:171
_gpx_parser_element_t
Definition gpx.c:40
@ GPX_PARSER_ELEMENT_NAME
Definition gpx.c:45
@ GPX_PARSER_ELEMENT_ELE
Definition gpx.c:44
@ GPX_PARSER_ELEMENT_TRKPT
Definition gpx.c:42
@ GPX_PARSER_ELEMENT_NONE
Definition gpx.c:41
@ GPX_PARSER_ELEMENT_TIME
Definition gpx.c:43
static gint _sort_segment(gconstpointer a, gconstpointer b)
Definition gpx.c:82
dt_gpx_t * dt_gpx_new(const gchar *filename)
Definition gpx.c:89
void _track_pts_free(dt_gpx_track_point_t *trkpt)
Definition gpx.c:147
void dt_gpx_geodesic_intermediate_point(const double lat1, const double lon1, const double lat2, const double lon2, const double delta, const gboolean first_time, double f, double *lat, double *lon)
Definition gpx.c:494
GList * dt_gpx_get_trkseg(struct dt_gpx_t *gpx)
Definition gpx.c:444
static void _gpx_parser_start_element(GMarkupParseContext *ctx, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer ueer_data, GError **error)
Definition gpx.c:275
void dt_gpx_geodesic_distance(double lat1, double lon1, double lat2, double lon2, double *d, double *delta)
Definition gpx.c:473
#define EARTH_RADIUS
Definition gpx.h:32
#define DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC
Definition gpx.h:34
float lat
Definition location.c:3
float lon
Definition location.c:2
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define M_PI
Definition math.h:47
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
Definition gpx.c:49
gboolean invalid_track_point
Definition gpx.c:57
GList * trksegs
Definition gpx.c:52
gboolean parsing_trk
Definition gpx.c:58
GList * trkpts
Definition gpx.c:51
_gpx_parser_element_t current_parser_element
Definition gpx.c:56
uint32_t segid
Definition gpx.c:59
char * seg_name
Definition gpx.c:60
dt_gpx_track_point_t * current_track_point
Definition gpx.c:55
gdouble longitude
Definition gpx.h:42
GDateTime * time
Definition gpx.h:43
gdouble elevation
Definition gpx.h:42
uint32_t segid
Definition gpx.h:44
gdouble latitude
Definition gpx.h:42
dt_gpx_track_point_t * trkpt
Definition gpx.h:53
GDateTime * start_dt
Definition gpx.h:50
GDateTime * end_dt
Definition gpx.h:51
uint32_t nb_trkpt
Definition gpx.h:54
double latitude
Definition image.h:332
double elevation
Definition image.h:332
double longitude
Definition image.h:332
typedef double((*spd)(unsigned long int wavelength, double TempK))