]> git.street.me.uk Git - andy/viking.git/blob - src/viktrack.c
Fix a bug that caused crashes when displaying elev graph for some track.
[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  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include <glib.h>
23 #include <time.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <math.h>
28 #include "coords.h"
29 #include "vikcoord.h"
30 #include "viktrack.h"
31 #include "globals.h"
32 #include "dems.h"
33
34 VikTrack *vik_track_new()
35 {
36   VikTrack *tr = g_malloc0 ( sizeof ( VikTrack ) );
37   tr->ref_count = 1;
38   return tr;
39 }
40
41 void vik_track_set_comment_no_copy(VikTrack *tr, gchar *comment)
42 {
43   if ( tr->comment )
44     g_free ( tr->comment );
45   tr->comment = comment;
46 }
47
48
49 void vik_track_set_comment(VikTrack *tr, const gchar *comment)
50 {
51   if ( tr->comment )
52     g_free ( tr->comment );
53
54   if ( comment && comment[0] != '\0' )
55     tr->comment = g_strdup(comment);
56   else
57     tr->comment = NULL;
58 }
59
60 void vik_track_ref(VikTrack *tr)
61 {
62   tr->ref_count++;
63 }
64
65 void vik_track_set_property_dialog(VikTrack *tr, GtkWidget *dialog)
66 {
67   /* Warning: does not check for existing dialog */
68   tr->property_dialog = dialog;
69 }
70
71 void vik_track_clear_property_dialog(VikTrack *tr)
72 {
73   tr->property_dialog = NULL;
74 }
75
76 void vik_track_free(VikTrack *tr)
77 {
78   if ( tr->ref_count-- > 1 )
79     return;
80
81   if ( tr->comment )
82     g_free ( tr->comment );
83   g_list_foreach ( tr->trackpoints, (GFunc) g_free, NULL );
84   g_list_free( tr->trackpoints );
85   if (tr->property_dialog)
86     gtk_widget_destroy ( GTK_WIDGET(tr->property_dialog) );
87   g_free ( tr );
88 }
89
90 VikTrack *vik_track_copy ( const VikTrack *tr )
91 {
92   VikTrack *new_tr = vik_track_new();
93   VikTrackpoint *new_tp;
94   GList *tp_iter = tr->trackpoints;
95   new_tr->visible = tr->visible;
96   new_tr->trackpoints = NULL;
97   while ( tp_iter )
98   {
99     new_tp = g_malloc ( sizeof ( VikTrackpoint ) );
100     *new_tp = *((VikTrackpoint *)(tp_iter->data));
101     new_tr->trackpoints = g_list_append ( new_tr->trackpoints, new_tp );
102     tp_iter = tp_iter->next;
103   }
104   vik_track_set_comment(new_tr,tr->comment);
105   return new_tr;
106 }
107
108 VikTrackpoint *vik_trackpoint_new()
109 {
110   VikTrackpoint *tp = g_malloc0(sizeof(VikTrackpoint));
111   tp->extended = FALSE;
112   tp->speed = NAN;
113   tp->course = NAN;
114   return tp;
115 }
116
117 void vik_trackpoint_free(VikTrackpoint *tp)
118 {
119   g_free(tp);
120 }
121
122 VikTrackpoint *vik_trackpoint_copy(VikTrackpoint *tp)
123 {
124   VikTrackpoint *rv = vik_trackpoint_new();
125   *rv = *tp;
126   return rv;
127 }
128
129 gdouble vik_track_get_length(const VikTrack *tr)
130 {
131   gdouble len = 0.0;
132   if ( tr->trackpoints )
133   {
134     GList *iter = tr->trackpoints->next;
135     while (iter)
136     {
137       if ( ! VIK_TRACKPOINT(iter->data)->newsegment )
138         len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
139                                 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
140       iter = iter->next;
141     }
142   }
143   return len;
144 }
145
146 gdouble vik_track_get_length_including_gaps(const VikTrack *tr)
147 {
148   gdouble len = 0.0;
149   if ( tr->trackpoints )
150   {
151     GList *iter = tr->trackpoints->next;
152     while (iter)
153     {
154       len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
155                               &(VIK_TRACKPOINT(iter->prev->data)->coord) );
156       iter = iter->next;
157     }
158   }
159   return len;
160 }
161
162 gulong vik_track_get_tp_count(const VikTrack *tr)
163 {
164   gulong num = 0;
165   GList *iter = tr->trackpoints;
166   while ( iter )
167   {
168     num++;
169     iter = iter->next;
170   }
171   return num;
172 }
173
174 gulong vik_track_get_dup_point_count ( const VikTrack *tr )
175 {
176   gulong num = 0;
177   GList *iter = tr->trackpoints;
178   while ( iter )
179   {
180     if ( iter->next && vik_coord_equals ( &(VIK_TRACKPOINT(iter->data)->coord),
181                        &(VIK_TRACKPOINT(iter->next->data)->coord) ) )
182       num++;
183     iter = iter->next;
184   }
185   return num;
186 }
187
188 void vik_track_remove_dup_points ( VikTrack *tr )
189 {
190   GList *iter = tr->trackpoints;
191   while ( iter )
192   {
193     if ( iter->next && vik_coord_equals ( &(VIK_TRACKPOINT(iter->data)->coord),
194                        &(VIK_TRACKPOINT(iter->next->data)->coord) ) )
195     {
196       g_free ( iter->next->data );
197       tr->trackpoints = g_list_delete_link ( tr->trackpoints, iter->next );
198     }
199     else
200       iter = iter->next;
201   }
202 }
203
204 guint vik_track_get_segment_count(const VikTrack *tr)
205 {
206   guint num = 1;
207   GList *iter = tr->trackpoints;
208   if ( !iter )
209     return 0;
210   while ( (iter = iter->next) )
211   {
212     if ( VIK_TRACKPOINT(iter->data)->newsegment )
213       num++;
214   }
215   return num;
216 }
217
218 VikTrack **vik_track_split_into_segments(VikTrack *t, guint *ret_len)
219 {
220   VikTrack **rv;
221   VikTrack *tr;
222   guint i;
223   guint segs = vik_track_get_segment_count(t);
224   GList *iter;
225
226   if ( segs < 2 )
227   {
228     *ret_len = 0;
229     return NULL;
230   }
231
232   rv = g_malloc ( segs * sizeof(VikTrack *) );
233   tr = vik_track_copy ( t );
234   rv[0] = tr;
235   iter = tr->trackpoints;
236
237   i = 1;
238   while ( (iter = iter->next) )
239   {
240     if ( VIK_TRACKPOINT(iter->data)->newsegment )
241     {
242       iter->prev->next = NULL;
243       iter->prev = NULL;
244       rv[i] = vik_track_new();
245       if ( tr->comment )
246         vik_track_set_comment ( rv[i], tr->comment );
247       rv[i]->visible = tr->visible;
248       rv[i]->trackpoints = iter;
249       i++;
250     }
251   }
252   *ret_len = segs;
253   return rv;
254 }
255
256 void vik_track_reverse ( VikTrack *tr )
257 {
258   GList *iter;
259   tr->trackpoints = g_list_reverse(tr->trackpoints);
260
261   /* fix 'newsegment' */
262   iter = g_list_last ( tr->trackpoints );
263   while ( iter )
264   {
265     if ( ! iter->next ) /* last segment, was first, cancel newsegment. */
266       VIK_TRACKPOINT(iter->data)->newsegment = FALSE;
267     if ( ! iter->prev ) /* first segment by convention has newsegment flag. */
268       VIK_TRACKPOINT(iter->data)->newsegment = TRUE;
269     else if ( VIK_TRACKPOINT(iter->data)->newsegment && iter->next )
270     {
271       VIK_TRACKPOINT(iter->next->data)->newsegment = TRUE;
272       VIK_TRACKPOINT(iter->data)->newsegment = FALSE;
273     }
274     iter = iter->prev;
275   }
276 }
277
278 gdouble vik_track_get_average_speed(const VikTrack *tr)
279 {
280   gdouble len = 0.0;
281   guint32 time = 0;
282   if ( tr->trackpoints )
283   {
284     GList *iter = tr->trackpoints->next;
285     while (iter)
286     {
287       if ( VIK_TRACKPOINT(iter->data)->has_timestamp && 
288           VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
289           (! VIK_TRACKPOINT(iter->data)->newsegment) )
290       {
291         len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
292                                 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
293         time += ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
294       }
295       iter = iter->next;
296     }
297   }
298   return (time == 0) ? 0 : ABS(len/time);
299 }
300
301 gdouble vik_track_get_max_speed(const VikTrack *tr)
302 {
303   gdouble maxspeed = 0.0, speed = 0.0;
304   if ( tr->trackpoints )
305   {
306     GList *iter = tr->trackpoints->next;
307     while (iter)
308     {
309       if ( VIK_TRACKPOINT(iter->data)->has_timestamp && 
310           VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
311           (! VIK_TRACKPOINT(iter->data)->newsegment) )
312       {
313         speed =  vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord), &(VIK_TRACKPOINT(iter->prev->data)->coord) )
314                  / ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
315         if ( speed > maxspeed )
316           maxspeed = speed;
317       }
318       iter = iter->next;
319     }
320   }
321   return maxspeed;
322 }
323
324 void vik_track_convert ( VikTrack *tr, VikCoordMode dest_mode )
325 {
326   GList *iter = tr->trackpoints;
327   while (iter)
328   {
329     vik_coord_convert ( &(VIK_TRACKPOINT(iter->data)->coord), dest_mode );
330     iter = iter->next;
331   }
332 }
333
334 /* I understood this when I wrote it ... maybe ... Basically it eats up the
335  * proper amounts of length on the track and averages elevation over that. */
336 gdouble *vik_track_make_elevation_map ( const VikTrack *tr, guint16 num_chunks )
337 {
338   gdouble *pts;
339   gdouble total_length, chunk_length, current_dist, current_area_under_curve, current_seg_length, dist_along_seg = 0.0;
340   gdouble altitude1, altitude2;
341   guint16 current_chunk;
342   gboolean ignore_it = FALSE;
343
344   GList *iter = tr->trackpoints;
345
346   if (!iter || !iter->next) /* zero- or one-point track */
347           return NULL;
348
349   { /* test if there's anything worth calculating */
350     gboolean okay = FALSE;
351     while ( iter )
352     {
353       if ( VIK_TRACKPOINT(iter->data)->altitude != VIK_DEFAULT_ALTITUDE ) {
354         okay = TRUE; break;
355       }
356       iter = iter->next;
357     }
358     if ( ! okay )
359       return NULL;
360   }
361
362   iter = tr->trackpoints;
363
364   g_assert ( num_chunks < 16000 );
365
366   pts = g_malloc ( sizeof(gdouble) * num_chunks );
367
368   total_length = vik_track_get_length_including_gaps ( tr );
369   chunk_length = total_length / num_chunks;
370
371   /* Zero chunk_length (eg, track of 2 tp with the same loc) will cause crash */
372   if (chunk_length <= 0)
373     return NULL;
374
375   current_dist = 0.0;
376   current_area_under_curve = 0;
377   current_chunk = 0;
378   current_seg_length = 0;
379
380   current_seg_length = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
381       &(VIK_TRACKPOINT(iter->next->data)->coord) );
382   altitude1 = VIK_TRACKPOINT(iter->data)->altitude;
383   altitude2 = VIK_TRACKPOINT(iter->next->data)->altitude;
384   dist_along_seg = 0;
385
386   while ( current_chunk < num_chunks ) {
387
388     /* go along current seg */
389     if ( current_seg_length && (current_seg_length - dist_along_seg) > chunk_length ) {
390       dist_along_seg += chunk_length;
391
392       /*        /
393        *   pt2 *
394        *      /x       altitude = alt_at_pt_1 + alt_at_pt_2 / 2 = altitude1 + slope * dist_value_of_pt_inbetween_pt1_and_pt2
395        *     /xx   avg altitude = area under curve / chunk len
396        *pt1 *xxx   avg altitude = altitude1 + (altitude2-altitude1)/(current_seg_length)*(dist_along_seg + (chunk_len/2))
397        *   / xxx
398        *  /  xxx
399        **/
400
401       if ( ignore_it )
402         pts[current_chunk] = VIK_DEFAULT_ALTITUDE;
403       else
404         pts[current_chunk] = altitude1 + (altitude2-altitude1)*((dist_along_seg - (chunk_length/2))/current_seg_length);
405
406       current_chunk++;
407     } else {
408       /* finish current seg */
409       if ( current_seg_length ) {
410         gdouble altitude_at_dist_along_seg = altitude1 + (altitude2-altitude1)/(current_seg_length)*dist_along_seg;
411         current_dist = current_seg_length - dist_along_seg;
412         current_area_under_curve = current_dist*(altitude_at_dist_along_seg + altitude2)*0.5;
413       } else { current_dist = current_area_under_curve = 0; } /* should only happen if first current_seg_length == 0 */
414
415       /* get intervening segs */
416       iter = iter->next;
417       while ( iter && iter->next ) {
418         current_seg_length = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
419             &(VIK_TRACKPOINT(iter->next->data)->coord) );
420         altitude1 = VIK_TRACKPOINT(iter->data)->altitude;
421         altitude2 = VIK_TRACKPOINT(iter->next->data)->altitude;
422         ignore_it = VIK_TRACKPOINT(iter->next->data)->newsegment;
423
424         if ( chunk_length - current_dist >= current_seg_length ) {
425           current_dist += current_seg_length;
426           current_area_under_curve += current_seg_length * (altitude1+altitude2) * 0.5;
427           iter = iter->next;
428         } else {
429           break;
430         }
431       }
432
433       /* final seg */
434       dist_along_seg = chunk_length - current_dist;
435       if ( ignore_it || !iter->next ) {
436         pts[current_chunk] = current_area_under_curve / current_dist;
437       } 
438       else {
439         current_area_under_curve += dist_along_seg * (altitude1 + (altitude2 - altitude1)*dist_along_seg/current_seg_length);
440         pts[current_chunk] = current_area_under_curve / chunk_length;
441       }
442
443       current_dist = 0;
444       current_chunk++;
445     }
446   }
447
448   return pts;
449 }
450
451
452 void vik_track_get_total_elevation_gain(const VikTrack *tr, gdouble *up, gdouble *down)
453 {
454   gdouble diff;
455   *up = *down = 0;
456   if ( tr->trackpoints && VIK_TRACKPOINT(tr->trackpoints->data)->altitude != VIK_DEFAULT_ALTITUDE )
457   {
458     GList *iter = tr->trackpoints->next;
459     while (iter)
460     {
461       diff = VIK_TRACKPOINT(iter->data)->altitude - VIK_TRACKPOINT(iter->prev->data)->altitude;
462       if ( diff > 0 )
463         *up += diff;
464       else
465         *down -= diff;
466       iter = iter->next;
467     }
468   } else
469     *up = *down = VIK_DEFAULT_ALTITUDE;
470 }
471
472 typedef struct {
473   double a, b, c, d;
474 } spline_coeff_t;
475
476 void compute_spline(int n, double *x, double *f, spline_coeff_t *p) 
477 {
478   double *h, *alpha, *B, *m;
479   int i;
480   int orig_n = n;
481   double new_x[3], new_f[3];
482   
483   if (n==0) return;
484   if (n==1) {
485     new_x[0] = x[0];
486     new_f[0] = f[0];
487     new_x[1] = x[0]+0.00001;
488     new_f[1] = f[0];
489     x = new_x;
490     f = new_f;
491     n = 3;
492   }
493   if (n==2) {
494     new_x[0] = x[0];
495     new_f[0] = f[0];
496     new_x[1] = x[1];
497     new_f[1] = f[1];
498     new_x[2] = x[1] + x[1]-x[0];
499     new_f[2] = f[1] + f[1]-f[0];
500     x = new_x;
501     f = new_f;
502     n = 3;
503   }
504   
505   /* we're solving a linear system of equations of the form Ax = B. 
506    * The matrix a is tridiagonal and consists of coefficients in 
507    * the h[] and alpha[] arrays. 
508    */
509
510   h = (double *)malloc(sizeof(double) * (n-1));
511   for (i=0; i<n-1; i++) {
512     h[i] = x[i+1]-x[i];
513   }
514
515   alpha = (double *)malloc(sizeof(double) * (n-2));
516   for (i=0; i<n-2; i++) {
517     alpha[i] = 2 * (h[i] + h[i+1]);
518   }
519
520   /* B[] is the vector on the right hand side of the equation */
521   B = (double *)malloc(sizeof(double) * (n-2));
522   for (i=0; i<n-2; i++) {
523     B[i] = 6 * ((f[i+2] - f[i+1])/h[i+1] - (f[i+1] - f[i])/h[i]);
524   }
525
526   /* Now solve the n-2 by n-2 system */
527   m = (double *)malloc(sizeof(double) * (n-2));
528   for (i=1; i<=n-3; i++) {
529     /*
530     d0 = alpha 0   
531     a0 = h1          
532     c0 = h1          
533
534       di = di - (ai-1 / di-1) * ci-1
535       bi = bi - (ai-1 / di-1) * bi-1
536       ;
537     */
538     alpha[i] = alpha[i] - (h[i]/alpha[i-1]) * h[i];
539     B[i] = B[i] - (h[i]/alpha[i-1]) * B[i-1];
540   }
541   /*  xn-3 = bn-3 / dn-3; */
542   m[n-3] = B[n-3]/alpha[n-3];
543   for (i=n-4; i>=0; i--) {
544     m[i] = (B[i]-h[i+1]*m[i+1])/alpha[i];
545   }
546
547   for (i=0; i<orig_n-1; i++) {
548     double mi, mi1;
549     mi = (i==(n-2)) ? 0 : m[i];
550     mi1 = (i==0) ? 0 : m[i-1];
551
552     p[i].a = f[i+1];
553     p[i].b = (f[i+1] - f[i]) / h[i]  +  h[i] * (2*mi + mi1) / 6;
554     p[i].c = mi/2;
555     p[i].d = (mi-mi1)/(6*h[i]);
556   }
557
558   free(alpha);
559   free(B);
560   free(h);
561   free(m);
562 }
563
564 /* by Alex Foobarian */
565 gdouble *vik_track_make_speed_map ( const VikTrack *tr, guint16 num_chunks )
566 {
567   gdouble *v, *s, *t;
568   gdouble duration, chunk_dur, T, s_prev, s_now;
569   time_t t1, t2;
570   int i, pt_count, numpts, spline;
571   GList *iter;
572   spline_coeff_t *p;
573
574   if ( ! tr->trackpoints )
575     return NULL;
576
577   g_assert ( num_chunks < 16000 );
578
579 #ifdef XXXXXXXXXXXXXXXXXX
580   iter = tr->trackpoints;
581   while (iter) {
582     
583   }
584 #endif /*XXXXXXXXXXXXXXXXXX*/
585
586   t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
587   t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
588   duration = t2 - t1;
589
590   if ( !t1 || !t2 || !duration )
591     return NULL;
592
593   if (duration < 0) {
594     g_warning("negative duration: unsorted trackpoint timestamps?\n");
595     return NULL;
596   }
597   pt_count = vik_track_get_tp_count(tr);
598
599   v = g_malloc ( sizeof(gdouble) * num_chunks );
600   chunk_dur = duration / num_chunks;
601
602   s = g_malloc(sizeof(double) * pt_count);
603   t = g_malloc(sizeof(double) * pt_count);
604   p = g_malloc(sizeof(spline_coeff_t) * (pt_count-1));
605
606   iter = tr->trackpoints->next;
607   numpts = 0;
608   s[0] = 0;
609   t[0] = VIK_TRACKPOINT(iter->prev->data)->timestamp;
610   numpts++;
611   while (iter) {
612     s[numpts] = s[numpts-1] + vik_coord_diff ( &(VIK_TRACKPOINT(iter->prev->data)->coord), &(VIK_TRACKPOINT(iter->data)->coord) );
613     t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
614     numpts++;
615     iter = iter->next;
616   }
617
618   compute_spline(numpts, t, s, p);
619
620   /* the spline gives us distances at chunk_dur intervals. from these,
621    * we obtain average speed in each interval.
622    */
623   spline = 0;
624   T = t[spline];
625   s_prev = 
626       p[spline].d * pow(T - t[spline+1], 3) + 
627       p[spline].c * pow(T - t[spline+1], 2) + 
628       p[spline].b * (T - t[spline+1]) + 
629       p[spline].a;
630   for (i = 0; i < num_chunks; i++, T+=chunk_dur) {
631     while (T > t[spline+1]) {
632       spline++;
633     }
634     s_now = 
635       p[spline].d * pow(T - t[spline+1], 3) + 
636       p[spline].c * pow(T - t[spline+1], 2) + 
637       p[spline].b * (T - t[spline+1]) + 
638       p[spline].a;
639     v[i] = (s_now - s_prev) / chunk_dur;
640     s_prev = s_now;
641     /* 
642      * old method of averages
643     v[i] = (s[spline+1]-s[spline])/(t[spline+1]-t[spline]);
644     */
645   }
646   g_free(s);
647   g_free(t);
648   g_free(p);
649   return v;
650 }
651
652 /* by Alex Foobarian */
653 VikTrackpoint *vik_track_get_closest_tp_by_percentage_dist ( VikTrack *tr, gdouble reldist, gdouble *meters_from_start )
654 {
655   gdouble dist = vik_track_get_length_including_gaps(tr) * reldist;
656   gdouble current_dist = 0.0;
657   gdouble current_inc = 0.0;
658   if ( tr->trackpoints )
659   {
660     GList *iter = tr->trackpoints->next;
661     GList *last_iter = NULL;
662     gdouble last_dist = 0.0;
663     while (iter)
664     {
665       current_inc = vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
666                                      &(VIK_TRACKPOINT(iter->prev->data)->coord) );
667       last_dist = current_dist;
668       current_dist += current_inc;
669       if ( current_dist >= dist )
670         break;
671       last_iter = iter;
672       iter = iter->next;
673     }
674     if (!iter) { /* passing the end the track */
675       if (last_iter) {
676         if (meters_from_start)
677           *meters_from_start = last_dist;
678         return(VIK_TRACKPOINT(last_iter->data));
679       }
680       else
681         return NULL;
682     }
683     /* we've gone past the dist already, was prev trackpoint closer? */
684     /* should do a vik_coord_average_weighted() thingy. */
685     if ( iter->prev && abs(current_dist-current_inc-dist) < abs(current_dist-dist) ) {
686       if (meters_from_start)
687         *meters_from_start = last_dist;
688       iter = iter->prev;
689     }
690     else
691       if (meters_from_start)
692         *meters_from_start = current_dist;
693
694     return VIK_TRACKPOINT(iter->data);
695
696   }
697   return NULL;
698 }
699
700 VikTrackpoint *vik_track_get_closest_tp_by_percentage_time ( VikTrack *tr, gdouble reltime, time_t *seconds_from_start )
701 {
702   time_t t_pos, t_start, t_end, t_total;
703   t_start = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
704   t_end = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
705   t_total = t_end - t_start;
706
707   t_pos = t_start + t_total * reltime;
708
709   if ( !tr->trackpoints )
710     return NULL;
711
712   GList *iter = tr->trackpoints;
713
714   while (iter) {
715     if (VIK_TRACKPOINT(iter->data)->timestamp == t_pos)
716       break;
717     if (VIK_TRACKPOINT(iter->data)->timestamp > t_pos) {
718       if (iter->prev == NULL)  /* first trackpoint */
719         break;
720       time_t t_before = t_pos - VIK_TRACKPOINT(iter->prev)->timestamp;
721       time_t t_after = VIK_TRACKPOINT(iter->data)->timestamp - t_pos;
722       if (t_before <= t_after)
723         iter = iter->prev;
724       break;
725     }
726     else if ((iter->next == NULL) && (t_pos < (VIK_TRACKPOINT(iter->data)->timestamp + 3))) /* last trackpoint: accommodate for round-off */
727       break;
728     iter = iter->next;
729   }
730
731   if (!iter)
732     return NULL;
733   if (seconds_from_start)
734     *seconds_from_start = VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
735   return VIK_TRACKPOINT(iter->data);
736 }
737
738 gboolean vik_track_get_minmax_alt ( const VikTrack *tr, gdouble *min_alt, gdouble *max_alt )
739 {
740   *min_alt = 25000;
741   *max_alt = -5000;
742   if ( tr && tr->trackpoints && tr->trackpoints->data && (VIK_TRACKPOINT(tr->trackpoints->data)->altitude != VIK_DEFAULT_ALTITUDE) ) {
743     GList *iter = tr->trackpoints->next;
744     gdouble tmp_alt;
745     while (iter)
746     {
747       tmp_alt = VIK_TRACKPOINT(iter->data)->altitude;
748       if ( tmp_alt > *max_alt )
749         *max_alt = tmp_alt;
750       if ( tmp_alt < *min_alt )
751         *min_alt = tmp_alt;
752       iter = iter->next;
753     }
754     return TRUE;
755   }
756   return FALSE;
757 }
758
759 void vik_track_marshall ( VikTrack *tr, guint8 **data, guint *datalen)
760 {
761   GList *tps;
762   GByteArray *b = g_byte_array_new();
763   guint len;
764   guint intp, ntp;
765
766   g_byte_array_append(b, (guint8 *)tr, sizeof(*tr));
767
768   /* we'll fill out number of trackpoints later */
769   intp = b->len;
770   g_byte_array_append(b, (guint8 *)&len, sizeof(len));
771
772   tps = tr->trackpoints;
773   ntp = 0;
774   while (tps) {
775     g_byte_array_append(b, (guint8 *)tps->data, sizeof(VikTrackpoint));
776     tps = tps->next;
777     ntp++;
778   }
779   *(guint *)(b->data + intp) = ntp;
780
781   len = (tr->comment) ? strlen(tr->comment)+1 : 0; 
782   g_byte_array_append(b, (guint8 *)&len, sizeof(len)); 
783   if (tr->comment) g_byte_array_append(b, (guint8 *)tr->comment, len);
784
785   *data = b->data;
786   *datalen = b->len;
787   g_byte_array_free(b, FALSE);
788 }
789
790 VikTrack *vik_track_unmarshall (guint8 *data, guint datalen)
791 {
792   guint len;
793   VikTrack *new_tr = vik_track_new();
794   VikTrackpoint *new_tp;
795   guint ntp;
796   gint i;
797
798   /* only the visibility is needed */
799   new_tr->visible = ((VikTrack *)data)->visible;
800   data += sizeof(*new_tr);
801
802   ntp = *(guint *)data;
803   data += sizeof(ntp);
804
805   for (i=0; i<ntp; i++) {
806     new_tp = vik_trackpoint_new();
807     memcpy(new_tp, data, sizeof(*new_tp));
808     data += sizeof(*new_tp);
809     new_tr->trackpoints = g_list_append(new_tr->trackpoints, new_tp);
810   }
811
812   len = *(guint *)data;
813   data += sizeof(len);
814   if (len) {
815     new_tr->comment = g_strdup((gchar *)data);
816   }
817   return new_tr;
818 }
819
820 void vik_track_apply_dem_data ( VikTrack *tr )
821 {
822   GList *tp_iter;
823   gint16 elev;
824   tp_iter = tr->trackpoints;
825   while ( tp_iter ) {
826     /* TODO: of the 4 possible choices we have for choosing an elevation
827      * (trackpoint in between samples), choose the one with the least elevation change
828      * as the last */
829     elev = a_dems_get_elev_by_coord ( &(VIK_TRACKPOINT(tp_iter->data)->coord), VIK_DEM_INTERPOL_BEST );
830     if ( elev != VIK_DEM_INVALID_ELEVATION )
831       VIK_TRACKPOINT(tp_iter->data)->altitude = elev;
832     tp_iter = tp_iter->next;
833   }
834 }
835
836 /* appends t2 to t1, leaving t2 with no trackpoints */
837 void vik_track_steal_and_append_trackpoints ( VikTrack *t1, VikTrack *t2 )
838 {
839   if ( t1->trackpoints ) {
840     GList *tpiter = t1->trackpoints;
841     while ( tpiter->next )
842       tpiter = tpiter->next;
843     tpiter->next = t2->trackpoints;
844     t2->trackpoints->prev = tpiter;
845   } else
846     t1->trackpoints = t2->trackpoints;
847   t2->trackpoints = NULL;
848 }
849
850 /* starting at the end, looks backwards for the last "double point", a duplicate trackpoint.
851  * this is indicative of magic scissors continued use. If there is no double point,
852  * deletes all the trackpoints. Returns the new end of the track (or the start if
853  * there are no double points
854  */
855 VikCoord *vik_track_cut_back_to_double_point ( VikTrack *tr )
856 {
857   GList *iter = tr->trackpoints;
858   VikCoord *rv;
859
860   if ( !iter )
861     return NULL;
862   while ( iter->next )
863     iter = iter->next;
864
865
866   while ( iter->prev ) {
867     if ( vik_coord_equals((VikCoord *)iter->data, (VikCoord *)iter->prev->data) ) {
868       GList *prev = iter->prev;
869
870       rv = g_malloc(sizeof(VikCoord));
871       *rv = *((VikCoord *) iter->data);
872
873       /* truncate trackpoint list */
874       iter->prev = NULL; /* pretend it's the end */
875       g_list_foreach ( iter, (GFunc) g_free, NULL );
876       g_list_free( iter );
877
878       prev->next = NULL;
879
880       return rv;
881     }
882     iter = iter->prev;
883   }
884
885   /* no double point found! */
886   rv = g_malloc(sizeof(VikCoord));
887   *rv = *((VikCoord *) tr->trackpoints->data);
888   g_list_foreach ( tr->trackpoints, (GFunc) g_free, NULL );
889   g_list_free( tr->trackpoints );
890   tr->trackpoints = NULL;
891   return rv;
892 }
893