import Foundation extension Double { /** Move to a different value by the given ratio of their distance. */ func move(_ ratio: Double, to other: Double) -> Double { self + (other - self) * ratio } } extension Collection where Element == Double { func sum() -> Double { reduce(0, +) } func average() -> Double { sum() / Double(count) } } extension Collection where Element == Double, Index == Int { func floatingMeanFiltered(windowSize: Int) -> [Double] { guard windowSize > 1 else { return self.map { $0 } } guard count >= windowSize else { return .init(repeating: average(), count: count) } let firstHalf = windowSize / 2 let secondHalf = windowSize - firstHalf var minSpeed: Double = 0 var maxSpeed: Double = 0 let averageScale = 1.0 / Double(windowSize) // First calculate the filtered speeds in the normal unit var currentAverage = self[0.. maxSpeed { maxSpeed = currentAverage } } result.append(contentsOf: [Double](repeating: currentAverage, count: secondHalf)) return result } func fittingAxisLimits(desiredNumSteps: Int = 5, minLimit: Double? = nil, maxLimit: Double? = nil) -> RouteProfile { let (dataMin, dataMax) = minMax(minLimit: minLimit, maxLimit: maxLimit) let dataRange = dataMax - dataMin let roughStep = dataRange / Double(desiredNumSteps) let exponent = floor(log10(roughStep)) let base = pow(10.0, exponent) let step: Double if roughStep <= base { step = base } else if roughStep <= 2 * base { step = 2 * base } else if roughStep <= 5 * base { step = 5 * base } else { step = 10 * base } let graphMin = floor(dataMin / step) * step let graphMax = ceil(dataMax / step) * step let numTicks = Int((graphMax - graphMin) / step) return .init(min: graphMin, max: graphMax, ticks: numTicks) } func minMax(minLimit: Double? = nil, maxLimit: Double? = nil) -> (min: Double, max: Double) { var dataMin = self.min() ?? 0 var dataMax = self.max() ?? 1 if let minLimit { dataMin = Swift.max(dataMin, minLimit) } if let maxLimit { dataMax = Swift.min(dataMax, maxLimit) } return (dataMin, dataMax) } func fittingTimeAxisLimits(minTicks: Int = 3, maxTicks: Int = 7, minLimit: Double? = nil, maxLimit: Double? = nil) -> RouteProfile { let (dataMin, dataMax) = minMax(minLimit: minLimit, maxLimit: maxLimit) let dataRange = dataMax - dataMin let niceSteps: [Double] = [15, 30, 60, 120, 300, 600, 900, 1800, 3600] // in seconds // Find the step size that gives a nice number of ticks var chosenStep = niceSteps.last! // fallback to largest if none fit for step in niceSteps { let numTicks = dataRange / step if Double(minTicks) <= numTicks && numTicks <= Double(maxTicks) { chosenStep = step break } } let graphMin = floor(dataMin / chosenStep) * chosenStep let graphMax = ceil(dataMax / chosenStep) * chosenStep let numTicks = Int((graphMax-graphMin) / chosenStep) return .init(min: graphMin, max: graphMax, ticks: numTicks) } }