Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Isochrone api #621

Merged
merged 19 commits into from
Nov 11, 2021
Merged

Isochrone api #621

merged 19 commits into from
Nov 11, 2021

Conversation

Udumft
Copy link
Contributor

@Udumft Udumft commented Oct 25, 2021

Resolves #296

This PR adds Isochrone object to access co-named API. It's structure and tooling entities are similar to Directions.

@Udumft Udumft added improvement Improvement for an existing feature. platform parity labels Oct 25, 2021
@Udumft Udumft added this to the v2.1-beta milestone Oct 25, 2021
@Udumft Udumft self-assigned this Oct 25, 2021
Comment on lines +11 to +21
case (200, "NoSegment"):
self = .unableToLocate
case (404, "ProfileNotFound"):
self = .profileNotFound
case (422, "InvalidInput"):
self = .invalidInput(message: message)
case (429, _):
self = .rateLimited(rateLimitInterval: response.rateLimitInterval, rateLimit: response.rateLimit, resetTime: response.rateLimitResetTime)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table lists a few more codes that should get dedicated cases, but they sound like codes that would be returned by other navigation service APIs too. Some of the codes may come with structured properties alongside the message, similar to the 429 error, which would be nice to parse into associated values.

Do any codes conflict with Directions API error codes? Could they conflict in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some troubles with this table. In the code I've listed all errors I managed to reproduce.

/**
Options determining the primary mode of transportation for the contours.
*/
public struct IsochroneProfileIdentifier: Codable, RawRepresentable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be consolidated with DirectionsProfileIdentifier unless there’s a possibility that some profile will intentionally be limited to one service or another.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a difference already: Isochrone does not support driving-traffic profile.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a limitation like that wouldn’t keep us from reusing DirectionsProfileIdentifier. We can just document the fact that the automobileAvoidingTraffic constant can’t be used with the Isochrone class. What would be a problem is if the Isochrone API’s mapbox/driving-traffic profile behaved like the Directions API’s mapbox/driving profile or vice versa. Besides, these are only constants in an extensible enumeration: the developer is always able to specify an invalid profile identifier like mabpox/unicycling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be wise to rename DirectionsProfileIdentifier to something more generic like ServiceProfileIdentifier since it is used in multiple services and we are going to document it's effect on each one in code docs? That would be a breaking change.
I would like to avoid using exactly Directions<Something> for Isochrone, plus mentioning Isochrones in it's automobileAvoidingTraffic would also look strange.

Copy link
Contributor

@1ec5 1ec5 Oct 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be wise to rename DirectionsProfileIdentifier to something more generic like ServiceProfileIdentifier since it is used in multiple services and we are going to document it's effect on each one in code docs? That would be a breaking change.

I think it’s totally reasonable. ProfileIdentifier should be sufficient; I don’t think it’s very likely to introduce a lot of conflicts with other libraries. We can avoid a breaking change by leaving in a type alias that’s marked as deprecated.

@Udumft Udumft force-pushed the vk/296-isochrone-api branch 2 times, most recently from 91ac378 to 34ffe06 Compare October 26, 2021 10:00
@Udumft Udumft marked this pull request as ready for review October 26, 2021 10:20
@Udumft Udumft requested a review from a team October 26, 2021 10:20
/**
Options determining the primary mode of transportation for the contours.
*/
public struct IsochroneProfileIdentifier: Codable, RawRepresentable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a limitation like that wouldn’t keep us from reusing DirectionsProfileIdentifier. We can just document the fact that the automobileAvoidingTraffic constant can’t be used with the Isochrone class. What would be a problem is if the Isochrone API’s mapbox/driving-traffic profile behaved like the Directions API’s mapbox/driving profile or vice versa. Besides, these are only constants in an extensible enumeration: the developer is always able to specify an invalid profile identifier like mabpox/unicycling.

CHANGELOG.md Outdated
@@ -6,6 +6,7 @@
* Added `RouteOptions.initialManeuverAvoidanceRadius` property to control avoidance of dangerous maneuvers when requesting a route from the Directions API. ([#609](https://github.com/mapbox/mapbox-directions-swift/pull/609))
* Added `RoadClasses.unpaved` property that enables the resulting routes to avoid unpaved roads. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added `RoadClasses.cashOnlyToll` property that enables the resulting routes to avoid toll roads that only accept cash payment. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added `Ischrone` API wrapper. The [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) computes areas that are reachable within a specified amount of time from a location, and returns 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))
Copy link
Contributor

@1ec5 1ec5 Oct 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s add a basic usage example to the readme. The example can assume the presence of the map SDK or MapboxStatic.swift. This can be tail work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an Example would be even better. Could we add it to navigation-ios-examples? It's not related to navigation though, but looks like we don't have a dedicated place for Directions examples at all?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, mapbox-navigation-ios-examples is a good place to showcase what MapboxDirections can do on its own. We should still nod to the existence of Isochrone API support in the readme though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added example usage to README

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incidentally, it never occurred to me that it’s the Isochrone API, singular. We had made a big push to name all the server APIs in the plural, but the navigation service APIs must’ve escaped notice apart from the Directions API.

Suggested change
* Added `Ischrone` API wrapper. The [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) computes areas that are reachable within a specified amount of time from a location, and returns 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))
* 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))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class is currently called Isochrones, plural.

Suggested change
* Added `Ischrone` API wrapper. The [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) computes areas that are reachable within a specified amount of time from a location, and returns 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))
* 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, returning 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))

/**
Contour bound definition value and contour color.
*/
public typealias ValueAndColor = (value: Value, color: Color)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion in #621 (comment) didn’t consider the possibility of specifying the color on none of the contours (which the API turns into a rainbow by default). The type system gets a bit complicated as a result. If additional per-contour properties get added in the future, such as opacity, we could be forced to make this enumeration more complicated. A struct also gives us more flexibility to maintain backwards compatibility.

What if we stick to something similar to the design in #621 (comment) but make color optional? On the client-side, we can fill in a default color at request time if colors are specified for some contours but not others. This wouldn’t violate the API contract, because the API doesn’t allow leaving some colors unspecified in the first place. But if it does in the future, it would be trivial to remove the fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filling colors gap automatically seems error prone. We cannot track if API will change the default color scheme, we'll also be basically guessing which default color to substitute in each particular gap.
Since we are making an API wrapper, it should make usage convenient but not alter the original behavior.

Copy link
Contributor

@1ec5 1ec5 Oct 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s not so much for the developer’s convenience but for us to ensure the client-side API’s flexibility:

  • If the Isochrone API later introduces the ability to leave one of the colors unspecified, then the fallbacks would become extraneous, but everything the developer could previously do would remain possible, and it wouldn’t be possible to specify an invalid set of contours. On the other hand, with these tuples, we’d have to introduce a ValueAndOptionalColor tuple and corresponding enumeration case.
  • If the API adds a contours_opacity parameter, then we’d need to introduce new enumeration cases and tuples for ValueAndColorAndOpacity and ValueAndOpacity, and it could be combinatorial explosion after that. On the other hand, with a single struct, we can add additional optional properties without any fuss.

We could fill in black as a reasonable default color. Rainbow colors would still be possible if none of the colors is specified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Struct approach turned out 1 word shorter during initialization. I decided that using black as a fallback color might be too much, so I chose a bit more neutral 50% gray. :)

@Udumft Udumft requested review from 1ec5 and jill-cardamon October 28, 2021 14:14
Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some additional documentation-related nits, but I think the big question is how to structure the type system to dissuade the user from making invalid requests while being forward-compatible: #621 (comment).

Comment on lines +247 to +258
let encoder = JSONEncoder()
let data = try! encoder.encode(response)
let geoJSONString = String(data: data, encoding: .utf8)!
let geoJSONOverlay = GeoJSON(objectString: geoJSONString)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapbox/MapboxStatic.swift#112 tracks streamlining these steps by adding Turf support to the library.

CHANGELOG.md Outdated
@@ -6,6 +6,7 @@
* Added `RouteOptions.initialManeuverAvoidanceRadius` property to control avoidance of dangerous maneuvers when requesting a route from the Directions API. ([#609](https://github.com/mapbox/mapbox-directions-swift/pull/609))
* Added `RoadClasses.unpaved` property that enables the resulting routes to avoid unpaved roads. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added `RoadClasses.cashOnlyToll` property that enables the resulting routes to avoid toll roads that only accept cash payment. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added `Ischrone` API wrapper. The [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) computes areas that are reachable within a specified amount of time from a location, and returns 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incidentally, it never occurred to me that it’s the Isochrone API, singular. We had made a big push to name all the server APIs in the plural, but the navigation service APIs must’ve escaped notice apart from the Directions API.

Suggested change
* Added `Ischrone` API wrapper. The [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) computes areas that are reachable within a specified amount of time from a location, and returns 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))
* 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))

@Udumft Udumft requested a review from 1ec5 October 29, 2021 12:36
@Udumft Udumft force-pushed the vk/296-isochrone-api branch from 2cab7b1 to ebc4ed1 Compare November 1, 2021 08:07
@1ec5 1ec5 added the isochrones Isochrone API label Nov 5, 2021
@Udumft Udumft force-pushed the vk/296-isochrone-api branch from e27b811 to c196293 Compare November 8, 2021 15:36
@Udumft Udumft requested a review from jill-cardamon November 8, 2021 15:48
CHANGELOG.md Outdated
@@ -6,6 +6,7 @@
* Added `RouteOptions.initialManeuverAvoidanceRadius` property to control avoidance of dangerous maneuvers when requesting a route from the Directions API. ([#609](https://github.com/mapbox/mapbox-directions-swift/pull/609))
* Added `RoadClasses.unpaved` property that enables the resulting routes to avoid unpaved roads. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added `RoadClasses.cashOnlyToll` property that enables the resulting routes to avoid toll roads that only accept cash payment. ([#620](https://github.com/mapbox/mapbox-directions-swift/pull/620))
* Added `Ischrone` API wrapper. The [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) computes areas that are reachable within a specified amount of time from a location, and returns 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class is currently called Isochrones, plural.

Suggested change
* Added `Ischrone` API wrapper. The [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) computes areas that are reachable within a specified amount of time from a location, and returns 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))
* 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, returning 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))

@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this!

/**
Contours GeoJSON format.
*/
public enum ContourFormat {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to nest this type under IsochroneOptions? Application code might end up needing to write IsochroneOptions.ContourFormat in some cases, but ContourFormat on its own would probably not risk future conflicts with other API wrappers in this library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've nested this one since there's no practical use of this enum other than inside IsochroneOptions. Cases when fully qualified name would be required for typing are pretty rare (Linux only?) since there is only 1 place (property) where it is used and it's strictly typed.

return
}

guard (disposition.code == nil && disposition.message == nil) || disposition.code == "Ok" else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a bit of redundancy between this method and some of the Directions methods. As tail work, we could try factoring out some common tasks so it’s easier and less error-prone to spin up support for a new API.

@1ec5 1ec5 modified the milestones: v2.1-beta, v2.1-rc Nov 11, 2021
…s and DirectionsProfileIdentifier alieases to original implementations; code docs corrected.
@Udumft Udumft merged commit f46b02e into main Nov 11, 2021
@Udumft Udumft deleted the vk/296-isochrone-api branch November 11, 2021 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
improvement Improvement for an existing feature. isochrones Isochrone API platform parity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Isochrone API
3 participants