diff --git a/CHANGELOG.md b/CHANGELOG.md index 558c534bc..32e5efcd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to Mapbox Directions for Swift +## main + +* Added `Waypoint.allowsSnappingToStaticallyClosedRoad` property to allow snapping the waypoint’s location to a statically (long-term) closed part of a road. ([#721](https://github.com/mapbox/mapbox-directions-swift/pull/721)) + ## v2.6.0 * MapboxDirections now requires [Turf v2.4](https://github.com/mapbox/turf-swift/releases/tag/v2.4.0). ([#703](https://github.com/mapbox/mapbox-directions-swift/pull/703)) diff --git a/Sources/MapboxDirections/DirectionsOptions.swift b/Sources/MapboxDirections/DirectionsOptions.swift index 00ad4ae9d..cd8a8bba0 100644 --- a/Sources/MapboxDirections/DirectionsOptions.swift +++ b/Sources/MapboxDirections/DirectionsOptions.swift @@ -182,7 +182,8 @@ open class DirectionsOptions: Codable { mappedQueryItems["bearings"]?.components(separatedBy: ";"), mappedQueryItems["radiuses"]?.components(separatedBy: ";"), mappedQueryItems["waypoint_names"]?.components(separatedBy: ";"), - mappedQueryItems["snapping_include_closures"]?.components(separatedBy: ";") + mappedQueryItems["snapping_include_closures"]?.components(separatedBy: ";"), + mappedQueryItems["snapping_include_static_closures"]?.components(separatedBy: ";") ] as [[String]?] let getElement: ((_ array: [String]?, _ index: Int) -> String?) = { array, index in @@ -209,6 +210,10 @@ open class DirectionsOptions: Codable { if let snaps = getElement(waypointsData[4], $0.offset) { $0.element.allowsSnappingToClosedRoad = snaps == "true" } + + if let snapsToStaticallyClosed = getElement(waypointsData[5], $0.offset) { + $0.element.allowsSnappingToStaticallyClosedRoad = snapsToStaticallyClosed == "true" + } } waypoints.filter { $0.separatesLegs }.enumerated().forEach { @@ -497,6 +502,10 @@ open class DirectionsOptions: Codable { if let snapping = self.closureSnapping { queryItems.append(URLQueryItem(name: "snapping_include_closures", value: snapping)) } + + if let staticClosureSnapping = self.staticClosureSnapping { + queryItems.append(URLQueryItem(name: "snapping_include_static_closures", value: staticClosureSnapping)) + } return queryItems } @@ -560,10 +569,16 @@ open class DirectionsOptions: Codable { } internal var closureSnapping: String? { - guard waypoints.contains(where: \.allowsSnappingToClosedRoad) else { - return nil - } - return waypoints.map { $0.allowsSnappingToClosedRoad ? "true": ""}.joined(separator: ";") + makeStringFromBoolProperties(of: waypoints, for: \.allowsSnappingToClosedRoad) + } + + internal var staticClosureSnapping: String? { + makeStringFromBoolProperties(of: waypoints, for: \.allowsSnappingToStaticallyClosedRoad) + } + + private func makeStringFromBoolProperties(of elements: [T], for keyPath: KeyPath) -> String? { + guard elements.contains(where: { $0[keyPath: keyPath] }) else { return nil } + return elements.map { $0[keyPath: keyPath] ? "true" : "" }.joined(separator: ";") } internal var httpBody: String { diff --git a/Sources/MapboxDirections/Waypoint.swift b/Sources/MapboxDirections/Waypoint.swift index 5df5ed8cd..9c22aa660 100644 --- a/Sources/MapboxDirections/Waypoint.swift +++ b/Sources/MapboxDirections/Waypoint.swift @@ -161,6 +161,13 @@ public class Waypoint: Codable, ForeignMemberContainerClass { If `true`, the waypoint may be snapped to a road segment that is closed due to a live traffic closure. This property is `false` by default. This property corresponds to the [`snapping_include_closures`](https://docs.mapbox.com/api/navigation/directions/#optional-parameters-for-the-mapboxdriving-traffic-profile) query parameter in the Mapbox Directions API. */ public var allowsSnappingToClosedRoad: Bool = false + + /** + A Boolean value indicating whether the waypoint may be snapped to a statically (long-term) closed road in the resulting `RouteResponse`. + + If `true`, the waypoint may be snapped to a road segment statically closed, that is long-term (for example, road under construction). This property is `false` by default. This property corresponds to the [`snapping_include_static_closures`](https://docs.mapbox.com/api/navigation/directions/#optional-parameters-for-the-mapboxdriving-traffic-profile) query parameter in the Mapbox Directions API. + */ + public var allowsSnappingToStaticallyClosedRoad: Bool = false /** The straight-line distance from the coordinate specified in the query to the location it was snapped to in the resulting `RouteResponse`. diff --git a/Tests/MapboxDirectionsTests/RouteOptionsTests.swift b/Tests/MapboxDirectionsTests/RouteOptionsTests.swift index 60e01abe2..3278e6a67 100644 --- a/Tests/MapboxDirectionsTests/RouteOptionsTests.swift +++ b/Tests/MapboxDirectionsTests/RouteOptionsTests.swift @@ -137,6 +137,7 @@ class RouteOptionsTests: XCTestCase { $0.element.targetCoordinate = $0.element.coordinate } $0.element.allowsSnappingToClosedRoad = $0.offset == 1 + $0.element.allowsSnappingToStaticallyClosedRoad = $0.offset == 1 } let url = Directions(credentials: BogusCredentials).url(forCalculating: originalOptions) @@ -157,6 +158,7 @@ class RouteOptionsTests: XCTestCase { zip(decodedWaypoints, originalOptions.waypoints).forEach { XCTAssertEqual($0.0.allowsSnappingToClosedRoad, $0.1.allowsSnappingToClosedRoad) + XCTAssertEqual($0.0.allowsSnappingToStaticallyClosedRoad, $0.1.allowsSnappingToStaticallyClosedRoad) XCTAssertEqual($0.0.allowsArrivingOnOppositeSide, $0.1.allowsArrivingOnOppositeSide) XCTAssertEqual($0.0.targetCoordinate, $0.1.targetCoordinate) XCTAssertEqual($0.0.separatesLegs, $0.1.separatesLegs) diff --git a/Tests/MapboxDirectionsTests/WaypointTests.swift b/Tests/MapboxDirectionsTests/WaypointTests.swift index 8982e2d85..844261585 100644 --- a/Tests/MapboxDirectionsTests/WaypointTests.swift +++ b/Tests/MapboxDirectionsTests/WaypointTests.swift @@ -158,13 +158,30 @@ class WaypointTests: XCTestCase { let from = Waypoint(coordinate: LocationCoordinate2D(latitude: 0, longitude: 0)) let to = Waypoint(coordinate: LocationCoordinate2D(latitude: 0, longitude: 0)) let through = Waypoint(coordinate: LocationCoordinate2D(latitude: 0, longitude: 0)) - + let routeOptions = RouteOptions(waypoints: [from, through, to]) let matchOptions = MatchOptions(waypoints: [from, through, to], profileIdentifier: nil) - + through.allowsSnappingToClosedRoad = true - + through.allowsSnappingToStaticallyClosedRoad = true + XCTAssertEqual(routeOptions.urlQueryItems.first { $0.name == "snapping_include_closures" }?.value, ";true;") XCTAssertEqual(matchOptions.urlQueryItems.first { $0.name == "snapping_include_closures" }?.value, ";true;") + XCTAssertEqual(routeOptions.urlQueryItems.first { $0.name == "snapping_include_static_closures" }?.value, ";true;") + XCTAssertEqual(matchOptions.urlQueryItems.first { $0.name == "snapping_include_static_closures" }?.value, ";true;") + } + + func testClosedRoadSnappingNotSet() { + let from = Waypoint(coordinate: LocationCoordinate2D(latitude: 0, longitude: 0)) + let to = Waypoint(coordinate: LocationCoordinate2D(latitude: 0, longitude: 0)) + let through = Waypoint(coordinate: LocationCoordinate2D(latitude: 0, longitude: 0)) + + let routeOptions = RouteOptions(waypoints: [from, through, to]) + let matchOptions = MatchOptions(waypoints: [from, through, to], profileIdentifier: nil) + + XCTAssertEqual(routeOptions.urlQueryItems.first { $0.name == "snapping_include_closures" }?.value, nil) + XCTAssertEqual(matchOptions.urlQueryItems.first { $0.name == "snapping_include_closures" }?.value, nil) + XCTAssertEqual(routeOptions.urlQueryItems.first { $0.name == "snapping_include_static_closures" }?.value, nil) + XCTAssertEqual(matchOptions.urlQueryItems.first { $0.name == "snapping_include_static_closures" }?.value, nil) } }