diff --git a/CHANGELOG.md b/CHANGELOG.md index aa125ff4d..dd2f316db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v2.3.0 * Added `VisualInstruction.Component.ShieldRepresentation` struct for displaying a highway shield. Added `VisualInstruction.Component.ImageRepresentation.shield` property. ([#644](https://github.com/mapbox/mapbox-directions-swift/pull/644), [#647](https://github.com/mapbox/mapbox-directions-swift/pull/647)) +* Added `RouteLeg.viaWaypoints` property and `SilentWaypoint` struct for describing silent waypoints along `RouteLeg`. ([#656](https://github.com/mapbox/mapbox-directions-swift/pull/656)) ## v2.2.0 diff --git a/MapboxDirections.xcodeproj/project.pbxproj b/MapboxDirections.xcodeproj/project.pbxproj index 391ddd144..864a14125 100644 --- a/MapboxDirections.xcodeproj/project.pbxproj +++ b/MapboxDirections.xcodeproj/project.pbxproj @@ -80,6 +80,10 @@ 2BF398C627620CD7000C9A72 /* RouteRefreshSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BF398C427620CD7000C9A72 /* RouteRefreshSource.swift */; }; 2BF398C727620CD7000C9A72 /* RouteRefreshSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BF398C427620CD7000C9A72 /* RouteRefreshSource.swift */; }; 2BF398C827620CD7000C9A72 /* RouteRefreshSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BF398C427620CD7000C9A72 /* RouteRefreshSource.swift */; }; + 2E44711727C4C80B0041CB84 /* SilentWaypoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E44711627C4C80B0041CB84 /* SilentWaypoint.swift */; }; + 2E44711827C4C80B0041CB84 /* SilentWaypoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E44711627C4C80B0041CB84 /* SilentWaypoint.swift */; }; + 2E44711927C4C80B0041CB84 /* SilentWaypoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E44711627C4C80B0041CB84 /* SilentWaypoint.swift */; }; + 2E44711A27C4C80B0041CB84 /* SilentWaypoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E44711627C4C80B0041CB84 /* SilentWaypoint.swift */; }; 35828C9E217A003F00ED546E /* OfflineDirections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35828C9D217A003F00ED546E /* OfflineDirections.swift */; }; 35828C9F217A003F00ED546E /* OfflineDirections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35828C9D217A003F00ED546E /* OfflineDirections.swift */; }; 35828CA0217A003F00ED546E /* OfflineDirections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35828C9D217A003F00ED546E /* OfflineDirections.swift */; }; @@ -494,6 +498,7 @@ 2BBBD05D257E61ED004EB3D6 /* MapboxStreetsRoadClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapboxStreetsRoadClass.swift; sourceTree = ""; }; 2BBBD08C257FA1CD004EB3D6 /* BlockedLanes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedLanes.swift; sourceTree = ""; }; 2BF398C427620CD7000C9A72 /* RouteRefreshSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRefreshSource.swift; sourceTree = ""; }; + 2E44711627C4C80B0041CB84 /* SilentWaypoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SilentWaypoint.swift; sourceTree = ""; }; 3556CE9922649CF2009397B5 /* MapboxDirectionsTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "MapboxDirectionsTests-Bridging-Header.h"; path = "../objc/MapboxDirectionsTests-Bridging-Header.h"; sourceTree = ""; }; 35828C9D217A003F00ED546E /* OfflineDirections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineDirections.swift; sourceTree = ""; }; 35CC310A2285739700EA1966 /* WalkingOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalkingOptionsTests.swift; sourceTree = ""; }; @@ -834,6 +839,7 @@ 2BF398C427620CD7000C9A72 /* RouteRefreshSource.swift */, 43538E3623ED3B1600E010D4 /* ResponseDisposition.swift */, DA2E03E81CB0E0B000D1269A /* RouteStep.swift */, + 2E44711627C4C80B0041CB84 /* SilentWaypoint.swift */, C55FB44A1F6AEBF6006BD1E9 /* SpokenInstruction.swift */, 35EFD00A207DFACA00BF3873 /* VisualInstruction.swift */, C52552B81FA15D5900B1545C /* VisualInstructionBanner.swift */, @@ -1411,6 +1417,7 @@ 35828C9F217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E01273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA95230B5FD10003B211 /* Measurement.swift in Sources */, + 2E44711827C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAAC9F20195AAE001F9261 /* Tracepoint.swift in Sources */, 2B9F3882272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, 431E93CC23466C2500A71B44 /* RouteResponse.swift in Sources */, @@ -1505,6 +1512,7 @@ 35828CA0217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E02273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA96230B5FD10003B211 /* Measurement.swift in Sources */, + 2E44711927C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAACA020195AAF001F9261 /* Tracepoint.swift in Sources */, 2B9F3883272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, 431E93CD23466C2700A71B44 /* RouteResponse.swift in Sources */, @@ -1599,6 +1607,7 @@ 35828CA1217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E03273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA97230B5FD10003B211 /* Measurement.swift in Sources */, + 2E44711A27C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAACA120195AAF001F9261 /* Tracepoint.swift in Sources */, 2B9F3884272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, 431E93CE23466C2800A71B44 /* RouteResponse.swift in Sources */, @@ -1659,6 +1668,7 @@ 35828C9E217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E00273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA94230B5FD10003B211 /* Measurement.swift in Sources */, + 2E44711727C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C59426071F1EA6C400C8E59C /* RoadClasses.swift in Sources */, 2B9F3881272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, 431E93CB23466C2400A71B44 /* RouteResponse.swift in Sources */, diff --git a/Sources/MapboxDirections/RouteLeg.swift b/Sources/MapboxDirections/RouteLeg.swift index a927bae3a..6ac03b18b 100644 --- a/Sources/MapboxDirections/RouteLeg.swift +++ b/Sources/MapboxDirections/RouteLeg.swift @@ -20,6 +20,7 @@ open class RouteLeg: Codable { case annotation case administrativeRegions = "admins" case incidents + case viaWaypoints = "via_waypoints" } // MARK: Creating a Leg @@ -85,6 +86,10 @@ open class RouteLeg: Codable { if let incidents = try container.decodeIfPresent([Incident].self, forKey: .incidents) { self.incidents = incidents } + + if let viaWaypoints = try container.decodeIfPresent([SilentWaypoint].self, forKey: .viaWaypoints) { + self.viaWaypoints = viaWaypoints + } } public func encode(to encoder: Encoder) throws { @@ -110,6 +115,10 @@ open class RouteLeg: Codable { if let incidents = incidents { try container.encode(incidents, forKey: .incidents) } + + if let viaWaypoints = viaWaypoints { + try container.encode(viaWaypoints, forKey: .viaWaypoints) + } } // MARK: Getting the Endpoints of the Leg @@ -309,6 +318,13 @@ open class RouteLeg: Codable { This property is set to `nil` if incidents data is not available. */ open var incidents: [Incident]? + + /** + Describes where a particular `Waypoint` passed to `RouteOptions` matches to the route along a `RouteLeg`. + + The property is non-nil when for one or several `Waypoint` objects passed to `RouteOptions` have `separatesLegs` property set to `false`. + */ + open var viaWaypoints: [SilentWaypoint]? /** The route leg’s typical travel time, measured in seconds. @@ -343,6 +359,9 @@ extension RouteLeg: Equatable { lhs.name == rhs.name && lhs.distance == rhs.distance && lhs.expectedTravelTime == rhs.expectedTravelTime && + lhs.administrativeRegions == rhs.administrativeRegions && + lhs.incidents == rhs.incidents && + lhs.viaWaypoints == rhs.viaWaypoints && lhs.typicalTravelTime == rhs.typicalTravelTime && lhs.profileIdentifier == rhs.profileIdentifier } diff --git a/Sources/MapboxDirections/SilentWaypoint.swift b/Sources/MapboxDirections/SilentWaypoint.swift new file mode 100644 index 000000000..1c1afdfcb --- /dev/null +++ b/Sources/MapboxDirections/SilentWaypoint.swift @@ -0,0 +1,36 @@ +import Foundation + +/** + Represents a silent waypoint along the `RouteLeg`. + + See `RouteLeg.viaWaypoints` for more details. + */ +public struct SilentWaypoint: Codable, Equatable { + public enum CodingKeys: String, CodingKey { + case waypointIndex = "waypoint_index" + case distanceFromStart = "distance_from_start" + case shapeCoordinateIndex = "geometry_index" + } + + /// The associated waypoint index in `RouteResponse.waypoints`, excluding the origin (index 0) and destination. + public var waypointIndex: Int + + /// The calculated distance, in meters, from the leg origin. + public var distanceFromStart: Double + + /// The associated `Route` shape index of the silent waypoint location. + public var shapeCoordinateIndex: Int + + public init(waypointIndex: Int, distanceFromStart: Double, shapeCoordinateIndex: Int) { + self.waypointIndex = waypointIndex + self.distanceFromStart = distanceFromStart + self.shapeCoordinateIndex = shapeCoordinateIndex + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + waypointIndex = try container.decode(Int.self, forKey: .waypointIndex) + distanceFromStart = try container.decode(Double.self, forKey: .distanceFromStart) + shapeCoordinateIndex = try container.decode(Int.self, forKey: .shapeCoordinateIndex) + } +} diff --git a/Tests/MapboxDirectionsTests/Fixtures/v5/v5_driving_oldenburg_polyline.json b/Tests/MapboxDirectionsTests/Fixtures/v5/v5_driving_oldenburg_polyline.json index f8d4f8dfb..54513afaf 100644 --- a/Tests/MapboxDirectionsTests/Fixtures/v5/v5_driving_oldenburg_polyline.json +++ b/Tests/MapboxDirectionsTests/Fixtures/v5/v5_driving_oldenburg_polyline.json @@ -1 +1 @@ -{"routes":[{"geometry":"ahboF~y`gOeA?k@?_@?O@I?S?c@?S?sC@K@S?wABaC?k@@QA?IA{D?iAAkDAQbB?fAAt@?h@AJ??O?oAAmB?K?kAAmDCaKA[AMCG?EIMMEo@@kBBsA@K??F@|@","legs":[{"summary":"Perlen Strasse, Haupt Strasse","weight":238.9,"duration":122.1,"steps":[{"intersections":[{"out":0,"entry":[true],"bearings":[0],"location":[-85.206241,39.33841]},{"out":0,"in":1,"entry":[true,false,true],"bearings":[0,180,240],"location":[-85.206241,39.338979]},{"out":0,"in":2,"entry":[true,true,false,true],"bearings":[0,90,180,270],"location":[-85.206244,39.339143]},{"out":0,"in":2,"entry":[true,true,false],"bearings":[0,90,180],"location":[-85.206273,39.340549]}],"driving_side":"right","geometry":"ahboF~y`gOeA?k@?_@?O@I?S?c@?S?sC@K@S?wABaC?k@@QA","mode":"driving","maneuver":{"bearing_after":0,"bearing_before":0,"location":[-85.206241,39.33841],"type":"depart","instruction":"Fahren Sie Richtung Norden auf Maulbeerfeigen Strasse (SR 229)"},"ref":"SR 229","weight":38.1,"duration":25.6,"name":"Maulbeerfeigen Strasse (SR 229)","distance":393.3},{"intersections":[{"out":1,"in":2,"entry":[true,true,false,true],"bearings":[0,90,180,270],"location":[-85.206293,39.341945]},{"out":0,"in":2,"entry":[true,true,false],"bearings":[90,180,270],"location":[-85.204929,39.341962]}],"driving_side":"right","geometry":"e~boFhz`gO?IA{D?iAAkDAQ","mode":"driving","maneuver":{"bearing_after":88,"bearing_before":358,"location":[-85.206293,39.341945],"modifier":"right","type":"turn","instruction":"Rechts abbiegen auf Weinstock Strasse"},"weight":32.9,"duration":19.9,"name":"Weinstock Strasse","distance":198.8},{"intersections":[{"out":2,"in":3,"entry":[true,true,true,false],"bearings":[0,90,180,270],"location":[-85.203982,39.341975]}],"driving_side":"right","geometry":"k~boFzk`gObB?fAAt@?h@AJ?","mode":"driving","maneuver":{"bearing_after":178,"bearing_before":88,"location":[-85.203982,39.341975],"modifier":"right","type":"turn","instruction":"Rechts abbiegen auf Perlen Strasse"},"weight":94.7,"duration":35.8,"name":"Perlen Strasse","distance":155.5},{"intersections":[{"out":1,"in":0,"entry":[false,true,true,true],"bearings":[0,90,180,270],"location":[-85.203962,39.340577]},{"out":1,"in":3,"entry":[true,true,true,false],"bearings":[0,90,180,270],"location":[-85.201616,39.3406]},{"out":0,"in":1,"entry":[true,false],"bearings":[75,255],"location":[-85.199436,39.340656]}],"driving_side":"right","geometry":"suboFvk`gO?O?oAAmB?K?kAAmDCaKA[AMCG?EIMMEo@@kBBsA@K?","mode":"driving","maneuver":{"bearing_after":88,"bearing_before":178,"location":[-85.203962,39.340577],"modifier":"left","type":"turn","instruction":"Links abbiegen auf Haupt Strasse (SR 229)"},"ref":"SR 229","weight":71.2,"duration":38.8,"name":"Haupt Strasse (SR 229)","distance":548.2},{"intersections":[{"out":2,"in":1,"entry":[true,false,true],"bearings":[0,180,270],"location":[-85.199353,39.342038]}],"driving_side":"right","geometry":"w~boF|n_gO?F@|@","mode":"driving","maneuver":{"bearing_after":268,"bearing_before":357,"location":[-85.199353,39.342038],"modifier":"left","type":"turn","instruction":"Links abbiegen auf Weinstock Strasse"},"weight":2,"duration":2,"name":"Weinstock Strasse","distance":29.6},{"intersections":[{"in":0,"entry":[true],"bearings":[89],"location":[-85.199697,39.342033]}],"driving_side":"right","geometry":"u~boFbq_gO","mode":"driving","maneuver":{"bearing_after":0,"bearing_before":269,"location":[-85.199697,39.342033],"type":"arrive","instruction":"Sie haben Ihr To"},"weight":0,"duration":0,"name":"Weinstock Strasse","distance":0}],"distance":1325.4}],"weight_name":"routability","weight":238.9,"duration":122.1,"distance":1325.4}],"waypoints":[{"distance":0.7760785081050579,"name":"Maulbeerfeigen Strasse","location":[-85.206241,39.33841]},{"distance":0.9485403982504417,"name":"Perlen Strasse","location":[-85.20398,39.34181]},{"distance":1.665293749024342,"name":"Weinstock Strasse","location":[-85.199697,39.342033]}],"code":"Ok","uuid":"cjslj6r7403r34jo289thx5ax"} \ No newline at end of file +{"routes":[{"geometry":"ahboF~y`gOeA?k@?_@?O@I?S?c@?S?sC@K@S?wABaC?k@@QA?IA{D?iAAkDAQbB?fAAt@?h@AJ??O?oAAmB?K?kAAmDCaKA[AMCG?EIMMEo@@kBBsA@K??F@|@","legs":[{"summary":"Perlen Strasse, Haupt Strasse","weight":238.9,"duration":122.1,"via_waypoints":[{"waypoint_index":1,"distance_from_start":610.733,"geometry_index":21}],"steps":[{"intersections":[{"out":0,"entry":[true],"bearings":[0],"location":[-85.206241,39.33841]},{"out":0,"in":1,"entry":[true,false,true],"bearings":[0,180,240],"location":[-85.206241,39.338979]},{"out":0,"in":2,"entry":[true,true,false,true],"bearings":[0,90,180,270],"location":[-85.206244,39.339143]},{"out":0,"in":2,"entry":[true,true,false],"bearings":[0,90,180],"location":[-85.206273,39.340549]}],"driving_side":"right","geometry":"ahboF~y`gOeA?k@?_@?O@I?S?c@?S?sC@K@S?wABaC?k@@QA","mode":"driving","maneuver":{"bearing_after":0,"bearing_before":0,"location":[-85.206241,39.33841],"type":"depart","instruction":"Fahren Sie Richtung Norden auf Maulbeerfeigen Strasse (SR 229)"},"ref":"SR 229","weight":38.1,"duration":25.6,"name":"Maulbeerfeigen Strasse (SR 229)","distance":393.3},{"intersections":[{"out":1,"in":2,"entry":[true,true,false,true],"bearings":[0,90,180,270],"location":[-85.206293,39.341945]},{"out":0,"in":2,"entry":[true,true,false],"bearings":[90,180,270],"location":[-85.204929,39.341962]}],"driving_side":"right","geometry":"e~boFhz`gO?IA{D?iAAkDAQ","mode":"driving","maneuver":{"bearing_after":88,"bearing_before":358,"location":[-85.206293,39.341945],"modifier":"right","type":"turn","instruction":"Rechts abbiegen auf Weinstock Strasse"},"weight":32.9,"duration":19.9,"name":"Weinstock Strasse","distance":198.8},{"intersections":[{"out":2,"in":3,"entry":[true,true,true,false],"bearings":[0,90,180,270],"location":[-85.203982,39.341975]}],"driving_side":"right","geometry":"k~boFzk`gObB?fAAt@?h@AJ?","mode":"driving","maneuver":{"bearing_after":178,"bearing_before":88,"location":[-85.203982,39.341975],"modifier":"right","type":"turn","instruction":"Rechts abbiegen auf Perlen Strasse"},"weight":94.7,"duration":35.8,"name":"Perlen Strasse","distance":155.5},{"intersections":[{"out":1,"in":0,"entry":[false,true,true,true],"bearings":[0,90,180,270],"location":[-85.203962,39.340577]},{"out":1,"in":3,"entry":[true,true,true,false],"bearings":[0,90,180,270],"location":[-85.201616,39.3406]},{"out":0,"in":1,"entry":[true,false],"bearings":[75,255],"location":[-85.199436,39.340656]}],"driving_side":"right","geometry":"suboFvk`gO?O?oAAmB?K?kAAmDCaKA[AMCG?EIMMEo@@kBBsA@K?","mode":"driving","maneuver":{"bearing_after":88,"bearing_before":178,"location":[-85.203962,39.340577],"modifier":"left","type":"turn","instruction":"Links abbiegen auf Haupt Strasse (SR 229)"},"ref":"SR 229","weight":71.2,"duration":38.8,"name":"Haupt Strasse (SR 229)","distance":548.2},{"intersections":[{"out":2,"in":1,"entry":[true,false,true],"bearings":[0,180,270],"location":[-85.199353,39.342038]}],"driving_side":"right","geometry":"w~boF|n_gO?F@|@","mode":"driving","maneuver":{"bearing_after":268,"bearing_before":357,"location":[-85.199353,39.342038],"modifier":"left","type":"turn","instruction":"Links abbiegen auf Weinstock Strasse"},"weight":2,"duration":2,"name":"Weinstock Strasse","distance":29.6},{"intersections":[{"in":0,"entry":[true],"bearings":[89],"location":[-85.199697,39.342033]}],"driving_side":"right","geometry":"u~boFbq_gO","mode":"driving","maneuver":{"bearing_after":0,"bearing_before":269,"location":[-85.199697,39.342033],"type":"arrive","instruction":"Sie haben Ihr To"},"weight":0,"duration":0,"name":"Weinstock Strasse","distance":0}],"distance":1325.4}],"weight_name":"routability","weight":238.9,"duration":122.1,"distance":1325.4}],"waypoints":[{"distance":0.7760785081050579,"name":"Maulbeerfeigen Strasse","location":[-85.206241,39.33841]},{"distance":0.9485403982504417,"name":"Perlen Strasse","location":[-85.20398,39.34181]},{"distance":1.665293749024342,"name":"Weinstock Strasse","location":[-85.199697,39.342033]}],"code":"Ok","uuid":"cjslj6r7403r34jo289thx5ax"} diff --git a/Tests/MapboxDirectionsTests/V5Tests.swift b/Tests/MapboxDirectionsTests/V5Tests.swift index e38072d30..9e6405ae6 100644 --- a/Tests/MapboxDirectionsTests/V5Tests.swift +++ b/Tests/MapboxDirectionsTests/V5Tests.swift @@ -160,7 +160,7 @@ class V5Tests: XCTestCase { XCTAssertEqual(RouteShapeFormat.geoJSON.rawValue, "geojson") test(shapeFormat: .geoJSON) } - + func testPolyline() { XCTAssertEqual(RouteShapeFormat.polyline.rawValue, "polyline") test(shapeFormat: .polyline) @@ -279,6 +279,11 @@ class V5Tests: XCTestCase { XCTAssertEqual(leg?.destination?.coordinate.latitude ?? 0, waypoints[2].coordinate.latitude, accuracy: 1e-4) XCTAssertEqual(leg?.destination?.coordinate.longitude ?? 0, waypoints[2].coordinate.longitude, accuracy: 1e-4) XCTAssertEqual(leg?.name, "Perlen Strasse, Haupt Strasse") + XCTAssertEqual(leg?.viaWaypoints!.count, 1) + let silentWaypoint = leg?.viaWaypoints?.first! + XCTAssertEqual(silentWaypoint?.waypointIndex, 1) + XCTAssertEqual(silentWaypoint?.distanceFromStart, 610.733) + XCTAssertEqual(silentWaypoint?.shapeCoordinateIndex, 21) } func testCoding() {