Skip to content

Commit

Permalink
RoadClasses restrictions violation. (#627)
Browse files Browse the repository at this point in the history
* vk-843-excluded-roadclasses: added RouteResponse.roadClassViolations calculated property; Added RouteRoadClassesViolations struct to expose RoadClasses violations to user; Added RouteResponse.exclusionViolations(routeIndex:legIndex:stepIndex:intersectionIndex:) methods for searching; Unit test added; CHANGELOG updated.
  • Loading branch information
Udumft committed Dec 10, 2021
1 parent 3371531 commit 4d0afa5
Show file tree
Hide file tree
Showing 6 changed files with 18,776 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* 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))
* Added the `RouteResponse.roadClassViolations` property, which indicates any requested `RouteOptions.roadClassesToAvoid` values that could not be satisfied when calculating the routes. You can use convenience `RouteResponse.exclusionViolations(routeIndex:legIndex:stepIndex:intersectionIndex:)` method to search for a specific item. ([#627](https://github.com/mapbox/mapbox-directions-swift/pull/627))

## v2.0.0

Expand Down
22 changes: 22 additions & 0 deletions MapboxDirections.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
2B01E4B92746ABBC0002A5F7 /* RouteResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A3B4C9A24EB55F60085DA64 /* RouteResponseTests.swift */; };
2B01E4BA2746ABBD0002A5F7 /* RouteResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A3B4C9A24EB55F60085DA64 /* RouteResponseTests.swift */; };
2B39DD40270F034700ED68E4 /* CodingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B39DD3F270F034700ED68E4 /* CodingOperation.swift */; };
2B4383022549C22700A3E38B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B4383002549C22700A3E38B /* main.swift */; };
2B5407ED2451B17E006C820B /* RouteRefreshResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5407EC2451B17E006C820B /* RouteRefreshResponse.swift */; };
Expand All @@ -33,6 +35,13 @@
2B540809245B23BE006C820B /* incorrectRouteRefreshResponse.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B540808245B23BE006C820B /* incorrectRouteRefreshResponse.json */; };
2B54080A245B23BE006C820B /* incorrectRouteRefreshResponse.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B540808245B23BE006C820B /* incorrectRouteRefreshResponse.json */; };
2B54080B245B23BE006C820B /* incorrectRouteRefreshResponse.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B540808245B23BE006C820B /* incorrectRouteRefreshResponse.json */; };
2B5F0DFC273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B5F0DFB273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json */; };
2B5F0DFD273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B5F0DFB273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json */; };
2B5F0DFE273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B5F0DFB273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json */; };
2B5F0E00273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5F0DFF273BEB3600CC2C1A /* RoadClassExclusionViolation.swift */; };
2B5F0E01273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5F0DFF273BEB3600CC2C1A /* RoadClassExclusionViolation.swift */; };
2B5F0E02273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5F0DFF273BEB3600CC2C1A /* RoadClassExclusionViolation.swift */; };
2B5F0E03273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5F0DFF273BEB3600CC2C1A /* RoadClassExclusionViolation.swift */; };
2B9F3881272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B9F387C272AE23A001DBA12 /* ProfileIdentifier.swift */; };
2B9F3882272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B9F387C272AE23A001DBA12 /* ProfileIdentifier.swift */; };
2B9F3883272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B9F387C272AE23A001DBA12 /* ProfileIdentifier.swift */; };
Expand Down Expand Up @@ -468,6 +477,8 @@
2B5407FF245B097D006C820B /* routeRefreshResponse.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = routeRefreshResponse.json; sourceTree = "<group>"; };
2B540804245B09E1006C820B /* routeRefreshRoute.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = routeRefreshRoute.json; sourceTree = "<group>"; };
2B540808245B23BE006C820B /* incorrectRouteRefreshResponse.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = incorrectRouteRefreshResponse.json; sourceTree = "<group>"; };
2B5F0DFB273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tollAndFerryDirectionsRoute.json; sourceTree = "<group>"; };
2B5F0DFF273BEB3600CC2C1A /* RoadClassExclusionViolation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoadClassExclusionViolation.swift; sourceTree = "<group>"; };
2B9F387C272AE23A001DBA12 /* ProfileIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileIdentifier.swift; sourceTree = "<group>"; };
2B9F387D272AE23A001DBA12 /* IsochroneOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IsochroneOptions.swift; sourceTree = "<group>"; };
2B9F387E272AE23A001DBA12 /* IsochroneError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IsochroneError.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -811,6 +822,7 @@
DA2E03EA1CB0E13D00D1269A /* RouteOptions.swift */,
43208BAC2343FF5500D8BD89 /* RouteResponse.swift */,
2B5407EC2451B17E006C820B /* RouteRefreshResponse.swift */,
2B5F0DFF273BEB3600CC2C1A /* RoadClassExclusionViolation.swift */,
2B5407F12452FA8C006C820B /* RefreshedRoute.swift */,
43538E3623ED3B1600E010D4 /* ResponseDisposition.swift */,
DA2E03E81CB0E0B000D1269A /* RouteStep.swift */,
Expand Down Expand Up @@ -870,6 +882,7 @@
DA6C9DAD1CAEC93800094FBC /* Fixtures */ = {
isa = PBXGroup;
children = (
2B5F0DFB273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json */,
2BA2E745257A667500D7AFC6 /* incidents.json */,
2B540803245B09B2006C820B /* RouteRefresh */,
35DBF017217F387F0009D2AE /* Offline */,
Expand Down Expand Up @@ -1241,6 +1254,7 @@
2B54080A245B23BE006C820B /* incorrectRouteRefreshResponse.json in Resources */,
C5C0D63520586419003A3B1D /* null-tracepoint.json in Resources */,
AEAB390E20D7F508008F4E54 /* subLaneInstructions.json in Resources */,
2B5F0DFD273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json in Resources */,
DAD31BC424D4ADBF00A1654D /* match-polyline6.json in Resources */,
C5D1D7F11F6AF92500A1C4F1 /* instructions.json in Resources */,
8D381B661FDB089E008D5A58 /* apiDestinationName.json in Resources */,
Expand Down Expand Up @@ -1273,6 +1287,7 @@
2B54080B245B23BE006C820B /* incorrectRouteRefreshResponse.json in Resources */,
C5C0D6362058641B003A3B1D /* null-tracepoint.json in Resources */,
AEAB390F20D7F50A008F4E54 /* subLaneInstructions.json in Resources */,
2B5F0DFE273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json in Resources */,
DAD31BC524D4ADC000A1654D /* match-polyline6.json in Resources */,
C5D1D7F21F6AF92600A1C4F1 /* instructions.json in Resources */,
8D381B671FDB089F008D5A58 /* apiDestinationName.json in Resources */,
Expand Down Expand Up @@ -1312,6 +1327,7 @@
2B540809245B23BE006C820B /* incorrectRouteRefreshResponse.json in Resources */,
C5C0D6342058523E003A3B1D /* null-tracepoint.json in Resources */,
AEAB390D20D7F4F4008F4E54 /* subLaneInstructions.json in Resources */,
2B5F0DFC273ACE3B00CC2C1A /* tollAndFerryDirectionsRoute.json in Resources */,
DAD31BC324D4ADBF00A1654D /* match-polyline6.json in Resources */,
C5D1D7F01F6AF91700A1C4F1 /* instructions.json in Resources */,
8D381B611FD9F5B1008D5A58 /* noDestinationName.json in Resources */,
Expand Down Expand Up @@ -1385,6 +1401,7 @@
DA1A10C91D00F969009F82FA /* RouteLeg.swift in Sources */,
C52552BA1FA15D5E00B1545C /* VisualInstructionBanner.swift in Sources */,
35828C9F217A003F00ED546E /* OfflineDirections.swift in Sources */,
2B5F0E01273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */,
DAE7EA95230B5FD10003B211 /* Measurement.swift in Sources */,
C5DAAC9F20195AAE001F9261 /* Tracepoint.swift in Sources */,
2B9F3882272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */,
Expand Down Expand Up @@ -1439,6 +1456,7 @@
912DF1A7240507640095D3D2 /* TransportTypeTests.swift in Sources */,
C53A02291E92C27A009837BD /* AnnotationTests.swift in Sources */,
4376A52823FB13D400C6038D /* MatchOptionsTests.swift in Sources */,
2B01E4B92746ABBC0002A5F7 /* RouteResponseTests.swift in Sources */,
DA688B3F21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */,
2B9F389B272AE28D001DBA12 /* IsochroneTests.swift in Sources */,
DAD06E36239F0B19001A917D /* DirectionsErrorTests.swift in Sources */,
Expand Down Expand Up @@ -1476,6 +1494,7 @@
DA1A10EF1D010247009F82FA /* RouteLeg.swift in Sources */,
C52552BB1FA15D5F00B1545C /* VisualInstructionBanner.swift in Sources */,
35828CA0217A003F00ED546E /* OfflineDirections.swift in Sources */,
2B5F0E02273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */,
DAE7EA96230B5FD10003B211 /* Measurement.swift in Sources */,
C5DAACA020195AAF001F9261 /* Tracepoint.swift in Sources */,
2B9F3883272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */,
Expand Down Expand Up @@ -1530,6 +1549,7 @@
912DF1A8240507640095D3D2 /* TransportTypeTests.swift in Sources */,
C53A022A1E92C27B009837BD /* AnnotationTests.swift in Sources */,
4376A52923FB13D400C6038D /* MatchOptionsTests.swift in Sources */,
2B01E4BA2746ABBD0002A5F7 /* RouteResponseTests.swift in Sources */,
DA688B4021B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */,
2B9F389C272AE28E001DBA12 /* IsochroneTests.swift in Sources */,
DAD06E37239F0B19001A917D /* DirectionsErrorTests.swift in Sources */,
Expand Down Expand Up @@ -1567,6 +1587,7 @@
DA1A11061D0103A3009F82FA /* RouteLeg.swift in Sources */,
C52552BC1FA15D6000B1545C /* VisualInstructionBanner.swift in Sources */,
35828CA1217A003F00ED546E /* OfflineDirections.swift in Sources */,
2B5F0E03273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */,
DAE7EA97230B5FD10003B211 /* Measurement.swift in Sources */,
C5DAACA120195AAF001F9261 /* Tracepoint.swift in Sources */,
2B9F3884272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */,
Expand Down Expand Up @@ -1625,6 +1646,7 @@
C582BA2E2073ED6300647DAA /* Array.swift in Sources */,
C52552B91FA15D5900B1545C /* VisualInstructionBanner.swift in Sources */,
35828C9E217A003F00ED546E /* OfflineDirections.swift in Sources */,
2B5F0E00273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */,
DAE7EA94230B5FD10003B211 /* Measurement.swift in Sources */,
C59426071F1EA6C400C8E59C /* RoadClasses.swift in Sources */,
2B9F3881272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */,
Expand Down
28 changes: 28 additions & 0 deletions Sources/MapboxDirections/RoadClassExclusionViolation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

import Foundation

/**
Exact `RoadClass` exclusion violation case.
*/
public struct RoadClassExclusionViolation {
/**
`RoadClasses` that were violated at this point.
*/
public var roadClasses: RoadClasses
/**
Index of a `Route` inside `RouteResponse` where violation occured.
*/
public var routeIndex: Int
/**
Index of a `RouteLeg` inside `Route` where violation occured.
*/
public var legIndex: Int
/**
Index of a `RouteStep` inside `RouteLeg` where violation occured.
*/
public var stepIndex: Int
/**
Index of an `Intersection` inside `RouteStep` where violation occured.
*/
public var intersectionIndex: Int
}
146 changes: 144 additions & 2 deletions Sources/MapboxDirections/RouteResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ public struct RouteResponse {
public let httpResponse: HTTPURLResponse?

public let identifier: String?
public var routes: [Route]?
public var routes: [Route]? {
didSet {
updateRoadClassExclusionViolations()
}
}
public let waypoints: [Waypoint]?

public let options: ResponseOptions
Expand All @@ -26,6 +30,15 @@ public struct RouteResponse {
This property does not persist after encoding and decoding.
*/
public var created: Date = Date()

/**
Managed array of `RoadClasses` restrictions specified to `RouteOptions.roadClassesToAvoid` which were violated during route calculation.
Routing engine may still utilize `RoadClasses` meant to be avoided in cases when routing is impossible otherwise.
Violations are ordered by routes from the `routes` array, then by a leg, step, and intersection, where `RoadClasses` restrictions were ignored. `nil` and empty return arrays correspond to `nil` and empty `routes` array respectively.
*/
public private(set) var roadClassExclusionViolations: [RoadClassExclusionViolation]?
}

extension RouteResponse: Codable {
Expand All @@ -41,10 +54,12 @@ extension RouteResponse: Codable {
public init(httpResponse: HTTPURLResponse?, identifier: String? = nil, routes: [Route]? = nil, waypoints: [Waypoint]? = nil, options: ResponseOptions, credentials: Credentials) {
self.httpResponse = httpResponse
self.identifier = identifier
self.options = options
self.routes = routes
self.waypoints = waypoints
self.options = options
self.credentials = credentials

updateRoadClassExclusionViolations()
}

public init(matching response: MapMatchingResponse, options: MatchOptions, credentials: Credentials) throws {
Expand Down Expand Up @@ -140,6 +155,8 @@ extension RouteResponse: Codable {
} else {
routes = nil
}

updateRoadClassExclusionViolations()
}

public func encode(to encoder: Encoder) throws {
Expand All @@ -150,3 +167,128 @@ extension RouteResponse: Codable {
}

}

extension RouteResponse {

mutating func updateRoadClassExclusionViolations() {
guard case let .route(routeOptions) = options else {
roadClassExclusionViolations = nil
return
}

guard let routes = routes else {
roadClassExclusionViolations = nil
return
}

let avoidedClasses = routeOptions.roadClassesToAvoid

guard !avoidedClasses.isEmpty else {
roadClassExclusionViolations = nil
return
}

var violations = [RoadClassExclusionViolation]()

for (routeIndex, route) in routes.enumerated() {
for (legIndex, leg) in route.legs.enumerated() {
for (stepIndex, step) in leg.steps.enumerated() {
for (intersectionIndex, intersection) in (step.intersections ?? []).enumerated() {
if let outletRoadClasses = intersection.outletRoadClasses,
!avoidedClasses.isDisjoint(with: outletRoadClasses) {
violations.append(RoadClassExclusionViolation(roadClasses: avoidedClasses.intersection(outletRoadClasses),
routeIndex: routeIndex,
legIndex: legIndex,
stepIndex: stepIndex,
intersectionIndex: intersectionIndex))
}
}
}
}
}
roadClassExclusionViolations = violations
}

/**
Filters `roadClassExclusionViolations` lazily to search for specific leg and step.
- parameter routeIndex: Index of a route inside current `RouteResponse` to search in.
- parameter legIndex: Index of a leg inside related `Route`to search in.
- returns: Lazy filtered array of `RoadClassExclusionViolation` under given indicies.
Passing `nil` as `legIndex` will result in searching for all legs.
*/
public func exclusionViolations(routeIndex: Int, legIndex: Int? = nil) -> LazyFilterSequence<[RoadClassExclusionViolation]> {
return filteredViolations(routeIndex: routeIndex,
legIndex: legIndex,
stepIndex: nil,
intersectionIndex: nil)
}

/**
Filters `roadClassExclusionViolations` lazily to search for specific leg and step.
- parameter routeIndex: Index of a route inside current `RouteResponse` to search in.
- parameter legIndex: Index of a leg inside related `Route`to search in.
- parameter stepIndex: Index of a step inside given `Route`'s leg.
- returns: Lazy filtered array of `RoadClassExclusionViolation` under given indicies.
Passing `nil` as `stepIndex` will result in searching for all steps.
*/
public func exclusionViolations(routeIndex: Int, legIndex: Int, stepIndex: Int? = nil) -> LazyFilterSequence<[RoadClassExclusionViolation]> {
return filteredViolations(routeIndex: routeIndex,
legIndex: legIndex,
stepIndex: stepIndex,
intersectionIndex: nil)
}

/**
Filters `roadClassExclusionViolations` lazily to search for specific leg, step and intersection.
- parameter routeIndex: Index of a route inside current `RouteResponse` to search in.
- parameter legIndex: Index of a leg inside related `Route`to search in.
- parameter stepIndex: Index of a step inside given `Route`'s leg.
- parameter intersectionIndex: Index of an intersection inside given `Route`'s leg and step.
- returns: Lazy filtered array of `RoadClassExclusionViolation` under given indicies.
Passing `nil` as `intersectionIndex` will result in searching for all intersections of given step.
*/
public func exclusionViolations(routeIndex: Int, legIndex: Int, stepIndex: Int, intersectionIndex: Int?) -> LazyFilterSequence<[RoadClassExclusionViolation]> {
return filteredViolations(routeIndex: routeIndex,
legIndex: legIndex,
stepIndex: stepIndex,
intersectionIndex: intersectionIndex)
}

private func filteredViolations(routeIndex: Int, legIndex: Int? = nil, stepIndex: Int? = nil, intersectionIndex: Int? = nil) -> LazyFilterSequence<[RoadClassExclusionViolation]> {
assert(!(stepIndex == nil && intersectionIndex != nil), "It is forbidden to select `intersectionIndex` without specifying `stepIndex`.")

guard let roadClassExclusionViolations = roadClassExclusionViolations else {
return LazyFilterSequence<[RoadClassExclusionViolation]>(_base: [], {_ in true})
}

var filtered = roadClassExclusionViolations.lazy.filter {
$0.routeIndex == routeIndex
}

if let legIndex = legIndex {
filtered = filtered.filter {
$0.legIndex == legIndex
}
}

if let stepIndex = stepIndex {
filtered = filtered.filter {
$0.stepIndex == stepIndex
}
}

if let intersectionIndex = intersectionIndex {
filtered = filtered.filter {
$0.intersectionIndex == intersectionIndex
}
}

return filtered
}
}
Loading

0 comments on commit 4d0afa5

Please sign in to comment.