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