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