Skip to content

Commit

Permalink
Added Waypoint.targetCoordinate
Browse files Browse the repository at this point in the history
Added the Waypoint.targetCoordinate property corresponding to the Directions API’s waypoint_targets property. Added unit tests of copying and coding waypoints, as well as specifying waypoint_targets and other waypoint properties in conjunction with a RouteOptions object. Added documentation associating some properties with their API counterparts for discoverability.
  • Loading branch information
1ec5 committed Dec 12, 2018
1 parent 1348574 commit 78dbc84
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

## master

* Added a `Waypoint.targetCoordinate` property for specifying a more specific destination for arrival instructions. ([#326](https://github.com/mapbox/MapboxDirections.swift/pull/326))
* Fixed an issue where the `Waypoint.allowsArrivingOnOppositeSide` property was not copied when copying a `Waypoint` object. ([#326](https://github.com/mapbox/MapboxDirections.swift/pull/326))

## v0.25.2
Expand Down
8 changes: 8 additions & 0 deletions MapboxDirections.xcodeproj/project.pbxproj
Expand Up @@ -201,6 +201,9 @@
DA1A110D1D01045E009F82FA /* DirectionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1A110A1D01045E009F82FA /* DirectionsTests.swift */; };
DA2E03E91CB0E0B000D1269A /* MBRouteStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2E03E81CB0E0B000D1269A /* MBRouteStep.swift */; };
DA2E03EB1CB0E13D00D1269A /* MBRouteOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2E03EA1CB0E13D00D1269A /* MBRouteOptions.swift */; };
DA4F84ED21C08BFB008A0434 /* WaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */; };
DA4F84EE21C08BFB008A0434 /* WaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */; };
DA4F84EF21C08BFB008A0434 /* WaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */; };
DA688B3E21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */; };
DA688B3F21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */; };
DA688B4021B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */; };
Expand Down Expand Up @@ -365,6 +368,7 @@
DA1A110A1D01045E009F82FA /* DirectionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionsTests.swift; sourceTree = "<group>"; };
DA2E03E81CB0E0B000D1269A /* MBRouteStep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBRouteStep.swift; sourceTree = "<group>"; };
DA2E03EA1CB0E13D00D1269A /* MBRouteOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBRouteOptions.swift; sourceTree = "<group>"; };
DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointTests.swift; sourceTree = "<group>"; };
DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualInstructionComponentTests.swift; sourceTree = SOURCE_ROOT; };
DA6C9D881CAE442B00094FBC /* MapboxDirections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapboxDirections.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DA6C9D8A1CAE442B00094FBC /* MapboxDirections.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapboxDirections.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -628,6 +632,7 @@
DA737EE71D0611CB005BDA16 /* V4Tests.swift */,
DA6C9DAB1CAEC72800094FBC /* V5Tests.swift */,
DA6C9DB11CAECA0E00094FBC /* Fixture.swift */,
DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */,
C5247D701E818A24004B6154 /* AnnotationTests.swift */,
DAE33A1A1F215DF600C06039 /* IntersectionTests.swift */,
C52CE3921F6AF6E70069963D /* IntructionsTests.swift */,
Expand Down Expand Up @@ -1268,6 +1273,7 @@
DA1A10CE1D00F972009F82FA /* Fixture.swift in Sources */,
DA1A110C1D01045E009F82FA /* DirectionsTests.swift in Sources */,
C5D1D7F31F6AFBD600A1C4F1 /* IntructionsTests.swift in Sources */,
DA4F84EE21C08BFB008A0434 /* WaypointTests.swift in Sources */,
F4D785F01DDD82C100FF4665 /* RouteStepTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1327,6 +1333,7 @@
DA1A10F51D010251009F82FA /* Fixture.swift in Sources */,
DA1A110D1D01045E009F82FA /* DirectionsTests.swift in Sources */,
C5D1D7F41F6AFBD600A1C4F1 /* IntructionsTests.swift in Sources */,
DA4F84EF21C08BFB008A0434 /* WaypointTests.swift in Sources */,
F4D785F11DDD82C100FF4665 /* RouteStepTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1425,6 +1432,7 @@
DA6C9DB21CAECA0E00094FBC /* Fixture.swift in Sources */,
DA1A110B1D01045E009F82FA /* DirectionsTests.swift in Sources */,
C52CE3931F6AF6E70069963D /* IntructionsTests.swift in Sources */,
DA4F84ED21C08BFB008A0434 /* WaypointTests.swift in Sources */,
F4D785EF1DDD82C100FF4665 /* RouteStepTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
10 changes: 10 additions & 0 deletions MapboxDirections/Extensions/CLLocationCoordinate2D.swift
Expand Up @@ -33,4 +33,14 @@ extension CLLocationCoordinate2D {
let coordinates = lineString["coordinates"] as! [[Double]]
return coordinates.map { self.init(geoJSON: $0) }
}

/**
A string representation of the coordinate suitable for insertion in a Directions API request URL.
*/
internal var stringForRequestURL: String? {
guard CLLocationCoordinate2DIsValid(self) else {
return nil
}
return "\(longitude.rounded(to: 1e6)),\(latitude.rounded(to: 1e6))"
}
}
2 changes: 1 addition & 1 deletion MapboxDirections/MBDirectionsOptions.swift
Expand Up @@ -328,7 +328,7 @@ open class DirectionsOptions: NSObject, NSSecureCoding, NSCopying {
An array of directions query strings to include in the request URL.
*/
internal var queries: [String] {
return waypoints.compactMap { return "\($0.coordinate.longitude.rounded(to: 1e6)),\($0.coordinate.latitude.rounded(to: 1e6))" }
return waypoints.compactMap{ $0.coordinate.stringForRequestURL }
}

internal var path: String {
Expand Down
5 changes: 5 additions & 0 deletions MapboxDirections/MBRouteOptions.swift
Expand Up @@ -142,6 +142,11 @@ open class RouteOptions: DirectionsOptions {
params.append(URLQueryItem(name: "exclude", value: firstRoadClass))
}
}

if waypoints.first(where: { CLLocationCoordinate2DIsValid($0.targetCoordinate) }) != nil {
let targetCoordinates = waypoints.map { $0.targetCoordinate.stringForRequestURL ?? "" }.joined(separator: ";")
params.append(URLQueryItem(name: "waypoint_targets", value: targetCoordinates))
}

return params
}
Expand Down
21 changes: 21 additions & 0 deletions MapboxDirections/MBWaypoint.swift
Expand Up @@ -67,6 +67,9 @@ open class Waypoint: NSObject, NSCopying, NSSecureCoding {
let longitude = decoder.decodeDouble(forKey: "longitude")
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
coordinateAccuracy = decoder.decodeDouble(forKey: "coordinateAccuracy")
let targetLatitude = decoder.decodeDouble(forKey: "targetLatitude")
let targetLongitude = decoder.decodeDouble(forKey: "targetLongitude")
targetCoordinate = CLLocationCoordinate2D(latitude: targetLatitude, longitude: targetLongitude)
heading = decoder.decodeDouble(forKey: "heading")
headingAccuracy = decoder.decodeDouble(forKey: "headingAccuracy")
name = decoder.decodeObject(of: NSString.self, forKey: "name") as String?
Expand All @@ -77,6 +80,8 @@ open class Waypoint: NSObject, NSCopying, NSSecureCoding {
coder.encode(coordinate.latitude, forKey: "latitude")
coder.encode(coordinate.longitude, forKey: "longitude")
coder.encode(coordinateAccuracy, forKey: "coordinateAccuracy")
coder.encode(targetCoordinate.latitude, forKey: "targetLatitude")
coder.encode(targetCoordinate.longitude, forKey: "targetLongitude")
coder.encode(heading, forKey: "heading")
coder.encode(headingAccuracy, forKey: "headingAccuracy")
coder.encode(name, forKey: "name")
Expand All @@ -85,6 +90,7 @@ open class Waypoint: NSObject, NSCopying, NSSecureCoding {

open func copy(with zone: NSZone?) -> Any {
let copy = Waypoint(coordinate: coordinate, coordinateAccuracy: coordinateAccuracy, name: name)
copy.targetCoordinate = targetCoordinate
copy.heading = heading
copy.headingAccuracy = headingAccuracy
copy.allowsArrivingOnOppositeSide = allowsArrivingOnOppositeSide
Expand All @@ -107,6 +113,17 @@ open class Waypoint: NSObject, NSCopying, NSSecureCoding {
*/
@objc open var coordinateAccuracy: CLLocationAccuracy = -1

/**
The geographic coordinate of the waypoint’s target.
The waypoint’s target affects arrival instructions without affecting the route’s shape. For example, a delivery or ride hailing application may specify a waypoint target that represents a drop-off location. The target determines whether the arrival visual and spoken instructions indicate that the destination is “on the left” or “on the right”.
By default, this property is set to `kCLLocationCoordinate2DInvalid`, meaning the waypoint has no target. This property is ignored if `DirectionsOptions.includesSteps` is `false`.
This property corresponds to the [`waypoint_targets`](https://www.mapbox.com/api-documentation/#retrieve-directions) query parameter in the Mapbox Directions API.
*/
@objc open var targetCoordinate: CLLocationCoordinate2D = kCLLocationCoordinate2DInvalid

// MARK: Getting the Direction of Approach

/**
Expand Down Expand Up @@ -145,13 +162,17 @@ open class Waypoint: NSObject, NSCopying, NSSecureCoding {
The name of the waypoint.
This parameter does not affect the route, but you can set the name of a waypoint you pass into a `RouteOptions` object to help you distinguish one waypoint from another in the array of waypoints passed into the completion handler of the `Directions.calculate(_:completionHandler:)` method.
This property corresponds to the [`waypoint_names`](https://www.mapbox.com/api-documentation/#retrieve-directions) query parameter in the Mapbox Directions API.
*/
@objc open var name: String?

/**
A boolean value indicating whether arriving on opposite side is allowed.
This property has no effect if `RouteOptions.includesSteps` is set to `false`.
This property corresponds to the [`approaches`](https://www.mapbox.com/api-documentation/#retrieve-directions) query parameter in the Mapbox Directions API.
*/
@objc open var allowsArrivingOnOppositeSide = true

Expand Down
11 changes: 11 additions & 0 deletions MapboxDirectionsTests/RouteOptionsTests.swift
Expand Up @@ -126,6 +126,17 @@ class RouteOptionsTests: XCTestCase {
XCTAssert(answer == correct, "Coordinates should be truncated.")

}

func testWaypointSerialization() {
let origin = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 39.15031, longitude: -84.47182), name: "XU")
let destination = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 39.12971, longitude: -84.51638), name: "UC")
destination.targetCoordinate = CLLocationCoordinate2D(latitude: 39.13115, longitude: -84.51619)
let options = RouteOptions(waypoints: [origin, destination])

XCTAssertEqual(options.queries, ["-84.47182,39.15031", "-84.51638,39.12971"])
XCTAssertTrue(options.params.contains(URLQueryItem(name: "waypoint_names", value: "XU;UC")))
XCTAssertTrue(options.params.contains(URLQueryItem(name: "waypoint_targets", value: ";-84.51619,39.13115")))
}
}

private extension RouteOptions {
Expand Down
57 changes: 57 additions & 0 deletions MapboxDirectionsTests/WaypointTests.swift
@@ -0,0 +1,57 @@
import XCTest
import MapboxDirections

class WaypointTests: XCTestCase {
func testCopying() {
let originalWaypoint = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), coordinateAccuracy: 5, name: "White House")
originalWaypoint.targetCoordinate = CLLocationCoordinate2D(latitude: 38.8952261, longitude: -77.0327882)
originalWaypoint.heading = 90
originalWaypoint.headingAccuracy = 10
originalWaypoint.allowsArrivingOnOppositeSide = false

guard let copy = originalWaypoint.copy() as? Waypoint else {
return XCTFail("Waypoint copy method should an object of same type")
}

XCTAssertEqual(copy.coordinate.latitude, originalWaypoint.coordinate.latitude)
XCTAssertEqual(copy.coordinate.longitude, originalWaypoint.coordinate.longitude)
XCTAssertEqual(copy.coordinateAccuracy, originalWaypoint.coordinateAccuracy)
XCTAssertEqual(copy.targetCoordinate.latitude, originalWaypoint.targetCoordinate.latitude)
XCTAssertEqual(copy.targetCoordinate.longitude, originalWaypoint.targetCoordinate.longitude)
XCTAssertEqual(copy.heading, originalWaypoint.heading)
XCTAssertEqual(copy.headingAccuracy, originalWaypoint.headingAccuracy)
XCTAssertEqual(copy.allowsArrivingOnOppositeSide, originalWaypoint.allowsArrivingOnOppositeSide)
}

func testCoding() {
let originalWaypoint = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), coordinateAccuracy: 5, name: "White House")
originalWaypoint.targetCoordinate = CLLocationCoordinate2D(latitude: 38.8952261, longitude: -77.0327882)
originalWaypoint.heading = 90
originalWaypoint.headingAccuracy = 10
originalWaypoint.allowsArrivingOnOppositeSide = false

let encodedData = NSMutableData()
let coder = NSKeyedArchiver(forWritingWith: encodedData)
coder.requiresSecureCoding = true
coder.encode(originalWaypoint, forKey: "waypoint")
coder.finishEncoding()

let decoder = NSKeyedUnarchiver(forReadingWith: encodedData as Data)
decoder.requiresSecureCoding = true
defer {
decoder.finishDecoding()
}
guard let decodedWaypoint = decoder.decodeObject(of: Waypoint.self, forKey: "waypoint") else {
return XCTFail("Unable to decode waypoint")
}

XCTAssertEqual(decodedWaypoint.coordinate.latitude, originalWaypoint.coordinate.latitude)
XCTAssertEqual(decodedWaypoint.coordinate.longitude, originalWaypoint.coordinate.longitude)
XCTAssertEqual(decodedWaypoint.coordinateAccuracy, originalWaypoint.coordinateAccuracy)
XCTAssertEqual(decodedWaypoint.targetCoordinate.latitude, originalWaypoint.targetCoordinate.latitude)
XCTAssertEqual(decodedWaypoint.targetCoordinate.longitude, originalWaypoint.targetCoordinate.longitude)
XCTAssertEqual(decodedWaypoint.heading, originalWaypoint.heading)
XCTAssertEqual(decodedWaypoint.headingAccuracy, originalWaypoint.headingAccuracy)
XCTAssertEqual(decodedWaypoint.allowsArrivingOnOppositeSide, originalWaypoint.allowsArrivingOnOppositeSide)
}
}

0 comments on commit 78dbc84

Please sign in to comment.