/// <summary> /// Adds track to cache for later recall. Replaces cache with new /// data track, if track already exists in cache. /// </summary> /// <param name="track">Data track to cache</param> /// <param name="refId">Unique Id of track</param> internal static void AddTrack(INumericTimeDataSeries track, string refId) { if (tracks.ContainsKey(refId)) { tracks.Remove(refId); } tracks.Add(refId, track); }
//Add a point last in the track (no check), override if point already exists internal static void trackAdd(INumericTimeDataSeries track, DateTime time, float val) { track.Add(time, val); //Check if the entry already existed, overwrite if necessary if (track[track.Count - 1].Value != val) { //(uint)(this.EndTime - this.StartTime.AddSeconds(-this.StartTime.Second)).TotalSeconds == m_activityDistanceMetersTrack[i - 1].ElapsedSeconds) track.SetValueAt(track.Count - 1, val); } }
/// <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); }
public double GetRatioVsAvgValue(List <RangeInfoCacheWrapper> allRanges) { if (allRanges.Count > 0 && allRanges[0].ActivityPauselessCadenceTrack.Count > 0) { INumericTimeDataSeries activityGears = ActivityGearTrackCache.Instance.CalculateGearTrack(allRanges[0].Activity); return(GetAvgRatioValue(allRanges) - activityGears.Avg); } return(0); }
/// <summary> /// Convert a track from speed to pace. Length units are maintained. Assumes input is in hours (mph, km/hr), and converts to seconds base (sec/mile, sec/km). /// </summary> /// <param name="track"></param> /// <returns></returns> public static INumericTimeDataSeries SpeedToPace(INumericTimeDataSeries track) { INumericTimeDataSeries result = new NumericTimeDataSeries(track); foreach (TimeValueEntry <float> item in result) { // Convert to pace if required item.Value = (float)Utilities.SpeedToPace(item.Value) * Common.SecondsPerMinute; } return(result); }
/// <summary> /// Convert a track data from meters to (toUnits) /// </summary> /// <param name="track">Input track</param> /// <param name="toUnits">Desired units</param> /// <returns>Converted track</returns> public static INumericTimeDataSeries ConvertDistanceUnits(INumericTimeDataSeries track, Length.Units toUnits) { INumericTimeDataSeries result = new NumericTimeDataSeries(track); foreach (TimeValueEntry <float> item in result) { // Convert to proper distance units from m/s item.Value = (float)Length.Convert(item.Value, Length.Units.Meter, toUnits) * Common.SecondsPerHour; } return(result); }
public static INumericTimeDataSeries GetRawGearTrack(INumericTimeDataSeries cadenceTrack, INumericTimeDataSeries distanceTrack) { if (cadenceTrack == null || cadenceTrack.Count == 0 || distanceTrack == null || distanceTrack.Count == 0) { return(new NumericTimeDataSeries()); } // Set min & max ratio for filtering data float minRatio = 0; float maxRatio = 12; double speed, deltaDist, deltaSeconds, cadence; float mPerRev; DateTime currTime, prevTime; ITimeValueEntry <float> currPoint, prevPoint; INumericTimeDataSeries gearSelection = new NumericTimeDataSeries(); // This loops through the cadence track, and interpolates the distance track // This is in case the distance track and cadence track don't match for whatever reason. // Cadence was chosen becase it will change erratically, while distance track should be comparitavely smooth. for (int i = 1; i < cadenceTrack.Count; i++) { // Store current point time prevTime = cadenceTrack.EntryDateTime(cadenceTrack[i - 1]); currTime = cadenceTrack.EntryDateTime(cadenceTrack[i]); prevPoint = distanceTrack.GetInterpolatedValue(prevTime); currPoint = distanceTrack.GetInterpolatedValue(currTime); if (prevPoint != null && currPoint != null) { deltaDist = currPoint.Value - prevPoint.Value; deltaSeconds = (currTime - prevTime).TotalSeconds; speed = deltaDist / deltaSeconds; cadence = cadenceTrack[i].Value; mPerRev = (float)(speed / cadence * 60); /* Filter bad values * 1) Not pedaling: cad == 0 * 2) Bad GPS (unlikely): deltaDist == 0 * 3) Out of bounds: minRatio < gearSelection < maxRatio */ if (cadence != 0 && deltaDist != 0 && mPerRev > minRatio && mPerRev < maxRatio) { gearSelection.Add(currTime, mPerRev); } } } return(gearSelection); }
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); }
private void UpdateCachedGearTrack(IActivity activity, INumericTimeDataSeries gearTrack) { ActivityGearTrackCacheItem newCacheItem = new ActivityGearTrackCacheItem(activity); if (!m_InfoCache.ContainsKey(activity)) { m_InfoCache.Add(activity, null); activity.PropertyChanged += new PropertyChangedEventHandler(OnActivityDataChanged); } m_InfoCache[activity] = newCacheItem; m_InfoCache[activity].m_GearTrack = gearTrack; }
/// <summary> /// Determines whether or not a series contains a particular time entry /// </summary> /// <param name="series"></param> /// <param name="time"></param> /// <returns></returns> public static bool ContainsTime(INumericTimeDataSeries series, DateTime time) { if (series != null && series.Count > 0 && series.StartTime <= time && series.StartTime.AddSeconds(series.TotalElapsedSeconds) >= time) { return(true); } else { return(false); } }
/// <summary> /// Add 'track' to 'graph' and apply labels based on 'chartType' /// </summary> /// <param name="track">Data track</param> /// <param name="graph">Which graph to stick the data on</param> /// <param name="chartType">This determines the labeling, coloring, etc. (all appearance related)</param> internal static void updateZedGraph(INumericTimeDataSeries track, ZedGraphControl graph, Common.ChartBasis chartType) { GraphPane myPane = graph.GraphPane; myPane.XAxis.Title.Text = CommonResources.Text.LabelTime; myPane.XAxis.Type = AxisType.Log; Color mainCurveColor = Color.FromArgb(204, 0, 0); switch (chartType) { case Common.ChartBasis.Cadence: mainCurveColor = Common.ColorCadence; myPane.YAxis.Title.Text = CommonResources.Text.LabelCadence; break; case Common.ChartBasis.HR: mainCurveColor = Common.ColorHR; myPane.YAxis.Title.Text = CommonResources.Text.LabelHeartRate + " " + CommonResources.Text.LabelBPM; break; case Common.ChartBasis.Power: mainCurveColor = Common.ColorPower; myPane.YAxis.Title.Text = CommonResources.Text.LabelPower; break; } myPane.XAxis.MinorTic.IsOutside = true; PointPairList zedTrack = new PointPairList(); foreach (ITimeValueEntry <float> item in track) { zedTrack.Add(item.ElapsedSeconds, item.Value); } myPane.CurveList.Clear(); LineItem curve = myPane.AddCurve("Curve Label", zedTrack, mainCurveColor, SymbolType.None); curve.Line.Width = 2f; curve.Line.Fill.Type = FillType.Solid; curve.Line.Fill.Color = Color.FromArgb(50, mainCurveColor); myPane.YAxis.Title.FontSpec.FontColor = mainCurveColor; myPane.YAxis.Scale.FontSpec.FontColor = mainCurveColor; graph.AxisChange(); graph.Refresh(); }
/// <summary> /// Multiple each value in a data series by 100. /// </summary> /// <param name="track"></param> /// <returns></returns> public static INumericTimeDataSeries GetPercentTrack(INumericTimeDataSeries track) { if (track == null || track.Count == 0) { return(track); } NumericTimeDataSeries percent = new NumericTimeDataSeries(track); foreach (TimeValueEntry <float> point in percent) { point.Value = point.Value * 100f; } return(percent); }
public static float GetSyncGraphOffset(INumericTimeDataSeries graphPoints, INumericTimeDataSeries refGraphPoints, SyncGraphMode syncGraph) { float syncGraphOffset = 0; if (graphPoints != refGraphPoints && refGraphPoints != null && refGraphPoints.Count > 1 && graphPoints != null && graphPoints.Count > 1) { switch (syncGraph) { case SyncGraphMode.None: break; case SyncGraphMode.Start: syncGraphOffset = refGraphPoints[0].Value - graphPoints[0].Value; break; case SyncGraphMode.End: syncGraphOffset = refGraphPoints[refGraphPoints.Count - 1].Value - graphPoints[graphPoints.Count - 1].Value; break; case SyncGraphMode.Average: syncGraphOffset = refGraphPoints.Avg - graphPoints.Avg; break; case SyncGraphMode.Min: syncGraphOffset = refGraphPoints.Min - graphPoints.Min; break; case SyncGraphMode.Max: syncGraphOffset = refGraphPoints.Max - graphPoints.Max; break; default: { Debug.Assert(false, string.Format("Unexpecteded SyncGraphMode {0}", syncGraph)); break; } } if (float.IsNaN(syncGraphOffset) || float.IsInfinity(syncGraphOffset)) { syncGraphOffset = 0; } } return(syncGraphOffset); }
public static INumericTimeDataSeries SetTrackStartValueToZero(INumericTimeDataSeries inTrack) { if (inTrack == null || inTrack.Count == 0) { return(inTrack); } NumericTimeDataSeries newTrack = new NumericTimeDataSeries(inTrack); float firstPoint = inTrack[0].Value; foreach (TimeValueEntry <float> point in newTrack) { point.Value = point.Value - firstPoint; } return(newTrack); }
/// <summary> /// Populate a dataseries with data from a NumericTimeDataSeries /// </summary> /// <param name="timeDataSeries">NumericTimeDataSeries containing data to add to chart data series</param> /// <param name="chartDataSeries">Dataseries to add data to</param> /// <param name="chartType">Chart basis (time or distance)</param> /// <returns>Populated dataseries</returns> private static ChartDataSeries PopulateDataSeries(INumericTimeDataSeries timeDataSeries, ChartDataSeries chartDataSeries, ChartBasis chartType, IActivity activity) { switch (chartType) { case ChartBasis.Gear_Distance: ActivityInfo info = ActivityInfoCache.Instance.GetInfo(activity); IDistanceDataTrack track = GearUtils.GetDistanceTrack(activity); float test = 0; foreach (ITimeValueEntry <float> entry in timeDataSeries) { DateTime time = track.EntryDateTime(entry); float distance = track.GetInterpolatedValue(time).Value; distance = (float)Length.Convert(distance, Length.Units.Meter, activity.Category.DistanceUnits); PointF point = new PointF(distance, entry.Value); if (!chartDataSeries.Points.ContainsKey(point.X)) { chartDataSeries.Points.Add(point.X, point); } else if (test != point.X) { test = point.X; } } break; case ChartBasis.Gear_Time: foreach (ITimeValueEntry <float> entry in timeDataSeries) { PointF point = new PointF(entry.ElapsedSeconds, entry.Value); chartDataSeries.Points.Add(point.X, point); } break; default: break; } return(chartDataSeries); }
/// <summary> /// Gets the Sprocket Track for a given activity. Returns an empty track if unable to calculate (for instance if no GPS or Distance track). /// </summary> /// <param name="activity"></param> /// <returns>Returns an ITimeDataSeries<SprocketCombo> of the sprocket combo used, or an empty track if nothing can be calculated.</returns> public static ITimeDataSeries <SprocketCombo> GetSprocketTrack(IActivity activity) { // Raw data INumericTimeDataSeries gearTrack = ActivityGearTrackCache.Instance.CalculateRawTrack(activity); ITimeDataSeries <SprocketCombo> sprocketTrack = new TimeDataSeries <SprocketCombo>(); if (gearTrack.Count > 0) { // Smooth data track float min, max; gearTrack = ZoneFiveSoftware.Common.Data.Algorithm.NumericTimeDataSeries.Smooth(gearTrack, Constants.GearTrackSmoothing, out max, out min); // Estimate/round gear track string id = Options.Instance.GetGearEquipmentId(activity); List <SprocketCombo> sprockets = Options.Instance.GetSprocketCombos(id); sprocketTrack = GearUtils.GuessSprockets(gearTrack, sprockets); } return(sprocketTrack); }
public static INumericTimeDataSeries RemoveBadTrackData(INumericTimeDataSeries track, double lowValue, double highValue) { if (track != null && track.Count > 0) { INumericTimeDataSeries newSeries = new NumericTimeDataSeries(); for (int i = 0; i < track.Count; i++) { // If the track data is in the high/low bounds, add it to the new series if (track[i].Value <highValue && track[i].Value> lowValue) { newSeries.Add(track.StartTime.AddSeconds(track[i].ElapsedSeconds), track[i].Value); } } return(newSeries); } else { return(null); } }
private void ExportButton_Click(object sender, EventArgs e) { // TODO: How to export multiple activities...? IActivity activity = ZoneFiveSoftware.Common.Visuals.Util.CollectionUtils.GetFirstItemOfType <IActivity>(activities); // Nothing to export if activity is empty if (activity == null) { return; } // Open File Save dialog to create new CSV Document SaveFileDialog saveFile = new SaveFileDialog(); saveFile.FileName = "MeanMax " + activity.StartTime.ToLocalTime().ToString("yyyy-MM-dd"); saveFile.Filter = "All Files (*.*)|*.*|Comma Separated Values (*.csv)|*.csv"; saveFile.FilterIndex = 2; saveFile.DefaultExt = "csv"; saveFile.OverwritePrompt = true; string comma = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator; // Cancel if user doesn't select a file if (saveFile.ShowDialog() != DialogResult.OK) { return; } // Export mean-max data // TODO: Export related data as well as MM data INumericTimeDataSeries timeTrack; INumericTimeDataSeries track = MeanMaxCache.GetMeanMaxTrack(activity, ChartType, out timeTrack); //Dictionary<INumericTimeDataSeries, string> tracks = new Dictionary<INumericTimeDataSeries, string>(); //tracks.Add(track, GlobalSettings.Instance.PrimaryChart.ToString()); //tracks.Add(timeTrack, CommonResources.Text.LabelTime); //Utilities.ExportTrack(tracks, saveFile.FileName); Utilities.ExportTrack(track, saveFile.FileName); }
/// <summary> /// Create gear guess selection chart based on gear ratios /// </summary> /// <param name="input">Pre-calculated raw data to estimate from. This should already be smoothed or filtered if desired.</param> /// <returns>Gear guess NumericTimeDataSeries</returns> public static NumericTimeDataSeries GuessGears(INumericTimeDataSeries input, List <SprocketCombo> sprockets) { ITimeDataSeries <SprocketCombo> guessSprockets = GuessSprockets(input, sprockets); NumericTimeDataSeries guessSeries = new NumericTimeDataSeries(); DateTime lastTime = input.StartTime; // Iterate through entire data series, recreating the result foreach (ITimeValueEntry <SprocketCombo> item in guessSprockets) { float ratio = 0; if (item.Value != null) { ratio = item.Value.GearRatio; } guessSeries.Add(guessSprockets.EntryDateTime(item), ratio); } return(guessSeries); }
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); }
/// <summary> /// Temporary output routine. Eventually should be deleted. /// </summary> /// <param name="track"></param> internal static void ExportTrack(INumericTimeDataSeries track, string name) { try { System.IO.StreamWriter writer = new System.IO.StreamWriter(name); // Write Header writer.WriteLine("Seconds, Value"); foreach (ITimeValueEntry <float> item in track) { // Write data writer.WriteLine(item.ElapsedSeconds + ", " + item.Value); } writer.Close(); } catch { } }
public void RefreshPage() { if (Activities == null) { // Nothing to do. Exit. return; } // Update display bnrReport.Text = MeanMax.Resources.Strings.Label_MeanMax + ": " + Common.GetText(GlobalSettings.Instance.ReportChart); // Track list: One track per selected line. Dictionary <CriticalLineDefinition, INumericTimeDataSeries> trackList = new Dictionary <CriticalLineDefinition, INumericTimeDataSeries>(); List <Color> colors = Utilities.Rainbow(GlobalSettings.Instance.SelectedCriticalLines.Count, 255); progressLine = 0; foreach (CriticalLineDefinition line in GlobalSettings.Instance.CriticalPowerLines) { if (line.Selected) { // Assign rainbowed line color line.LineColor = colors[progressLine++]; // Get critical track described by this line from all Activities INumericTimeDataSeries criticalTrack = MeanMaxCache.GetCriticalTrack(Activities, GlobalSettings.Instance.ReportChart, line.Seconds); // Add track to list trackList.Add(line, criticalTrack); } } // Display tracks on graph updateZedGraph(trackList, zedChart, GlobalSettings.Instance.ReportChart); ZoomChartButton_Click(null, null); }
public static INumericTimeDataSeries GetGearTrack(IActivity activity, INumericTimeDataSeries cadenceTrack, INumericTimeDataSeries distanceTrack) { // Raw data INumericTimeDataSeries gearTrack = GearUtils.GetRawGearTrack(cadenceTrack, distanceTrack); if (gearTrack.Count > 0) { // Smooth data track float min, max; gearTrack = ZoneFiveSoftware.Common.Data.Algorithm.NumericTimeDataSeries.Smooth(gearTrack, Constants.GearTrackSmoothing, out max, out min); // Estimate/round gear track string id = Options.Instance.GetGearEquipmentId(activity); List <SprocketCombo> sprockets = Options.Instance.GetSprocketCombos(id); gearTrack = GearUtils.GuessGears(gearTrack, sprockets); return(gearTrack); } else { return(new NumericTimeDataSeries()); } }
public ITimeDataSeries <SprocketCombo> GetRangeSprocketsTrack(RangeInfoCacheWrapper range) { ITimeDataSeries <SprocketCombo> result = null; if (range.ContainsStoredInfo(m_SprocketTrackStoredInfoId) && range.RetrieveStoredInfo(m_SprocketTrackStoredInfoId) != null) { result = range.RetrieveStoredInfo(m_SprocketTrackStoredInfoId) as ITimeDataSeries <SprocketCombo>; } else { INumericTimeDataSeries gears = ActivityGearTrackCache.Instance.CalculateGearTrack(range.Activity); gears = Utils.Utils.RemovePausedTimesInTrack(gears, range.Activity); NumericTimeDataSeries tempResult = new NumericTimeDataSeries(); Utils.Utils.ExtractRangeFromDataTrack(gears, range.PauselessRange, tempResult); result = GearChart.UI.GearUtils.GuessSprockets(tempResult, Common.Data.GetSprocketCombos(range.Activity)); range.SaveStoredInfo(m_SprocketTrackStoredInfoId, result); } return(result); }
public static INumericTimeDataSeries GetSectionOfTrack(INumericTimeDataSeries track, DateTime start, DateTime end) { if (track != null && track.Count > 0) { INumericTimeDataSeries newSeries = new NumericTimeDataSeries(); for (int i = 0; i < track.Count; i++) { if (track.StartTime.AddSeconds(track[i].ElapsedSeconds) >= start && track.StartTime.AddSeconds(track[i].ElapsedSeconds) <= end) { newSeries.Add(track.StartTime.AddSeconds(track[i].ElapsedSeconds), track[i].Value); } else if (track.StartTime.AddSeconds(track[i].ElapsedSeconds) >= end) { break; } } return(newSeries); } else { return(null); } }
/// <summary> /// Removes the track pauses within a data track. /// </summary> /// <param name="sourceTrack">Data track</param> /// <param name="activity">Activity containing pause definitions</param> /// <returns>Returns the source track with the track pause times removed</returns> public static INumericTimeDataSeries RemovePausedTimesInTrack(INumericTimeDataSeries sourceTrack, IActivity activity) { ActivityInfo activityInfo = ActivityInfoCache.Instance.GetInfo(activity); if (activityInfo != null && sourceTrack != null) { if (activityInfo.NonMovingTimes.Count == 0) { return(sourceTrack); } else { INumericTimeDataSeries result = new NumericTimeDataSeries(); DateTime currentTime = sourceTrack.StartTime; IEnumerator <ITimeValueEntry <float> > sourceEnumerator = sourceTrack.GetEnumerator(); IEnumerator <IValueRange <DateTime> > pauseEnumerator = activityInfo.NonMovingTimes.GetEnumerator(); double totalPausedTimeToDate = 0; bool sourceEnumeratorIsValid; bool pauseEnumeratorIsValid; pauseEnumeratorIsValid = pauseEnumerator.MoveNext(); sourceEnumeratorIsValid = sourceEnumerator.MoveNext(); while (sourceEnumeratorIsValid) { bool addCurrentSourceEntry = true; bool advanceCurrentSourceEntry = true; // Loop to handle all pauses up to this current track point if (pauseEnumeratorIsValid) { if (currentTime >= pauseEnumerator.Current.Lower && currentTime <= pauseEnumerator.Current.Upper) { addCurrentSourceEntry = false; } else if (currentTime > pauseEnumerator.Current.Upper) { // Advance pause enumerator totalPausedTimeToDate += (pauseEnumerator.Current.Upper - pauseEnumerator.Current.Lower).TotalSeconds; pauseEnumeratorIsValid = pauseEnumerator.MoveNext(); // Make sure we retry with the next pause addCurrentSourceEntry = false; advanceCurrentSourceEntry = false; } } if (addCurrentSourceEntry) { result.Add(currentTime - new TimeSpan(0, 0, (int)totalPausedTimeToDate), sourceEnumerator.Current.Value); } if (advanceCurrentSourceEntry) { sourceEnumeratorIsValid = sourceEnumerator.MoveNext(); currentTime = sourceTrack.StartTime + new TimeSpan(0, 0, (int)sourceEnumerator.Current.ElapsedSeconds); } } return(result); } } return(null); }
private INumericTimeDataSeries GetSmoothedActivityTrack(LineChartTypes chartType) { // Fail safe INumericTimeDataSeries result = new NumericTimeDataSeries(); if (Activity != null) { switch (chartType) { case LineChartTypes.Cadence: { result = m_ActivityInfoCache.SmoothedCadenceTrack; break; } case LineChartTypes.Elevation: { INumericTimeDataSeries tempResult = m_ActivityInfoCache.SmoothedElevationTrack; // Value is in meters so convert to the right unit result = new NumericTimeDataSeries(); foreach (ITimeValueEntry <float> entry in tempResult) { double temp = Length.Convert(entry.Value, Length.Units.Meter, Activity.Category.ElevationUnits); result.Add(tempResult.EntryDateTime(entry), (float)temp); } break; } case LineChartTypes.Grade: { result = new NumericTimeDataSeries(); INumericTimeDataSeries tempResult = m_ActivityInfoCache.SmoothedGradeTrack; foreach (ITimeValueEntry <float> entry in tempResult) { result.Add(tempResult.EntryDateTime(entry), entry.Value * 100.0f); } break; } case LineChartTypes.HeartRateBPM: { result = m_ActivityInfoCache.SmoothedHeartRateTrack; break; } case LineChartTypes.HeartRatePercentMax: { result = new NumericTimeDataSeries(); IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(m_ActivityInfoCache.ActualTrackStart); // Value is in BPM so convert to the % max HR if we have the info if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute)) { INumericTimeDataSeries tempResult = m_ActivityInfoCache.SmoothedHeartRateTrack; foreach (ITimeValueEntry <float> entry in tempResult) { double temp = (entry.Value / lastAthleteEntry.MaximumHeartRatePerMinute) * 100; result.Add(tempResult.EntryDateTime(entry), (float)temp); } } break; } case LineChartTypes.Power: { result = m_ActivityInfoCache.SmoothedPowerTrack; break; } case LineChartTypes.Speed: { INumericTimeDataSeries tempResult = m_ActivityInfoCache.SmoothedSpeedTrack; // Value is in m/sec so convert to the right unit and to // pace if necessary result = new NumericTimeDataSeries(); foreach (ITimeValueEntry <float> entry in tempResult) { double temp = Length.Convert(entry.Value, Length.Units.Meter, Utils.Utils.MajorLengthUnit(Activity.Category.DistanceUnits)) * Utils.Constants.SecondsPerHour; if (Activity.Category.SpeedUnits == Speed.Units.Pace) { // Convert to pace and then in second temp = Utils.Utils.SpeedToPace(temp) * Utils.Constants.SecondsPerMinute; } result.Add(tempResult.EntryDateTime(entry), (float)temp); } break; } default: { Debug.Assert(false); break; } } } return(result); }
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); } } } }
/**********************************/ internal static float getValFromDateTime(INumericTimeDataSeries track, DateTime t) { int status; return(getValFromDateTime(track, t, out status)); }
/// <summary> /// Gets the cached track if available, or calculates from scratch if not available. /// </summary> /// <param name="activity">Activity to calculate</param> /// <param name="chartType">Which track to calculate</param> /// <param name="create">Cache the track if it doesn't exist. /// True will always return a track. /// False will return the cached track, or null if not already cached.</param> /// <returns>Cached track if available, or empty track if not found.</returns> private static INumericTimeDataSeries GetMeanMaxTrack(IActivity activity, Common.TrackType chartType, out INumericTimeDataSeries timeTrack, bool create) { if (activity == null) { timeTrack = new NumericTimeDataSeries(); return(new NumericTimeDataSeries()); } string id = activity.ReferenceId + chartType; if (tracks.ContainsKey(id)) { // Returned cached value from memory :) timeTrack = tracks[id + "T"]; return(tracks[id]); } else if (create) { // Not in cache, create a new mean-max track :( INumericTimeDataSeries track = new NumericTimeDataSeries(); timeTrack = null; switch (chartType) { case Common.TrackType.HR: { track = GetMeanMaxTrack(activity.HeartRatePerMinuteTrack, out timeTrack, activity.StartTime); break; } case Common.TrackType.Power: { track = GetMeanMaxTrack(activity.PowerWattsTrack, out timeTrack, activity.StartTime); break; } case Common.TrackType.Cadence: { track = GetMeanMaxTrack(activity.CadencePerMinuteTrack, out timeTrack, activity.StartTime); break; } } // Add data track and related 'T'ime track to cache for next time MeanMaxCache.AddTrack(track, id); MeanMaxCache.AddTrack(timeTrack, id + "T"); return(track); } else { // Not previously cached, AND requested not to create a new cached item. timeTrack = new NumericTimeDataSeries(); return(null); } }