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