]> git.street.me.uk Git - andy/viking.git/blame - src/viktrack.c
Fix drawing of copied MBTiles map layers.
[andy/viking.git] / src / viktrack.c
CommitLineData
50a14534
EB
1/*
2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3 *
4 * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
0d2b891f 5 * Copyright (c) 2012, Rob Norris <rw_norris@hotmail.com>
50a14534
EB
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
8c00358d
GB
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
50a14534
EB
25
26#include <glib.h>
27#include <time.h>
bf35388d 28#include <stdlib.h>
8c00358d 29#ifdef HAVE_STRING_H
ddc47a46 30#include <string.h>
8c00358d
GB
31#endif
32#ifdef HAVE_MATH_H
bf35388d 33#include <math.h>
8c00358d
GB
34#endif
35
50a14534
EB
36#include "coords.h"
37#include "vikcoord.h"
38#include "viktrack.h"
39#include "globals.h"
ad0a8c2d 40#include "dems.h"
387ff7ac 41#include "settings.h"
50a14534
EB
42
43VikTrack *vik_track_new()
44{
8c4f1350 45 VikTrack *tr = g_malloc0 ( sizeof ( VikTrack ) );
50a14534
EB
46 tr->ref_count = 1;
47 return tr;
48}
49
387ff7ac
RN
50#define VIK_SETTINGS_TRACK_NAME_MODE "track_draw_name_mode"
51#define VIK_SETTINGS_TRACK_NUM_DIST_LABELS "track_number_dist_labels"
52
53/**
54 * vik_track_set_defaults:
55 *
56 * Set some default values for a track.
57 * ATM This uses the 'settings' method to get values,
58 * so there is no GUI way to control these yet...
59 */
60void vik_track_set_defaults(VikTrack *tr)
61{
62 gint tmp;
63 if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_NAME_MODE, &tmp ) )
64 tr->draw_name_mode = tmp;
65
66 if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_NUM_DIST_LABELS, &tmp ) )
67 tr->max_number_dist_labels = tmp;
68}
69
50a14534
EB
70void vik_track_set_comment_no_copy(VikTrack *tr, gchar *comment)
71{
72 if ( tr->comment )
73 g_free ( tr->comment );
74 tr->comment = comment;
75}
76
77
ce4bd1cf
RN
78void vik_track_set_name(VikTrack *tr, const gchar *name)
79{
80 if ( tr->name )
81 g_free ( tr->name );
82
27d267f4 83 tr->name = g_strdup(name);
ce4bd1cf
RN
84}
85
50a14534
EB
86void vik_track_set_comment(VikTrack *tr, const gchar *comment)
87{
88 if ( tr->comment )
89 g_free ( tr->comment );
90
91 if ( comment && comment[0] != '\0' )
92 tr->comment = g_strdup(comment);
93 else
94 tr->comment = NULL;
95}
96
6b2f262e
RN
97void vik_track_set_description(VikTrack *tr, const gchar *description)
98{
99 if ( tr->description )
100 g_free ( tr->description );
101
102 if ( description && description[0] != '\0' )
103 tr->description = g_strdup(description);
104 else
105 tr->description = NULL;
106}
107
50a14534
EB
108void vik_track_ref(VikTrack *tr)
109{
110 tr->ref_count++;
111}
112
21700912
QT
113void vik_track_set_property_dialog(VikTrack *tr, GtkWidget *dialog)
114{
115 /* Warning: does not check for existing dialog */
116 tr->property_dialog = dialog;
117}
118
119void vik_track_clear_property_dialog(VikTrack *tr)
120{
121 tr->property_dialog = NULL;
122}
123
50a14534
EB
124void vik_track_free(VikTrack *tr)
125{
126 if ( tr->ref_count-- > 1 )
127 return;
128
ce4bd1cf
RN
129 if ( tr->name )
130 g_free ( tr->name );
50a14534
EB
131 if ( tr->comment )
132 g_free ( tr->comment );
6b2f262e
RN
133 if ( tr->description )
134 g_free ( tr->description );
f629b00b 135 g_list_foreach ( tr->trackpoints, (GFunc) vik_trackpoint_free, NULL );
50a14534 136 g_list_free( tr->trackpoints );
21700912 137 if (tr->property_dialog)
63b31477
RN
138 if ( GTK_IS_WIDGET(tr->property_dialog) )
139 gtk_widget_destroy ( GTK_WIDGET(tr->property_dialog) );
50a14534
EB
140 g_free ( tr );
141}
142
03817fbf
RN
143/**
144 * vik_track_copy:
145 * @tr: The Track to copy
146 * @copy_points: Whether to copy the track points or not
147 *
148 * Normally for copying the track it's best to copy all the trackpoints
149 * However for some operations such as splitting tracks the trackpoints will be managed separately, so no need to copy them.
150 *
151 * Returns: the copied VikTrack
152 */
153VikTrack *vik_track_copy ( const VikTrack *tr, gboolean copy_points )
50a14534
EB
154{
155 VikTrack *new_tr = vik_track_new();
b45865b4 156 new_tr->name = g_strdup(tr->name);
50a14534 157 new_tr->visible = tr->visible;
0d2b891f 158 new_tr->is_route = tr->is_route;
387ff7ac
RN
159 new_tr->draw_name_mode = tr->draw_name_mode;
160 new_tr->max_number_dist_labels = tr->max_number_dist_labels;
b1453c16
RN
161 new_tr->has_color = tr->has_color;
162 new_tr->color = tr->color;
20981fd6 163 new_tr->bbox = tr->bbox;
50a14534 164 new_tr->trackpoints = NULL;
03817fbf 165 if ( copy_points )
50a14534 166 {
f629b00b 167 GList *tp_iter = tr->trackpoints;
03817fbf
RN
168 while ( tp_iter )
169 {
f629b00b
RN
170 VikTrackpoint *new_tp = vik_trackpoint_copy ( (VikTrackpoint*)(tp_iter->data) );
171 new_tr->trackpoints = g_list_prepend ( new_tr->trackpoints, new_tp );
03817fbf
RN
172 tp_iter = tp_iter->next;
173 }
f629b00b
RN
174 if ( new_tr->trackpoints )
175 new_tr->trackpoints = g_list_reverse ( new_tr->trackpoints );
50a14534 176 }
ce4bd1cf 177 vik_track_set_name(new_tr,tr->name);
50a14534 178 vik_track_set_comment(new_tr,tr->comment);
6b2f262e 179 vik_track_set_description(new_tr,tr->description);
50a14534
EB
180 return new_tr;
181}
182
183VikTrackpoint *vik_trackpoint_new()
184{
a2817d3c 185 VikTrackpoint *tp = g_malloc0(sizeof(VikTrackpoint));
a2817d3c
QT
186 tp->speed = NAN;
187 tp->course = NAN;
8541f2cf
T
188 tp->altitude = VIK_DEFAULT_ALTITUDE;
189 tp->hdop = VIK_DEFAULT_DOP;
190 tp->vdop = VIK_DEFAULT_DOP;
191 tp->pdop = VIK_DEFAULT_DOP;
a2817d3c 192 return tp;
50a14534
EB
193}
194
195void vik_trackpoint_free(VikTrackpoint *tp)
196{
b45865b4 197 g_free(tp->name);
50a14534
EB
198 g_free(tp);
199}
200
b45865b4
RN
201void vik_trackpoint_set_name(VikTrackpoint *tp, const gchar *name)
202{
203 if ( tp->name )
204 g_free ( tp->name );
205
206 // If the name is blank then completely remove it
207 if ( name && name[0] == '\0' )
208 tp->name = NULL;
209 else if ( name )
210 tp->name = g_strdup(name);
211 else
212 tp->name = NULL;
213}
214
50a14534
EB
215VikTrackpoint *vik_trackpoint_copy(VikTrackpoint *tp)
216{
f629b00b
RN
217 VikTrackpoint *new_tp = vik_trackpoint_new();
218 memcpy ( new_tp, tp, sizeof(VikTrackpoint) );
219 if ( tp->name )
220 new_tp->name = g_strdup (tp->name);
221 return new_tp;
50a14534
EB
222}
223
9bc95d58
RN
224/**
225 * track_recalculate_bounds_last_tp:
226 * @trk: The track to consider the recalculation on
227 *
228 * A faster bounds check, since it only considers the last track point
229 */
230static void track_recalculate_bounds_last_tp ( VikTrack *trk )
231{
232 GList *tpl = g_list_last ( trk->trackpoints );
233
234 if ( tpl ) {
235 struct LatLon ll;
236 // See if this trackpoint increases the track bounds and update if so
237 vik_coord_to_latlon ( &(VIK_TRACKPOINT(tpl->data)->coord), &ll );
238 if ( ll.lat > trk->bbox.north )
239 trk->bbox.north = ll.lat;
240 if ( ll.lon < trk->bbox.west )
241 trk->bbox.west = ll.lon;
242 if ( ll.lat < trk->bbox.south )
243 trk->bbox.south = ll.lat;
244 if ( ll.lon > trk->bbox.east )
245 trk->bbox.east = ll.lon;
246 }
247}
248
249/**
250 * vik_track_add_trackpoint:
251 * @tr: The track to which the trackpoint will be added
252 * @tp: The trackpoint to add
253 * @recalculate: Whether to perform any associated properties recalculations
254 * Generally one should avoid recalculation via this method if adding lots of points
255 * (But ensure calculate_bounds() is called after adding all points!!)
256 *
257 * The trackpoint is added to the end of the existing trackpoint list
258 */
259void vik_track_add_trackpoint ( VikTrack *tr, VikTrackpoint *tp, gboolean recalculate )
260{
b6aef7c4
RN
261 // When it's the first trackpoint need to ensure the bounding box is initialized correctly
262 gboolean adding_first_point = tr->trackpoints ? FALSE : TRUE;
9bc95d58 263 tr->trackpoints = g_list_append ( tr->trackpoints, tp );
b6aef7c4
RN
264 if ( adding_first_point )
265 vik_track_calculate_bounds ( tr );
266 else if ( recalculate )
9bc95d58
RN
267 track_recalculate_bounds_last_tp ( tr );
268}
269
db478c57
RN
270/**
271 * vik_track_get_length_to_trackpoint:
272 *
273 */
274gdouble vik_track_get_length_to_trackpoint (const VikTrack *tr, const VikTrackpoint *tp)
275{
276 gdouble len = 0.0;
277 if ( tr->trackpoints )
278 {
c707ab6a
RN
279 // Is it the very first track point?
280 if ( VIK_TRACKPOINT(tr->trackpoints->data) == tp )
281 return len;
282
db478c57
RN
283 GList *iter = tr->trackpoints->next;
284 while (iter)
285 {
286 VikTrackpoint *tp1 = VIK_TRACKPOINT(iter->data);
287 if ( ! tp1->newsegment )
288 len += vik_coord_diff ( &(tp1->coord),
289 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
290
291 // Exit when we reach the desired point
292 if ( tp1 == tp )
c707ab6a 293 break;
db478c57
RN
294
295 iter = iter->next;
296 }
297 }
298 return len;
299}
300
50a14534
EB
301gdouble vik_track_get_length(const VikTrack *tr)
302{
303 gdouble len = 0.0;
304 if ( tr->trackpoints )
305 {
306 GList *iter = tr->trackpoints->next;
307 while (iter)
308 {
309 if ( ! VIK_TRACKPOINT(iter->data)->newsegment )
310 len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
311 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
312 iter = iter->next;
313 }
314 }
315 return len;
316}
317
318gdouble vik_track_get_length_including_gaps(const VikTrack *tr)
319{
320 gdouble len = 0.0;
321 if ( tr->trackpoints )
322 {
323 GList *iter = tr->trackpoints->next;
324 while (iter)
325 {
326 len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
327 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
328 iter = iter->next;
329 }
330 }
331 return len;
332}
333
334gulong vik_track_get_tp_count(const VikTrack *tr)
335{
a619ecac 336 return g_list_length(tr->trackpoints);
50a14534
EB
337}
338
339gulong vik_track_get_dup_point_count ( const VikTrack *tr )
340{
341 gulong num = 0;
342 GList *iter = tr->trackpoints;
343 while ( iter )
344 {
345 if ( iter->next && vik_coord_equals ( &(VIK_TRACKPOINT(iter->data)->coord),
346 &(VIK_TRACKPOINT(iter->next->data)->coord) ) )
347 num++;
348 iter = iter->next;
349 }
350 return num;
351}
352
4f48c541
RN
353/*
354 * Deletes adjacent points that have the same position
355 * Returns the number of points that were deleted
356 */
357gulong vik_track_remove_dup_points ( VikTrack *tr )
50a14534 358{
4f48c541 359 gulong num = 0;
50a14534
EB
360 GList *iter = tr->trackpoints;
361 while ( iter )
362 {
363 if ( iter->next && vik_coord_equals ( &(VIK_TRACKPOINT(iter->data)->coord),
364 &(VIK_TRACKPOINT(iter->next->data)->coord) ) )
365 {
4f48c541 366 num++;
d6caf4c0
RN
367 // Maintain track segments
368 if ( VIK_TRACKPOINT(iter->next->data)->newsegment && (iter->next)->next )
369 VIK_TRACKPOINT(((iter->next)->next)->data)->newsegment = TRUE;
370
371 vik_trackpoint_free ( iter->next->data );
2a0cbd46
RN
372 tr->trackpoints = g_list_delete_link ( tr->trackpoints, iter->next );
373 }
374 else
375 iter = iter->next;
376 }
20981fd6
RN
377
378 // NB isn't really be necessary as removing duplicate points shouldn't alter the bounds!
379 vik_track_calculate_bounds ( tr );
380
2a0cbd46
RN
381 return num;
382}
383
384/*
385 * Get a count of trackpoints with the same defined timestamp
386 * Note is using timestamps with a resolution with 1 second
387 */
388gulong vik_track_get_same_time_point_count ( const VikTrack *tr )
389{
390 gulong num = 0;
391 GList *iter = tr->trackpoints;
392 while ( iter ) {
393 if ( iter->next &&
394 ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
395 VIK_TRACKPOINT(iter->next->data)->has_timestamp ) &&
396 ( VIK_TRACKPOINT(iter->data)->timestamp ==
397 VIK_TRACKPOINT(iter->next->data)->timestamp) )
398 num++;
399 iter = iter->next;
400 }
401 return num;
402}
403
404/*
405 * Deletes adjacent points that have the same defined timestamp
406 * Returns the number of points that were deleted
407 */
408gulong vik_track_remove_same_time_points ( VikTrack *tr )
409{
410 gulong num = 0;
411 GList *iter = tr->trackpoints;
412 while ( iter ) {
413 if ( iter->next &&
414 ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
415 VIK_TRACKPOINT(iter->next->data)->has_timestamp ) &&
416 ( VIK_TRACKPOINT(iter->data)->timestamp ==
417 VIK_TRACKPOINT(iter->next->data)->timestamp) ) {
418
419 num++;
420
421 // Maintain track segments
422 if ( VIK_TRACKPOINT(iter->next->data)->newsegment && (iter->next)->next )
423 VIK_TRACKPOINT(((iter->next)->next)->data)->newsegment = TRUE;
424
425 vik_trackpoint_free ( iter->next->data );
50a14534
EB
426 tr->trackpoints = g_list_delete_link ( tr->trackpoints, iter->next );
427 }
428 else
429 iter = iter->next;
430 }
20981fd6
RN
431
432 vik_track_calculate_bounds ( tr );
433
4f48c541 434 return num;
50a14534
EB
435}
436
2f5d7ea1
RN
437/*
438 * Deletes all 'extra' trackpoint information
439 * such as time stamps, speed, course etc...
440 */
441void vik_track_to_routepoints ( VikTrack *tr )
442{
443 GList *iter = tr->trackpoints;
444 while ( iter ) {
445
446 // c.f. with vik_trackpoint_new()
447
448 VIK_TRACKPOINT(iter->data)->has_timestamp = FALSE;
449 VIK_TRACKPOINT(iter->data)->timestamp = 0;
450 VIK_TRACKPOINT(iter->data)->speed = NAN;
451 VIK_TRACKPOINT(iter->data)->course = NAN;
452 VIK_TRACKPOINT(iter->data)->hdop = VIK_DEFAULT_DOP;
453 VIK_TRACKPOINT(iter->data)->vdop = VIK_DEFAULT_DOP;
454 VIK_TRACKPOINT(iter->data)->pdop = VIK_DEFAULT_DOP;
455 VIK_TRACKPOINT(iter->data)->nsats = 0;
456 VIK_TRACKPOINT(iter->data)->fix_mode = VIK_GPS_MODE_NOT_SEEN;
457
458 iter = iter->next;
459 }
460}
461
50a14534
EB
462guint vik_track_get_segment_count(const VikTrack *tr)
463{
464 guint num = 1;
465 GList *iter = tr->trackpoints;
466 if ( !iter )
467 return 0;
468 while ( (iter = iter->next) )
469 {
470 if ( VIK_TRACKPOINT(iter->data)->newsegment )
471 num++;
472 }
473 return num;
474}
475
476VikTrack **vik_track_split_into_segments(VikTrack *t, guint *ret_len)
477{
478 VikTrack **rv;
479 VikTrack *tr;
480 guint i;
481 guint segs = vik_track_get_segment_count(t);
482 GList *iter;
483
484 if ( segs < 2 )
485 {
486 *ret_len = 0;
487 return NULL;
488 }
489
490 rv = g_malloc ( segs * sizeof(VikTrack *) );
03817fbf 491 tr = vik_track_copy ( t, TRUE );
50a14534
EB
492 rv[0] = tr;
493 iter = tr->trackpoints;
494
495 i = 1;
496 while ( (iter = iter->next) )
497 {
498 if ( VIK_TRACKPOINT(iter->data)->newsegment )
499 {
500 iter->prev->next = NULL;
501 iter->prev = NULL;
03817fbf 502 rv[i] = vik_track_copy ( tr, FALSE );
50a14534 503 rv[i]->trackpoints = iter;
20981fd6
RN
504
505 vik_track_calculate_bounds ( rv[i] );
506
50a14534
EB
507 i++;
508 }
509 }
510 *ret_len = segs;
511 return rv;
512}
513
ce1c0489
RN
514/*
515 * Simply remove any subsequent segment markers in a track to form one continuous track
516 * Return the number of segments merged
517 */
518guint vik_track_merge_segments(VikTrack *tr)
519{
520 guint num = 0;
521 GList *iter = tr->trackpoints;
522 if ( !iter )
523 return num;
524
525 // Always skip the first point as this should be the first segment
526 iter = iter->next;
527
528 while ( (iter = iter->next) )
529 {
530 if ( VIK_TRACKPOINT(iter->data)->newsegment ) {
531 VIK_TRACKPOINT(iter->data)->newsegment = FALSE;
532 num++;
533 }
534 }
535 return num;
536}
537
50a14534
EB
538void vik_track_reverse ( VikTrack *tr )
539{
3b5e6737
RN
540 if ( ! tr->trackpoints )
541 return;
542
50a14534
EB
543 tr->trackpoints = g_list_reverse(tr->trackpoints);
544
545 /* fix 'newsegment' */
3b5e6737 546 GList *iter = g_list_last ( tr->trackpoints );
50a14534
EB
547 while ( iter )
548 {
549 if ( ! iter->next ) /* last segment, was first, cancel newsegment. */
550 VIK_TRACKPOINT(iter->data)->newsegment = FALSE;
551 if ( ! iter->prev ) /* first segment by convention has newsegment flag. */
552 VIK_TRACKPOINT(iter->data)->newsegment = TRUE;
553 else if ( VIK_TRACKPOINT(iter->data)->newsegment && iter->next )
554 {
555 VIK_TRACKPOINT(iter->next->data)->newsegment = TRUE;
556 VIK_TRACKPOINT(iter->data)->newsegment = FALSE;
557 }
558 iter = iter->prev;
559 }
560}
561
07378116
RN
562/**
563 * vik_track_get_duration:
564 * @trk: The track
565 *
566 * Returns: The time in seconds that covers the whole track including gaps
567 * NB this may be negative particularly if the track has been reversed
568 */
569time_t vik_track_get_duration(const VikTrack *trk)
570{
571 time_t duration = 0;
572 if ( trk->trackpoints ) {
573 // Ensure times are available
574 if ( vik_track_get_tp_first(trk)->has_timestamp ) {
575 // Get trkpt only once - as using vik_track_get_tp_last() iterates whole track each time
576 VikTrackpoint *trkpt_last = vik_track_get_tp_last(trk);
577 if ( trkpt_last->has_timestamp ) {
578 time_t t1 = vik_track_get_tp_first(trk)->timestamp;
579 time_t t2 = trkpt_last->timestamp;
580 duration = t2 - t1;
581 }
582 }
583 }
584 return duration;
585}
586
50a14534
EB
587gdouble vik_track_get_average_speed(const VikTrack *tr)
588{
589 gdouble len = 0.0;
590 guint32 time = 0;
591 if ( tr->trackpoints )
592 {
593 GList *iter = tr->trackpoints->next;
594 while (iter)
595 {
596 if ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
597 VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
598 (! VIK_TRACKPOINT(iter->data)->newsegment) )
599 {
600 len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
601 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
602 time += ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
603 }
604 iter = iter->next;
605 }
606 }
607 return (time == 0) ? 0 : ABS(len/time);
608}
609
4c21c2fa
RN
610/**
611 * Based on a simple average speed, but with a twist - to give a moving average.
612 * . GPSs often report a moving average in their statistics output
613 * . bicycle speedos often don't factor in time when stopped - hence reporting a moving average for speed
614 *
615 * Often GPS track will record every second but not when stationary
616 * This method doesn't use samples that differ over the specified time limit - effectively skipping that time chunk from the total time
617 *
618 * Suggest to use 60 seconds as the stop length (as the default used in the TrackWaypoint draw stops factor)
619 */
620gdouble vik_track_get_average_speed_moving (const VikTrack *tr, int stop_length_seconds)
621{
622 gdouble len = 0.0;
623 guint32 time = 0;
624 if ( tr->trackpoints )
625 {
626 GList *iter = tr->trackpoints->next;
627 while (iter)
628 {
629 if ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
630 VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
631 (! VIK_TRACKPOINT(iter->data)->newsegment) )
632 {
633 if ( ( VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp ) < stop_length_seconds ) {
634 len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
635 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
636
637 time += ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
638 }
639 }
640 iter = iter->next;
641 }
642 }
643 return (time == 0) ? 0 : ABS(len/time);
644}
645
50a14534
EB
646gdouble vik_track_get_max_speed(const VikTrack *tr)
647{
648 gdouble maxspeed = 0.0, speed = 0.0;
649 if ( tr->trackpoints )
650 {
651 GList *iter = tr->trackpoints->next;
652 while (iter)
653 {
654 if ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
655 VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
656 (! VIK_TRACKPOINT(iter->data)->newsegment) )
657 {
658 speed = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord), &(VIK_TRACKPOINT(iter->prev->data)->coord) )
659 / ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
660 if ( speed > maxspeed )
661 maxspeed = speed;
662 }
663 iter = iter->next;
664 }
665 }
666 return maxspeed;
667}
668
669void vik_track_convert ( VikTrack *tr, VikCoordMode dest_mode )
670{
671 GList *iter = tr->trackpoints;
672 while (iter)
673 {
674 vik_coord_convert ( &(VIK_TRACKPOINT(iter->data)->coord), dest_mode );
675 iter = iter->next;
676 }
677}
678
679/* I understood this when I wrote it ... maybe ... Basically it eats up the
680 * proper amounts of length on the track and averages elevation over that. */
681gdouble *vik_track_make_elevation_map ( const VikTrack *tr, guint16 num_chunks )
682{
683 gdouble *pts;
684 gdouble total_length, chunk_length, current_dist, current_area_under_curve, current_seg_length, dist_along_seg = 0.0;
685 gdouble altitude1, altitude2;
686 guint16 current_chunk;
687 gboolean ignore_it = FALSE;
688
689 GList *iter = tr->trackpoints;
690
c3deba01 691 if (!iter || !iter->next) /* zero- or one-point track */
4b31042b
QT
692 return NULL;
693
c79f0206
EB
694 { /* test if there's anything worth calculating */
695 gboolean okay = FALSE;
696 while ( iter )
697 {
5c280098
RN
698 // Sometimes a GPS device (or indeed any random file) can have stupid numbers for elevations
699 // Since when is 9.9999e+24 a valid elevation!!
700 // This can happen when a track (with no elevations) is uploaded to a GPS device and then redownloaded (e.g. using a Garmin Legend EtrexHCx)
701 // Some protection against trying to work with crazily massive numbers (otherwise get SIGFPE, Arithmetic exception)
702 if ( VIK_TRACKPOINT(iter->data)->altitude != VIK_DEFAULT_ALTITUDE &&
703 VIK_TRACKPOINT(iter->data)->altitude < 1E9 ) {
c79f0206
EB
704 okay = TRUE; break;
705 }
706 iter = iter->next;
707 }
708 if ( ! okay )
709 return NULL;
710 }
711
0f941532 712 iter = tr->trackpoints;
c79f0206 713
50a14534
EB
714 g_assert ( num_chunks < 16000 );
715
716 pts = g_malloc ( sizeof(gdouble) * num_chunks );
717
718 total_length = vik_track_get_length_including_gaps ( tr );
719 chunk_length = total_length / num_chunks;
720
6374e157 721 /* Zero chunk_length (eg, track of 2 tp with the same loc) will cause crash */
3b36279c
JJ
722 if (chunk_length <= 0) {
723 g_free(pts);
6374e157 724 return NULL;
3b36279c 725 }
6374e157 726
50a14534
EB
727 current_dist = 0.0;
728 current_area_under_curve = 0;
729 current_chunk = 0;
730 current_seg_length = 0;
731
732 current_seg_length = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
733 &(VIK_TRACKPOINT(iter->next->data)->coord) );
734 altitude1 = VIK_TRACKPOINT(iter->data)->altitude;
735 altitude2 = VIK_TRACKPOINT(iter->next->data)->altitude;
736 dist_along_seg = 0;
737
738 while ( current_chunk < num_chunks ) {
739
740 /* go along current seg */
741 if ( current_seg_length && (current_seg_length - dist_along_seg) > chunk_length ) {
742 dist_along_seg += chunk_length;
743
744 /* /
745 * pt2 *
746 * /x altitude = alt_at_pt_1 + alt_at_pt_2 / 2 = altitude1 + slope * dist_value_of_pt_inbetween_pt1_and_pt2
747 * /xx avg altitude = area under curve / chunk len
748 *pt1 *xxx avg altitude = altitude1 + (altitude2-altitude1)/(current_seg_length)*(dist_along_seg + (chunk_len/2))
749 * / xxx
750 * / xxx
751 **/
752
753 if ( ignore_it )
aebc49f4
RN
754 // Seemly can't determine average for this section - so use last known good value (much better than just sticking in zero)
755 pts[current_chunk] = altitude1;
50a14534 756 else
9903c388 757 pts[current_chunk] = altitude1 + (altitude2-altitude1)*((dist_along_seg - (chunk_length/2))/current_seg_length);
50a14534
EB
758
759 current_chunk++;
760 } else {
761 /* finish current seg */
762 if ( current_seg_length ) {
763 gdouble altitude_at_dist_along_seg = altitude1 + (altitude2-altitude1)/(current_seg_length)*dist_along_seg;
764 current_dist = current_seg_length - dist_along_seg;
765 current_area_under_curve = current_dist*(altitude_at_dist_along_seg + altitude2)*0.5;
766 } else { current_dist = current_area_under_curve = 0; } /* should only happen if first current_seg_length == 0 */
767
768 /* get intervening segs */
769 iter = iter->next;
770 while ( iter && iter->next ) {
771 current_seg_length = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
772 &(VIK_TRACKPOINT(iter->next->data)->coord) );
773 altitude1 = VIK_TRACKPOINT(iter->data)->altitude;
774 altitude2 = VIK_TRACKPOINT(iter->next->data)->altitude;
775 ignore_it = VIK_TRACKPOINT(iter->next->data)->newsegment;
776
777 if ( chunk_length - current_dist >= current_seg_length ) {
778 current_dist += current_seg_length;
779 current_area_under_curve += current_seg_length * (altitude1+altitude2) * 0.5;
780 iter = iter->next;
781 } else {
782 break;
783 }
784 }
785
786 /* final seg */
787 dist_along_seg = chunk_length - current_dist;
fa396b8a 788 if ( ignore_it || ( iter && !iter->next ) ) {
50a14534 789 pts[current_chunk] = current_area_under_curve / current_dist;
61950ef8
QT
790 if (!iter->next) {
791 int i;
792 for (i = current_chunk + 1; i < num_chunks; i++)
793 pts[i] = pts[current_chunk];
794 break;
795 }
9903c388 796 }
50a14534
EB
797 else {
798 current_area_under_curve += dist_along_seg * (altitude1 + (altitude2 - altitude1)*dist_along_seg/current_seg_length);
799 pts[current_chunk] = current_area_under_curve / chunk_length;
800 }
801
802 current_dist = 0;
803 current_chunk++;
804 }
805 }
806
807 return pts;
808}
809
810
811void vik_track_get_total_elevation_gain(const VikTrack *tr, gdouble *up, gdouble *down)
812{
813 gdouble diff;
814 *up = *down = 0;
8c4f1350 815 if ( tr->trackpoints && VIK_TRACKPOINT(tr->trackpoints->data)->altitude != VIK_DEFAULT_ALTITUDE )
50a14534
EB
816 {
817 GList *iter = tr->trackpoints->next;
818 while (iter)
819 {
820 diff = VIK_TRACKPOINT(iter->data)->altitude - VIK_TRACKPOINT(iter->prev->data)->altitude;
821 if ( diff > 0 )
822 *up += diff;
823 else
824 *down -= diff;
825 iter = iter->next;
826 }
bf35388d
EB
827 } else
828 *up = *down = VIK_DEFAULT_ALTITUDE;
829}
830
0ba33e1d
FA
831gdouble *vik_track_make_gradient_map ( const VikTrack *tr, guint16 num_chunks )
832{
833 gdouble *pts;
834 gdouble *altitudes;
835 gdouble total_length, chunk_length, current_gradient;
836 gdouble altitude1, altitude2;
837 guint16 current_chunk;
838
839 g_assert ( num_chunks < 16000 );
840
841 total_length = vik_track_get_length_including_gaps ( tr );
842 chunk_length = total_length / num_chunks;
843
844 /* Zero chunk_length (eg, track of 2 tp with the same loc) will cause crash */
845 if (chunk_length <= 0) {
846 return NULL;
847 }
848
849 altitudes = vik_track_make_elevation_map (tr, num_chunks);
850 if (altitudes == NULL) {
851 return NULL;
852 }
853
854 current_gradient = 0.0;
855 pts = g_malloc ( sizeof(gdouble) * num_chunks );
856 for (current_chunk = 0; current_chunk < (num_chunks - 1); current_chunk++) {
857 altitude1 = altitudes[current_chunk];
858 altitude2 = altitudes[current_chunk + 1];
859 current_gradient = 100.0 * (altitude2 - altitude1) / chunk_length;
860
861 pts[current_chunk] = current_gradient;
862 }
863
864 pts[current_chunk] = current_gradient;
865
0215dea4
RN
866 g_free ( altitudes );
867
0ba33e1d
FA
868 return pts;
869}
25e44eac 870
bf35388d 871/* by Alex Foobarian */
25e44eac
AF
872gdouble *vik_track_make_speed_map ( const VikTrack *tr, guint16 num_chunks )
873{
bf35388d 874 gdouble *v, *s, *t;
0654760a 875 gdouble duration, chunk_dur;
25e44eac 876 time_t t1, t2;
d03d80e6 877 int i, pt_count, numpts, index;
bf35388d 878 GList *iter;
25e44eac 879
24d5c7e2
EB
880 if ( ! tr->trackpoints )
881 return NULL;
25e44eac 882
24d5c7e2 883 g_assert ( num_chunks < 16000 );
25e44eac
AF
884
885 t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
886 t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
887 duration = t2 - t1;
c79f0206
EB
888
889 if ( !t1 || !t2 || !duration )
890 return NULL;
891
25e44eac 892 if (duration < 0) {
4258f4e2 893 g_warning("negative duration: unsorted trackpoint timestamps?");
25e44eac
AF
894 return NULL;
895 }
bf35388d 896 pt_count = vik_track_get_tp_count(tr);
24d5c7e2 897
bf35388d 898 v = g_malloc ( sizeof(gdouble) * num_chunks );
25e44eac 899 chunk_dur = duration / num_chunks;
bf35388d
EB
900
901 s = g_malloc(sizeof(double) * pt_count);
902 t = g_malloc(sizeof(double) * pt_count);
bf35388d
EB
903
904 iter = tr->trackpoints->next;
905 numpts = 0;
906 s[0] = 0;
e979bdab 907 t[0] = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
bf35388d
EB
908 numpts++;
909 while (iter) {
910 s[numpts] = s[numpts-1] + vik_coord_diff ( &(VIK_TRACKPOINT(iter->prev->data)->coord), &(VIK_TRACKPOINT(iter->data)->coord) );
911 t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
912 numpts++;
913 iter = iter->next;
25e44eac
AF
914 }
915
d03d80e6
AF
916 /* In the following computation, we iterate through periods of time of duration chunk_dur.
917 * The first period begins at the beginning of the track. The last period ends at the end of the track.
bf35388d 918 */
d03d80e6
AF
919 index = 0; /* index of the current trackpoint. */
920 for (i = 0; i < num_chunks; i++) {
921 /* we are now covering the interval from t[0] + i*chunk_dur to t[0] + (i+1)*chunk_dur.
922 * find the first trackpoint outside the current interval, averaging the speeds between intermediate trackpoints.
923 */
924 if (t[0] + i*chunk_dur >= t[index]) {
925 gdouble acc_t = 0, acc_s = 0;
d03d80e6
AF
926 while (t[0] + i*chunk_dur >= t[index]) {
927 acc_s += (s[index+1]-s[index]);
928 acc_t += (t[index+1]-t[index]);
929 index++;
d03d80e6
AF
930 }
931 v[i] = acc_s/acc_t;
932 }
933 else if (i) {
934 v[i] = v[i-1];
935 }
936 else {
937 v[i] = 0;
bf35388d 938 }
bf35388d
EB
939 }
940 g_free(s);
941 g_free(t);
bf35388d 942 return v;
25e44eac 943}
24d5c7e2 944
926c8140
RN
945/**
946 * Make a distance/time map, heavily based on the vik_track_make_speed_map method
947 */
948gdouble *vik_track_make_distance_map ( const VikTrack *tr, guint16 num_chunks )
949{
950 gdouble *v, *s, *t;
951 gdouble duration, chunk_dur;
952 time_t t1, t2;
953 int i, pt_count, numpts, index;
954 GList *iter;
955
956 if ( ! tr->trackpoints )
957 return NULL;
958
959 t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
960 t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
961 duration = t2 - t1;
962
963 if ( !t1 || !t2 || !duration )
964 return NULL;
965
966 if (duration < 0) {
967 g_warning("negative duration: unsorted trackpoint timestamps?");
968 return NULL;
969 }
970 pt_count = vik_track_get_tp_count(tr);
971
972 v = g_malloc ( sizeof(gdouble) * num_chunks );
973 chunk_dur = duration / num_chunks;
974
975 s = g_malloc(sizeof(double) * pt_count);
976 t = g_malloc(sizeof(double) * pt_count);
977
978 iter = tr->trackpoints->next;
979 numpts = 0;
980 s[0] = 0;
e979bdab 981 t[0] = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
926c8140
RN
982 numpts++;
983 while (iter) {
984 s[numpts] = s[numpts-1] + vik_coord_diff ( &(VIK_TRACKPOINT(iter->prev->data)->coord), &(VIK_TRACKPOINT(iter->data)->coord) );
985 t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
986 numpts++;
987 iter = iter->next;
988 }
989
990 /* In the following computation, we iterate through periods of time of duration chunk_dur.
991 * The first period begins at the beginning of the track. The last period ends at the end of the track.
992 */
993 index = 0; /* index of the current trackpoint. */
994 for (i = 0; i < num_chunks; i++) {
995 /* we are now covering the interval from t[0] + i*chunk_dur to t[0] + (i+1)*chunk_dur.
996 * find the first trackpoint outside the current interval, averaging the distance between intermediate trackpoints.
997 */
998 if (t[0] + i*chunk_dur >= t[index]) {
999 gdouble acc_s = 0; // No need for acc_t
1000 while (t[0] + i*chunk_dur >= t[index]) {
1001 acc_s += (s[index+1]-s[index]);
1002 index++;
1003 }
1004 // The only bit that's really different from the speed map - just keep an accululative record distance
1005 v[i] = i ? v[i-1]+acc_s : acc_s;
1006 }
1007 else if (i) {
1008 v[i] = v[i-1];
1009 }
1010 else {
1011 v[i] = 0;
1012 }
1013 }
1014 g_free(s);
1015 g_free(t);
1016 return v;
1017}
1018
8de26632
RN
1019/**
1020 * This uses the 'time' based method to make the graph, (which is a simpler compared to the elevation/distance)
1021 * This results in a slightly blocky graph when it does not have many trackpoints: <60
1022 * NB Somehow the elevation/distance applies some kind of smoothing algorithm,
1023 * but I don't think any one understands it any more (I certainly don't ATM)
1024 */
1025gdouble *vik_track_make_elevation_time_map ( const VikTrack *tr, guint16 num_chunks )
1026{
1027 time_t t1, t2;
1028 gdouble duration, chunk_dur;
1029 GList *iter = tr->trackpoints;
1030
1031 if (!iter || !iter->next) /* zero- or one-point track */
1032 return NULL;
1033
1034 /* test if there's anything worth calculating */
1035 gboolean okay = FALSE;
1036 while ( iter ) {
1037 if ( VIK_TRACKPOINT(iter->data)->altitude != VIK_DEFAULT_ALTITUDE ) {
1038 okay = TRUE;
1039 break;
1040 }
1041 iter = iter->next;
1042 }
1043 if ( ! okay )
1044 return NULL;
1045
1046 t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1047 t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
1048 duration = t2 - t1;
1049
1050 if ( !t1 || !t2 || !duration )
1051 return NULL;
1052
1053 if (duration < 0) {
1054 g_warning("negative duration: unsorted trackpoint timestamps?");
1055 return NULL;
1056 }
1057 gint pt_count = vik_track_get_tp_count(tr);
1058
1059 // Reset iterator back to the beginning
1060 iter = tr->trackpoints;
1061
1062 gdouble *pts = g_malloc ( sizeof(gdouble) * num_chunks ); // The return altitude values
1063 gdouble *s = g_malloc(sizeof(double) * pt_count); // calculation altitudes
1064 gdouble *t = g_malloc(sizeof(double) * pt_count); // calculation times
1065
1066 chunk_dur = duration / num_chunks;
1067
1068 s[0] = VIK_TRACKPOINT(iter->data)->altitude;
1069 t[0] = VIK_TRACKPOINT(iter->data)->timestamp;
1070 iter = tr->trackpoints->next;
1071 gint numpts = 1;
1072 while (iter) {
1073 s[numpts] = VIK_TRACKPOINT(iter->data)->altitude;
1074 t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
1075 numpts++;
1076 iter = iter->next;
1077 }
1078
1079 /* In the following computation, we iterate through periods of time of duration chunk_dur.
1080 * The first period begins at the beginning of the track. The last period ends at the end of the track.
1081 */
1082 gint index = 0; /* index of the current trackpoint. */
1083 gint i;
1084 for (i = 0; i < num_chunks; i++) {
1085 /* we are now covering the interval from t[0] + i*chunk_dur to t[0] + (i+1)*chunk_dur.
1086 * find the first trackpoint outside the current interval, averaging the heights between intermediate trackpoints.
1087 */
1088 if (t[0] + i*chunk_dur >= t[index]) {
1089 gdouble acc_s = s[index]; // initialise to first point
1090 while (t[0] + i*chunk_dur >= t[index]) {
1091 acc_s += (s[index+1]-s[index]);
1092 index++;
1093 }
1094 pts[i] = acc_s;
1095 }
1096 else if (i) {
1097 pts[i] = pts[i-1];
1098 }
1099 else {
1100 pts[i] = 0;
1101 }
1102 }
1103 g_free(s);
1104 g_free(t);
1105
1106 return pts;
1107}
1108
7b624086
RN
1109/**
1110 * Make a speed/distance map
1111 */
1112gdouble *vik_track_make_speed_dist_map ( const VikTrack *tr, guint16 num_chunks )
1113{
1114 gdouble *v, *s, *t;
1115 time_t t1, t2;
1116 gint i, pt_count, numpts, index;
1117 GList *iter;
1118 gdouble duration, total_length, chunk_length;
1119
1120 if ( ! tr->trackpoints )
1121 return NULL;
1122
1123 t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1124 t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
1125 duration = t2 - t1;
1126
1127 if ( !t1 || !t2 || !duration )
1128 return NULL;
1129
1130 if (duration < 0) {
1131 g_warning("negative duration: unsorted trackpoint timestamps?");
1132 return NULL;
1133 }
1134
1135 total_length = vik_track_get_length_including_gaps ( tr );
1136 chunk_length = total_length / num_chunks;
1137 pt_count = vik_track_get_tp_count(tr);
1138
1139 if (chunk_length <= 0) {
1140 return NULL;
1141 }
1142
1143 v = g_malloc ( sizeof(gdouble) * num_chunks );
1144 s = g_malloc ( sizeof(double) * pt_count );
1145 t = g_malloc ( sizeof(double) * pt_count );
1146
1147 // No special handling of segments ATM...
1148 iter = tr->trackpoints->next;
1149 numpts = 0;
1150 s[0] = 0;
e979bdab 1151 t[0] = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
7b624086
RN
1152 numpts++;
1153 while (iter) {
1154 s[numpts] = s[numpts-1] + vik_coord_diff ( &(VIK_TRACKPOINT(iter->prev->data)->coord), &(VIK_TRACKPOINT(iter->data)->coord) );
1155 t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
1156 numpts++;
1157 iter = iter->next;
1158 }
1159
1160 // Iterate through a portion of the track to get an average speed for that part
1161 // This will essentially interpolate between segments, which I think is right given the usage of 'get_length_including_gaps'
1162 index = 0; /* index of the current trackpoint. */
1163 for (i = 0; i < num_chunks; i++) {
1164 // Similar to the make_speed_map, but instead of using a time chunk, use a distance chunk
1165 if (s[0] + i*chunk_length >= s[index]) {
1166 gdouble acc_t = 0, acc_s = 0;
1167 while (s[0] + i*chunk_length >= s[index]) {
1168 acc_s += (s[index+1]-s[index]);
1169 acc_t += (t[index+1]-t[index]);
1170 index++;
1171 }
1172 v[i] = acc_s/acc_t;
1173 }
1174 else if (i) {
1175 v[i] = v[i-1];
1176 }
1177 else {
1178 v[i] = 0;
1179 }
1180 }
1181 g_free(s);
1182 g_free(t);
1183 return v;
1184}
1185
e58bcab1
RN
1186/**
1187 * vik_track_get_tp_by_dist:
1188 * @trk: The Track on which to find a Trackpoint
1189 * @meters_from_start: The distance along a track that the trackpoint returned is near
1190 * @get_next_point: Since there is a choice of trackpoints, this determines which one to return
1191 * @tp_metres_from_start: For the returned Trackpoint, returns the distance along the track
1192 *
1193 * TODO: Consider changing the boolean get_next_point into an enum with these options PREVIOUS, NEXT, NEAREST
1194 *
1195 * Returns: The #VikTrackpoint fitting the criteria or NULL
1196 */
1197VikTrackpoint *vik_track_get_tp_by_dist ( VikTrack *trk, gdouble meters_from_start, gboolean get_next_point, gdouble *tp_metres_from_start )
1198{
1199 gdouble current_dist = 0.0;
1200 gdouble current_inc = 0.0;
1201 if ( tp_metres_from_start )
1202 *tp_metres_from_start = 0.0;
1203
1204 if ( trk->trackpoints ) {
1205 GList *iter = g_list_next ( g_list_first ( trk->trackpoints ) );
1206 while (iter) {
1207 current_inc = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
1208 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
1209 current_dist += current_inc;
1210 if ( current_dist >= meters_from_start )
1211 break;
1212 iter = g_list_next ( iter );
1213 }
1214 // passed the end of the track
1215 if ( !iter )
1216 return NULL;
1217
1218 if ( tp_metres_from_start )
1219 *tp_metres_from_start = current_dist;
1220
1221 // we've gone past the distance already, is the previous trackpoint wanted?
1222 if ( !get_next_point ) {
1223 if ( iter->prev ) {
1224 if ( tp_metres_from_start )
1225 *tp_metres_from_start = current_dist-current_inc;
1226 return VIK_TRACKPOINT(iter->prev->data);
1227 }
1228 }
1229 return VIK_TRACKPOINT(iter->data);
1230 }
1231
1232 return NULL;
1233}
1234
bf35388d 1235/* by Alex Foobarian */
ddc2372e 1236VikTrackpoint *vik_track_get_closest_tp_by_percentage_dist ( VikTrack *tr, gdouble reldist, gdouble *meters_from_start )
24d5c7e2
EB
1237{
1238 gdouble dist = vik_track_get_length_including_gaps(tr) * reldist;
1239 gdouble current_dist = 0.0;
1240 gdouble current_inc = 0.0;
24d5c7e2
EB
1241 if ( tr->trackpoints )
1242 {
1243 GList *iter = tr->trackpoints->next;
ecb51018 1244 GList *last_iter = NULL;
ddc2372e 1245 gdouble last_dist = 0.0;
24d5c7e2
EB
1246 while (iter)
1247 {
1248 current_inc = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
1249 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
ddc2372e 1250 last_dist = current_dist;
24d5c7e2
EB
1251 current_dist += current_inc;
1252 if ( current_dist >= dist )
1253 break;
ecb51018 1254 last_iter = iter;
24d5c7e2
EB
1255 iter = iter->next;
1256 }
ddc2372e
QT
1257 if (!iter) { /* passing the end the track */
1258 if (last_iter) {
1259 if (meters_from_start)
1260 *meters_from_start = last_dist;
1261 return(VIK_TRACKPOINT(last_iter->data));
1262 }
1263 else
1264 return NULL;
1265 }
24d5c7e2
EB
1266 /* we've gone past the dist already, was prev trackpoint closer? */
1267 /* should do a vik_coord_average_weighted() thingy. */
4f5f4ec2 1268 if ( iter->prev && fabs(current_dist-current_inc-dist) < fabs(current_dist-dist) ) {
ddc2372e
QT
1269 if (meters_from_start)
1270 *meters_from_start = last_dist;
24d5c7e2 1271 iter = iter->prev;
ddc2372e
QT
1272 }
1273 else
1274 if (meters_from_start)
1275 *meters_from_start = current_dist;
24d5c7e2 1276
e1e2f2c6 1277 return VIK_TRACKPOINT(iter->data);
24d5c7e2
EB
1278
1279 }
1280 return NULL;
1281}
b42a25ba 1282
ddc2372e 1283VikTrackpoint *vik_track_get_closest_tp_by_percentage_time ( VikTrack *tr, gdouble reltime, time_t *seconds_from_start )
32e48121
QT
1284{
1285 time_t t_pos, t_start, t_end, t_total;
1286 t_start = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1287 t_end = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
1288 t_total = t_end - t_start;
1289
1290 t_pos = t_start + t_total * reltime;
1291
ddc2372e
QT
1292 if ( !tr->trackpoints )
1293 return NULL;
32e48121 1294
ddc2372e
QT
1295 GList *iter = tr->trackpoints;
1296
1297 while (iter) {
1298 if (VIK_TRACKPOINT(iter->data)->timestamp == t_pos)
1299 break;
1300 if (VIK_TRACKPOINT(iter->data)->timestamp > t_pos) {
1301 if (iter->prev == NULL) /* first trackpoint */
1302 break;
16dcb4dd 1303 time_t t_before = t_pos - VIK_TRACKPOINT(iter->prev->data)->timestamp;
ddc2372e
QT
1304 time_t t_after = VIK_TRACKPOINT(iter->data)->timestamp - t_pos;
1305 if (t_before <= t_after)
1306 iter = iter->prev;
1307 break;
32e48121 1308 }
ddc2372e
QT
1309 else if ((iter->next == NULL) && (t_pos < (VIK_TRACKPOINT(iter->data)->timestamp + 3))) /* last trackpoint: accommodate for round-off */
1310 break;
1311 iter = iter->next;
32e48121 1312 }
ddc2372e
QT
1313
1314 if (!iter)
1315 return NULL;
1316 if (seconds_from_start)
1317 *seconds_from_start = VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1318 return VIK_TRACKPOINT(iter->data);
32e48121
QT
1319}
1320
03e7da75
RN
1321VikTrackpoint* vik_track_get_tp_by_max_speed ( const VikTrack *tr )
1322{
1323 gdouble maxspeed = 0.0, speed = 0.0;
1324
1325 if ( !tr->trackpoints )
1326 return NULL;
1327
1328 GList *iter = tr->trackpoints;
1329 VikTrackpoint *max_speed_tp = NULL;
1330
1331 while (iter) {
7d7acd6d 1332 if (iter->prev) {
03e7da75
RN
1333 if ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
1334 VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
1335 (! VIK_TRACKPOINT(iter->data)->newsegment) ) {
1336 speed = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord), &(VIK_TRACKPOINT(iter->prev->data)->coord) )
1337 / ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
1338 if ( speed > maxspeed ) {
1339 maxspeed = speed;
1340 max_speed_tp = VIK_TRACKPOINT(iter->data);
1341 }
1342 }
1343 }
1344 iter = iter->next;
1345 }
1346
1347 if (!max_speed_tp)
1348 return NULL;
1349
1350 return max_speed_tp;
1351}
1352
c28faca8
RN
1353VikTrackpoint* vik_track_get_tp_by_max_alt ( const VikTrack *tr )
1354{
1355 gdouble maxalt = -5000.0;
1356 if ( !tr->trackpoints )
1357 return NULL;
1358
1359 GList *iter = tr->trackpoints;
1360 VikTrackpoint *max_alt_tp = NULL;
1361
1362 while (iter) {
1363 if ( VIK_TRACKPOINT(iter->data)->altitude > maxalt ) {
1364 maxalt = VIK_TRACKPOINT(iter->data)->altitude;
1365 max_alt_tp = VIK_TRACKPOINT(iter->data);
1366 }
1367 iter = iter->next;
1368 }
1369
1370 if (!max_alt_tp)
1371 return NULL;
1372
1373 return max_alt_tp;
1374}
1375
1376VikTrackpoint* vik_track_get_tp_by_min_alt ( const VikTrack *tr )
1377{
1378 gdouble minalt = 25000.0;
1379 if ( !tr->trackpoints )
1380 return NULL;
1381
1382 GList *iter = tr->trackpoints;
1383 VikTrackpoint *min_alt_tp = NULL;
1384
1385 while (iter) {
1386 if ( VIK_TRACKPOINT(iter->data)->altitude < minalt ) {
1387 minalt = VIK_TRACKPOINT(iter->data)->altitude;
1388 min_alt_tp = VIK_TRACKPOINT(iter->data);
1389 }
1390 iter = iter->next;
1391 }
1392
1393 if (!min_alt_tp)
1394 return NULL;
1395
1396 return min_alt_tp;
1397}
1398
215ebe59
RN
1399VikTrackpoint *vik_track_get_tp_first( const VikTrack *tr )
1400{
1401 if ( !tr->trackpoints )
1402 return NULL;
1403
1404 return (VikTrackpoint*)g_list_first(tr->trackpoints)->data;
1405}
1406
1407VikTrackpoint *vik_track_get_tp_last ( const VikTrack *tr )
1408{
1409 if ( !tr->trackpoints )
1410 return NULL;
1411
1412 return (VikTrackpoint*)g_list_last(tr->trackpoints)->data;
1413}
1414
f41c73bd
RN
1415VikTrackpoint *vik_track_get_tp_prev ( const VikTrack *tr, VikTrackpoint *tp )
1416{
1417 if ( !tr->trackpoints )
1418 return NULL;
1419
1420 GList *iter = tr->trackpoints;
1421 VikTrackpoint *tp_prev = NULL;
1422
1423 while (iter) {
1424 if (iter->prev) {
1425 if ( VIK_TRACKPOINT(iter->data) == tp ) {
1426 tp_prev = VIK_TRACKPOINT(iter->prev->data);
1427 break;
1428 }
1429 }
1430 iter = iter->next;
1431 }
1432
1433 return tp_prev;
1434}
1435
b42a25ba
EB
1436gboolean vik_track_get_minmax_alt ( const VikTrack *tr, gdouble *min_alt, gdouble *max_alt )
1437{
1438 *min_alt = 25000;
1439 *max_alt = -5000;
1440 if ( tr && tr->trackpoints && tr->trackpoints->data && (VIK_TRACKPOINT(tr->trackpoints->data)->altitude != VIK_DEFAULT_ALTITUDE) ) {
1441 GList *iter = tr->trackpoints->next;
1442 gdouble tmp_alt;
1443 while (iter)
1444 {
1445 tmp_alt = VIK_TRACKPOINT(iter->data)->altitude;
1446 if ( tmp_alt > *max_alt )
1447 *max_alt = tmp_alt;
1448 if ( tmp_alt < *min_alt )
1449 *min_alt = tmp_alt;
1450 iter = iter->next;
1451 }
1452 return TRUE;
1453 }
1454 return FALSE;
1455}
ddc47a46
AF
1456
1457void vik_track_marshall ( VikTrack *tr, guint8 **data, guint *datalen)
1458{
1459 GList *tps;
1460 GByteArray *b = g_byte_array_new();
1461 guint len;
1462 guint intp, ntp;
1463
1464 g_byte_array_append(b, (guint8 *)tr, sizeof(*tr));
1465
1466 /* we'll fill out number of trackpoints later */
1467 intp = b->len;
1468 g_byte_array_append(b, (guint8 *)&len, sizeof(len));
1469
f629b00b
RN
1470 // This allocates space for variant sized strings
1471 // and copies that amount of data from the track to byte array
1472#define vtm_append(s) \
1473 len = (s) ? strlen(s)+1 : 0; \
1474 g_byte_array_append(b, (guint8 *)&len, sizeof(len)); \
1475 if (s) g_byte_array_append(b, (guint8 *)s, len);
1476
ddc47a46
AF
1477 tps = tr->trackpoints;
1478 ntp = 0;
1479 while (tps) {
1480 g_byte_array_append(b, (guint8 *)tps->data, sizeof(VikTrackpoint));
f629b00b 1481 vtm_append(VIK_TRACKPOINT(tps->data)->name);
ddc47a46
AF
1482 tps = tps->next;
1483 ntp++;
1484 }
1485 *(guint *)(b->data + intp) = ntp;
1486
6b2f262e
RN
1487 vtm_append(tr->name);
1488 vtm_append(tr->comment);
1489 vtm_append(tr->description);
ddc47a46
AF
1490
1491 *data = b->data;
1492 *datalen = b->len;
1493 g_byte_array_free(b, FALSE);
1494}
1495
6b2f262e
RN
1496/*
1497 * Take a byte array and convert it into a Track
1498 */
ddc47a46
AF
1499VikTrack *vik_track_unmarshall (guint8 *data, guint datalen)
1500{
1501 guint len;
1502 VikTrack *new_tr = vik_track_new();
1503 VikTrackpoint *new_tp;
1504 guint ntp;
1505 gint i;
1506
0d2b891f 1507 /* basic properties: */
ddc47a46 1508 new_tr->visible = ((VikTrack *)data)->visible;
0d2b891f 1509 new_tr->is_route = ((VikTrack *)data)->is_route;
387ff7ac
RN
1510 new_tr->draw_name_mode = ((VikTrack *)data)->draw_name_mode;
1511 new_tr->max_number_dist_labels = ((VikTrack *)data)->max_number_dist_labels;
b1453c16
RN
1512 new_tr->has_color = ((VikTrack *)data)->has_color;
1513 new_tr->color = ((VikTrack *)data)->color;
20981fd6 1514 new_tr->bbox = ((VikTrack *)data)->bbox;
0d2b891f 1515
ddc47a46
AF
1516 data += sizeof(*new_tr);
1517
1518 ntp = *(guint *)data;
1519 data += sizeof(ntp);
1520
6b2f262e
RN
1521#define vtu_get(s) \
1522 len = *(guint *)data; \
1523 data += sizeof(len); \
1524 if (len) { \
1525 (s) = g_strdup((gchar *)data); \
1526 } else { \
1527 (s) = NULL; \
1528 } \
ce4bd1cf
RN
1529 data += len;
1530
f629b00b
RN
1531 for (i=0; i<ntp; i++) {
1532 new_tp = vik_trackpoint_new();
1533 memcpy(new_tp, data, sizeof(*new_tp));
1534 data += sizeof(*new_tp);
1535 vtu_get(new_tp->name);
1536 new_tr->trackpoints = g_list_prepend(new_tr->trackpoints, new_tp);
1537 }
1538 if ( new_tr->trackpoints )
1539 new_tr->trackpoints = g_list_reverse(new_tr->trackpoints);
1540
6b2f262e
RN
1541 vtu_get(new_tr->name);
1542 vtu_get(new_tr->comment);
1543 vtu_get(new_tr->description);
1544
ddc47a46
AF
1545 return new_tr;
1546}
ad0a8c2d 1547
20981fd6
RN
1548/**
1549 * (Re)Calculate the bounds of the given track,
1550 * updating the track's bounds data.
1551 * This should be called whenever a track's trackpoints are changed
1552 */
1553void vik_track_calculate_bounds ( VikTrack *trk )
1554{
1555 GList *tp_iter;
1556 tp_iter = trk->trackpoints;
1557
1558 struct LatLon topleft, bottomright, ll;
1559
1560 // Set bounds to first point
1561 if ( tp_iter ) {
1562 vik_coord_to_latlon ( &(VIK_TRACKPOINT(tp_iter->data)->coord), &topleft );
1563 vik_coord_to_latlon ( &(VIK_TRACKPOINT(tp_iter->data)->coord), &bottomright );
1564 }
1565 while ( tp_iter ) {
1566
1567 // See if this trackpoint increases the track bounds.
1568
1569 vik_coord_to_latlon ( &(VIK_TRACKPOINT(tp_iter->data)->coord), &ll );
1570
1571 if ( ll.lat > topleft.lat) topleft.lat = ll.lat;
1572 if ( ll.lon < topleft.lon) topleft.lon = ll.lon;
1573 if ( ll.lat < bottomright.lat) bottomright.lat = ll.lat;
1574 if ( ll.lon > bottomright.lon) bottomright.lon = ll.lon;
1575
1576 tp_iter = tp_iter->next;
1577 }
1578
644eea0e 1579 g_debug ( "Bounds of track: '%s' is: %f,%f to: %f,%f", trk->name, topleft.lat, topleft.lon, bottomright.lat, bottomright.lon );
20981fd6
RN
1580
1581 trk->bbox.north = topleft.lat;
1582 trk->bbox.east = bottomright.lon;
1583 trk->bbox.south = bottomright.lat;
1584 trk->bbox.west = topleft.lon;
1585}
1586
d6218717
RN
1587/**
1588 * vik_track_anonymize_times:
1589 *
1590 * Shift all timestamps to be relatively offset from 1901-01-01
1591 */
1592void vik_track_anonymize_times ( VikTrack *tr )
1593{
1594 GTimeVal gtv;
1595 g_time_val_from_iso8601 ( "1901-01-01T00:00:00Z", &gtv );
1596
1597 time_t anon_timestamp = gtv.tv_sec;
1598 time_t offset = 0;
1599
1600 GList *tp_iter;
1601 tp_iter = tr->trackpoints;
1602 while ( tp_iter ) {
1603 VikTrackpoint *tp = VIK_TRACKPOINT(tp_iter->data);
1604 if ( tp->has_timestamp ) {
1605 // Calculate an offset in time using the first available timestamp
1606 if ( offset == 0 )
1607 offset = tp->timestamp - anon_timestamp;
1608
1609 // Apply this offset to shift all timestamps towards 1901 & hence anonymising the time
1610 // Note that the relative difference between timestamps is kept - thus calculating speeds will still work
1611 tp->timestamp = tp->timestamp - offset;
1612 }
1613 tp_iter = tp_iter->next;
1614 }
1615}
1616
1617
20981fd6 1618/**
4d333042
RN
1619 * vik_track_apply_dem_data:
1620 * @skip_existing: When TRUE, don't change the elevation if the trackpoint already has a value
20981fd6 1621 *
4d333042 1622 * Set elevation data for a track using any available DEM information
20981fd6 1623 */
4d333042 1624gulong vik_track_apply_dem_data ( VikTrack *tr, gboolean skip_existing )
ad0a8c2d 1625{
4d333042 1626 gulong num = 0;
ad0a8c2d
EB
1627 GList *tp_iter;
1628 gint16 elev;
ad0a8c2d
EB
1629 tp_iter = tr->trackpoints;
1630 while ( tp_iter ) {
4d333042
RN
1631 // Don't apply if the point already has a value and the overwrite is off
1632 if ( !(skip_existing && VIK_TRACKPOINT(tp_iter->data)->altitude != VIK_DEFAULT_ALTITUDE) ) {
1633 /* TODO: of the 4 possible choices we have for choosing an elevation
1634 * (trackpoint in between samples), choose the one with the least elevation change
1635 * as the last */
1636 elev = a_dems_get_elev_by_coord ( &(VIK_TRACKPOINT(tp_iter->data)->coord), VIK_DEM_INTERPOL_BEST );
1637
1638 if ( elev != VIK_DEM_INVALID_ELEVATION ) {
1639 VIK_TRACKPOINT(tp_iter->data)->altitude = elev;
1640 num++;
1641 }
1642 }
ad0a8c2d
EB
1643 tp_iter = tp_iter->next;
1644 }
4d333042 1645 return num;
ad0a8c2d 1646}
bddd2056 1647
210ef020
GB
1648/**
1649 * vik_track_apply_dem_data_last_trackpoint:
e85535ea
RN
1650 * Apply DEM data (if available) - to only the last trackpoint
1651 */
1652void vik_track_apply_dem_data_last_trackpoint ( VikTrack *tr )
1653{
1654 gint16 elev;
1655 if ( tr->trackpoints ) {
1656 /* As in vik_track_apply_dem_data above - use 'best' interpolation method */
1657 elev = a_dems_get_elev_by_coord ( &(VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->coord), VIK_DEM_INTERPOL_BEST );
1658 if ( elev != VIK_DEM_INVALID_ELEVATION )
1659 VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->altitude = elev;
1660 }
1661}
1662
024f0e93
RN
1663
1664/**
1665 * smoothie:
1666 *
1667 * Apply elevation smoothing over range of trackpoints between the list start and end points
1668 */
1669static void smoothie ( GList *tp1, GList *tp2, gdouble elev1, gdouble elev2, guint points )
1670{
1671 // If was really clever could try and weigh interpolation according to the distance between trackpoints somehow
1672 // Instead a simple average interpolation for the number of points given.
1673 gdouble change = (elev2 - elev1)/(points+1);
1674 gint count = 1;
1675 GList *tp_iter = tp1;
1676 while ( tp_iter != tp2 && tp_iter ) {
1677 VikTrackpoint *tp = VIK_TRACKPOINT(tp_iter->data);
1678
1679 tp->altitude = elev1 + (change*count);
1680
1681 count++;
1682 tp_iter = tp_iter->next;
1683 }
1684}
1685
1686/**
1687 * vik_track_smooth_missing_elevation_data:
81ac2835 1688 * @flat: Specify how the missing elevations will be set.
024f0e93
RN
1689 * When TRUE it uses a simple flat method, using the last known elevation
1690 * When FALSE is uses an interpolation method to the next known elevation
1691 *
1692 * For each point with a missing elevation, set it to use the last known available elevation value.
1693 * Primarily of use for smallish DEM holes where it is missing elevation data.
1694 * Eg see Austria: around N47.3 & E13.8
1695 *
1696 * Returns: The number of points that were adjusted
1697 */
1698gulong vik_track_smooth_missing_elevation_data ( VikTrack *tr, gboolean flat )
1699{
1700 gulong num = 0;
1701
1702 GList *tp_iter;
1703 gdouble elev = VIK_DEFAULT_ALTITUDE;
1704
1705 VikTrackpoint *tp_missing = NULL;
1706 GList *iter_first = NULL;
1707 guint points = 0;
1708
1709 tp_iter = tr->trackpoints;
1710 while ( tp_iter ) {
1711 VikTrackpoint *tp = VIK_TRACKPOINT(tp_iter->data);
1712
1713 if ( VIK_DEFAULT_ALTITUDE == tp->altitude ) {
1714 if ( flat ) {
1715 // Simply assign to last known value
1716 if ( elev != VIK_DEFAULT_ALTITUDE ) {
1717 tp->altitude = elev;
1718 num++;
1719 }
1720 }
1721 else {
1722 if ( !tp_missing ) {
1723 // Remember the first trackpoint (and the list pointer to it) of a section of no altitudes
1724 tp_missing = tp;
1725 iter_first = tp_iter;
1726 points = 1;
1727 }
1728 else {
1729 // More missing altitudes
1730 points++;
1731 }
1732 }
1733 }
1734 else {
1735 // Altitude available (maybe again!)
1736 // If this marks the end of a section of altitude-less points
1737 // then apply smoothing for that section of points
1738 if ( points > 0 && elev != VIK_DEFAULT_ALTITUDE )
1739 if ( !flat ) {
1740 smoothie ( iter_first, tp_iter, elev, tp->altitude, points );
1741 num = num + points;
1742 }
1743
1744 // reset
1745 points = 0;
1746 tp_missing = NULL;
1747
1748 // Store for reuse as the last known good value
1749 elev = tp->altitude;
1750 }
1751
1752 tp_iter = tp_iter->next;
1753 }
1754
1755 return num;
1756}
1757
210ef020
GB
1758/**
1759 * vik_track_steal_and_append_trackpoints:
1760 *
1761 * appends t2 to t1, leaving t2 with no trackpoints
1762 */
bddd2056
EB
1763void vik_track_steal_and_append_trackpoints ( VikTrack *t1, VikTrack *t2 )
1764{
1765 if ( t1->trackpoints ) {
78af4063 1766 t1->trackpoints = g_list_concat ( t1->trackpoints, t2->trackpoints );
bddd2056
EB
1767 } else
1768 t1->trackpoints = t2->trackpoints;
1769 t2->trackpoints = NULL;
20981fd6
RN
1770
1771 // Trackpoints updated - so update the bounds
1772 vik_track_calculate_bounds ( t1 );
bddd2056 1773}
c3deba01 1774
210ef020
GB
1775/**
1776 * vik_track_cut_back_to_double_point:
1777 *
1778 * starting at the end, looks backwards for the last "double point", a duplicate trackpoint.
7ff7d728 1779 * If there is no double point, deletes all the trackpoints.
210ef020
GB
1780 *
1781 * Returns: the new end of the track (or the start if there are no double points)
c3deba01
EB
1782 */
1783VikCoord *vik_track_cut_back_to_double_point ( VikTrack *tr )
1784{
1785 GList *iter = tr->trackpoints;
1786 VikCoord *rv;
1787
1788 if ( !iter )
1789 return NULL;
1790 while ( iter->next )
1791 iter = iter->next;
1792
1793
1794 while ( iter->prev ) {
129c0ff2
MH
1795 VikCoord *cur_coord = &((VikTrackpoint*)iter->data)->coord;
1796 VikCoord *prev_coord = &((VikTrackpoint*)iter->prev->data)->coord;
1797 if ( vik_coord_equals(cur_coord, prev_coord) ) {
c3deba01
EB
1798 GList *prev = iter->prev;
1799
1800 rv = g_malloc(sizeof(VikCoord));
129c0ff2 1801 *rv = *cur_coord;
c3deba01
EB
1802
1803 /* truncate trackpoint list */
1804 iter->prev = NULL; /* pretend it's the end */
1805 g_list_foreach ( iter, (GFunc) g_free, NULL );
1806 g_list_free( iter );
1807
1808 prev->next = NULL;
1809
1810 return rv;
1811 }
1812 iter = iter->prev;
1813 }
1814
1815 /* no double point found! */
1816 rv = g_malloc(sizeof(VikCoord));
129c0ff2 1817 *rv = ((VikTrackpoint*) tr->trackpoints->data)->coord;
c3deba01
EB
1818 g_list_foreach ( tr->trackpoints, (GFunc) g_free, NULL );
1819 g_list_free( tr->trackpoints );
1820 tr->trackpoints = NULL;
1821 return rv;
1822}
1823
5c97b8a7
RN
1824/**
1825 * Function to compare two tracks by their first timestamp
1826 **/
1827int vik_track_compare_timestamp (const void *x, const void *y)
1828{
1829 VikTrack *a = (VikTrack *)x;
1830 VikTrack *b = (VikTrack *)y;
1831
1832 VikTrackpoint *tpa = NULL;
1833 VikTrackpoint *tpb = NULL;
1834
1835 if ( a->trackpoints )
1836 tpa = VIK_TRACKPOINT(g_list_first(a->trackpoints)->data);
1837
1838 if ( b->trackpoints )
1839 tpb = VIK_TRACKPOINT(g_list_first(b->trackpoints)->data);
1840
1841 if ( tpa && tpb ) {
1842 if ( tpa->timestamp < tpb->timestamp )
1843 return -1;
1844 if ( tpa->timestamp > tpb->timestamp )
1845 return 1;
1846 }
1847
1848 if ( tpa && !tpb )
1849 return 1;
1850
1851 if ( !tpa && tpb )
1852 return -1;
1853
1854 return 0;
1855}