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