/// <summary> /// Copy constructor. /// </summary> /// <param name="i"></param> public Interval(Interval i) { start = i.start; end = i.end; }
public bool Equals(Interval obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; return obj.start == start && obj.end == end; }
public double CalculateSpeedPercentualStandardDeviation(ParameterizedLocation start, ParameterizedLocation end, Interval slidingAverageInterval) { // first collect all waypoints var nodes = new List<Pair<ParameterizedLocation, Waypoint>>(); ParameterizedLocation adjustedStart = GetParameterizedLocationOfNextWaypoint(start, false); ParameterizedLocation adjustedEnd = GetParameterizedLocationOfPreviousWaypoint(end, false); var pl = new ParameterizedLocation(adjustedStart); if (!start.IsNode) nodes.Add(new Pair<ParameterizedLocation, Waypoint>(start, CreateWaypointFromParameterizedLocation(start))); while (pl != null && pl <= adjustedEnd) { if (pl.Value > segments[pl.SegmentIndex].Waypoints.Count - 1) { pl.SegmentIndex++; pl.Value = 0; } nodes.Add(new Pair<ParameterizedLocation, Waypoint>(pl, GetClosestWaypointFromParameterizedLocation(pl))); pl = GetNextPLNode(pl, ParameterizedLocation.Direction.Forward); } if (!end.IsNode) nodes.Add(new Pair<ParameterizedLocation, Waypoint>(end, CreateWaypointFromParameterizedLocation(end))); var speeds = new List<StatisticsUtil.WeightedItem>(); if (slidingAverageInterval.Length == 0) { double elapsedTime = GetAttributeFromParameterizedLocation(WaypointAttribute.ElapsedTime, LastPL).Value; double meanSpeed = (GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, LastPL).Value - GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, FirstPL).Value) / (elapsedTime == 0 ? 0 : elapsedTime); for (int i = 0; i < nodes.Count; i++) { ParameterizedLocation p = nodes[i].First; Waypoint w = nodes[i].Second; double beforeWeight = (i == 0 || IsFirstPLInSegment(p) ? 0 : (double)(w.Time.Ticks - nodes[i - 1].Second.Time.Ticks) / TimeSpan.TicksPerSecond / 2); double afterWeight = (i == nodes.Count - 1 || IsLastPLInSegment(p) ? 0 : (double)(nodes[i + 1].Second.Time.Ticks - w.Time.Ticks) / TimeSpan.TicksPerSecond / 2); speeds.Add( new StatisticsUtil.WeightedItem( beforeWeight + afterWeight, (meanSpeed == 0 ? 0 : segments[p.SegmentIndex].Waypoints[(int)p.Value].Attributes[WaypointAttribute.Speed].Value / meanSpeed) ) ); } } else { ParameterizedLocation siStartPL = GetParameterizedLocationFromTime(nodes[0].Second.Time.AddSeconds(slidingAverageInterval.Start)); ParameterizedLocation siEndPL = GetParameterizedLocationFromTime(nodes[0].Second.Time.AddSeconds(slidingAverageInterval.End)); for (int i = 0; i < nodes.Count; i++) { // start of sliding interval siStartPL = GetParameterizedLocationFromTime(nodes[i].Second.Time.AddSeconds(slidingAverageInterval.Start), siStartPL, ParameterizedLocation.Direction.Forward); // end of sliding interval siEndPL = GetParameterizedLocationFromTime(nodes[i].Second.Time.AddSeconds(slidingAverageInterval.End), siEndPL, ParameterizedLocation.Direction.Forward); if (siStartPL != null && siEndPL != null) { if (siStartPL.SegmentIndex < nodes[i].First.SegmentIndex) siStartPL = new ParameterizedLocation(nodes[i].First.SegmentIndex, 0); if (siEndPL.SegmentIndex > nodes[i].First.SegmentIndex) siEndPL = new ParameterizedLocation(nodes[i].First.SegmentIndex, segments[nodes[i].First.SegmentIndex].Waypoints.Count - 1); ParameterizedLocation p = nodes[i].First; Waypoint w = nodes[i].Second; double siStartDistance = GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, siStartPL).Value; double siEndDistance = GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, siEndPL).Value; double siLength = GetTimeFromParameterizedLocation(siEndPL).Subtract(GetTimeFromParameterizedLocation(siStartPL)). TotalSeconds; double meanSpeed = (siLength == 0 ? 0 : (siEndDistance - siStartDistance) / siLength); double beforeWeight = (i == 0 || IsFirstPLInSegment(p) ? 0 : (double)(w.Time.Ticks - nodes[i - 1].Second.Time.Ticks) / TimeSpan.TicksPerSecond / 2); double afterWeight = (i == nodes.Count - 1 || IsLastPLInSegment(p) ? 0 : (double)(nodes[i + 1].Second.Time.Ticks - w.Time.Ticks) / TimeSpan.TicksPerSecond / 2); speeds.Add(new StatisticsUtil.WeightedItem((beforeWeight + afterWeight), (meanSpeed == 0 ? 0 : w.Attributes[WaypointAttribute.Speed].Value / meanSpeed))); } } } return StatisticsUtil.GetStandardDeviation(speeds); }
/// <summary> /// Calculates speeds and paces. /// </summary> private void CalculateSpeeds() { // using optimized but hard-to-understand algorithm var actualInterval = new Interval(SmoothingIntervals[WaypointAttribute.Speed]); if (actualInterval.Length == 0) { // need to have non-zero smothing interval when calculating speed, set it to a millisecond var center = actualInterval.Start; actualInterval = new Interval(center - 0.0005, center + 0.0005); } ParameterizedLocation siStartPL = GetParameterizedLocationFromTime(FirstWaypoint.Time.AddSeconds(actualInterval.Start)); ParameterizedLocation siEndPL = GetParameterizedLocationFromTime(FirstWaypoint.Time.AddSeconds(actualInterval.End)); for (int i = 0; i < segments.Count; i++) { for (int j = 0; j < segments[i].Waypoints.Count; j++) { Waypoint w = segments[i].Waypoints[j]; // start of sliding interval siStartPL = GetParameterizedLocationFromTime(w.Time.AddSeconds(actualInterval.Start), siStartPL, ParameterizedLocation.Direction.Forward); // end of sliding interval siEndPL = GetParameterizedLocationFromTime(w.Time.AddSeconds(actualInterval.End), siEndPL, ParameterizedLocation.Direction.Forward); if (siStartPL != null && siEndPL != null) { if (siStartPL.SegmentIndex < i) siStartPL = new ParameterizedLocation(i, 0); if (siEndPL.SegmentIndex > i) siEndPL = new ParameterizedLocation(i, segments[i].Waypoints.Count - 1); double siStartDistance = GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, siStartPL).Value; double siEndDistance = GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, siEndPL).Value; double siLength = (GetTimeFromParameterizedLocation(siEndPL) - GetTimeFromParameterizedLocation(siStartPL)).TotalSeconds; w.Attributes[WaypointAttribute.Speed] = (siLength == 0 ? 0 : 3.6 * (siEndDistance - siStartDistance) / siLength); } else { w.Attributes[WaypointAttribute.Speed] = 0; } w.Attributes[WaypointAttribute.Pace] = ConvertUtil.ToPace(w.Attributes[WaypointAttribute.Speed].Value).TotalSeconds; } } }
private void CalculateSlidingAverageAttributes(WaypointAttribute attribute, Interval smoothingInterval) { // using optimized but hard-to-understand algorithm if (attribute != WaypointAttribute.HeartRate && attribute != WaypointAttribute.Altitude) throw new ArgumentException("The 'attribute' parameter must be either WaypointAttribute.HeartRate or WaypointAttribute.Altitude"); bool zeroLengthInterval = smoothingInterval.Length == 0; bool containsAttribute = ContainsWaypointAttribute(attribute); ParameterizedLocation siStartPL = null; ParameterizedLocation siEndPL = null; if (!zeroLengthInterval) { siStartPL = GetParameterizedLocationFromTime(FirstWaypoint.Time.AddSeconds(smoothingInterval.Start)); siEndPL = GetParameterizedLocationFromTime(FirstWaypoint.Time.AddSeconds(smoothingInterval.End)); } for (int i = 0; i < segments.Count; i++) { double[] valueSums = null; bool[] valueIsSet = null; DateTime[] valueTimes = null; var nullValueFound = false; if (containsAttribute && !zeroLengthInterval) { valueSums = new double[segments[i].Waypoints.Count]; valueIsSet = new bool[segments[i].Waypoints.Count]; valueTimes = new DateTime[segments[i].Waypoints.Count]; valueSums[0] = 0; if (segments[i].Waypoints.Count > 0) { valueIsSet[0] = segments[i].Waypoints[0].GetOriginalAttribute(attribute).HasValue; valueTimes[0] = segments[i].Waypoints[0].Time; nullValueFound = !valueIsSet[0]; } for (int j = 1; j < segments[i].Waypoints.Count; j++) { var previousWaypoint = segments[i].Waypoints[j - 1]; var thisWaypoint = segments[i].Waypoints[j]; var previousOriginalAttribute = previousWaypoint.GetOriginalAttribute(attribute); var thisOriginalAttribute = thisWaypoint.GetOriginalAttribute(attribute); valueIsSet[j] = thisOriginalAttribute.HasValue; valueTimes[j] = thisWaypoint.Time; nullValueFound = nullValueFound || !valueIsSet[j]; if (valueIsSet[j - 1] && valueIsSet[j]) { valueSums[j] = valueSums[j - 1] + (valueTimes[j] - valueTimes[j - 1]).TotalSeconds * (previousOriginalAttribute.Value + thisOriginalAttribute.Value) / 2; } else { valueSums[j] = valueSums[j - 1]; } } } for (int j = 0; j < segments[i].Waypoints.Count; j++) { Waypoint w = segments[i].Waypoints[j]; if (!containsAttribute) { w.Attributes[attribute] = null; } else if (zeroLengthInterval) { w.Attributes[attribute] = w.GetOriginalAttribute(attribute); } else { // start of sliding interval siStartPL = GetParameterizedLocationFromTime(w.Time.AddSeconds(smoothingInterval.Start), siStartPL, ParameterizedLocation.Direction.Forward); // end of sliding interval siEndPL = GetParameterizedLocationFromTime(w.Time.AddSeconds(smoothingInterval.End), siEndPL, ParameterizedLocation.Direction.Forward); if (siStartPL != null && siEndPL != null) { if (siStartPL.SegmentIndex < i) siStartPL = new ParameterizedLocation(i, 0); if (siEndPL.SegmentIndex > i) siEndPL = new ParameterizedLocation(i, segments[i].Waypoints.Count - 1); double startSum; double endSum; var adjustedIntervalLength = (GetTimeFromParameterizedLocation(siEndPL) - GetTimeFromParameterizedLocation(siStartPL)).TotalSeconds; int startIndex; int endIndex; if (siStartPL.IsNode) { startSum = valueSums[(int)siStartPL.Value]; startIndex = (int)siStartPL.Value; } else { var d = siStartPL.Value - Math.Floor(siStartPL.Value); startSum = (1 - d) * valueSums[(int)siStartPL.Value] + d * valueSums[(int)siStartPL.Value + 1]; startIndex = (int)siStartPL.Value; } if (siEndPL.IsNode) { endSum = valueSums[(int)siEndPL.Value]; endIndex = (int)siEndPL.Value; } else { var d = siEndPL.Value - Math.Floor(siEndPL.Value); endSum = (1 - d) * valueSums[(int)siEndPL.Value] + d * valueSums[(int)siEndPL.Value + 1]; endIndex = (int)siEndPL.Value + 1; } if (adjustedIntervalLength == 0) { w.Attributes[attribute] = w.GetOriginalAttribute(attribute); } else { // check if there are any null values in this interval bool nullValueFoundInInterval; if (!nullValueFound) { // no null value in whole route, don't need to check this interval nullValueFoundInInterval = false; } else { // there is at least one null value in the route, need to check if there exists any in this interval nullValueFoundInInterval = false; for (var k = startIndex; k <= endIndex; k++) { if (!valueIsSet[k]) { nullValueFoundInInterval = true; break; } } } if (!nullValueFoundInInterval) { // no null values in this interval, perform normal calculation w.Attributes[attribute] = (endSum - startSum) / adjustedIntervalLength; } else { // null value found, calculate based on each non-null value waypoint pair adjustedIntervalLength = 0; double sum = 0; for (var k = startIndex; k < endIndex; k++) { if (valueIsSet[k] && valueIsSet[k + 1]) { double start = Math.Max(k, siStartPL.Value); double end = Math.Min(k + 1, siEndPL.Value); adjustedIntervalLength += (end - start) * (valueTimes[k + 1] - valueTimes[k]).TotalSeconds; sum += (end - start) * (valueSums[k + 1] - valueSums[k]); } } w.Attributes[attribute] = (adjustedIntervalLength == 0 ? (double?)null : sum / adjustedIntervalLength); } } } else { w.Attributes[attribute] = null; } } } } }
private void CalculateInclinations() { var containsAltitude = ContainsWaypointAttribute(WaypointAttribute.Altitude); var altitudeSmoothingInterval = new Interval(-0.005, 0.005); var distanceSmoothingInterval = SmoothingIntervals[WaypointAttribute.Altitude]; if (distanceSmoothingInterval.Length == 0) distanceSmoothingInterval = altitudeSmoothingInterval; for (int i = 0; i < segments.Count; i++) { for (int j = 0; j < segments[i].Waypoints.Count; j++) { var waypoint = segments[i].Waypoints[j]; if (containsAltitude) { var pl = new ParameterizedLocation(i, j); var startDirection = altitudeSmoothingInterval.Start < 0 ? ParameterizedLocation.Direction.Backward : ParameterizedLocation.Direction.Forward; var endDirection = altitudeSmoothingInterval.End < 0 ? ParameterizedLocation.Direction.Backward : ParameterizedLocation.Direction.Forward; // altitude calculations var altitudeStartTime = waypoint.Time.AddSeconds(altitudeSmoothingInterval.Start); var altitudeEndTime = waypoint.Time.AddSeconds(altitudeSmoothingInterval.End); var altitudeStartPL = GetParameterizedLocationFromTime(altitudeStartTime, pl, startDirection); var altitudeEndPL = GetParameterizedLocationFromTime(altitudeEndTime, pl, endDirection); if (altitudeStartPL.SegmentIndex < i) { altitudeStartPL = new ParameterizedLocation(i, 0); altitudeStartTime = segments[i].FirstWaypoint.Time; } if (altitudeEndPL.SegmentIndex > i) { altitudeEndPL = new ParameterizedLocation(i, segments[i].Waypoints.Count - 1); altitudeEndTime = segments[i].LastWaypoint.Time; } var altitudeDifference = GetAttributeFromParameterizedLocation(WaypointAttribute.Altitude, altitudeEndPL) - GetAttributeFromParameterizedLocation(WaypointAttribute.Altitude, altitudeStartPL); var altitudeDuration = (altitudeEndTime - altitudeStartTime).TotalSeconds; // distance calculations var distanceStartTime = waypoint.Time.AddSeconds(distanceSmoothingInterval.Start); var distanceEndTime = waypoint.Time.AddSeconds(distanceSmoothingInterval.End); var distanceStartPL = GetParameterizedLocationFromTime(distanceStartTime, pl, startDirection); var distanceEndPL = GetParameterizedLocationFromTime(distanceEndTime, pl, endDirection); if (distanceStartPL.SegmentIndex < i) { distanceStartPL = new ParameterizedLocation(i, 0); distanceStartTime = segments[i].FirstWaypoint.Time; } if (distanceEndPL.SegmentIndex > i) { distanceEndPL = new ParameterizedLocation(i, segments[i].Waypoints.Count - 1); distanceEndTime = segments[i].LastWaypoint.Time; } var distanceDifference = GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, distanceEndPL) - GetAttributeFromParameterizedLocation(WaypointAttribute.Distance, distanceStartPL); var distanceDuration = (distanceEndTime - distanceStartTime).TotalSeconds; // calculate the inclination if (!distanceDifference.HasValue || !altitudeDifference.HasValue) { waypoint.Attributes[WaypointAttribute.Inclination] = null; } else if (altitudeDuration == 0 || distanceDuration == 0) { waypoint.Attributes[WaypointAttribute.Inclination] = 0; } else { waypoint.Attributes[WaypointAttribute.Inclination] = LinearAlgebraUtil.ToDegrees(Math.Atan2(altitudeDifference.Value / altitudeDuration, distanceDifference.Value / distanceDuration)); } } else { waypoint.Attributes[WaypointAttribute.Inclination] = null; } } } }
private void CalculateDirectionDeviationsToNextLap() { if (lapTimes.Count >= 2) { int lapIndex = 0; var lapStartPL = GetParameterizedLocationFromTime(lapTimes[lapIndex]); var lapEndPL = GetParameterizedLocationFromTime(lapTimes[lapIndex + 1]); // using optimized but hard-to-understand algorithm var actualInterval = new Interval(SmoothingIntervals[WaypointAttribute.DirectionDeviationToNextLap]); if (actualInterval.Length == 0) { // need to have non-zero smothing interval when calculating direction vectors, set it to a millisecond var center = actualInterval.Start; actualInterval = new Interval(center - 0.0005, center + 0.0005); } ParameterizedLocation siStartPL = GetParameterizedLocationFromTime(FirstWaypoint.Time.AddSeconds(actualInterval.Start)); ParameterizedLocation siEndPL = GetParameterizedLocationFromTime(FirstWaypoint.Time.AddSeconds(actualInterval.End)); for (int i = 0; i < segments.Count; i++) { // 1. calculate the direction angles in this segment, taking laps into account (never consider positions outside current lap) var directionAngles = new double[segments[i].Waypoints.Count]; var startLapIndexInThisSegment = lapIndex; var lapStartPLInThisSegment = GetParameterizedLocationFromTime(lapTimes[lapIndex]); var lapEndPLInThisSegment = GetParameterizedLocationFromTime(lapTimes[lapIndex + 1]); for (int j = 0; j < segments[i].Waypoints.Count; j++) { Waypoint w = segments[i].Waypoints[j]; while (w.Time.ToUniversalTime() > lapTimes[lapIndex].ToUniversalTime() && lapIndex < lapTimes.Count - 1) { lapIndex++; lapStartPL = GetParameterizedLocationFromTime(lapTimes[lapIndex - 1]); lapEndPL = GetParameterizedLocationFromTime(lapTimes[lapIndex]); } // start of sliding interval siStartPL = GetParameterizedLocationFromTime(w.Time.AddSeconds(actualInterval.Start), siStartPL, ParameterizedLocation.Direction.Forward); // end of sliding interval siEndPL = GetParameterizedLocationFromTime(w.Time.AddSeconds(actualInterval.End), siEndPL, ParameterizedLocation.Direction.Forward); if (siStartPL != null && siEndPL != null) { if (siStartPL.SegmentIndex < i) siStartPL = new ParameterizedLocation(i, 0); if (siEndPL.SegmentIndex > i) siEndPL = new ParameterizedLocation(i, segments[i].Waypoints.Count - 1); if (siStartPL < lapStartPL) siStartPL = new ParameterizedLocation(lapStartPL); if (siEndPL > lapEndPL) siEndPL = new ParameterizedLocation(lapEndPL); var siStartLocation = GetLocationFromParameterizedLocation(siStartPL); var siEndLocation = GetLocationFromParameterizedLocation(siEndPL); var middle = (siStartLocation / 2 + siEndLocation / 2); var p0 = siStartLocation.Project(middle); var p1 = siEndLocation.Project(middle); directionAngles[j] = LinearAlgebraUtil.GetAngleD(LinearAlgebraUtil.Normalize(p1 - p0)); } else { directionAngles[j] = 0; } } // 2. calculate the directions and direction deviations based on values achieved in step 1 lapIndex = startLapIndexInThisSegment; lapStartPL = lapStartPLInThisSegment; lapEndPL = lapEndPLInThisSegment; var lapLongLat = GetLocationFromParameterizedLocation(GetParameterizedLocationFromTime(lapTimes[lapIndex + 1])); for (int j = 0; j < segments[i].Waypoints.Count; j++) { Waypoint w = segments[i].Waypoints[j]; // direction (clockwise from north: n = 0, e = 90, s = 180, w = 270) var direction = -directionAngles[j] + 90; if (direction < 0) direction += 360; w.Attributes[WaypointAttribute.Direction] = direction; // direction deviation to next lap while (w.Time.ToUniversalTime() > lapTimes[lapIndex].ToUniversalTime() && lapIndex < lapTimes.Count - 1) { lapIndex++; lapLongLat = GetLocationFromParameterizedLocation(GetParameterizedLocationFromTime(lapTimes[lapIndex])); } LongLat middle = (w.LongLat / 2 + lapLongLat / 2); PointD p0 = w.LongLat.Project(middle); PointD p1 = lapLongLat.Project(middle); PointD directionVectorToNextLap = LinearAlgebraUtil.Normalize(p1 - p0); w.Attributes[WaypointAttribute.DirectionDeviationToNextLap] = Math.Abs(LinearAlgebraUtil.GetAngleD(directionVectorToNextLap, LinearAlgebraUtil.CreateNormalizedVectorFromAngleD(directionAngles[j]))); } } } }