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

Add speed limit data returned by MapboxDirections.swift to the RouteProgress object #2114

Merged
merged 14 commits into from
Dec 27, 2019
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* Since pure Swift protocols cannot have optional methods, various delegate protocols now provide default no-op implementations for all their methods and conform to the `UnimplementedLogging` protocol, which can inform you at runtime when a delegate method is called but has not been implemented. Messages are sent through Apple Unified Logging and can be disabled globally through [Unifed Logging](https://developer.apple.com/documentation/os/logging#2878594), or by overriding the delegate function with a no-op implementation. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* Renamed `RouteProgress.nearbyCoordinates` to `RouteProgress.nearbyShape`. ([#2275](https://github.com/mapbox/mapbox-navigation-ios/pull/2275))
* Removed `RouteLegProgress.nearbyCoordinates`. ([#2275](https://github.com/mapbox/mapbox-navigation-ios/pull/2275))
* Added the `RouteLegProgress.currentSpeedLimit` property. ([#2114](https://github.com/mapbox/mapbox-navigation-ios/pull/2114))

## v0.38.1

Expand Down
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ github "CedarBDD/Cedar" "v1.0"
github "Quick/Nimble" "v8.0.4"
github "Quick/Quick" "v2.2.0"
github "ceeK/Solar" "2.1.0"
github "mapbox/MapboxDirections.swift" "472cbced199ef2d9c5c2fa49a21e8f6b249e9bf8"
github "mapbox/MapboxDirections.swift" "93a790ab18168c61a99f72b94e984b00e5bc7476"
github "mapbox/MapboxGeocoder.swift" "v0.10.2"
github "mapbox/mapbox-events-ios" "v0.9.5"
github "mapbox/mapbox-speech-swift" "v0.1.1"
Expand Down
9 changes: 8 additions & 1 deletion MapboxCoreNavigation/NavigationRouteOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ open class NavigationRouteOptions: RouteOptions {
shapeFormat = .polyline6
includesSteps = true
routeShapeResolution = .full
attributeOptions = [.congestionLevel, .expectedTravelTime]
if profileIdentifier == .walking {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profileIdentifier is mutable, so technically it’s possible for the developer to change the profile identifier to walking after initializing the object. We could address that case by conditionally adding maximumSpeedLimit in an override of the URLQueryItems property instead of this initializer. But maybe it isn’t a big deal.

1ec5 marked this conversation as resolved.
Show resolved Hide resolved
attributeOptions = [.congestionLevel, .expectedTravelTime]
} else {
attributeOptions = [.congestionLevel, .expectedTravelTime, .maximumSpeedLimit]
1ec5 marked this conversation as resolved.
Show resolved Hide resolved
}
includesSpokenInstructions = true
locale = Locale.nationalizedCurrent
distanceMeasurementSystem = Locale.current.usesMetricSystem ? .metric : .imperial
Expand Down Expand Up @@ -75,6 +79,9 @@ open class NavigationMatchOptions: MatchOptions {
routeShapeResolution = .full
shapeFormat = .polyline6
attributeOptions = [.congestionLevel, .expectedTravelTime]
if profileIdentifier == .automobile || profileIdentifier == .automobileAvoidingTraffic {
attributeOptions.insert(.maximumSpeedLimit)
}
includesSpokenInstructions = true
locale = Locale.nationalizedCurrent
distanceMeasurementSystem = Locale.current.usesMetricSystem ? .metric : .imperial
Expand Down
21 changes: 21 additions & 0 deletions MapboxCoreNavigation/RouteProgress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,27 @@ open class RouteLegProgress: NSObject {
return accumulatedCoordinates <= userCoordinateIndex
})
}

/**
Returns the SpeedLimit for the current position along the route. Returns SpeedLimit.invalid if the speed limit is unknown or missing.

The maximum speed may be an advisory speed limit for segments where legal limits are not posted, such as highway entrance and exit ramps. If the speed limit along a particular segment is unknown, it is set to `nil`. If the speed is unregulated along the segment, such as on the German _Autobahn_ system, it is represented by a measurement whose value is `Double.infinity`.

Speed limit data is available in [a number of countries and territories worldwide](https://docs.mapbox.com/help/how-mapbox-works/directions/).
*/
public var currentSpeedLimit: Measurement<UnitSpeed>? {
let distanceTraveled = currentStepProgress.distanceTraveled
guard let index = currentStep.shape?.indexedCoordinateFromStart(distance: distanceTraveled)?.index else {
return nil
}
let range = leg.segmentRangesByStep[stepIndex]
let speedLimit = leg.segmentMaximumSpeedLimits?[range][index]
if let speedUnit = currentStep.speedLimitUnit {
return speedLimit?.converted(to: speedUnit)
} else {
return speedLimit
}
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion MapboxCoreNavigationTests/OptionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class OptionsTests: XCTestCase {
XCTAssertEqual(options.profileIdentifier, .automobileAvoidingTraffic)
XCTAssertEqual(options.routeShapeResolution, .full)
XCTAssertEqual(options.shapeFormat, .polyline6)
XCTAssertEqual(options.attributeOptions, [.congestionLevel, .expectedTravelTime])
XCTAssertEqual(options.attributeOptions, [.congestionLevel, .expectedTravelTime, .maximumSpeedLimit])
XCTAssertTrue(options.includesVisualInstructions)
XCTAssertTrue(options.includesSpokenInstructions)
}
Expand Down
63 changes: 61 additions & 2 deletions MapboxCoreNavigationTests/RouteProgressTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class RouteProgressTests: XCTestCase {
XCTAssertEqual(waypoints.1.map { $0.coordinate }, [coordinates.last!])
}

func routeLegProgress(options: RouteOptions, routeCoordinates: [CLLocationCoordinate2D]) -> RouteLegProgress {
func routeLeg(options: RouteOptions, routeCoordinates: [CLLocationCoordinate2D]) -> RouteLeg {
let source = options.waypoints.first!
let destination = options.waypoints.last!
options.shapeFormat = .polyline
Expand All @@ -97,7 +97,11 @@ class RouteProgressTests: XCTestCase {
RouteStep(transportType: .automobile, maneuverLocation: destination.coordinate, maneuverType: .arrive, maneuverDirection: nil, instructions: "", initialHeading: nil, finalHeading: nil, drivingSide: .right, exitCodes: nil, exitNames: nil, phoneticExitNames: nil, distance: 0, expectedTravelTime: 0, names: nil, phoneticNames: nil, codes: nil, destinationCodes: nil, destinations: nil, intersections: nil, instructionsSpokenAlongStep: nil, instructionsDisplayedAlongStep: nil),
]
steps[0].shape = LineString(routeCoordinates)
let leg = RouteLeg(steps: steps, name: "", distance: 0, expectedTravelTime: 0, profileIdentifier: .automobile)
return RouteLeg(steps: steps, name: "", distance: 0, expectedTravelTime: 0, profileIdentifier: .automobile)
}

func routeLegProgress(options: RouteOptions, routeCoordinates: [CLLocationCoordinate2D]) -> RouteLegProgress {
let leg = routeLeg(options: options, routeCoordinates: routeCoordinates)
return RouteLegProgress(leg: leg)
}

Expand Down Expand Up @@ -212,4 +216,59 @@ class RouteProgressTests: XCTestCase {
XCTAssertEqual(remainingWaypoints.count, 0,
"At the last via point after backtracking, nothing should remain")
}

func testSpeedLimits() {
let coordinates = [
CLLocationCoordinate2D(latitude: 0, longitude: 0),
CLLocationCoordinate2D(latitude: 2, longitude: 3),
CLLocationCoordinate2D(latitude: 4, longitude: 6),
CLLocationCoordinate2D(latitude: 6, longitude: 9),
CLLocationCoordinate2D(latitude: 8, longitude: 12),
CLLocationCoordinate2D(latitude: 10, longitude: 15),
CLLocationCoordinate2D(latitude: 12, longitude: 18),
]
let lineString = LineString(coordinates)

let options = RouteOptions(coordinates: [coordinates.first!, coordinates.last!])
let leg = routeLeg(options: options, routeCoordinates: coordinates)
leg.segmentMaximumSpeedLimits = [
.init(value: 10, unit: .kilometersPerHour),
.init(value: 20, unit: .milesPerHour),
nil,
.init(value: 40, unit: .milesPerHour),
.init(value: 50, unit: .kilometersPerHour),
.init(value: .infinity, unit: .kilometersPerHour),
]
let legProgress = RouteLegProgress(leg: leg)

XCTAssertEqual(legProgress.distanceTraveled, 0)
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 10, unit: UnitSpeed.kilometersPerHour))
legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[1]) / 2.0
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 10, unit: UnitSpeed.kilometersPerHour))

legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[1])
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 20, unit: UnitSpeed.milesPerHour))
legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[1]) + lineString.distance(from: coordinates[1], to: coordinates[2]) / 2.0
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 20, unit: UnitSpeed.milesPerHour))

legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[2])
XCTAssertNil(legProgress.currentSpeedLimit)
legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[2]) + lineString.distance(from: coordinates[2], to: coordinates[3]) / 2.0
XCTAssertNil(legProgress.currentSpeedLimit)

legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[3])
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 40, unit: UnitSpeed.milesPerHour))
legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[3]) + lineString.distance(from: coordinates[3], to: coordinates[4]) / 2.0
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 40, unit: UnitSpeed.milesPerHour))

legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[4])
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 50, unit: UnitSpeed.kilometersPerHour))
legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[4]) + lineString.distance(from: coordinates[4], to: coordinates[5]) / 2.0
XCTAssertEqual(legProgress.currentSpeedLimit, Measurement(value: 50, unit: UnitSpeed.kilometersPerHour))

legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[5])
XCTAssertTrue(legProgress.currentSpeedLimit?.value.isInfinite ?? false)
legProgress.currentStepProgress.distanceTraveled = lineString.distance(to: coordinates[5]) + (lineString.distance() - lineString.distance(to: coordinates[5])) / 2.0
XCTAssertTrue(legProgress.currentSpeedLimit?.value.isInfinite ?? false)
}
}