Skip to content

Commit

Permalink
Merge pull request #406 from mapbox/jerrad/directions-calculate-inter…
Browse files Browse the repository at this point in the history
…face

Public Response Types
  • Loading branch information
JThramer authored Apr 4, 2020
2 parents 7c1ced4 + a6607fb commit c915a0f
Show file tree
Hide file tree
Showing 29 changed files with 815 additions and 547 deletions.
11 changes: 6 additions & 5 deletions Directions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ class ViewController: UIViewController, MBDrawingViewDelegate {
options.routeShapeResolution = .full
options.attributeOptions = [.congestionLevel, .maximumSpeedLimit]

Directions.shared.calculate(options) { (waypoints, routes, error) in
Directions.shared.calculate(options) { (response, error) in
if let error = error {
print("Error calculating directions: \(error)")
return
}

if let route = routes?.first, let leg = route.legs.first {
if let route = response.routes?.first, let leg = route.legs.first {
print("Route via \(leg):")

let distanceFormatter = LengthFormatter()
Expand All @@ -89,7 +89,8 @@ class ViewController: UIViewController, MBDrawingViewDelegate {
print("Distance: \(formattedDistance); ETA: \(formattedTravelTime!)")

for step in leg.steps {
print("\(step.instructions) [\(step.maneuverType) \(step.maneuverDirection)]")
let direction = step.maneuverDirection?.rawValue ?? "none"
print("\(step.instructions) [\(step.maneuverType) \(direction)]")
if step.distance > 0 {
let formattedDistance = distanceFormatter.string(fromMeters: step.distance)
print("\(step.transportType) for \(formattedDistance)")
Expand Down Expand Up @@ -143,7 +144,7 @@ class ViewController: UIViewController, MBDrawingViewDelegate {
func makeMatchRequest(locations: [CLLocationCoordinate2D]) {
let matchOptions = MatchOptions(coordinates: locations)

Directions.shared.calculate(matchOptions) { (matches, error) in
Directions.shared.calculate(matchOptions) { (response, error) in
if let error = error {
let errorString = """
⚠️ Error Enountered. ⚠️
Expand All @@ -156,7 +157,7 @@ class ViewController: UIViewController, MBDrawingViewDelegate {
return
}

guard let matches = matches, let match = matches.first else { return }
guard let matches = response.matches, let match = matches.first else { return }

if let annotations = self.mapView.annotations {
self.mapView.removeAnnotations(annotations)
Expand Down
82 changes: 62 additions & 20 deletions MapboxDirections.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

342 changes: 149 additions & 193 deletions Sources/MapboxDirections/Directions.swift

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions Sources/MapboxDirections/DirectionsCredentials.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

/// The Mapbox access token specified in the main application bundle’s Info.plist.
let defaultAccessToken = Bundle.main.object(forInfoDictionaryKey: "MGLMapboxAccessToken") as? String
let defaultApiEndPointURLString = Bundle.main.object(forInfoDictionaryKey: "MGLMapboxAPIBaseURL") as? String

public struct DirectionsCredentials: Equatable {

/**
The mapbox access token. You can find this in your Mapbox account dashboard.
*/
public let accessToken: String?

/**
The host to reach. defaults to `api.mapbox.com`.
*/
public let host: URL

/**
The SKU Token associated with the request. Used for billing.
*/
public var skuToken: String? {
guard let mbx: AnyClass = NSClassFromString("MBXAccounts") else { return nil }
guard mbx.responds(to: Selector(("serviceSkuToken"))) else { return nil }
return mbx.value(forKeyPath: "serviceSkuToken") as? String
}

/**
Intialize a new credential.
- parameter accessToken: Optional. An access token to provide. If this value is nil, the SDK will attempt to find a token from your app's `info.plist`.
- parameter host: Optional. A parameter to pass a custom host. If `nil` is provided, the SDK will attempt to find a host from your app's `info.plist`, and barring that will default to `https://api.mapbox.com`.
*/
public init(accessToken: String? = nil, host: URL? = nil) {
self.accessToken = accessToken ?? defaultAccessToken

precondition(accessToken != nil && !accessToken!.isEmpty, "A Mapbox access token is required. Go to <https://account.mapbox.com/access-tokens/>. In Info.plist, set the MGLMapboxAccessToken key to your access token, or use the Directions(accessToken:host:) initializer.")

if let host = host {
self.host = host
} else if let defaultHostString = defaultApiEndPointURLString, let defaultHost = URL(string: defaultHostString) {
self.host = defaultHost
} else {
self.host = URL(string: "https://api.mapbox.com")!
}
}
}

73 changes: 58 additions & 15 deletions Sources/MapboxDirections/DirectionsError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,54 @@ import Foundation
An error that occurs when calculating directions.
*/
public enum DirectionsError: LocalizedError {

public init(code: String?, message: String?, response: URLResponse?, underlyingError error: Error?) {
if let response = response as? HTTPURLResponse {
switch (response.statusCode, code ?? "") {
case (200, "NoRoute"):
self = .unableToRoute
case (200, "NoSegment"):
self = .unableToLocate
case (200, "NoMatch"):
self = .noMatches
case (422, "TooManyCoordinates"):
self = .tooManyCoordinates
case (404, "ProfileNotFound"):
self = .profileNotFound

case (413, _):
self = .requestTooLarge
case (422, "InvalidInput"):
self = .invalidInput(message: message)
case (429, _):
self = .rateLimited(rateLimitInterval: response.rateLimitInterval, rateLimit: response.rateLimit, resetTime: response.rateLimitResetTime)
default:
self = .unknown(response: response, underlying: error, code: code, message: message)
}
} else {
self = .unknown(response: response, underlying: error, code: code, message: message)
}
}

/**
There is no network connection available to perform the network request.
*/
case network(_: URLError)

/**
The server returned an empty response.
*/
case noData

/**
The API recieved input that it didn't understand.
*/
case invalidInput(message: String?)

/**
The server returned a response that isn’t correctly formatted.
*/
case invalidResponse
case invalidResponse(_: URLResponse?)

/**
No route could be found between the specified locations.
Expand Down Expand Up @@ -65,15 +102,22 @@ public enum DirectionsError: LocalizedError {
*/
case rateLimited(rateLimitInterval: TimeInterval?, rateLimit: UInt?, resetTime: Date?)


/**
Unknown error case. Look at associated values for more details.
*/

case unknown(response: URLResponse?, underlying: Error?, code: String?, message: String?)

public var failureReason: String? {
switch self {
case .network(_):
return "The client does not have a network connection to the server."
case .noData:
return "The server returned an empty response."
case let .invalidInput(message):
return message
case .invalidResponse:
case .invalidResponse(_):
return "The server returned a response that isn’t correctly formatted."
case .unableToRoute:
return "No route could be found between the specified locations."
Expand Down Expand Up @@ -105,7 +149,7 @@ public enum DirectionsError: LocalizedError {

public var recoverySuggestion: String? {
switch self {
case .noData, .invalidInput, .invalidResponse:
case .network(_), .noData, .invalidInput, .invalidResponse:
return nil
case .unableToRoute:
return "Make sure it is possible to travel between the locations with the mode of transportation implied by the profileIdentifier option. For example, it is impossible to travel by car from one continent to another without either a land bridge or a ferry connection."
Expand Down Expand Up @@ -135,14 +179,17 @@ extension DirectionsError: Equatable {
public static func == (lhs: DirectionsError, rhs: DirectionsError) -> Bool {
switch (lhs, rhs) {
case (.noData, .noData),
(.invalidResponse, .invalidResponse),
(.unableToRoute, .unableToRoute),
(.noMatches, .noMatches),
(.tooManyCoordinates, .tooManyCoordinates),
(.unableToLocate, .unableToLocate),
(.profileNotFound, .profileNotFound),
(.requestTooLarge, .requestTooLarge):
return true
case let (.network(lhsError), .network(rhsError)):
return lhsError == rhsError
case let (.invalidResponse(lhsResponse), .invalidResponse(rhsResponse)):
return lhsResponse == rhsResponse
case let (.invalidInput(lhsMessage), .invalidInput(rhsMessage)):
return lhsMessage == rhsMessage
case (.rateLimited(let lhsRateLimitInterval, let lhsRateLimit, let lhsResetTime),
Expand All @@ -157,17 +204,7 @@ extension DirectionsError: Equatable {
&& lhsUnderlying?.localizedDescription == rhsUnderlying?.localizedDescription
&& lhsCode == rhsCode
&& lhsMessage == rhsMessage
case (.noData, _),
(.invalidResponse, _),
(.unableToRoute, _),
(.noMatches, _),
(.tooManyCoordinates, _),
(.unableToLocate, _),
(.profileNotFound, _),
(.requestTooLarge, _),
(.invalidInput, _),
(.rateLimited, _),
(.unknown, _):
default:
return false
}
}
Expand All @@ -181,4 +218,10 @@ public enum DirectionsCodingError: Error {
Decoding this type requires the `Decoder.userInfo` dictionary to contain the `CodingUserInfoKey.options` key.
*/
case missingOptions


/**
Decoding this type requires the `Decoder.userInfo` dictionary to contain the `CodingUserInfoKey.credentials` key.
*/
case missingCredentials
}
13 changes: 13 additions & 0 deletions Sources/MapboxDirections/DirectionsOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ open class DirectionsOptions: Codable {
self.profileIdentifier = profileIdentifier ?? .automobile
}


private enum CodingKeys: String, CodingKey {
case waypoints
case profileIdentifier
Expand Down Expand Up @@ -281,6 +282,17 @@ open class DirectionsOptions: Codable {
*/
open var includesVisualInstructions = false

/**
The time immediately before a `Directions` object fetched this result.
If you manually start fetching a task returned by `Directions.url(forCalculating:)`, this property is set to `nil`; use the `URLSessionTaskTransactionMetrics.fetchStartDate` property instead. This property may also be set to `nil` if you create this result from a JSON object or encoded object.
This property does not persist after encoding and decoding.
*/
open var fetchStartDate: Date?



// MARK: Getting the Request URL

/**
Expand Down Expand Up @@ -418,6 +430,7 @@ open class DirectionsOptions: Codable {
]
return components.percentEncodedQuery ?? ""
}

}

extension DirectionsOptions: Equatable {
Expand Down
Loading

0 comments on commit c915a0f

Please sign in to comment.