Begin statistics creation
This commit is contained in:
13
CHDataManagement/Workouts/File/DataRanges.swift
Normal file
13
CHDataManagement/Workouts/File/DataRanges.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
struct DataRanges {
|
||||
|
||||
let duration: RangeInterval
|
||||
|
||||
let time: RangeInterval
|
||||
|
||||
let distance: RangeInterval
|
||||
}
|
||||
|
||||
extension DataRanges: Codable {
|
||||
|
||||
}
|
||||
11
CHDataManagement/Workouts/File/RangeInterval.swift
Normal file
11
CHDataManagement/Workouts/File/RangeInterval.swift
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
struct RangeInterval {
|
||||
|
||||
let min: Double
|
||||
|
||||
let max: Double
|
||||
}
|
||||
|
||||
extension RangeInterval: Codable {
|
||||
|
||||
}
|
||||
26
CHDataManagement/Workouts/File/RouteData.swift
Normal file
26
CHDataManagement/Workouts/File/RouteData.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
All data needed to create statistic displays
|
||||
*/
|
||||
struct RouteData {
|
||||
|
||||
let series: RouteSeries
|
||||
|
||||
let ranges: DataRanges
|
||||
|
||||
let samples: [RouteSample]
|
||||
|
||||
}
|
||||
|
||||
extension RouteData: Codable {
|
||||
|
||||
func encoded(prettyPrinted: Bool = false) -> Data {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .sortedKeys
|
||||
if prettyPrinted {
|
||||
encoder.outputFormatting.insert(.prettyPrinted)
|
||||
}
|
||||
return try! encoder.encode(self)
|
||||
}
|
||||
}
|
||||
32
CHDataManagement/Workouts/File/RouteProfile.swift
Normal file
32
CHDataManagement/Workouts/File/RouteProfile.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
struct RouteProfile: Codable {
|
||||
|
||||
let min: Double
|
||||
|
||||
let max: Double
|
||||
|
||||
let ticks: Int
|
||||
|
||||
let span: Double
|
||||
|
||||
let scale: Double
|
||||
|
||||
init(min: Double, max: Double, ticks: Int) {
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.ticks = ticks
|
||||
self.span = max - min
|
||||
self.scale = 1 / span
|
||||
}
|
||||
|
||||
func scale(_ value: Double) -> Double {
|
||||
if value < min {
|
||||
return 0
|
||||
}
|
||||
if value > max {
|
||||
return 1
|
||||
}
|
||||
return (value - min) / span
|
||||
}
|
||||
}
|
||||
|
||||
95
CHDataManagement/Workouts/File/RouteSample.swift
Normal file
95
CHDataManagement/Workouts/File/RouteSample.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
struct RouteSample {
|
||||
|
||||
/// The x-coordinate in the map image (left to right) in the range [0,1]
|
||||
var x: Double
|
||||
|
||||
/// The y-coordinate in the map image (top to bottom) in the range [0,1]
|
||||
var y: Double
|
||||
|
||||
/// The timestamp of the sample in the range [0,1]
|
||||
var time: Double
|
||||
|
||||
/// The distance of the sample in the range [0,1]
|
||||
var distance: Double
|
||||
|
||||
/// The elevation of the sample in the range [0,1]
|
||||
var elevation: Double
|
||||
|
||||
/// The speed of the sample in the range [0,1]
|
||||
var speed: Double
|
||||
|
||||
/// The pace of the sample in the range [0,1]
|
||||
var pace: Double
|
||||
|
||||
/// The heart rate of the sample in the range [0,1]
|
||||
var hr: Double
|
||||
|
||||
/// The active energy rate of the sample in the range [0,1]
|
||||
var energy: Double?
|
||||
}
|
||||
|
||||
extension RouteSample: Codable {
|
||||
|
||||
func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(x.rounded(decimals: 4), forKey: .x)
|
||||
try container.encode(y.rounded(decimals: 4), forKey: .y)
|
||||
try container.encode(time.rounded(decimals: 4), forKey: .time)
|
||||
try container.encode(distance.rounded(decimals: 4), forKey: .distance)
|
||||
try container.encode(elevation.rounded(decimals: 4), forKey: .elevation)
|
||||
try container.encode(speed.rounded(decimals: 4), forKey: .speed)
|
||||
try container.encode(pace.rounded(decimals: 4), forKey: .pace)
|
||||
try container.encode(hr.rounded(decimals: 4), forKey: .hr)
|
||||
try container.encodeIfPresent(energy?.rounded(decimals: 4), forKey: .energy)
|
||||
}
|
||||
}
|
||||
|
||||
extension RouteSample: Identifiable {
|
||||
|
||||
var id: Double { x }
|
||||
}
|
||||
|
||||
extension RouteSample {
|
||||
|
||||
static var zero: RouteSample {
|
||||
.init(x: 0, y: 0, time: 0, distance: 0, elevation: 0, speed: 0, pace: 0, hr: 0, energy: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension Collection where Element == RouteSample {
|
||||
|
||||
var averageSample: RouteSample {
|
||||
guard var average = first else {
|
||||
return .zero
|
||||
}
|
||||
var energySamples = average.energy == nil ? 0 : 1
|
||||
for sample in dropFirst() {
|
||||
average.x += sample.x
|
||||
average.y += sample.y
|
||||
average.time += sample.time
|
||||
average.distance += sample.distance
|
||||
average.elevation += sample.elevation
|
||||
average.speed += sample.speed
|
||||
average.pace += sample.pace
|
||||
average.hr += sample.hr
|
||||
if let energy = sample.energy {
|
||||
average.energy = (average.energy ?? 0) + energy
|
||||
energySamples += 1
|
||||
}
|
||||
}
|
||||
let scale = 1 / Double(count)
|
||||
average.x *= scale
|
||||
average.y *= scale
|
||||
average.time *= scale
|
||||
average.distance *= scale
|
||||
average.elevation *= scale
|
||||
average.speed *= scale
|
||||
average.pace *= scale
|
||||
average.hr *= scale
|
||||
if let energy = average.energy, energySamples > 0 {
|
||||
average.energy = energy / Double(energySamples)
|
||||
}
|
||||
return average
|
||||
}
|
||||
}
|
||||
18
CHDataManagement/Workouts/File/RouteSeries.swift
Normal file
18
CHDataManagement/Workouts/File/RouteSeries.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
|
||||
struct RouteSeries {
|
||||
|
||||
let elevation: RouteProfile
|
||||
|
||||
let speed: RouteProfile
|
||||
|
||||
let pace: RouteProfile
|
||||
|
||||
let hr: RouteProfile
|
||||
|
||||
let energy: RouteProfile?
|
||||
}
|
||||
|
||||
extension RouteSeries: Codable {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user