Skip to content

Commit

Permalink
Revamped waypoint types
Browse files Browse the repository at this point in the history
Replaced the Waypoint and Tracepoint classes with separate classes for requests and responses.
  • Loading branch information
1ec5 committed Nov 23, 2019
1 parent 9f88ac6 commit be7816b
Show file tree
Hide file tree
Showing 17 changed files with 404 additions and 314 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
* Swift is now required to directly use public types and methods defined by this library. If your application is written in Objective-C or Cocoa-AppleScript, you need to implement your own wrapper in Swift that bridges to Objective-C. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
* This library now depends on [Turf](https://github.com/mapbox/turf-swift/). ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))

### Locations and geometry

* Replaced the `Waypoint` and `Tracepoint` classes with separate classes for requests and responses. Now you create a `RouteOptions` or `MatchOptions` using a series of `DirectionsOptions.Waypoint` instances (also known as `RouteOptions.Waypoint` or `MatchOptions.Waypoint`). The `RouteCompletionHandler` and `MatchCompletionHandler` closures and the response types represent waypoints as `DirectionsResult.Waypoint` (also known as `RouteOptions.Waypoint`) and tracepoints as `Match.Tracepoint`. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
* Replaced the `Route.coordinates` property with `Route.shape` and the `RouteStep.coordinates` property with `RouteStep.shape`. The `Route.coordinateCount` and `RouteStep.coordinateCount` properties have been removed, but you can use the `LineString.coordinates` property to get the array of `CLLocationCoordinate2D`s. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
* The `
* Renamed the `Tracepoint.alternateCount` property to `Tracepoint.countOfAlternatives`. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))

### Error handling

* The `RouteCompletionHandler` and `MatchCompletionHandler` closures’ `error` argument is now a `DirectionsError` instead of an `NSError`. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
Expand All @@ -23,7 +30,6 @@

* Removed support for [Mapbox Directions API v4](https://docs.mapbox.com/api/legacy/directions-v4/). ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
* Replaced the `MBDefaultWalkingSpeed`, `MBMinimumWalkingSpeed`, and `MBMaximumWalkingSpeed` constants with `CLLocationSpeed.normalWalking`, `CLLocationSpeed.minimumWalking`, and `CLLocationSpeed.maximumWalking`, respectively.
* Replaced the `Route.coordinates` property with `Route.shape` and the `RouteStep.coordinates` property with `RouteStep.shape`. The `Route.coordinateCount` and `RouteStep.coordinateCount` properties have been removed, but you can use the `LineString.coordinates` property to get the array of `CLLocationCoordinate2D`s. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))

## v0.30.0

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ With the directions object in hand, construct a RouteOptions object and pass it
// main.swift

let waypoints = [
Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047), name: "Mapbox"),
Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House"),
RouteOptions.Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047), name: "Mapbox"),
RouteOptions.Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House"),
]
let options = RouteOptions(waypoints: waypoints, profileIdentifier: .automobileAvoidingTraffic)
options.includesSteps = true
Expand Down
8 changes: 4 additions & 4 deletions Sources/MapboxDirections/Directions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ open class Directions: NSObject {
If the request was canceled or there was an error obtaining the routes, this argument is `nil`. This is not to be confused with the situation in which no results were found, in which case the array is present but empty.
- parameter error: The error that occurred, or `nil` if the placemarks were obtained successfully.
*/
public typealias RouteCompletionHandler = (_ waypoints: [Waypoint]?, _ routes: [Route]?, _ error: DirectionsError?) -> Void
public typealias RouteCompletionHandler = (_ waypoints: [Route.Waypoint]?, _ routes: [Route]?, _ error: DirectionsError?) -> Void

/**
A closure (block) to be called when a map matching request is complete.
Expand Down Expand Up @@ -306,7 +306,7 @@ open class Directions: NSObject {
let decoder = DirectionsDecoder(options: options)
let result = try decoder.decode(MapMatchingResponse.self, from: data)
guard result.code == "Ok" else {
let apiError = Directions.informativeError(code: result.code, message:nil, response: response, underlyingError: possibleError)
let apiError = Directions.informativeError(code: result.code, message: nil, response: response, underlyingError: possibleError)
completionHandler(nil, nil, apiError)
return
}
Expand Down Expand Up @@ -464,9 +464,9 @@ public class DirectionsDecoder: JSONDecoder {
}
}

var tracepoints: [Tracepoint?]? {
var tracepoints: [Match.Tracepoint?]? {
get {
return userInfo[.tracepoints] as? [Tracepoint?]
return userInfo[.tracepoints] as? [Match.Tracepoint?]
} set {
userInfo[.tracepoints] = newValue
}
Expand Down
5 changes: 2 additions & 3 deletions Sources/MapboxDirections/DirectionsResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ open class DirectionsResult: Codable {
}

// Associate each leg JSON with a source and destination. The sequence of destinations is offset by one from the sequence of sources.

let waypoints = directionsOptions.legSeparators //we don't want to name via points
// Create waypoints from waypoints in the options. Skip waypoints that don’t separate legs.
let waypoints = directionsOptions.legSeparators.map { Waypoint(coordinate: $0.coordinate, correction: 0, name: $0.name) }
let legInfo = zip(zip(waypoints.prefix(upTo: waypoints.endIndex - 1), waypoints.suffix(from: 1)), legs)

for (endpoints, leg) in legInfo {
leg.source = endpoints.0
leg.destination = endpoints.1
Expand Down
17 changes: 9 additions & 8 deletions Sources/MapboxDirections/MapMatching/MapMatchingResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
class MapMatchingResponse: Decodable {
var code: String
var routes : [Route]?
var waypoints: [Waypoint]
var waypoints: [Match.Waypoint]

private enum CodingKeys: String, CodingKey {
case code
Expand All @@ -17,17 +17,17 @@ class MapMatchingResponse: Decodable {
routes = try container.decodeIfPresent([Route].self, forKey: .matches)

// Decode waypoints from the response and update their names according to the waypoints from DirectionsOptions.waypoints.
let decodedWaypoints = try container.decode([Waypoint].self, forKey: .tracepoints)
let decodedWaypoints = try container.decode([Match.Waypoint].self, forKey: .tracepoints)
if let options = decoder.userInfo[.options] as? DirectionsOptions {
// The response lists the same number of tracepoints as the waypoints in the request, whether or not a given waypoint is leg-separating.
waypoints = zip(decodedWaypoints, options.waypoints).map { (pair) -> Waypoint in
waypoints = zip(decodedWaypoints, options.waypoints).map { (pair) -> Match.Waypoint in
let (decodedWaypoint, waypointInOptions) = pair
let waypoint = Waypoint(coordinate: decodedWaypoint.coordinate, coordinateAccuracy: waypointInOptions.coordinateAccuracy, name: waypointInOptions.name?.nonEmptyString ?? decodedWaypoint.name)
waypoint.separatesLegs = waypointInOptions.separatesLegs
var waypoint = decodedWaypoint
if waypointInOptions.separatesLegs, let name = waypointInOptions.name?.nonEmptyString {
waypoint.name = name
}
return waypoint
}
waypoints.first?.separatesLegs = true
waypoints.last?.separatesLegs = true
} else {
waypoints = decodedWaypoints
}
Expand All @@ -36,7 +36,8 @@ class MapMatchingResponse: Decodable {
// Postprocess each route.
for route in routes {
// Imbue each route’s legs with the leg-separating waypoints refined above.
route.legSeparators = waypoints.filter { $0.separatesLegs }
// TODO: Filter these waypoints by whether they separate legs, based on the options, if given.
route.legSeparators = waypoints
}
self.routes = routes
} else {
Expand Down
12 changes: 7 additions & 5 deletions Sources/MapboxDirections/MapMatching/MatchResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class MatchResponse: Codable {
var code: String
var message: String?
var matches : [Match]?
var tracepoints: [Tracepoint?]?
var tracepoints: [Match.Tracepoint?]?

private enum CodingKeys: String, CodingKey {
case code
Expand All @@ -17,12 +17,14 @@ class MatchResponse: Codable {
let container = try decoder.container(keyedBy: CodingKeys.self)
code = try container.decode(String.self, forKey: .code)
message = try container.decodeIfPresent(String.self, forKey: .message)
tracepoints = try container.decodeIfPresent([Tracepoint?].self, forKey: .tracepoints)
tracepoints = try container.decodeIfPresent([Match.Tracepoint?].self, forKey: .tracepoints)
matches = try container.decodeIfPresent([Match].self, forKey: .matches)

if let points = self.tracepoints {
matches?.forEach {
$0.tracepoints = points
if let tracepoints = self.tracepoints, let matches = matches {
for match in matches {
// TODO: Filter on matchings_index.
// TODO: Push tracepoints down to individual legs to reflect waypoint_index.
match.tracepoints = tracepoints
}
}
}
Expand Down
62 changes: 33 additions & 29 deletions Sources/MapboxDirections/MapMatching/Tracepoint.swift
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
import Foundation
import CoreLocation

/**
A `Tracepoint` represents a location matched to the road network.
*/
public class Tracepoint: Waypoint {
public extension Match {
/**
Number of probable alternative matchings for this tracepoint. A value of zero indicates that this point was matched unambiguously.
A tracepoint represents a location matched to the road network.
*/
public let alternateCount: Int

private enum CodingKeys: String, CodingKey {
case alternateCount = "alternatives_count"
}

init(coordinate: CLLocationCoordinate2D, alternateCount: Int?, name: String?) {
self.alternateCount = alternateCount ?? NSNotFound
super.init(coordinate: coordinate, name: name)
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
alternateCount = try container.decode(Int.self, forKey: .alternateCount)
try super.init(from: decoder)
struct Tracepoint: Matchpoint, Equatable {
// MARK: Positioning the Waypoint

/**
The geographic coordinate of the waypoint, snapped to the road network.
*/
public var coordinate: CLLocationCoordinate2D

/**
The straight-line distance from this waypoint to the corresponding waypoint in the `RouteOptions` or `MatchOptions` object.

The requested waypoint is snapped to the road network. This property contains the straight-line distance from the original requested waypoint’s `DirectionsOptions.Waypoint.coordinate` property to the `coordinate` property.
*/
public var correction: CLLocationDistance

// MARK: Determining the Degree of Confidence

/**
Number of probable alternative matchings for this tracepoint. A value of zero indicates that this point was matched unambiguously.
*/
public var countOfAlternatives: Int
}

public override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(alternateCount, forKey: .alternateCount)
try super.encode(to: encoder)
}

extension Match.Tracepoint: Codable {
private enum CodingKeys: String, CodingKey {
case coordinate = "location"
case correction = "distance"
case countOfAlternatives = "alternatives_count"
}
}

extension Tracepoint { //Equatable
public static func ==(lhs: Tracepoint, rhs: Tracepoint) -> Bool {
let superEquals = (lhs as Waypoint == rhs as Waypoint)
return superEquals && lhs.alternateCount == rhs.alternateCount
extension Match.Tracepoint: CustomStringConvertible {
public var description: String {
return "<latitude: \(coordinate.latitude); longitude: \(coordinate.longitude)>"
}
}
9 changes: 5 additions & 4 deletions Sources/MapboxDirections/RouteLeg.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ open class RouteLeg: Codable {
*/
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
source = try container.decodeIfPresent(Waypoint.self, forKey: .source)
destination = try container.decodeIfPresent(Waypoint.self, forKey: .destination)

source = try container.decodeIfPresent(Route.Waypoint.self, forKey: .source)
destination = try container.decodeIfPresent(Route.Waypoint.self, forKey: .destination)
steps = try container.decode([RouteStep].self, forKey: .steps)
name = try container.decode(String.self, forKey: .name)
distance = try container.decode(CLLocationDistance.self, forKey: .distance)
Expand Down Expand Up @@ -84,7 +85,7 @@ open class RouteLeg: Codable {

This property is set to `nil` if the leg was decoded from a JSON RouteLeg object.
*/
public var source: Waypoint?
public var source: Route.Waypoint?

/**
The endpoint of the route leg.
Expand All @@ -93,7 +94,7 @@ open class RouteLeg: Codable {

This property is set to `nil` if the leg was decoded from a JSON RouteLeg object.
*/
public var destination: Waypoint?
public var destination: Route.Waypoint?

// MARK: Getting the Steps Along the Leg

Expand Down
17 changes: 9 additions & 8 deletions Sources/MapboxDirections/RouteResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ struct RouteResponse {
var error: String?
let uuid: String?
let routes: [Route]?
let waypoints: [Waypoint]?
let waypoints: [Route.Waypoint]?

init(code: String?, message: String?, error: String?) {
self.code = code
Expand Down Expand Up @@ -37,17 +37,17 @@ extension RouteResponse: Codable {
self.uuid = try container.decodeIfPresent(String.self, forKey: .uuid)

// Decode waypoints from the response and update their names according to the waypoints from DirectionsOptions.waypoints.
let decodedWaypoints = try container.decodeIfPresent([Waypoint].self, forKey: .waypoints)
let decodedWaypoints = try container.decodeIfPresent([Route.Waypoint].self, forKey: .waypoints)
if let decodedWaypoints = decodedWaypoints, let options = decoder.userInfo[.options] as? DirectionsOptions {
// The response lists the same number of tracepoints as the waypoints in the request, whether or not a given waypoint is leg-separating.
waypoints = zip(decodedWaypoints, options.waypoints).map { (pair) -> Waypoint in
waypoints = zip(decodedWaypoints, options.waypoints).map { (pair) -> Route.Waypoint in
let (decodedWaypoint, waypointInOptions) = pair
let waypoint = Waypoint(coordinate: decodedWaypoint.coordinate, coordinateAccuracy: waypointInOptions.coordinateAccuracy, name: waypointInOptions.name?.nonEmptyString ?? decodedWaypoint.name)
waypoint.separatesLegs = waypointInOptions.separatesLegs
var waypoint = decodedWaypoint
if waypointInOptions.separatesLegs, let name = waypointInOptions.name?.nonEmptyString {
waypoint.name = name
}
return waypoint
}
waypoints?.first?.separatesLegs = true
waypoints?.last?.separatesLegs = true
} else {
waypoints = decodedWaypoints
}
Expand All @@ -57,8 +57,9 @@ extension RouteResponse: Codable {
for route in routes {
route.routeIdentifier = uuid
// Imbue each route’s legs with the waypoints refined above.
// TODO: Filter these waypoints by whether they separate legs, based on the options, if given.
if let waypoints = waypoints {
route.legSeparators = waypoints.filter { $0.separatesLegs }
route.legSeparators = waypoints
}
}
self.routes = routes
Expand Down
Loading

0 comments on commit be7816b

Please sign in to comment.