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