]> git.street.me.uk Git - andy/viking.git/blobdiff - src/viktrack.c
Add a Gradient Graph to the Track properties display
[andy/viking.git] / src / viktrack.c
index 627ab94572570304e50ef97b47afbf66f3ebdfc3..8a23b0624b33a16e4e4bd69c44f1b549e08d3640 100644 (file)
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
 
 #include <glib.h>
 #include <time.h>
-#include <stdio.h>
 #include <stdlib.h>
+#ifdef HAVE_STRING_H
 #include <string.h>
+#endif
+#ifdef HAVE_MATH_H
 #include <math.h>
+#endif
+
 #include "coords.h"
 #include "vikcoord.h"
 #include "viktrack.h"
@@ -83,7 +90,8 @@ void vik_track_free(VikTrack *tr)
   g_list_foreach ( tr->trackpoints, (GFunc) g_free, NULL );
   g_list_free( tr->trackpoints );
   if (tr->property_dialog)
-    gtk_widget_destroy ( GTK_WIDGET(tr->property_dialog) );
+    if ( GTK_IS_WIDGET(tr->property_dialog) )
+      gtk_widget_destroy ( GTK_WIDGET(tr->property_dialog) );
   g_free ( tr );
 }
 
@@ -108,9 +116,12 @@ VikTrack *vik_track_copy ( const VikTrack *tr )
 VikTrackpoint *vik_trackpoint_new()
 {
   VikTrackpoint *tp = g_malloc0(sizeof(VikTrackpoint));
-  tp->extended = FALSE;
   tp->speed = NAN;
   tp->course = NAN;
+  tp->altitude = VIK_DEFAULT_ALTITUDE;
+  tp->hdop = VIK_DEFAULT_DOP;
+  tp->vdop = VIK_DEFAULT_DOP;
+  tp->pdop = VIK_DEFAULT_DOP;
   return tp;
 }
 
@@ -298,6 +309,42 @@ gdouble vik_track_get_average_speed(const VikTrack *tr)
   return (time == 0) ? 0 : ABS(len/time);
 }
 
+/**
+ * Based on a simple average speed, but with a twist - to give a moving average.
+ *  . GPSs often report a moving average in their statistics output
+ *  . bicycle speedos often don't factor in time when stopped - hence reporting a moving average for speed
+ *
+ * Often GPS track will record every second but not when stationary
+ * This method doesn't use samples that differ over the specified time limit - effectively skipping that time chunk from the total time
+ *
+ * Suggest to use 60 seconds as the stop length (as the default used in the TrackWaypoint draw stops factor)
+ */
+gdouble vik_track_get_average_speed_moving (const VikTrack *tr, int stop_length_seconds)
+{
+  gdouble len = 0.0;
+  guint32 time = 0;
+  if ( tr->trackpoints )
+  {
+    GList *iter = tr->trackpoints->next;
+    while (iter)
+    {
+      if ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
+          VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
+          (! VIK_TRACKPOINT(iter->data)->newsegment) )
+      {
+       if ( ( VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp ) < stop_length_seconds ) {
+         len += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
+                                 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
+       
+         time += ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
+       }
+      }
+      iter = iter->next;
+    }
+  }
+  return (time == 0) ? 0 : ABS(len/time);
+}
+
 gdouble vik_track_get_max_speed(const VikTrack *tr)
 {
   gdouble maxspeed = 0.0, speed = 0.0;
@@ -369,8 +416,10 @@ gdouble *vik_track_make_elevation_map ( const VikTrack *tr, guint16 num_chunks )
   chunk_length = total_length / num_chunks;
 
   /* Zero chunk_length (eg, track of 2 tp with the same loc) will cause crash */
-  if (chunk_length <= 0)
+  if (chunk_length <= 0) {
+    g_free(pts);
     return NULL;
+  }
 
   current_dist = 0.0;
   current_area_under_curve = 0;
@@ -399,7 +448,8 @@ gdouble *vik_track_make_elevation_map ( const VikTrack *tr, guint16 num_chunks )
        **/
 
       if ( ignore_it )
-        pts[current_chunk] = VIK_DEFAULT_ALTITUDE;
+       // Seemly can't determine average for this section - so use last known good value (much better than just sticking in zero)
+        pts[current_chunk] = altitude1;
       else
         pts[current_chunk] = altitude1 + (altitude2-altitude1)*((dist_along_seg - (chunk_length/2))/current_seg_length);
 
@@ -432,7 +482,7 @@ gdouble *vik_track_make_elevation_map ( const VikTrack *tr, guint16 num_chunks )
 
       /* final seg */
       dist_along_seg = chunk_length - current_dist;
-      if ( ignore_it || !iter->next ) {
+      if ( ignore_it || ( iter && !iter->next ) ) {
         pts[current_chunk] = current_area_under_curve / current_dist;
         if (!iter->next) {
           int i;
@@ -475,12 +525,49 @@ void vik_track_get_total_elevation_gain(const VikTrack *tr, gdouble *up, gdouble
     *up = *down = VIK_DEFAULT_ALTITUDE;
 }
 
+gdouble *vik_track_make_gradient_map ( const VikTrack *tr, guint16 num_chunks )
+{
+  gdouble *pts;
+  gdouble *altitudes;
+  gdouble total_length, chunk_length, current_gradient;
+  gdouble altitude1, altitude2;
+  guint16 current_chunk;
+
+  g_assert ( num_chunks < 16000 );
+
+  total_length = vik_track_get_length_including_gaps ( tr );
+  chunk_length = total_length / num_chunks;
+
+  /* Zero chunk_length (eg, track of 2 tp with the same loc) will cause crash */
+  if (chunk_length <= 0) {
+    return NULL;
+  }
+
+  altitudes = vik_track_make_elevation_map (tr, num_chunks);
+  if (altitudes == NULL) {
+    return NULL;
+  }
+
+  current_gradient = 0.0;
+  pts = g_malloc ( sizeof(gdouble) * num_chunks );
+  for (current_chunk = 0; current_chunk < (num_chunks - 1); current_chunk++) {
+    altitude1 = altitudes[current_chunk];
+    altitude2 = altitudes[current_chunk + 1];
+    current_gradient = 100.0 * (altitude2 - altitude1) / chunk_length;
+
+    pts[current_chunk] = current_gradient;
+  }
+
+  pts[current_chunk] = current_gradient;
+
+  return pts;
+}
 
 /* by Alex Foobarian */
 gdouble *vik_track_make_speed_map ( const VikTrack *tr, guint16 num_chunks )
 {
   gdouble *v, *s, *t;
-  gdouble duration, chunk_dur, T, s_prev, s_now;
+  gdouble duration, chunk_dur;
   time_t t1, t2;
   int i, pt_count, numpts, index;
   GList *iter;
@@ -531,12 +618,10 @@ gdouble *vik_track_make_speed_map ( const VikTrack *tr, guint16 num_chunks )
      */
     if (t[0] + i*chunk_dur >= t[index]) {
       gdouble acc_t = 0, acc_s = 0;
-      numpts = 0;
       while (t[0] + i*chunk_dur >= t[index]) {
        acc_s += (s[index+1]-s[index]);
        acc_t += (t[index+1]-t[index]);
        index++;
-       numpts++;
       }
       v[i] = acc_s/acc_t;
     } 
@@ -552,6 +637,247 @@ gdouble *vik_track_make_speed_map ( const VikTrack *tr, guint16 num_chunks )
   return v;
 }
 
+/**
+ * Make a distance/time map, heavily based on the vik_track_make_speed_map method
+ */
+gdouble *vik_track_make_distance_map ( const VikTrack *tr, guint16 num_chunks )
+{
+  gdouble *v, *s, *t;
+  gdouble duration, chunk_dur;
+  time_t t1, t2;
+  int i, pt_count, numpts, index;
+  GList *iter;
+
+  if ( ! tr->trackpoints )
+    return NULL;
+
+  t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
+  t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
+  duration = t2 - t1;
+
+  if ( !t1 || !t2 || !duration )
+    return NULL;
+
+  if (duration < 0) {
+    g_warning("negative duration: unsorted trackpoint timestamps?");
+    return NULL;
+  }
+  pt_count = vik_track_get_tp_count(tr);
+
+  v = g_malloc ( sizeof(gdouble) * num_chunks );
+  chunk_dur = duration / num_chunks;
+
+  s = g_malloc(sizeof(double) * pt_count);
+  t = g_malloc(sizeof(double) * pt_count);
+
+  iter = tr->trackpoints->next;
+  numpts = 0;
+  s[0] = 0;
+  t[0] = VIK_TRACKPOINT(iter->prev->data)->timestamp;
+  numpts++;
+  while (iter) {
+    s[numpts] = s[numpts-1] + vik_coord_diff ( &(VIK_TRACKPOINT(iter->prev->data)->coord), &(VIK_TRACKPOINT(iter->data)->coord) );
+    t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
+    numpts++;
+    iter = iter->next;
+  }
+
+  /* In the following computation, we iterate through periods of time of duration chunk_dur.
+   * The first period begins at the beginning of the track.  The last period ends at the end of the track.
+   */
+  index = 0; /* index of the current trackpoint. */
+  for (i = 0; i < num_chunks; i++) {
+    /* we are now covering the interval from t[0] + i*chunk_dur to t[0] + (i+1)*chunk_dur.
+     * find the first trackpoint outside the current interval, averaging the distance between intermediate trackpoints.
+     */
+    if (t[0] + i*chunk_dur >= t[index]) {
+      gdouble acc_s = 0; // No need for acc_t
+      while (t[0] + i*chunk_dur >= t[index]) {
+       acc_s += (s[index+1]-s[index]);
+       index++;
+      }
+      // The only bit that's really different from the speed map - just keep an accululative record distance
+      v[i] = i ? v[i-1]+acc_s : acc_s;
+    }
+    else if (i) {
+      v[i] = v[i-1];
+    }
+    else {
+      v[i] = 0;
+    }
+  }
+  g_free(s);
+  g_free(t);
+  return v;
+}
+
+/**
+ * This uses the 'time' based method to make the graph, (which is a simpler compared to the elevation/distance)
+ * This results in a slightly blocky graph when it does not have many trackpoints: <60
+ * NB Somehow the elevation/distance applies some kind of smoothing algorithm,
+ *   but I don't think any one understands it any more (I certainly don't ATM)
+ */
+gdouble *vik_track_make_elevation_time_map ( const VikTrack *tr, guint16 num_chunks )
+{
+  time_t t1, t2;
+  gdouble duration, chunk_dur;
+  GList *iter = tr->trackpoints;
+
+  if (!iter || !iter->next) /* zero- or one-point track */
+    return NULL;
+
+  /* test if there's anything worth calculating */
+  gboolean okay = FALSE;
+  while ( iter ) {
+    if ( VIK_TRACKPOINT(iter->data)->altitude != VIK_DEFAULT_ALTITUDE ) {
+      okay = TRUE;
+      break;
+    }
+    iter = iter->next;
+  }
+  if ( ! okay )
+    return NULL;
+
+  t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
+  t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
+  duration = t2 - t1;
+
+  if ( !t1 || !t2 || !duration )
+    return NULL;
+
+  if (duration < 0) {
+    g_warning("negative duration: unsorted trackpoint timestamps?");
+    return NULL;
+  }
+  gint pt_count = vik_track_get_tp_count(tr);
+
+  // Reset iterator back to the beginning
+  iter = tr->trackpoints;
+
+  gdouble *pts = g_malloc ( sizeof(gdouble) * num_chunks ); // The return altitude values
+  gdouble *s = g_malloc(sizeof(double) * pt_count); // calculation altitudes
+  gdouble *t = g_malloc(sizeof(double) * pt_count); // calculation times
+
+  chunk_dur = duration / num_chunks;
+
+  s[0] = VIK_TRACKPOINT(iter->data)->altitude;
+  t[0] = VIK_TRACKPOINT(iter->data)->timestamp;
+  iter = tr->trackpoints->next;
+  gint numpts = 1;
+  while (iter) {
+    s[numpts] = VIK_TRACKPOINT(iter->data)->altitude;
+    t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
+    numpts++;
+    iter = iter->next;
+  }
+
+ /* In the following computation, we iterate through periods of time of duration chunk_dur.
+   * The first period begins at the beginning of the track.  The last period ends at the end of the track.
+   */
+  gint index = 0; /* index of the current trackpoint. */
+  gint i;
+  for (i = 0; i < num_chunks; i++) {
+    /* we are now covering the interval from t[0] + i*chunk_dur to t[0] + (i+1)*chunk_dur.
+     * find the first trackpoint outside the current interval, averaging the heights between intermediate trackpoints.
+     */
+    if (t[0] + i*chunk_dur >= t[index]) {
+      gdouble acc_s = s[index]; // initialise to first point
+      while (t[0] + i*chunk_dur >= t[index]) {
+       acc_s += (s[index+1]-s[index]);
+       index++;
+      }
+      pts[i] = acc_s;
+    }
+    else if (i) {
+      pts[i] = pts[i-1];
+    }
+    else {
+      pts[i] = 0;
+    }
+  }
+  g_free(s);
+  g_free(t);
+
+  return pts;
+}
+
+/**
+ * Make a speed/distance map
+ */
+gdouble *vik_track_make_speed_dist_map ( const VikTrack *tr, guint16 num_chunks )
+{
+  gdouble *v, *s, *t;
+  time_t t1, t2;
+  gint i, pt_count, numpts, index;
+  GList *iter;
+  gdouble duration, total_length, chunk_length;
+
+  if ( ! tr->trackpoints )
+    return NULL;
+
+  t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
+  t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
+  duration = t2 - t1;
+
+  if ( !t1 || !t2 || !duration )
+    return NULL;
+
+  if (duration < 0) {
+    g_warning("negative duration: unsorted trackpoint timestamps?");
+    return NULL;
+  }
+
+  total_length = vik_track_get_length_including_gaps ( tr );
+  chunk_length = total_length / num_chunks;
+  pt_count = vik_track_get_tp_count(tr);
+
+  if (chunk_length <= 0) {
+    return NULL;
+  }
+
+  v = g_malloc ( sizeof(gdouble) * num_chunks );
+  s = g_malloc ( sizeof(double) * pt_count );
+  t = g_malloc ( sizeof(double) * pt_count );
+
+  // No special handling of segments ATM...
+  iter = tr->trackpoints->next;
+  numpts = 0;
+  s[0] = 0;
+  t[0] = VIK_TRACKPOINT(iter->prev->data)->timestamp;
+  numpts++;
+  while (iter) {
+    s[numpts] = s[numpts-1] + vik_coord_diff ( &(VIK_TRACKPOINT(iter->prev->data)->coord), &(VIK_TRACKPOINT(iter->data)->coord) );
+    t[numpts] = VIK_TRACKPOINT(iter->data)->timestamp;
+    numpts++;
+    iter = iter->next;
+  }
+
+  // Iterate through a portion of the track to get an average speed for that part
+  // This will essentially interpolate between segments, which I think is right given the usage of 'get_length_including_gaps'
+  index = 0; /* index of the current trackpoint. */
+  for (i = 0; i < num_chunks; i++) {
+    // Similar to the make_speed_map, but instead of using a time chunk, use a distance chunk
+    if (s[0] + i*chunk_length >= s[index]) {
+      gdouble acc_t = 0, acc_s = 0;
+      while (s[0] + i*chunk_length >= s[index]) {
+       acc_s += (s[index+1]-s[index]);
+       acc_t += (t[index+1]-t[index]);
+       index++;
+      }
+      v[i] = acc_s/acc_t;
+    }
+    else if (i) {
+      v[i] = v[i-1];
+    }
+    else {
+      v[i] = 0;
+    }
+  }
+  g_free(s);
+  g_free(t);
+  return v;
+}
+
 /* by Alex Foobarian */
 VikTrackpoint *vik_track_get_closest_tp_by_percentage_dist ( VikTrack *tr, gdouble reldist, gdouble *meters_from_start )
 {
@@ -638,6 +964,84 @@ VikTrackpoint *vik_track_get_closest_tp_by_percentage_time ( VikTrack *tr, gdoub
   return VIK_TRACKPOINT(iter->data);
 }
 
+VikTrackpoint* vik_track_get_tp_by_max_speed ( const VikTrack *tr )
+{
+  gdouble maxspeed = 0.0, speed = 0.0;
+
+  if ( !tr->trackpoints )
+    return NULL;
+
+  GList *iter = tr->trackpoints;
+  VikTrackpoint *max_speed_tp = NULL;
+
+  while (iter) {
+    if (iter->prev) {
+      if ( VIK_TRACKPOINT(iter->data)->has_timestamp &&
+          VIK_TRACKPOINT(iter->prev->data)->has_timestamp &&
+          (! VIK_TRACKPOINT(iter->data)->newsegment) ) {
+       speed =  vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord), &(VIK_TRACKPOINT(iter->prev->data)->coord) )
+         / ABS(VIK_TRACKPOINT(iter->data)->timestamp - VIK_TRACKPOINT(iter->prev->data)->timestamp);
+       if ( speed > maxspeed ) {
+         maxspeed = speed;
+         max_speed_tp = VIK_TRACKPOINT(iter->data);
+       }
+      }
+    }
+    iter = iter->next;
+  }
+  
+  if (!max_speed_tp)
+    return NULL;
+
+  return max_speed_tp;
+}
+
+VikTrackpoint* vik_track_get_tp_by_max_alt ( const VikTrack *tr )
+{
+  gdouble maxalt = -5000.0;
+  if ( !tr->trackpoints )
+    return NULL;
+
+  GList *iter = tr->trackpoints;
+  VikTrackpoint *max_alt_tp = NULL;
+
+  while (iter) {
+    if ( VIK_TRACKPOINT(iter->data)->altitude > maxalt ) {
+      maxalt = VIK_TRACKPOINT(iter->data)->altitude;
+      max_alt_tp = VIK_TRACKPOINT(iter->data);
+    }
+    iter = iter->next;
+  }
+
+  if (!max_alt_tp)
+    return NULL;
+
+  return max_alt_tp;
+}
+
+VikTrackpoint* vik_track_get_tp_by_min_alt ( const VikTrack *tr )
+{
+  gdouble minalt = 25000.0;
+  if ( !tr->trackpoints )
+    return NULL;
+
+  GList *iter = tr->trackpoints;
+  VikTrackpoint *min_alt_tp = NULL;
+
+  while (iter) {
+    if ( VIK_TRACKPOINT(iter->data)->altitude < minalt ) {
+      minalt = VIK_TRACKPOINT(iter->data)->altitude;
+      min_alt_tp = VIK_TRACKPOINT(iter->data);
+    }
+    iter = iter->next;
+  }
+
+  if (!min_alt_tp)
+    return NULL;
+
+  return min_alt_tp;
+}
+
 gboolean vik_track_get_minmax_alt ( const VikTrack *tr, gdouble *min_alt, gdouble *max_alt )
 {
   *min_alt = 25000;
@@ -736,6 +1140,20 @@ void vik_track_apply_dem_data ( VikTrack *tr )
   }
 }
 
+/*
+ * Apply DEM data (if available) - to only the last trackpoint
+ */
+void vik_track_apply_dem_data_last_trackpoint ( VikTrack *tr )
+{
+  gint16 elev;
+  if ( tr->trackpoints ) {
+    /* As in vik_track_apply_dem_data above - use 'best' interpolation method */
+    elev = a_dems_get_elev_by_coord ( &(VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->coord), VIK_DEM_INTERPOL_BEST );
+    if ( elev != VIK_DEM_INVALID_ELEVATION )
+      VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->altitude = elev;
+  }
+}
+
 /* appends t2 to t1, leaving t2 with no trackpoints */
 void vik_track_steal_and_append_trackpoints ( VikTrack *t1, VikTrack *t2 )
 {
@@ -751,9 +1169,8 @@ void vik_track_steal_and_append_trackpoints ( VikTrack *t1, VikTrack *t2 )
 }
 
 /* starting at the end, looks backwards for the last "double point", a duplicate trackpoint.
- * this is indicative of magic scissors continued use. If there is no double point,
- * deletes all the trackpoints. Returns the new end of the track (or the start if
- * there are no double points
+ * If there is no double point, deletes all the trackpoints.
+ * Returns the new end of the track (or the start if there are no double points)
  */
 VikCoord *vik_track_cut_back_to_double_point ( VikTrack *tr )
 {