Skip to content

Commit

Permalink
Merge pull request #623 from mapbox/pad-max-dimensions
Browse files Browse the repository at this point in the history
Add maximum width and height to RouteOptions
  • Loading branch information
chezzdev authored Nov 8, 2021
2 parents 1dd5552 + 02bb4e3 commit 9d74955
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 13 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
* Added the `RouteOptions.initialManeuverAvoidanceRadius` property to avoid a sudden maneuver when calculating a route while the user is in motion. ([#609](https://github.com/mapbox/mapbox-directions-swift/pull/609))
* Added the `RoadClasses.unpaved` option for avoiding unpaved roads. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added the `RoadClasses.cashOnlyToll` property for avoiding toll roads that only accept cash payment. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added the `RouteOptions.maximumHeight` and `RouteOptions.maximumWidth` properties for ensuring that the resulting routes can accommodate a vehicle of a certain size. ([#623](https://github.com/mapbox/mapbox-directions-swift/pull/623))
* The `DirectionsPriority` struct now conforms to the `Codable` protocol. ([#623](https://github.com/mapbox/mapbox-directions-swift/pull/623))
* Fixed an issue where the `RouteOptions.alleyPriority`, `RouteOptions.walkwayPriority`, and `RouteOptions.speed` properties were excluded from the encoded representation of a `RouteOptions` object. ([#623](https://github.com/mapbox/mapbox-directions-swift/pull/623))

## v2.0.0

Expand Down
2 changes: 1 addition & 1 deletion Sources/MapboxDirections/DirectionsOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public typealias MBDirectionsPriority = DirectionsPriority
/**
A number that influences whether a route should prefer or avoid roadways or pathways of a given type.
*/
public struct DirectionsPriority: Hashable, RawRepresentable {
public struct DirectionsPriority: Hashable, RawRepresentable, Codable {
public init(rawValue: Double) {
self.rawValue = rawValue
}
Expand Down
76 changes: 65 additions & 11 deletions Sources/MapboxDirections/RouteOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ open class RouteOptions: DirectionsOptions {
case roadClassesToAllow = "include"
case refreshingEnabled = "enable_refresh"
case initialManeuverAvoidanceRadius = "avoid_maneuver_radius"
case maximumHeight = "max_height"
case maximumWidth = "max_width"
case alleyPriority = "alley_bias"
case walkwayPriority = "walkway_bias"
case speed = "walking_speed"
case waypointTargets = "waypoint_targets"
}

public override func encode(to encoder: Encoder) throws {
Expand All @@ -70,6 +76,11 @@ open class RouteOptions: DirectionsOptions {
try container.encode(roadClassesToAllow, forKey: .roadClassesToAllow)
try container.encode(refreshingEnabled, forKey: .refreshingEnabled)
try container.encodeIfPresent(initialManeuverAvoidanceRadius, forKey: .initialManeuverAvoidanceRadius)
try container.encodeIfPresent(maximumHeight?.converted(to: .meters).value, forKey: .maximumHeight)
try container.encodeIfPresent(maximumWidth?.converted(to: .meters).value, forKey: .maximumWidth)
try container.encodeIfPresent(alleyPriority, forKey: .alleyPriority)
try container.encodeIfPresent(walkwayPriority, forKey: .walkwayPriority)
try container.encodeIfPresent(speed, forKey: .speed)
}

public required init(from decoder: Decoder) throws {
Expand All @@ -87,6 +98,21 @@ open class RouteOptions: DirectionsOptions {
refreshingEnabled = try container.decode(Bool.self, forKey: .refreshingEnabled)

_initialManeuverAvoidanceRadius = try container.decodeIfPresent(LocationDistance.self, forKey: .initialManeuverAvoidanceRadius)

if let maximumHeightValue = try container.decodeIfPresent(Double.self, forKey: .maximumHeight) {
maximumHeight = Measurement(value: maximumHeightValue, unit: .meters)
}

if let maximumWidthValue = try container.decodeIfPresent(Double.self, forKey: .maximumWidth) {
maximumWidth = Measurement(value: maximumWidthValue, unit: .meters)
}

alleyPriority = try container.decodeIfPresent(DirectionsPriority.self, forKey: .alleyPriority)

walkwayPriority = try container.decodeIfPresent(DirectionsPriority.self, forKey: .walkwayPriority)

speed = try container.decodeIfPresent(LocationSpeed.self, forKey: .speed)

try super.init(from: decoder)
}

Expand Down Expand Up @@ -193,6 +219,24 @@ open class RouteOptions: DirectionsOptions {
To refresh the `RouteLeg.expectedSegmentTravelTimes`, `RouteLeg.segmentSpeeds`, and `RouteLeg.segmentCongestionLevels` properties, use the `Directions.refreshRoute(responseIdentifier:routeIndex:fromLegAtIndex:completionHandler:)` method. This property is ignored unless `profileIdentifier` is `DirectionsProfileIdentifier.automobileAvoidingTraffic`. This option is set to `false` by default.
*/
open var refreshingEnabled = false

/**
The maximum vehicle height.
If this parameter is provided, `Directions` will compute a route that includes only roads with a height limit greater than or equal to the max vehicle height or no height limit.
This property is supported by `DirectionsProfileIdentifier.automobile` and `DirectionsProfileIdentifier.automobileAvoidingTraffic` profiles.
The value must be between 0 and 10 when converted to meters.
*/
open var maximumHeight: Measurement<UnitLength>?

/**
The maximum vehicle width.
If this parameter is provided, `Directions` will compute a route that includes only roads with a width limit greater than or equal to the max vehicle width or no width limit.
This property is supported by `DirectionsProfileIdentifier.automobile` and `DirectionsProfileIdentifier.automobileAvoidingTraffic` profiles.
The value must be between 0 and 10 when converted to meters.
*/
open var maximumWidth: Measurement<UnitLength>?

/**
A radius around the starting point in which the API will avoid returning any significant maneuvers.
Expand All @@ -219,51 +263,61 @@ open class RouteOptions: DirectionsOptions {

override open var urlQueryItems: [URLQueryItem] {
var params: [URLQueryItem] = [
URLQueryItem(name: "alternatives", value: String(includesAlternativeRoutes)),
URLQueryItem(name: "continue_straight", value: String(!allowsUTurnAtWaypoint)),
URLQueryItem(name: CodingKeys.includesAlternativeRoutes.stringValue, value: String(includesAlternativeRoutes)),
URLQueryItem(name: CodingKeys.allowsUTurnAtWaypoint.stringValue, value: String(!allowsUTurnAtWaypoint)),
]

if includesExitRoundaboutManeuver {
params.append(URLQueryItem(name: "roundabout_exits", value: String(includesExitRoundaboutManeuver)))
params.append(URLQueryItem(name: CodingKeys.includesExitRoundaboutManeuver.stringValue, value: String(includesExitRoundaboutManeuver)))
}
if let alleyPriority = alleyPriority?.rawValue {
params.append(URLQueryItem(name: "alley_bias", value: String(alleyPriority)))
params.append(URLQueryItem(name: CodingKeys.alleyPriority.stringValue, value: String(alleyPriority)))
}

if let walkwayPriority = walkwayPriority?.rawValue {
params.append(URLQueryItem(name: "walkway_bias", value: String(walkwayPriority)))
params.append(URLQueryItem(name: CodingKeys.walkwayPriority.stringValue, value: String(walkwayPriority)))
}

if let speed = speed {
params.append(URLQueryItem(name: "walking_speed", value: String(speed)))
params.append(URLQueryItem(name: CodingKeys.speed.stringValue, value: String(speed)))
}

if !roadClassesToAvoid.isEmpty && roadClassesToAvoid.isDisjoint(with: [.highOccupancyVehicle2, .highOccupancyVehicle3, .highOccupancyToll]) {
let allRoadClasses = roadClassesToAvoid.description.components(separatedBy: ",").filter { !$0.isEmpty }
precondition(allRoadClasses.count < 2, "You can only avoid one road class at a time.")
if let firstRoadClass = allRoadClasses.first {
params.append(URLQueryItem(name: "exclude", value: firstRoadClass))
params.append(URLQueryItem(name: CodingKeys.roadClassesToAvoid.stringValue, value: firstRoadClass))
}
}

if !roadClassesToAllow.isEmpty && roadClassesToAllow.isSubset(of: [.highOccupancyVehicle2, .highOccupancyVehicle3, .highOccupancyToll]) {
let allRoadClasses = roadClassesToAllow.description.components(separatedBy: ",").filter { !$0.isEmpty }
allRoadClasses.forEach { roadClass in
params.append(URLQueryItem(name: "include", value: roadClass))
params.append(URLQueryItem(name: CodingKeys.roadClassesToAllow.stringValue, value: roadClass))
}
}

if refreshingEnabled && profileIdentifier == .automobileAvoidingTraffic {
params.append(URLQueryItem(name: "enable_refresh", value: String(refreshingEnabled)))
params.append(URLQueryItem(name: CodingKeys.refreshingEnabled.stringValue, value: String(refreshingEnabled)))
}

if waypoints.first(where: { $0.targetCoordinate != nil }) != nil {
let targetCoordinates = waypoints.filter { $0.separatesLegs }.map { $0.targetCoordinate?.requestDescription ?? "" }.joined(separator: ";")
params.append(URLQueryItem(name: "waypoint_targets", value: targetCoordinates))
params.append(URLQueryItem(name: CodingKeys.waypointTargets.stringValue, value: targetCoordinates))
}

if let initialManeuverAvoidanceRadius = initialManeuverAvoidanceRadius {
params.append(URLQueryItem(name: "avoid_maneuver_radius", value: String(initialManeuverAvoidanceRadius)))
params.append(URLQueryItem(name: CodingKeys.initialManeuverAvoidanceRadius.stringValue, value: String(initialManeuverAvoidanceRadius)))
}

if let maximumHeight = maximumHeight {
let heightInMeters = maximumHeight.converted(to: .meters).value
params.append(URLQueryItem(name: CodingKeys.maximumHeight.stringValue, value: String(heightInMeters)))
}

if let maximumWidth = maximumWidth {
let widthInMeters = maximumWidth.converted(to: .meters).value
params.append(URLQueryItem(name: CodingKeys.maximumWidth.stringValue, value: String(widthInMeters)))
}

return params + super.urlQueryItems
Expand Down
30 changes: 29 additions & 1 deletion Tests/MapboxDirectionsTests/RouteOptionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class RouteOptionsTests: XCTestCase {
XCTAssertEqual(unarchivedOptions.roadClassesToAvoid, options.roadClassesToAvoid)
XCTAssertEqual(unarchivedOptions.roadClassesToAllow, options.roadClassesToAllow)
XCTAssertEqual(unarchivedOptions.initialManeuverAvoidanceRadius, options.initialManeuverAvoidanceRadius)
XCTAssertEqual(unarchivedOptions.maximumWidth, options.maximumWidth)
XCTAssertEqual(unarchivedOptions.maximumHeight, options.maximumHeight)
XCTAssertEqual(unarchivedOptions.alleyPriority, options.alleyPriority)
XCTAssertEqual(unarchivedOptions.walkwayPriority, options.walkwayPriority)
XCTAssertEqual(unarchivedOptions.speed, options.speed)
}

func testCodingWithRawCodingKeys() {
Expand All @@ -56,7 +61,11 @@ class RouteOptionsTests: XCTestCase {
"exclude": ["toll"],
"include": ["hov3", "hot"],
"enable_refresh": false,
"avoid_maneuver_radius": 300
"avoid_maneuver_radius": 300,
"max_width": 2.3,
"max_height": 3,
"alley_bias": DirectionsPriority.low.rawValue,
"walkway_bias": DirectionsPriority.high.rawValue,
]

let routeOptionsData = try! JSONSerialization.data(withJSONObject: routeOptionsJSON, options: [])
Expand All @@ -79,6 +88,8 @@ class RouteOptionsTests: XCTestCase {
XCTAssertEqual(routeOptions.roadClassesToAllow, [.highOccupancyVehicle3, .highOccupancyToll])
XCTAssertEqual(routeOptions.refreshingEnabled, false)
XCTAssertEqual(routeOptions.initialManeuverAvoidanceRadius, 300)
XCTAssertEqual(routeOptions.maximumWidth, Measurement(value: 2.3, unit: .meters))
XCTAssertEqual(routeOptions.maximumHeight, Measurement(value: 3, unit: .meters))

let encodedRouteOptions: Data = try! JSONEncoder().encode(routeOptions)
let optionsString: String = String(data: encodedRouteOptions, encoding: .utf8)!
Expand All @@ -102,6 +113,8 @@ class RouteOptionsTests: XCTestCase {
XCTAssertEqual(unarchivedOptions.roadClassesToAllow, routeOptions.roadClassesToAllow)
XCTAssertEqual(unarchivedOptions.refreshingEnabled, routeOptions.refreshingEnabled)
XCTAssertEqual(unarchivedOptions.initialManeuverAvoidanceRadius, routeOptions.initialManeuverAvoidanceRadius)
XCTAssertEqual(unarchivedOptions.maximumWidth, routeOptions.maximumWidth)
XCTAssertEqual(unarchivedOptions.maximumHeight, routeOptions.maximumHeight)
}

// MARK: API name-handling tests
Expand Down Expand Up @@ -224,6 +237,16 @@ class RouteOptionsTests: XCTestCase {

XCTAssertFalse(options.urlQueryItems.contains(URLQueryItem(name: "avoid_maneuver_radius", value: nil)))
}

func testMaximumWidthAndMaximimHeightSerialization() {
let options = RouteOptions(coordinates: [])
let widthValue = 2.3
let heightValue = 2.0
options.maximumWidth = Measurement(value: widthValue, unit: .meters)
options.maximumHeight = Measurement(value: heightValue, unit: .meters)
XCTAssertTrue(options.urlQueryItems.contains(URLQueryItem(name: "max_width", value: String(widthValue))))
XCTAssertTrue(options.urlQueryItems.contains(URLQueryItem(name: "max_height", value: String(heightValue))))
}
}

fileprivate let testCoordinates = [
Expand All @@ -246,6 +269,11 @@ var testRouteOptions: RouteOptions {
opts.roadClassesToAvoid = .toll
opts.roadClassesToAllow = [.highOccupancyVehicle3, .highOccupancyToll]
opts.initialManeuverAvoidanceRadius = 100
opts.maximumWidth = Measurement(value: 2, unit: .meters)
opts.maximumHeight = Measurement(value: 3, unit: .meters)
opts.alleyPriority = .low
opts.walkwayPriority = .high
opts.speed = 1

return opts
}

0 comments on commit 9d74955

Please sign in to comment.