package ride import "time" const MovingTimeAlgorithmVersion = "v2" // movingTimeMinSpeedMps is the minimum implied speed between two consecutive // track points for the interval to count as moving (0.4 km/h). const movingTimeMinSpeedMps = 0.4 / 3.6 // movingTimeMinPowerW is the minimum power output for an interval to count as // moving when GPS speed is below the minimum speed. const movingTimeMinPowerW = 20 // movingTimeMaxGap is the maximum time gap between consecutive track points // that can count as moving. Longer gaps are pauses (stops, lost signal). const movingTimeMaxGap = 120 * time.Second // Bench (104 rides, `-backtest`, distance >= 10 km, moving/elapsed ratio >= 0.8): // absMean 0.4%, stderr 0.1, p50 0.2, p75 0.4, p90 0.8, p95 1.4, p99 2.9. func MovingTime(r Ride) time.Duration { return movingTime(r.timestamps, r.Distances, r.powers, r.cadences, movingTimeMinSpeedMps, movingTimeMaxGap) } func movingTime(timestamps []time.Time, distances, powers, cadences []float64, minSpeedMps float64, maxGap time.Duration) time.Duration { var moving time.Duration for i := 1; i < len(timestamps); i++ { dt := timestamps[i].Sub(timestamps[i-1]) if dt <= 0 || dt > maxGap { continue } dd := distances[i] - distances[i-1] gpsMoving := dd/dt.Seconds() > minSpeedMps powerMoving := false if len(powers) > 0 { powerMoving = powers[i] >= movingTimeMinPowerW } cadenceMoving := false if len(cadences) > 0 { cadenceMoving = cadences[i] > 0 } if gpsMoving || powerMoving || cadenceMoving { moving += dt } } return moving }