Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing / Verification #12

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode9.4
osx_image: xcode10

script:
- bundle exec fastlane test
Expand Down
78 changes: 73 additions & 5 deletions GeoTrackKit/Core/Models/Analyze/Leg.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,25 @@ import CoreLocation

/// This class keeps track of statistics for a Leg (ascent or descent) of a track.
public class Stat {

public static var zero: Stat {
return Stat()
}

/// Minimum altitude in meters
fileprivate(set) public var minimumAltitude: CLLocationDistance = 0
fileprivate(set) public var minimumAltitude: CLLocationDistance = CLLocationDistanceMax
/// Maximum altitude in meters
fileprivate(set) public var maximumAltitude: CLLocationDistance = 0
/// The distance travelled
fileprivate(set) public var distance: CLLocationDistance = 0
/// The maximum recorded speed (in meters per second)
fileprivate(set) public var maximumSpeed: CLLocationSpeed = 0

/// What is the change in vertical?
public var verticalDelta: CLLocationDistance {
return maximumAltitude - minimumAltitude
}

fileprivate var initialized = false
internal(set) public var direction: Direction = .unknown

Expand Down Expand Up @@ -53,12 +60,28 @@ public class Stat {
maximumSpeed = max(maximumSpeed, stat.maximumSpeed)
initialized = initialized || stat.initialized
}

}

// MARK: - Stat: CustomDebugStringConvertible

extension Stat: CustomDebugStringConvertible {

public var debugDescription: String {
return "\(direction.rawValue) - \(verticalDelta.int)m, " +
"Altitude: \(minimumAltitude.int)m - \(maximumAltitude.int)m, " +
"Distance: \(distance.int)m, Max Speed: \(maximumSpeed.int)"
}

}


/// This class keeps track of statistics for an entire Geo Track. It summarizes the stats of all of the legs that comprise it and it keeps trak of the number of runs.
public class TrackStat: Stat {
public class TrackStat {
/// The number of "ski runs" (aka the number of times descended)
public let runs: Int
/// The number of "legs" (ascent count + descent count)
public let legs: Int
/// The total vertical ascent for the entire track
public let verticalAscent: CLLocationDistance
/// The total vertical descent for the entire track
Expand All @@ -69,23 +92,36 @@ public class TrackStat: Stat {
public let descentDistance: CLLocationDistance
/// The total distance covered for this track
public let totalDistance: CLLocationDistance
/// Minimum altitude in meters
public let minimumAltitude: CLLocationDistance
/// Maximum altitude in meters
public let maximumAltitude: CLLocationDistance
/// The maximum recorded speed (in meters per second)
public let maximumSpeed: CLLocationSpeed

/// Initialize this TrackStat with the required properties. Generally you want to create this stat using the `summarize(from legs: [Leg])` factory creation function to create one of these objects. That function will compute all of the required fields and delegate to this initializer.
///
/// - Parameters:
/// - runs: the number of runs for the track
/// - legs: The number of legs for the track (number of ascents + descents)
/// - ascent: the vertical ascent for the track
/// - descent: the vertical descent for this track
/// - ascentDistance: the total ascent distance for this track
/// - descentDistance: the total descent distance for this track
/// - totalDistance: the total distance for this track
init(runs: Int, ascent: CLLocationDistance, descent: CLLocationDistance, ascentDistance: CLLocationDistance, descentDistance: CLLocationDistance, totalDistance: CLLocationDistance) {
init(runs: Int, legs: Int, ascent: CLLocationDistance, descent: CLLocationDistance,
ascentDistance: CLLocationDistance, descentDistance: CLLocationDistance, totalDistance: CLLocationDistance,
minimumAltitude: CLLocationDistance, maximumAltitude: CLLocationDistance, maximumSpeed: CLLocationSpeed) {
self.runs = runs
self.legs = legs
verticalAscent = ascent
verticalDescent = descent
self.ascentDistance = ascentDistance
self.descentDistance = descentDistance
self.totalDistance = totalDistance
self.minimumAltitude = minimumAltitude
self.maximumAltitude = maximumAltitude
self.maximumSpeed = maximumSpeed
}

/// Using the provided array of Legs, this function will compute the track summary stats and provide you with a a summary TrackStat for the entire track.
Expand All @@ -100,6 +136,9 @@ public class TrackStat: Stat {
var aDistance: CLLocationDistance = 0
var dDistance: CLLocationDistance = 0
var tDistance: CLLocationDistance = 0
var minAlt: CLLocationDistance = CLLocationDistanceMax
var maxAlt: CLLocationDistance = 0
var maxSpeed: CLLocationSpeed = 0

for leg in legs {
let stat = leg.stat
Expand All @@ -113,12 +152,31 @@ public class TrackStat: Stat {
runs += 1
}
tDistance += stat.distance
minAlt = min(minAlt, stat.minimumAltitude)
maxAlt = max(maxAlt, stat.maximumAltitude)
maxSpeed = max(maxSpeed, stat.maximumSpeed)
}

let stat = TrackStat(runs: runs, ascent: vAscent, descent: vDescent, ascentDistance: aDistance, descentDistance: dDistance, totalDistance: tDistance)
stat.combine(with: baseOverallStat)
let stat = TrackStat(runs: runs, legs: legs.count, ascent: vAscent, descent: vDescent,
ascentDistance: aDistance, descentDistance: dDistance, totalDistance: tDistance,
minimumAltitude: minAlt, maximumAltitude: maxAlt, maximumSpeed: maxSpeed)
return stat
}

}

// MARK: - Stat: CustomDebugStringConvertible

extension TrackStat: CustomDebugStringConvertible {


public var debugDescription: String {
return "\(runs) runs, (\(legs) legs), " +
"Altitude: \(minimumAltitude.int)m - \(maximumAltitude.int)m, " +
"Vertical ascent: \(verticalAscent.int)m, Vertical descent: \(verticalDescent.int)m, " +
"Distance(ascent): \(ascentDistance.int)m, Distance(descent): \(descentDistance.int)m, " +
"Distance(total): \(totalDistance.int)m, Max Speed: \(maximumSpeed.int)m/s"
}
}

/// The direction that we're going
Expand Down Expand Up @@ -266,3 +324,13 @@ extension Leg: Equatable {
return true
}
}

// MARK: - Number Helpers

extension CLLocationDistance {

var int: Int {
return Int(self)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,6 @@ class GeoTrackAnalyzerTests: XCTestCase {
}
}

// func testRewriteInOrder() {
// guard let track = track else {
// return XCTFail("No track")
// }
//
// guard let data = try? JSONSerialization.data(withJSONObject: track.map, options: .prettyPrinted) else {
// return XCTFail("Failed to serialize the track into data")
// }
//
// let path = "/tmp/out.json"
// do {
// try data.write(to: URL(fileURLWithPath: path))
// } catch {
// XCTFail(error.localizedDescription)
// }
// }

func testAnalyze() {
guard let track = track else {
return XCTFail("No track")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,70 @@
// Copyright © 2018 Eric Internicola. All rights reserved.
//

import CoreLocation
@testable import GeoTrackKit
import XCTest

class GeoTrackStatisticsTests: XCTestCase {


var track: GeoTrack?

override func setUp() {
super.setUp()
guard let referenceTrack = TrackReader(filename: "reference-track-1", type: "json").track else {
return XCTFail("Failed to load the reference track")
}
track = referenceTrack
}

func testVerifyLegStats() {
guard let track = track else {
return XCTFail("No reference track")
}
let analyzer = GeoTrackAnalyzer(track: track)
analyzer.calculate()

guard let stats = analyzer.stats else {
return XCTFail("No stats from the track")
}
XCTAssertEqual(6, stats.runs, "Wrong number of runs")
XCTAssertEqual(12, stats.legs, "Wrong number of legs")
XCTAssertTrue(stats.ascentDistance > 0, "The ascent distance is wrong")
XCTAssertTrue(stats.descentDistance > 0, "The descent distance is wrong")
XCTAssertTrue(stats.totalDistance > 0, "The total distance is wrong")
XCTAssertTrue(stats.verticalAscent > 0, "The vertical ascent is wrong")
XCTAssertTrue(stats.verticalDescent < 0, "The vertical descent is wrong")
XCTAssertTrue(stats.totalDistance > 0, "The distance is wrong")
XCTAssertTrue(stats.maximumAltitude > 0, "The maximum altitude is wrong")
XCTAssertTrue(stats.maximumSpeed > 0, "The maximum speed is wrong")
XCTAssertTrue(stats.minimumAltitude < CLLocationDistanceMax, "The minimum altitude is wrong")

let total = Stat.zero
var ascentDistance = CLLocationDistance(0)
var descentDistance = CLLocationDistance(0)
var verticalAscent = CLLocationDistance(0)
var verticalDescent = CLLocationDistance(0)

for leg in analyzer.legs {
total.combine(with: leg.stat)
if leg.direction == .downward {
descentDistance += leg.stat.distance
verticalDescent += leg.stat.verticalDelta
}
if leg.direction == .upward {
ascentDistance += leg.stat.distance
verticalAscent += leg.stat.verticalDelta
}
}

XCTAssertEqual(total.distance, stats.totalDistance, accuracy: 0.01)
XCTAssertEqual(total.maximumAltitude, stats.maximumAltitude, accuracy: 0.01)
XCTAssertEqual(total.minimumAltitude, stats.minimumAltitude, accuracy: 0.01)
XCTAssertEqual(total.maximumSpeed, stats.maximumSpeed, accuracy: 0.01)
XCTAssertEqual(ascentDistance, stats.ascentDistance, accuracy: 0.01)
XCTAssertEqual(descentDistance, stats.descentDistance, accuracy: 0.01)
XCTAssertEqual(verticalAscent, stats.verticalAscent, accuracy: 0.01)
XCTAssertEqual(abs(verticalDescent), abs(stats.verticalDescent), accuracy: 0.01)
}

}