Example #1
0
        public static IGPSPoint getGpsLoc(ZoneFiveSoftware.Common.Data.Fitness.IActivity activity, DateTime time)
        {
            IGPSPoint result = null;

            if (activity != null && activity.GPSRoute != null && activity.GPSRoute.Count > 0)
            {
                ITimeValueEntry <IGPSPoint> p = activity.GPSRoute.GetInterpolatedValue(time);
                if (null != p)
                {
                    result = p.Value;
                }
                else
                {
                    //This can happen if the activity starts before the GPS track
                    if (time <= activity.GPSRoute.StartTime)
                    {
                        result = activity.GPSRoute[0].Value;
                    }
                    else if (time > activity.GPSRoute.EntryDateTime(activity.GPSRoute[activity.GPSRoute.Count - 1]))
                    {
                        result = activity.GPSRoute[activity.GPSRoute.Count - 1].Value;
                    }
                }
            }
            return(result);
        }
Example #2
0
        /// <summary>
        /// Gets the 'critical value' for a particular activity.  For instance, 5 minute power in an given activity.
        /// </summary>
        /// <param name="activity">Activity to analyze</param>
        /// <param name="chartType">Which data track to analyze</param>
        /// <param name="seconds">Seconds representing the period.  For example, 5 minute power would be 300 seconds.</param>
        /// <returns>The critical value requested.  5 minute power might return 350 watts for example.</returns>
        internal static float GetCriticalValue(IActivity activity, Common.TrackType chartType, float seconds)
        {
            float min, criticalValue;
            INumericTimeDataSeries source, mmTrack, timeTrack;
            string id = activity.ReferenceId + chartType + seconds;

            mmTrack = GetMeanMaxTrack(activity, chartType, out timeTrack, false);

            // Return value from cache
            if (criticalCache.ContainsKey(id))
            {
                // Critical cache
                return(criticalCache[id]);
            }
            else if (mmTrack != null)
            {
                // value was cached previously.  saves a little time.
                ITimeValueEntry <float> point = mmTrack.GetInterpolatedValue(mmTrack.StartTime.AddSeconds(seconds));
                if (point != null)
                {
                    criticalValue = point.Value;
                }
                else
                {
                    // Value does not exist for this activity
                    criticalValue = float.NaN;
                }
            }
            else
            {
                // Calculate value
                switch (chartType)
                {
                case Common.TrackType.Cadence:
                    source = activity.CadencePerMinuteTrack;
                    break;

                case Common.TrackType.Power:
                    source = activity.PowerWattsTrack;
                    break;

                default:
                case Common.TrackType.HR:
                    source = activity.HeartRatePerMinuteTrack;
                    break;
                }

                Utilities.Smooth(source, (uint)seconds, out min, out criticalValue);
            }

            // Save to cache
            if (!float.IsNaN(criticalValue) && criticalValue != 0)
            {
                criticalCache.Add(id, criticalValue);
            }

            return(criticalValue);
        }
Example #3
0
        /// <summary>
        /// Combine a list of tracks by averaging all of the values together.
        /// Start times are ignored, ElapsedSeconds is used for track alignment.  Each track is weighted equally.
        /// </summary>
        /// <param name="tracks">List of tracks to average together</param>
        /// <returns>A single track representing the average data series.</returns>
        private static INumericTimeDataSeries CombineAvgTracks(IList <INumericTimeDataSeries> tracks)
        {
            // Quick data analysis... return quickly if possible
            if (tracks.Count == 0)
            {
                // No data to analyze
                return(null);
            }
            else if (tracks.Count == 1)
            {
                // Only 1 track.  It is it's own average :)
                return(tracks[0]);
            }

            // Setup for analysis...
            INumericTimeDataSeries avgTrack  = new NumericTimeDataSeries();
            INumericTimeDataSeries longTrack = tracks[0];

            // Find longest track to be used as index later
            foreach (INumericTimeDataSeries track in tracks)
            {
                if (longTrack.TotalElapsedSeconds < track.TotalElapsedSeconds)
                {
                    longTrack = track;
                }
            }

            // Average the values:  Use longTrack as the reference because it'll cover all tracks
            //  (some may be shorter, which is OK)
            // Using this track as a reference will also be more efficient because it
            //  won't go through every second, it'll take advantage of the logarithmic algos elsewhere.
            foreach (ITimeValueEntry <float> index in longTrack)
            {
                // Sum & count used later to determine average
                float sum   = 0;
                int   count = 0;

                // Collect average point in each track
                foreach (INumericTimeDataSeries track in tracks)
                {
                    ITimeValueEntry <float> item = track.GetInterpolatedValue(track.StartTime.AddSeconds(index.ElapsedSeconds));
                    if (item != null)
                    {
                        sum += item.Value;
                        count++;
                    }
                }

                // Store average
                avgTrack.Add(longTrack.EntryDateTime(index), sum / count);
            }

            return(avgTrack);
        }
Example #4
0
        private bool FillSingleDataSerie(LineChartTypes chartType, ChartDataSeries dataSerie)
        {
            INumericTimeDataSeries graphPoints = GetSmoothedActivityTrack(chartType);

            if (graphPoints.Count > 0)
            {
                TimeSpan trackStartDiffWithActivity = graphPoints.StartTime - m_ActivityInfoCache.ActualTrackStart;
                float    trackSecondDifference      = (float)trackStartDiffWithActivity.TotalSeconds;

                if (XAxisReferential == XAxisValue.Time)
                {
                    graphPoints = Utils.Utils.RemovePausedTimesInTrack(graphPoints, Activity);

                    foreach (ITimeValueEntry <float> entry in graphPoints)
                    {
                        float key = trackSecondDifference + entry.ElapsedSeconds;
                        if (!dataSerie.Points.ContainsKey(key))
                        {
                            dataSerie.Points.Add(key, new PointF(trackSecondDifference + entry.ElapsedSeconds, entry.Value));
                        }
                    }
                }
                else if (m_ActivityInfoCache.MovingDistanceMetersTrack != null)
                {
                    IDistanceDataTrack distanceTrack = m_ActivityInfoCache.MovingDistanceMetersTrack;
                    int pointCount = Math.Min(distanceTrack.Count, graphPoints.Count);
                    ITimeValueEntry <float> startEntry = distanceTrack.GetInterpolatedValue(graphPoints.StartTime);

                    foreach (ITimeValueEntry <float> entry in graphPoints)
                    {
                        ITimeValueEntry <float> interpolatedEntry = distanceTrack.GetInterpolatedValue(graphPoints.StartTime + new TimeSpan(0, 0, (int)entry.ElapsedSeconds));

                        if (interpolatedEntry != null)
                        {
                            float distanceAtTime = interpolatedEntry.Value;
                            float distanceValue  = (float)Length.Convert(distanceAtTime, Length.Units.Meter, Activity.Category.DistanceUnits);

                            float key = trackSecondDifference + entry.ElapsedSeconds;
                            if (!dataSerie.Points.ContainsKey(key))
                            {
                                dataSerie.Points.Add(key, new PointF(distanceValue, entry.Value));
                            }
                        }
                    }
                }

                return(true);
            }

            return(false);
        }
        public static IList <IList <IGPSPoint> > GpsPoints(IGPSRoute gpsRoute, IValueRangeSeries <DateTime> selections)
        {
            IList <IList <IGPSPoint> > result = new List <IList <IGPSPoint> >();

            if (selections != null && selections.Count > 0 && gpsRoute != null && gpsRoute.Count > 1)
            {
                //selection and gps points are sorted without overlap so traverse over gps points only once
                //(previous version had much more complicated version, that also accounted for pauses)
                int i = 0;
                foreach (IValueRange <DateTime> sel in selections)
                {
                    IList <IGPSPoint> track = new List <IGPSPoint>();
                    //Use start/end "with priority", even if extra points are added. Care needed if returning GPSRoute
                    DateTime t = DateTimeRangeSeries.Latest(sel.Lower, gpsRoute.StartTime);
                    ITimeValueEntry <IGPSPoint> pt = gpsRoute.GetInterpolatedValue(t);
                    if (pt != null)
                    {
                        track.Add(pt.Value);
                    }
                    while (i < gpsRoute.Count)
                    {
                        ITimeValueEntry <IGPSPoint> entry = gpsRoute[i];
                        DateTime time = gpsRoute.EntryDateTime(entry);
                        i++;
                        if (sel.Lower > time)
                        {
                            continue;
                        }
                        if (sel.Upper < time)
                        {
                            //Do not increase counter here, it could be needed
                            i--;
                            break;
                        }
                        track.Add(entry.Value);
                    }
                    t  = DateTimeRangeSeries.Earliest(sel.Upper, gpsRoute.StartTime.AddSeconds(gpsRoute.TotalElapsedSeconds));
                    pt = gpsRoute.GetInterpolatedValue(t);
                    if (pt != null)
                    {
                        track.Add(pt.Value);
                    }
                    result.Add(track);
                }
            }

            return(result);
        }
Example #6
0
        private static bool SumValues(ITimeValueEntry <float> p1, ITimeValueEntry <float> p2, ref double value, ref long secondsRemaining)
        {
            double spanSeconds = Math.Abs((double)p2.ElapsedSeconds - (double)p1.ElapsedSeconds);

            if (spanSeconds <= secondsRemaining)
            {
                value            += (p1.Value + p2.Value) * spanSeconds;
                secondsRemaining -= (long)spanSeconds;
                return(true);
            }
            else
            {
                double percent = (double)secondsRemaining / (double)spanSeconds;
                value           += (p1.Value * ((float)2 - percent) + p2.Value * percent) * secondsRemaining;
                secondsRemaining = 0;
                return(false);
            }
        }
Example #7
0
        public static void InsertValue <T>(DateTime atime, ITimeDataSeries <T> track, ITimeDataSeries <T> source)
        {
            //Interpolation is down to seconds
            //TBD: Inefficient, source.IndexOf often fails
#if ST_3_1_5314_BUG
            //http://www.zonefivesoftware.com/sporttracks/forums/viewtopic.php?p=84638#p84638
            //System.Exception: FindPosOnOrBefore: Didn't find element properly.
            try
            {
#endif
            ITimeValueEntry <T> interpolatedP = source.GetInterpolatedValue(atime);
            if (interpolatedP != null)
            {
                int index = source.IndexOf(interpolatedP);
                T   val   = interpolatedP.Value;
                if (index >= 0)
                {
                    val = TrackUtil.getValFromDateTimeIndex(source, atime, index);
                }
                else
                {
                }
                try
                {
#if !NO_ST_INSERT_START_TIME
                    //ST bug: not inserted in order if ms differs for start
                    if (Math.Abs((atime - track.StartTime).TotalSeconds) < 1)
                    {
                        track.RemoveAt(0);
                    }
#endif
                    track.Add(atime, val);
                    //T val2 = track.GetInterpolatedValue(atime).Value;
                }
                catch { }
            }
#if ST_3_1_5314_BUG
        }

        catch (Exception e)
        {
        }
#endif
        }
Example #8
0
        internal static float getValFromDateTime(INumericTimeDataSeries track, DateTime t, out int status)
        {
            //Ignore malformed activities and selection outside the result
            float res = 0;

            status = 1;
            ITimeValueEntry <float> entry = track.GetInterpolatedValue(t);

            if (entry != null)
            {
                res    = entry.Value;
                status = 0;
            }
            else if (track.Count > 0 && t >= track.StartTime.AddSeconds(2))
            {
                //Seem to be out of bounds
                //Any time after start is handled as end, as pauses may complicate calcs (default is 0, i.e. before)
                res = track[track.Count - 1].Value;
            }
            return(res);
        }
Example #9
0
        public static void ExtractRangeFromDataTrack(INumericTimeDataSeries sourceTrack, ValueRange <DateTime> timeToKeep, INumericTimeDataSeries resultTrack)
        {
            resultTrack.Clear();

            if (sourceTrack != null && sourceTrack.Count > 0)
            {
                TimeSpan endElapsedTime      = timeToKeep.Upper - sourceTrack.StartTime;
                TimeSpan totalElapsedTime    = timeToKeep.Upper - timeToKeep.Lower;
                int      endElapsedSeconds   = (int)endElapsedTime.TotalSeconds;
                int      totalElapsedSeconds = (int)totalElapsedTime.TotalSeconds;
                DateTime timeStart           = timeToKeep.Lower;
                DateTime timeEnd             = timeToKeep.Upper;
                DateTime sourceDataEndTime   = sourceTrack.StartTime + new TimeSpan(0, 0, (int)sourceTrack.TotalElapsedSeconds);

                // Readjust to match the track since it can start after the activity start time
                //  or end before the end time
                if (timeStart < sourceTrack.StartTime)
                {
                    timeStart = sourceTrack.StartTime;
                }
                else if (timeStart > sourceDataEndTime)
                {
                    timeStart = sourceDataEndTime;
                }

                if (timeEnd < sourceTrack.StartTime)
                {
                    timeEnd = sourceTrack.StartTime;
                }
                else if (timeEnd > sourceDataEndTime)
                {
                    timeEnd = sourceDataEndTime;
                }

                ITimeValueEntry <float> startDistance = sourceTrack.GetInterpolatedValue(timeStart);
                int currentItemIndex = sourceTrack.IndexOf(startDistance);

                // Since we use an interpolated value, it is possible that there is no matching
                //  index, in which case we add it so we can have a start point in the array
                if (currentItemIndex == -1)
                {
                    sourceTrack.Add(timeStart, startDistance.Value);
                    startDistance    = sourceTrack.GetInterpolatedValue(timeStart);
                    currentItemIndex = sourceTrack.IndexOf(startDistance);
                }

                // Just make sure we have a start index.  Can be == -1 if there is no track?
                if (currentItemIndex != -1)
                {
                    // Now go through all indexes until we hit the end
                    while (currentItemIndex < sourceTrack.Count && sourceTrack[currentItemIndex].ElapsedSeconds <= endElapsedSeconds)
                    {
                        ITimeValueEntry <float> currentDistance = sourceTrack[currentItemIndex];
                        DateTime currentTime = timeStart + new TimeSpan(0, 0, (int)(currentDistance.ElapsedSeconds - startDistance.ElapsedSeconds));

                        resultTrack.Add(currentTime, currentDistance.Value);

                        ++currentItemIndex;
                    }
                }

                // Since we check the elapsed seconds, it is possible that our end point needs to
                //  be interpolated.  Let's make sure we have our end point in our cached data
                ITimeValueEntry <float> lastEntry = sourceTrack.GetInterpolatedValue(timeEnd);

                // Should never be empty, we at least added the start above...
                if (resultTrack.Count != 0 && lastEntry != null)
                {
                    if (resultTrack[resultTrack.Count - 1].ElapsedSeconds != totalElapsedSeconds)
                    {
                        // Add our final interpolated point
                        resultTrack.Add(timeEnd, lastEntry.Value);
                    }
                }
            }
        }
Example #10
0
        /// <summary>
        /// Gets an average value over a specific timeframe in an activity track.
        /// Used to calculate associated values, for example.
        /// </summary>
        /// <param name="activity">Activity to analyze</param>
        /// <param name="pointType">Which data track is data requested from</param>
        /// <param name="start">Start time of requested average period (in UTC time)</param>
        /// <param name="period">Duration of time (in seconds) to evaluate with respect to start time</param>
        /// <returns>The average value of X track, from 'start' to 'period' seconds later.</returns>
        internal static float GetAvgValue(IActivity activity, Common.TrackType pointType, DateTime start, uint period)
        {
            // Check for bad data
            if (activity == null || period < 0)
            {
                return(float.NaN);
            }

            INumericTimeDataSeries track = null;

            switch (pointType)
            {
            case Common.TrackType.Cadence:
                track = activity.CadencePerMinuteTrack;
                break;

            case Common.TrackType.HR:
                track = activity.HeartRatePerMinuteTrack;
                break;

            case Common.TrackType.Power:
                track = activity.PowerWattsTrack;
                break;

            case Common.TrackType.Grade:
                track = new NumericTimeDataSeries(ActivityInfoCache.Instance.GetInfo(activity).SmoothedGradeTrack);
                for (int i = 0; i < track.Count; i++)
                {
                    track.SetValueAt(i, track[i].Value * 100f);
                }
                break;
            }

            // No track data
            if (track == null)
            {
                return(float.NaN);
            }

            // Find average value:  Sum entries every 1 second, then divide it out at the end
            float    sum  = 0;
            DateTime time = start;
            TimeSpan span = TimeSpan.Zero;

            while (time < start.AddSeconds(period))
            {
                // Sum all values (we'll divide it out at the end)
                ITimeValueEntry <float> item = track.GetInterpolatedValue(time);

                // Ignore bad values... they're simply excluded from calculation
                //  and the average is taken from a smaller subset of values
                if (item != null)
                {
                    sum += item.Value;
                    span = span.Add(TimeSpan.FromSeconds(1));
                }

                time = time.AddSeconds(1);
            }

            // Divide sum by period to get the average value
            if (span.TotalSeconds > 0)
            {
                return(sum / (int)span.TotalSeconds);
            }
            else
            {
                // Bad data.  Oops, requested time range didn't exist for the requested track.
                return(float.NaN);
            }
        }
Example #11
0
        //public IDistanceDataTrack GetSmoothedDistanceTrack(int seconds, out float min, out float max)
        //{
        //    IDistanceDataTrack distanceTrack;
        //    if (record.Activity.DistanceMetersTrack != null)
        //    {
        //        // #1 Use Distance track from activity
        //        distanceTrack = record.Activity.DistanceMetersTrack;
        //    }
        //    else
        //    {
        //        if (record.Activity.GPSRoute != null)
        //        {
        //            // #2 Otherwise create a distance track from GPS
        //            distanceTrack = Utilities.CreateDistanceDataTrack(record.Activity);
        //            return Utilities.STSmooth(distanceTrack, seconds, min, max);
        //        }
        //        else
        //        {
        //            // Else, no distance track, and cannot create one.
        //            distanceTrack = new DistanceDataTrack();
        //        }
        //    }
        //}

        //public INumericTimeDataSeries GetSmoothedGradeTrack(int seconds, out float min, out float max)
        //{
        //    NumericTimeDataSeries gradeTrack = new NumericTimeDataSeries();
        //    for (int i = 0; i < record.Activity.ElevationMetersTrack.Count; i++)
        //    {
        //        if (i == 0)
        //        {
        //            gradeTrack.Add(record.Activity.ElevationMetersTrack[i].ElapsedSeconds, 0);
        //        }
        //    }
        //}

        #region Constructors

        public Feature(IActivity activity, feature_type type, DateTime inStartTime, DateTime inEndTime)
        {
            startTime        = inStartTime;
            endTime          = inEndTime;
            added            = false;
            hillNumber       = 0;
            _feature_type    = type;
            masterActivityID = activity.ReferenceId;

            // Default fill and line color
            fillColor     = Color.FromArgb(125, 146, 94, 9);
            lineColor     = Color.FromArgb(255, 146, 94, 9);
            lineWidth     = 1;
            selectedColor = Color.Empty;
            routeWidth    = PluginMain.GetApplication().SystemPreferences.RouteSettings.RouteWidth;

            IGPSRoute recordGPS = new GPSRoute();
            INumericTimeDataSeries recordHRTrack = new NumericTimeDataSeries();
            INumericTimeDataSeries pwrTrack      = new NumericTimeDataSeries();
            INumericTimeDataSeries elevTrack     = new NumericTimeDataSeries();
            INumericTimeDataSeries cadTrack      = new NumericTimeDataSeries();
            IDistanceDataTrack     distTrack     = new DistanceDataTrack();
            RecordCategory         category      = new RecordCategory();

            ActivityInfo ai    = ActivityInfoCache.Instance.GetInfo(activity);
            DateTime     start = activity.StartTime;

            if (activity.GPSRoute != null)
            {
                // Check and make sure the route has points
                if (activity.GPSRoute.Count > 0)
                {
                    // If the time passed in is before the start of the gps track, get the first value
                    if (activity.GPSRoute.StartTime > inStartTime)
                    {
                        startPoint = activity.GPSRoute[0].Value;
                    }
                    else
                    {
                        // Set the start point
                        ITimeValueEntry <IGPSPoint> sPoint = activity.GPSRoute.GetInterpolatedValue(inStartTime);
                        if (sPoint != null)
                        {
                            startPoint = sPoint.Value;
                        }
                    }

                    // If the time passed in is after the end of the gps track, get the last value
                    if (activity.GPSRoute.StartTime.AddSeconds(activity.GPSRoute[activity.GPSRoute.Count - 1].ElapsedSeconds) < inEndTime)
                    {
                        endPoint = activity.GPSRoute[activity.GPSRoute.Count - 1].Value;
                    }
                    else
                    {
                        // Set the end point
                        ITimeValueEntry <IGPSPoint> ePoint = activity.GPSRoute.GetInterpolatedValue(inEndTime);
                        if (ePoint != null)
                        {
                            endPoint = ePoint.Value;
                        }
                    }
                }


                // Create the GPSRoute
                for (int i = 0; i < activity.GPSRoute.Count; i++)
                {
                    if (activity.GPSRoute.StartTime.AddSeconds(activity.GPSRoute[i].ElapsedSeconds) >= inStartTime &&
                        activity.GPSRoute.StartTime.AddSeconds(activity.GPSRoute[i].ElapsedSeconds) <= inEndTime)
                    {
                        recordGPS.Add(activity.GPSRoute.StartTime.AddSeconds(activity.GPSRoute[i].ElapsedSeconds), activity.GPSRoute[i].Value);
                    }
                }
            }

            // Create the Distance Track
            INumericTimeDataSeries allDistanceTrack  = ai.MovingDistanceMetersTrack; // Utilities.GetDistanceTrack(activity);
            INumericTimeDataSeries allElevationTrack = ai.SmoothedElevationTrack;    // Utilities.GetElevationTrack(activity);

            // Work your way through the moving meters track to create all others
            if (allDistanceTrack != null)
            {
                for (int i = 0; i < allDistanceTrack.Count; i++)
                {
                    DateTime time = allDistanceTrack.StartTime.AddSeconds(allDistanceTrack[i].ElapsedSeconds);
                    if (time >= inStartTime &&
                        time <= inEndTime)
                    {
                        // Add the distance point
                        distTrack.Add(time, allDistanceTrack[i].Value);
                        ITimeValueEntry <float> point = null;

                        // Find the elevation point at this time and add it
                        if (allElevationTrack != null && allElevationTrack.Count > 0)
                        {
                            point = allElevationTrack.GetInterpolatedValue(time);
                            if (point != null)
                            {
                                elevTrack.Add(time, point.Value);
                            }
                        }

                        // Find the HR point at this time and add it
                        if (activity.HeartRatePerMinuteTrack != null && activity.HeartRatePerMinuteTrack.Count > 0)
                        {
                            point = activity.HeartRatePerMinuteTrack.GetInterpolatedValue(time);
                            if (point != null)
                            {
                                recordHRTrack.Add(time, point.Value);
                            }
                        }

                        // Find the power point at this time and add it
                        if (activity.PowerWattsTrack != null && activity.PowerWattsTrack.Count > 0)
                        {
                            point = activity.PowerWattsTrack.GetInterpolatedValue(time);
                            if (point != null)
                            {
                                pwrTrack.Add(time, point.Value);
                            }
                        }

                        // Find the cadence point at this time and add it
                        if (activity.CadencePerMinuteTrack != null && activity.CadencePerMinuteTrack.Count > 0)
                        {
                            point = activity.CadencePerMinuteTrack.GetInterpolatedValue(time);
                            if (point != null)
                            {
                                cadTrack.Add(time, point.Value);
                            }
                        }
                    }
                    else if (allDistanceTrack.StartTime.AddSeconds(allDistanceTrack[i].ElapsedSeconds) > inEndTime)
                    {
                        break;
                    }
                }
            }

            // Get the start/end distance
            if (distTrack != null && distTrack.Count > 0)
            {
                startDistance = distTrack[0].Value;
                endDistance   = distTrack[distTrack.Count - 1].Value;
            }
            else
            {
                startDistance = 0;
                endDistance   = 0;
            }

            // Get the start/end elevation
            if (elevTrack != null && elevTrack.Count > 0)
            {
                startElevation = elevTrack[0].Value;
                endElevation   = elevTrack[elevTrack.Count - 1].Value;
            }
            else
            {
                startElevation = 0;
                endElevation   = 0;
            }

            // Build the record
            record = new Record(activity, category, recordGPS, recordHRTrack, pwrTrack, cadTrack, distTrack, elevTrack, inStartTime);

            // Create a reference id for this hill
            refId = Guid.NewGuid().ToString("D");

            double distanceX = endDistance - startDistance;

            distance = distanceX;

            double elev = endElevation - startElevation;

            elevGain = elev;

            // Find the start percents from the distance track
            if (allDistanceTrack != null && allDistanceTrack.Count > 0)
            {
                startPercentDistance = startDistance / allDistanceTrack[allDistanceTrack.Count - 1].Value;
                endPercentDistance   = endDistance / allDistanceTrack[allDistanceTrack.Count - 1].Value;

                startPercentTime = ((inStartTime - allDistanceTrack.StartTime).TotalSeconds / allDistanceTrack[allDistanceTrack.Count - 1].ElapsedSeconds);
                endPercentTime   = ((inEndTime - allDistanceTrack.StartTime).TotalSeconds / allDistanceTrack[allDistanceTrack.Count - 1].ElapsedSeconds);
            }

            // Calculate the VAM (Velocity Ascended, Meters per hour)
            // Calculate the W/kg (Relative power)
            vam = 0;
            wKg = 0;
            if (elevGain > 0)
            {
                vam = (elevGain * 60f * 60f) / record.TotalTime.TotalSeconds;
                wKg = vam / ((2 + (avgGrade * 10f)) * 100f);
            }

            ActivityInfo aiRec = ActivityInfoCache.Instance.GetInfo(record.Activity);

            stoppedTime = aiRec.TimeNotMoving;
        }
Example #12
0
        protected void AddActivities(IImportResults importResults, IList <GlobalsatPacket.Train> trains, bool importSpeedTrackAsDistance, int detectPauses, int verbose)
        {
            foreach (GlobalsatPacket.Train train in trains)
            {
                DateTime  pointTime = train.StartTime;
                IActivity activity  = importResults.AddActivity(pointTime);
                activity.Metadata.Source            = string.Format(CommonResources.Text.Devices.ImportJob_ActivityImportSource, sourceDescription);
                activity.TotalTimeEntered           = train.TotalTime;
                activity.TotalDistanceMetersEntered = train.TotalDistanceMeters;
                activity.TotalCalories = train.TotalCalories;
                activity.MaximumHeartRatePerMinuteEntered = train.MaximumHeartRate;
                activity.AverageHeartRatePerMinuteEntered = train.AverageHeartRate;
                activity.MaximumCadencePerMinuteEntered   = train.MaximumCadence;
                activity.AverageCadencePerMinuteEntered   = train.AverageCadence;
                activity.MaximumPowerWattsEntered         = train.MaximumPower;
                activity.AveragePowerWattsEntered         = train.AveragePower;
                activity.TotalAscendMetersEntered         = train.TotalAscend;
                activity.TotalDescendMetersEntered        = -train.TotalDescend;
                activity.Notes += train.Comment;

                bool foundGPSPoint     = false;
                bool foundCadencePoint = false;
                bool foundPowerPoint   = false;

                activity.GPSRoute = new GPSRoute();
                activity.HeartRatePerMinuteTrack = new NumericTimeDataSeries();
                activity.CadencePerMinuteTrack   = new NumericTimeDataSeries();
                activity.PowerWattsTrack         = new NumericTimeDataSeries();
                activity.TemperatureCelsiusTrack = new NumericTimeDataSeries();
                activity.ElevationMetersTrack    = new NumericTimeDataSeries();
                activity.DistanceMetersTrack     = new DistanceDataTrack();

                float  pointDist    = 0;
                double pointElapsed = 0;
                //As interval to first is not zero, add point
                activity.DistanceMetersTrack.Add(pointTime, pointDist);

                //Fix for (GB-580 only?) recording problem with interval 10 or larger (in fw before 2012-09)
                double?fixInterval = null;
                if (train.TrackPoints.Count > 1)
                {
                    //All points except last has 0.1s interval
                    double testIntervall = (train.TotalTime.TotalSeconds - train.TrackPoints[train.TrackPoints.Count - 1].IntervalTime) / (train.TrackPointCount - 1 - 1);
                    if (testIntervall > 9.6)
                    {
                        fixInterval = testIntervall;
                    }
                }

                DateTime pointTimePrev    = System.DateTime.Now;
                DateTime pointTimeNext    = System.DateTime.Now;
                bool     insertPauseFirst = false;
                bool     insertPauseLast  = false;
                foreach (GhPacketBase.TrackPoint point in train.TrackPoints)
                {
                    double time = point.IntervalTime;
                    if (time < 0.11 && fixInterval != null)
                    {
                        time = (double)fixInterval;
                    }
                    pointElapsed += time;
                    //Note: There is an intervall time to the first point, also if TGP says it is 0
                    pointTime = pointTime.AddSeconds(time);

                    float dist = (float)(point.Speed * time);
                    pointDist += dist;

                    // TODO: How are GPS points indicated in indoor activities?
                    //It seems like all are the same
                    IGPSPoint gpsPoint = new GPSPoint((float)point.Latitude, (float)point.Longitude, point.Altitude);

                    //There are no pause markers in the Globalsat protocol
                    //Insert pauses when estimated/listed distance differs "too much"
                    //Guess pauses - no info of real pause, but this can at least be marked in the track
                    //Share setting with global split
                    if (foundGPSPoint && activity.GPSRoute.Count > 0)
                    {
                        //estimated time for the pause
                        double estimatedSec = 0;
                        string info         = "";

                        //speed & cadence method
                        if (detectPauses == 2 && !float.IsNaN(point.Cadence))
                        {
                            if (activity.GPSRoute.Count > 0)
                            {
                                float gpsDist = gpsPoint.DistanceMetersToPoint(activity.GPSRoute[activity.GPSRoute.Count - 1].Value);
                                //Some limit on when to include pause
                                //gpsDist must be higher than (all) GPS errors
                                if (point.Cadence == 0 && point.Speed == 0 && gpsDist > 0 && insertPauseFirst == false)
                                {
                                    insertPauseFirst = true;
                                    //We cannot know the true time for the expected pause, just set time between as pause to show on map
                                    estimatedSec  = time;
                                    pointTimePrev = pointTime;
                                }
                                else if (point.Cadence != 0 && point.Speed != 0 && insertPauseFirst)
                                {
                                    //last point
                                    insertPauseLast = true;
                                    pointTimeNext   = pointTime;
                                }
                            }

                            if (insertPauseLast)
                            {
                                insertPauseFirst = false;
                                insertPauseLast  = false;

                                //Only add rounded pauses, ST only handles complete seconds
                                activity.TimerPauses.Add(new ValueRange <DateTime>(
                                                             pointTimePrev.AddMilliseconds(-pointTimePrev.Millisecond),
                                                             pointTimeNext.AddMilliseconds(-pointTimeNext.Millisecond)));
                                if (verbose >= 10)
                                {
                                    //TODO: Remove remark when stable
                                    activity.Notes += string.Format("Added pause from {0} to {1} (dist:{2}, elapsedSec:{3} {4}) ",
                                                                    pointTimePrev.ToLocalTime(), pointTimeNext.ToLocalTime(), dist, pointTimeNext.Subtract(pointTimePrev).TotalSeconds, info) +
                                                      System.Environment.NewLine;
                                }
                            }
                        }
                        else if (detectPauses == 1)
                        {   //distance method
                            bool insertPause = false;
                            //how far from the first point
                            double perc = 0;

                            //This code previously had code to detect pauses from HR too
                            if (activity.GPSRoute.Count > 0)
                            {
                                float gpsDist = gpsPoint.DistanceMetersToPoint(activity.GPSRoute[activity.GPSRoute.Count - 1].Value);
                                //Some limit on when to include pause
                                //gpsDist must be higher than (all) GPS errors
                                if (gpsDist > 100 && gpsDist > 3 * dist)
                                {
                                    insertPause = true;
                                    //We cannot know the true time for the expected pause, just set time between as pause to show on map
                                    estimatedSec = time;

                                    if (gpsDist > 0)
                                    {
                                        perc = dist / gpsDist;
                                    }
                                    info += "gps: " + gpsDist;
                                }
                            }

                            if (insertPause)
                            {
                                //Use complete seconds only - pause is estimated, ST handles sec internally and this must be synced to laps
                                estimatedSec = Math.Round(estimatedSec);
                                IGPSPoint newPoint = (new GPSPoint.ValueInterpolator()).Interpolate(
                                    activity.GPSRoute[activity.GPSRoute.Count - 1].Value, gpsPoint, perc);

                                if (point == train.TrackPoints[train.TrackPoints.Count - 1])
                                {
                                    //Last point is incorrect, adjust (added normally)
                                    gpsPoint = newPoint;
                                }
                                else
                                {
                                    if (estimatedSec <= time + 1)
                                    {
                                        pointTimePrev = pointTime.AddSeconds(-time);
                                        pointTimeNext = pointTime;
                                    }
                                    else
                                    {
                                        //Add extra point
                                        activity.DistanceMetersTrack.Add(pointTime, pointDist);

                                        if (point.Latitude != 0 || point.Longitude != 0)
                                        {
                                            activity.GPSRoute.Add(pointTime, newPoint);
                                        }
                                        else if (device.FitnessDevice.HasElevationTrack && !float.IsNaN(newPoint.ElevationMeters))
                                        {
                                            activity.ElevationMetersTrack.Add(pointTime, newPoint.ElevationMeters);
                                        }
                                        pointTimePrev = pointTime;
                                        pointTimeNext = pointTime.AddSeconds((int)(estimatedSec - time));
                                        pointTime     = pointTimeNext;
                                    }
                                    //Only add rounded pauses, ST only handles complete seconds
                                    activity.TimerPauses.Add(new ValueRange <DateTime>(
                                                                 pointTimePrev.AddMilliseconds(-pointTimePrev.Millisecond + 1000),
                                                                 pointTimeNext.AddMilliseconds(-pointTimeNext.Millisecond - 9000)));
                                    if (verbose >= 10)
                                    {
                                        //TODO: Remove remark when stable
                                        activity.Notes += string.Format("Added pause from {0} to {1} (dist:{2}, elapsedSec:{3}, per:{4} {5}) ",
                                                                        pointTimePrev.ToLocalTime(), pointTimeNext.ToLocalTime(), dist, time, perc, info) +
                                                          System.Environment.NewLine;
                                    }
                                }
                            }
                        }
                    }
                    activity.DistanceMetersTrack.Add(pointTime, pointDist);

                    //lat/lon is 0 if a device has never had a fix
                    if (point.Latitude != 0 || point.Longitude != 0)
                    {
                        activity.GPSRoute.Add(pointTime, gpsPoint);

                        //Check if lat/lon ever change (ignore altitude), GlobalSat reports last known location without a fix
                        if (point.Latitude != train.TrackPoints[0].Latitude || point.Longitude != train.TrackPoints[0].Longitude)
                        {
                            foundGPSPoint = true;
                        }
                    }
                    if (device.FitnessDevice.HasElevationTrack && !float.IsNaN(point.Altitude))
                    {
                        activity.ElevationMetersTrack.Add(pointTime, point.Altitude);
                    }

                    //zero HR is invalid reading - drop
                    if (point.HeartRate > 0)
                    {
                        activity.HeartRatePerMinuteTrack.Add(pointTime, point.HeartRate);
                    }

                    //Zero Cadence/Power may be valid values, if there are any values (no way to detect lost communication)
                    activity.CadencePerMinuteTrack.Add(pointTime, point.Cadence);
                    if (point.Cadence > 0)
                    {
                        foundCadencePoint = true;
                    }

                    activity.PowerWattsTrack.Add(pointTime, point.Power);
                    if (point.Power > 0)
                    {
                        foundPowerPoint = true;
                    }

                    if (point.Temperature != 0x7fff)
                    {
                        activity.TemperatureCelsiusTrack.Add(pointTime, point.Temperature / 10.0F);
                    }
                }

                TimeSpan lapElapsed    = TimeSpan.Zero;
                int      totalDistance = 0;
                foreach (GlobalsatPacket.Lap lapPacket in train.Laps)
                {
                    DateTime lapTime = ZoneFiveSoftware.Common.Data.Algorithm.DateTimeRangeSeries.AddTimeAndPauses(activity.StartTime, lapElapsed, activity.TimerPauses);
                    lapElapsed += lapPacket.LapTime; //Same as lapPacket.EndTime for unpaused
                    ILapInfo lap = activity.Laps.Add(lapTime, lapPacket.LapTime);
                    //Adding Distance would previously make ST fail to add new laps. The distance is needed only when there is no GPS (Markers added)
                    if (activity.TotalDistanceMetersEntered > 0)
                    {
                        lap.TotalDistanceMeters = lapPacket.LapDistanceMeters;
                    }
                    if (lapPacket.LapCalories > 0)
                    {
                        lap.TotalCalories = lapPacket.LapCalories;
                    }
                    if (lapPacket.AverageHeartRate > 0)
                    {
                        lap.AverageHeartRatePerMinute = lapPacket.AverageHeartRate;
                    }
                    if (lapPacket.AverageCadence > 0)
                    {
                        lap.AverageCadencePerMinute = lapPacket.AverageCadence;
                    }
                    if (lapPacket.AveragePower > 0)
                    {
                        lap.AveragePowerWatts = lapPacket.AveragePower;
                    }
                    if (!foundGPSPoint && activity.ElevationMetersTrack != null && activity.ElevationMetersTrack.Count > 1)
                    {
                        //Limitation in ST: lap elevation not auto calc without GPS, lap preferred to elevation
                        DateTime lapEnd            = ZoneFiveSoftware.Common.Data.Algorithm.DateTimeRangeSeries.AddTimeAndPauses(activity.StartTime, lapElapsed, activity.TimerPauses);
                        ITimeValueEntry <float> p1 = activity.ElevationMetersTrack.GetInterpolatedValue(lapTime);
                        ITimeValueEntry <float> p2 = activity.ElevationMetersTrack.GetInterpolatedValue(lapEnd);
                        if (p1 != null && p2 != null)
                        {
                            lap.ElevationChangeMeters = p2.Value - p1.Value;
                        }
                    }
                    if (verbose >= 5)
                    {
                        //TODO: Localise outputs?
                        lap.Notes = string.Format("MaxSpeed:{0:0.##}m/s MaxHr:{1} MinAlt:{2}m MaxAlt:{3}m",
                                                  lapPacket.MaximumSpeed, lapPacket.MaximumHeartRate, lapPacket.MinimumAltitude, lapPacket.MaximumAltitude);
                        //Not adding Power/Cadence - not available
                        //lap.Notes = string.Format("MaxSpeed={0} MaxHr={1} MinAlt={2} MaxAlt={3} MaxCadence={4} MaxPower={5}",
                        //    lapPacket.MaximumSpeed, lapPacket.MaximumHeartRate, lapPacket.MinimumAltitude, lapPacket.MaximumAltitude, lapPacket.MaximumCadence, lapPacket.MaximumPower);
                    }
                    //Add distance markers from Globalsat. Will for sure be incorrect after pause insertion
                    totalDistance += lapPacket.LapDistanceMeters;
                    activity.DistanceMarkersMeters.Add(totalDistance);
                }

                if (!foundGPSPoint)
                {
                    if (activity.GPSRoute.Count > 0)
                    {
                        activity.Notes += string.Format("No GPS. Last known latitude:{0}, longitude:{1}",
                                                        activity.GPSRoute[0].Value.LatitudeDegrees, activity.GPSRoute[0].Value.LongitudeDegrees);
                    }
                    activity.GPSRoute = null;
                }
                //Keep elevation only if the device (may) record elevation separately from GPS
                //It may be used also if the user drops GPS if points have been recorded.
                //(ST may have partial use of elevation together with GPS on other parts in the future?)
                if (!device.FitnessDevice.HasElevationTrack || activity.ElevationMetersTrack.Count == 0)
                {
                    activity.ElevationMetersTrack = null;
                }

                //Barometric devices occasionally have bad points last
                if (activity.ElevationMetersTrack != null && activity.ElevationMetersTrack.Count > 1 &&
                    Math.Abs(activity.ElevationMetersTrack[activity.ElevationMetersTrack.Count - 1].Value -
                             activity.ElevationMetersTrack[activity.ElevationMetersTrack.Count - 2].Value) > 1)
                {
                    if (activity.GPSRoute != null &&
                        activity.ElevationMetersTrack.StartTime.AddSeconds(activity.ElevationMetersTrack.TotalElapsedSeconds) ==
                        activity.GPSRoute.StartTime.AddSeconds(activity.GPSRoute.TotalElapsedSeconds))
                    {
                        IGPSPoint g = activity.GPSRoute[activity.GPSRoute.Count - 1].Value;
                        activity.GPSRoute.SetValueAt(activity.GPSRoute.Count - 1, new GPSPoint(g.LatitudeDegrees, g.LongitudeDegrees, float.NaN));
                    }
                    activity.ElevationMetersTrack.RemoveAt(activity.ElevationMetersTrack.Count - 1);
                }
                if (activity.HeartRatePerMinuteTrack.Count == 0)
                {
                    activity.HeartRatePerMinuteTrack = null;
                }
                if (!foundCadencePoint)
                {
                    activity.CadencePerMinuteTrack = null;
                }
                if (!foundPowerPoint)
                {
                    activity.PowerWattsTrack = null;
                }
                if (activity.TemperatureCelsiusTrack.Count == 0)
                {
                    activity.TemperatureCelsiusTrack = null;
                }
                if (pointDist == 0 || !importSpeedTrackAsDistance && foundGPSPoint)
                {
                    activity.DistanceMetersTrack = null;
                }
#if DISTANCETRACK_FIX
                //attempt to fix import of distance track in 3.1.4515 - to be updated when rereleased
                //for now import distance track, to keep compatibility(?)
                try
                {
                    activity.CalcSpeedFromDistanceTrack = importSpeedTrackAsDistance;
                }
                catch
                {
                    //older than 3.1.4515, disable
                    if (!importSpeedTrackAsDistance && !foundGPSPoint)
                    {
                        activity.DistanceMetersTrack = null;
                    }
                }
#endif
            }
        }
Example #13
0
        /// <summary>
        /// Create a grade track from an activity's elevation track
        /// </summary>
        /// <param name="activity">Activity that needs an grade track</param>
        /// <returns>A grade track created from the elevation track</returns>
        public static INumericTimeDataSeries GetGradeTrack(IActivity activity)
        {
            INumericTimeDataSeries gradeTrack     = new NumericTimeDataSeries();
            INumericTimeDataSeries elevationTrack = new NumericTimeDataSeries();
            INumericTimeDataSeries distanceTrack  = new NumericTimeDataSeries();

            elevationTrack = GetElevationTrack(activity);
            distanceTrack  = GetDistanceMovingTrack(activity);
            float    grade            = 0;
            float    lastElevation    = 0;
            float    lastDistance     = 0;
            float    currentElevation = 0;
            float    currentDistance  = 0;
            DateTime startTime        = activity.StartTime;

            if (gradeTrack != null && elevationTrack != null && distanceTrack != null)
            {
                for (int i = 0; i < elevationTrack.Count; i++)
                {
                    if (i == 0)
                    {
                        gradeTrack.Add(startTime, grade);
                        lastElevation = elevationTrack[i].Value;
                        ITimeValueEntry <float> point = distanceTrack.GetInterpolatedValue(startTime.AddSeconds(elevationTrack[i].ElapsedSeconds));
                        if (point != null)
                        {
                            lastDistance = point.Value;
                        }
                    }
                    else
                    {
                        ITimeValueEntry <float> point = distanceTrack.GetInterpolatedValue(startTime.AddSeconds(elevationTrack[i].ElapsedSeconds));
                        if (point != null)
                        {
                            currentDistance = point.Value;
                        }
                        else
                        {
                            currentDistance = lastDistance;
                        }

                        currentElevation = elevationTrack[i].Value;
                        grade            = (currentElevation - lastElevation) / (currentDistance - lastDistance);
                        if (float.IsNaN(grade) || float.IsInfinity(grade))
                        {
                            grade = 0;
                        }
                        gradeTrack.Add(startTime.AddSeconds(elevationTrack[i].ElapsedSeconds), grade);

                        lastDistance  = currentDistance;
                        lastElevation = currentElevation;
                    }
                }

                return(gradeTrack);
            }
            else
            {
                return(null);
            }
        }
Example #14
0
        internal static INumericTimeDataSeries STSmooth(INumericTimeDataSeries data, int seconds, out float min, out float max)
        {
            min = float.NaN;
            max = float.NaN;
            if (data != null)
            {
                if (data.Count == 0)
                {
                    // Special case, no data
                    return(new ZoneFiveSoftware.Common.Data.NumericTimeDataSeries());
                }
                else if (data.Count == 1 || seconds < 1)
                {
                    // Special case
                    INumericTimeDataSeries copyData = new ZoneFiveSoftware.Common.Data.NumericTimeDataSeries();
                    min = data[0].Value;
                    max = data[0].Value;
                    foreach (ITimeValueEntry <float> entry in data)
                    {
                        copyData.Add(data.StartTime.AddSeconds(entry.ElapsedSeconds), entry.Value);
                        min = Math.Min(min, entry.Value);
                        max = Math.Max(max, entry.Value);
                    }
                    return(copyData);
                }
                min = float.MaxValue;
                max = float.MinValue;
                int smoothWidth = Math.Max(0, seconds * 2); // Total width/period.  'seconds' is the half-width... seconds on each side to smooth
                int denom       = smoothWidth * 2;          // Final value to divide by.  It's divide by 2 because we're double-adding everything
                INumericTimeDataSeries smoothedData = new ZoneFiveSoftware.Common.Data.NumericTimeDataSeries();

                // Loop through entire dataset
                for (int nEntry = 0; nEntry < data.Count; nEntry++)
                {
                    ITimeValueEntry <float> entry = data[nEntry];
                    // TODO: Don't reset value & index markers, instead continue data here...
                    double value = 0;
                    double delta;
                    // Data prior to entry
                    long secondsRemaining = seconds;
                    ITimeValueEntry <float> p1, p2;
                    int increment = -1;
                    int pos = nEntry - 1;
                    p2 = data[nEntry];


                    while (secondsRemaining > 0 && pos >= 0)
                    {
                        p1 = data[pos];
                        if (SumValues(p2, p1, ref value, ref secondsRemaining))
                        {
                            pos += increment;
                            p2   = p1;
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (secondsRemaining > 0)
                    {
                        // Occurs at beginning of track when period extends before beginning of track.
                        delta  = data[0].Value * secondsRemaining * 2;
                        value += delta;
                    }
                    // Data after entry
                    secondsRemaining = seconds;
                    increment        = 1;
                    pos = nEntry;
                    p1  = data[nEntry];
                    while (secondsRemaining > 0 && pos < data.Count - 1)
                    {
                        p2 = data[pos + 1];
                        if (SumValues(p1, p2, ref value, ref secondsRemaining))
                        {
                            // Move to next point
                            pos += increment;
                            p1   = p2;
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (secondsRemaining > 0)
                    {
                        // Occurs at end of track when period extends past end of track
                        value += data[data.Count - 1].Value * secondsRemaining * 2;
                    }
                    float entryValue = (float)(value / denom);
                    smoothedData.Add(data.StartTime.AddSeconds(entry.ElapsedSeconds), entryValue);
                    min = Math.Min(min, entryValue);
                    max = Math.Max(max, entryValue);

                    // TODO: Remove 'first' p1 & p2 SumValues from 'value'
                    if (data[nEntry].ElapsedSeconds - seconds < 0)
                    {
                        // Remove 1 second worth of first data point (multiply by 2 because everything is double here)
                        value -= data[0].Value * 2;
                    }
                    else
                    {
                        // Remove data in middle of track (typical scenario)
                        //value -=
                    }
                }
                return(smoothedData);
            }
            else
            {
                return(null);
            }
        }