Skip to content

Commit

Permalink
Isochrone api (#621)
Browse files Browse the repository at this point in the history
* vk-296-isochrone-api: added Isochrone class with related entities. Unit tests added.

* vk-296-isochrone-api: CHANGELOG updated

* vk-296-isochrone-api: replace IsochroneCredentials definition to be a typealias;

* vk-296-isochrone-api: added README example;

* vk-296-isochrone-api: deprecated DirectionsCredentials and DirectionsProfileIdentifier to reuse with Isohrones. Tests updated

* vk-296-isochrone-api: moved DirectionsCredentials and DirectionsProfileIdentifier alieases to original implementations;
  • Loading branch information
Udumft committed Nov 11, 2021
1 parent 9d74955 commit f46b02e
Show file tree
Hide file tree
Showing 32 changed files with 1,009 additions and 168 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Added the `RouteOptions.initialManeuverAvoidanceRadius` property to avoid a sudden maneuver when calculating a route while the user is in motion. ([#609](https://github.com/mapbox/mapbox-directions-swift/pull/609))
* Added the `RoadClasses.unpaved` option for avoiding unpaved roads. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added the `RoadClasses.cashOnlyToll` property for avoiding toll roads that only accept cash payment. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added `Isochrones`, which connects to the [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) to compute areas that are reachable within a specified amount of time from a location and return the reachable regions as contours of polygons or lines that you can display on a map. ([#621](https://github.com/mapbox/mapbox-directions-swift/pull/621))
* Renamed `DirectionsCredentials` and `DirectionsProfileIdentifier` to `Credentials` and `ProfileIdentifier`, respectively. ([#621](https://github.com/mapbox/mapbox-directions-swift/pull/621))
* Added the `RouteOptions.maximumHeight` and `RouteOptions.maximumWidth` properties for ensuring that the resulting routes can accommodate a vehicle of a certain size. ([#623](https://github.com/mapbox/mapbox-directions-swift/pull/623))
* The `DirectionsPriority` struct now conforms to the `Codable` protocol. ([#623](https://github.com/mapbox/mapbox-directions-swift/pull/623))
* Fixed an issue where the `RouteOptions.alleyPriority`, `RouteOptions.walkwayPriority`, and `RouteOptions.speed` properties were excluded from the encoded representation of a `RouteOptions` object. ([#623](https://github.com/mapbox/mapbox-directions-swift/pull/623))
Expand Down
86 changes: 58 additions & 28 deletions MapboxDirections.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

67 changes: 66 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The main directions class is `Directions`. Create a directions object using your
// main.swift
import MapboxDirections

let directions = Directions(credentials: DirectionsCredentials(accessToken: "<#your access token#>"))
let directions = Directions(credentials: Credentials(accessToken: "<#your access token#>"))
```

Alternatively, you can place your access token in the `MBXAccessToken` key of your application’s Info.plist file, then use the shared directions object:
Expand Down Expand Up @@ -178,6 +178,27 @@ let task = directions.calculate(options) { (session, result) in

You can also use the `Directions.calculateRoutes(matching:completionHandler:)` method to get Route objects suitable for use anywhere a standard Directions API response would be used.

### Build an isochrone map

Tell the user how far they can travel within certain distances or times of a given location using the Isochrone API. `Isochrones` uses the same access token initialization as `Directions`. Once that is configured, you need to fill `IsochronesOptions` parameters to calculate the desired GeoJSON:

```swift
let isochrones = Isochrones(credentials: Credentials(accessToken: "<#your access token#>"))

let isochroneOptions = IsochroneOptions(centerCoordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944),
contours: .byDistances([
.init(value: 500, unit: .meters, color: .orange),
.init(value: 1, unit: .kilometers, color: .red)
]))

isochrones.calculate(isochroneOptions) { session, result in
if case .success(let response) = result {
print(response)
}
}
```
...

## Usage with other Mapbox libraries

### Drawing the route on a map
Expand All @@ -204,6 +225,50 @@ if var routeCoordinates = route.shape?.coordinates, routeCoordinates.count > 0 {

The [Mapbox Navigation SDK for iOS](https://github.com/mapbox/mapbox-navigation-ios/) provides a full-fledged user interface for turn-by-turn navigation along routes supplied by MapboxDirections.

### Drawing Isochrones contours on a map snapshot

[MapboxStatic.swift](https://github.com/mapbox/MapboxStatic.swift) provides an easy way to draw a isochrone contours on a map.

```swift
// main.swift
import MapboxStatic
import MapboxDirections

let centerCoordinate = CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944)
let accessToken = "<#your access token#>"

// Setup snapshot parameters
let camera = SnapshotCamera(
lookingAtCenter: centerCoordinate,
zoomLevel: 12)
let options = SnapshotOptions(
styleURL: URL(string: "<#your mapbox: style URL#>")!,
camera: camera,
size: CGSize(width: 200, height: 200))

// Request Isochrone contour to draw on a map
let isochrones = Isochrones(credentials: Credentials(accessToken: accessToken))
isochrones.calculate(IsochroneOptions(centerCoordinate: centerCoordinate,
contours: .byDistances([.init(value: 500, unit: .meters)]))) { session, result in
if case .success(let response) = result {
// Serialize the geoJSON
let encoder = JSONEncoder()
let data = try! encoder.encode(response)
let geoJSONString = String(data: data, encoding: .utf8)!
let geoJSONOverlay = GeoJSON(objectString: geoJSONString)

// Feed resulting geoJSON to snapshot options
options.overlays.append(geoJSONOverlay)

let snapshot = Snapshot(
options: options,
accessToken: accessToken)

// Display the result!
drawImage(snapshot.image)
}
}
```

## Directions CLI

Expand Down
4 changes: 2 additions & 2 deletions Sources/MapboxDirections/AttributeOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible {
When this attribute is specified, the `RouteLeg.congestionLevels` property contains one value for each segment in the leg’s full geometry.
This attribute requires `DirectionsProfileIdentifier.automobileAvoidingTraffic`. Any other profile identifier produces `CongestionLevel.unknown` for each segment along the route.
This attribute requires `ProfileIdentifier.automobileAvoidingTraffic`. Any other profile identifier produces `CongestionLevel.unknown` for each segment along the route.
*/
public static let congestionLevel = AttributeOptions(rawValue: 1 << 4)

Expand All @@ -54,7 +54,7 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible {
When this attribute is specified, the `RouteLeg.numericCongestionLevels` property contains one value for each segment in the leg’s full geometry.
This attribute requires `DirectionsProfileIdentifier.automobileAvoidingTraffic`. Any other profile identifier produces `nil` for each segment along the route.
This attribute requires `ProfileIdentifier.automobileAvoidingTraffic`. Any other profile identifier produces `nil` for each segment along the route.
*/
public static let numericCongestionLevel = AttributeOptions(rawValue: 1 << 6)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let defaultAccessToken: String? =
UserDefaults.standard.string(forKey: "MBXAccessToken")
let defaultApiEndPointURLString = Bundle.main.object(forInfoDictionaryKey: "MGLMapboxAPIBaseURL") as? String

public struct DirectionsCredentials: Equatable {
public struct Credentials: Equatable {

/**
The mapbox access token. You can find this in your Mapbox account dashboard.
Expand Down Expand Up @@ -65,3 +65,5 @@ public struct DirectionsCredentials: Equatable {
}
}

@available(*, deprecated, renamed: "Credentials")
public typealias DirectionsCredentials = Credentials
16 changes: 5 additions & 11 deletions Sources/MapboxDirections/Directions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ open class Directions: NSObject {
- parameter credentials: A object containing the credentials used to make the request.
*/
public typealias Session = (options: DirectionsOptions, credentials: DirectionsCredentials)
public typealias Session = (options: DirectionsOptions, credentials: Credentials)

/**
A closure (block) to be called when a directions request is complete.
Expand Down Expand Up @@ -103,7 +103,7 @@ open class Directions: NSObject {
- postcondition: To update the original route, pass `RouteRefreshResponse.route` into the `Route.refreshLegAttributes(from:)` method.
*/
public typealias RouteRefreshCompletionHandler = (_ credentials: DirectionsCredentials, _ result: Result<RouteRefreshResponse, DirectionsError>) -> Void
public typealias RouteRefreshCompletionHandler = (_ credentials: Credentials, _ result: Result<RouteRefreshResponse, DirectionsError>) -> Void

// MARK: Creating a Directions Object

Expand All @@ -119,14 +119,8 @@ open class Directions: NSObject {
If nothing is provided, the default behavior is to read credential values from the developer's Info.plist.
*/
public let credentials: DirectionsCredentials
public let credentials: Credentials

/**
Initializes a newly created directions object with an optional access token and host.
- parameter credentials: A `DirectionsCredentials` object that, optionally, contains customized Token and Endpoint information. If no credentials object is supplied, then defaults are used.
*/

private var authenticationParams: [URLQueryItem] {
var params: [URLQueryItem] = [
URLQueryItem(name: "access_token", value: credentials.accessToken)
Expand All @@ -148,7 +142,7 @@ open class Directions: NSObject {
- urlSession: URLSession that will be used to submit API requests to Mapbox Directions API.
- processingQueue: A DispatchQueue that will be used for CPU intensive work.
*/
public init(credentials: DirectionsCredentials = .init(),
public init(credentials: Credentials = .init(),
urlSession: URLSession = .shared,
processingQueue: DispatchQueue = .global(qos: .userInitiated)) {
self.credentials = credentials
Expand Down Expand Up @@ -506,7 +500,7 @@ open class Directions: NSObject {
open func urlRequest(forRefreshing responseIdentifier: String, routeIndex: Int, fromLegAtIndex startLegIndex: Int) -> URLRequest {
let params: [URLQueryItem] = authenticationParams

var unparameterizedURL = URL(string: "directions-refresh/v1/\(DirectionsProfileIdentifier.automobileAvoidingTraffic.rawValue)", relativeTo: credentials.host)!
var unparameterizedURL = URL(string: "directions-refresh/v1/\(ProfileIdentifier.automobileAvoidingTraffic.rawValue)", relativeTo: credentials.host)!
unparameterizedURL.appendPathComponent(responseIdentifier)
unparameterizedURL.appendPathComponent(String(routeIndex))
unparameterizedURL.appendPathComponent(String(startLegIndex))
Expand Down
4 changes: 2 additions & 2 deletions Sources/MapboxDirections/DirectionsError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public enum DirectionsError: LocalizedError {
/**
Unrecognized profile identifier.
Make sure the `DirectionsOptions.profileIdentifier` option is set to one of the predefined values, such as `DirectionsProfileIdentifier.automobile`.
Make sure the `DirectionsOptions.profileIdentifier` option is set to one of the predefined values, such as `ProfileIdentifier.automobile`.
*/
case profileNotFound

Expand Down Expand Up @@ -166,7 +166,7 @@ public enum DirectionsError: LocalizedError {
case .unableToLocate:
return "Make sure the locations are close enough to a roadway or pathway. Try setting the coordinateAccuracy property of all the waypoints to nil."
case .profileNotFound:
return "Make sure the profileIdentifier option is set to one of the provided constants, such as DirectionsProfileIdentifier.automobile."
return "Make sure the profileIdentifier option is set to one of the provided constants, such as ProfileIdentifier.automobile."
case .requestTooLarge:
return "Try specifying fewer waypoints or giving the waypoints shorter names."
case let .rateLimited(rateLimitInterval: _, rateLimit: _, resetTime: rolloverTime):
Expand Down
14 changes: 7 additions & 7 deletions Sources/MapboxDirections/DirectionsOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ open class DirectionsOptions: Codable {
Do not call `DirectionsOptions(waypoints:profileIdentifier:)` directly; instead call the corresponding initializer of `RouteOptions` or `MatchOptions`.
- parameter waypoints: An array of `Waypoint` objects representing locations that the route should visit in chronological order. The array should contain at least two waypoints (the source and destination) and at most 25 waypoints. (Some profiles, such as `DirectionsProfileIdentifier.automobileAvoidingTraffic`, [may have lower limits](https://docs.mapbox.com/api/navigation/#directions).)
- parameter profileIdentifier: A string specifying the primary mode of transportation for the routes. `DirectionsProfileIdentifier.automobile` is used by default.
- parameter waypoints: An array of `Waypoint` objects representing locations that the route should visit in chronological order. The array should contain at least two waypoints (the source and destination) and at most 25 waypoints. (Some profiles, such as `ProfileIdentifier.automobileAvoidingTraffic`, [may have lower limits](https://docs.mapbox.com/api/navigation/#directions).)
- parameter profileIdentifier: A string specifying the primary mode of transportation for the routes. `ProfileIdentifier.automobile` is used by default.
*/
required public init(waypoints: [Waypoint], profileIdentifier: DirectionsProfileIdentifier? = nil) {
required public init(waypoints: [Waypoint], profileIdentifier: ProfileIdentifier? = nil) {
self.waypoints = waypoints
self.profileIdentifier = profileIdentifier ?? .automobile
}
Expand Down Expand Up @@ -159,7 +159,7 @@ open class DirectionsOptions: Codable {
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
waypoints = try container.decode([Waypoint].self, forKey: .waypoints)
profileIdentifier = try container.decode(DirectionsProfileIdentifier.self, forKey: .profileIdentifier)
profileIdentifier = try container.decode(ProfileIdentifier.self, forKey: .profileIdentifier)
includesSteps = try container.decode(Bool.self, forKey: .includesSteps)
shapeFormat = try container.decode(RouteShapeFormat.self, forKey: .shapeFormat)
routeShapeResolution = try container.decode(RouteShapeResolution.self, forKey: .routeShapeResolution)
Expand Down Expand Up @@ -197,9 +197,9 @@ open class DirectionsOptions: Codable {
/**
A string specifying the primary mode of transportation for the routes.
The default value of this property is `DirectionsProfileIdentifier.automobile`, which specifies driving directions.
The default value of this property is `ProfileIdentifier.automobile`, which specifies driving directions.
*/
open var profileIdentifier: DirectionsProfileIdentifier
open var profileIdentifier: ProfileIdentifier

// MARK: Specifying the Response Format

Expand Down Expand Up @@ -291,7 +291,7 @@ open class DirectionsOptions: Codable {
// MARK: Getting the Request URL

/**
An array of URL query items to include in an HTTP request.
The path of the request URL, specifying service name, version and profile.
The query items are included in the URL of a GET request or the body of a POST request.
*/
Expand Down
4 changes: 2 additions & 2 deletions Sources/MapboxDirections/DirectionsResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ open class DirectionsResult: Codable {
/**
The route’s expected travel time, measured in seconds.
The value of this property reflects the time it takes to traverse the entire route. It is the sum of the `expectedTravelTime` properties of the route’s legs. If the route was calculated using the `DirectionsProfileIdentifier.automobileAvoidingTraffic` profile, this property reflects current traffic conditions at the time of the request, not necessarily the traffic conditions at the time the user would begin the route. For other profiles, this property reflects travel time under ideal conditions and does not account for traffic congestion. If the route makes use of a ferry or train, the actual travel time may additionally be subject to the schedules of those services.
The value of this property reflects the time it takes to traverse the entire route. It is the sum of the `expectedTravelTime` properties of the route’s legs. If the route was calculated using the `ProfileIdentifier.automobileAvoidingTraffic` profile, this property reflects current traffic conditions at the time of the request, not necessarily the traffic conditions at the time the user would begin the route. For other profiles, this property reflects travel time under ideal conditions and does not account for traffic congestion. If the route makes use of a ferry or train, the actual travel time may additionally be subject to the schedules of those services.
Do not assume that the user would travel along the route at a fixed speed. For more granular travel times, use the `RouteLeg.expectedTravelTime` or `RouteStep.expectedTravelTime`. For even more granularity, specify the `AttributeOptions.expectedTravelTime` option and use the `RouteLeg.expectedSegmentTravelTimes` property.
*/
Expand All @@ -141,7 +141,7 @@ open class DirectionsResult: Codable {
/**
The route’s typical travel time, measured in seconds.
The value of this property reflects the typical time it takes to traverse the entire route. It is the sum of the `typicalTravelTime` properties of the route’s legs. This property is available when using the `DirectionsProfileIdentifier.automobileAvoidingTraffic` profile. This property reflects typical traffic conditions at the time of the request, not necessarily the typical traffic conditions at the time the user would begin the route. If the route makes use of a ferry, the typical travel time may additionally be subject to the schedule of this service.
The value of this property reflects the typical time it takes to traverse the entire route. It is the sum of the `typicalTravelTime` properties of the route’s legs. This property is available when using the `ProfileIdentifier.automobileAvoidingTraffic` profile. This property reflects typical traffic conditions at the time of the request, not necessarily the typical traffic conditions at the time the user would begin the route. If the route makes use of a ferry, the typical travel time may additionally be subject to the schedule of this service.
Do not assume that the user would travel along the route at a fixed speed. For more granular typical travel times, use the `RouteLeg.typicalTravelTime` or `RouteStep.typicalTravelTime`.
*/
Expand Down
Loading

0 comments on commit f46b02e

Please sign in to comment.