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