Route Providers
Route providers expose common interfaces for making requests to a routing engine and getting the data back in a standardized format for navigation. This layer of indirection makes Ferrostar extremely extensible.
NOTE: Extensible route providers are currently available on iOS and Android. The JavaScript platform presents some unique challenges, so only Valhalla backends are supported directly from the JavaScript API and published web components. Contributions and discussion around the best ways to enable this are very much welcome.
RouteProvider
There are two types of route providers:
one more suited to HTTP servers (RouteAdapter
),
and another designed for other use cases like local route generation (CustomRouteProvider
).
The core ships with common implementations,
but you’re free to define your own in your application code
without waiting for a PR to land or running a custom fork!
Route Adapters
A route adapter is the first class of route provider.
It is designed for HTTP, sockets, and other request/response flows.
A RouteAdapter
is typically nothing more than the composition of two halves:
a RouteRequestGenerator
and a RouteResponseParser
(both of which are traits at the Rust level;
protocols and interfaces at the platform level).
You can mix and match these freely
(though it probably goes without saying that you won’t get very far
swapping out the wrong parser for a server type).
Let’s illustrate why this architecture is nice with an example. Valhalla can generate responses in multiple formats. The default is its own JSON format, but it also has more compact Protobuf and (much) more verbose OSRM serializers.
The OSRM serializer is the one that’s typically used for navigation use cases,
so with a single RouteResponseParser
implementation,
we can parse responses from a Valhalla server or an OSRM server!
In fact, we can also parse responses from
the Mapbox Directions API or GraphHopper (in certain modes),
as they offer OSRM-compatible responses.
Every “extended OSRM” API includes additional data which are useful for navigation applications. The parser in the core of Ferrostar handles all of these “flavors” gracefully, and provides either direct or indirect support for most extensions. Of special note, the voice and banner instructions (popularized by Mapbox and supported in Valhalla) are always parsed, when available, and included in the route object. Annotations, which are available in some form or other on all OSRM-like APIs, can be parsed as anything. This leaves annotations open to extension, since it’s already used this way in practice.
OSRM parser in hand, all that we need to do to support these different APIs
is a RouteRequestGenerator
for each.
While all services mentioned are HTTP-based,
each has a different request format.
Writing a RouteRequestGenerator
is pretty easy though.
Request generators return a sum type (enum/sealed class)
indicating the type of request to make
and associated data like the URL, headers, and request body.
By splitting up the request generation,
request execution, and response parsing into distinct steps,
we reduce the work required to support a new API.
In this example, all we had to do was supply a RouteRequestGenerator
.
Our RouteAdapter
was able to use the existing OsrmResponseParser
,
and the core (ex: FerrostarCore
on iOS or Android)
used the platform native HTTP stack to execute the request on our behalf.
Here’s a sequence diagram illustrating the flow of data between components.
Don’t worry too much about the internal complexity;
you’ll only interface with FerrostarCore
at the boundary.
sequenceDiagram FerrostarCore->>+RouteAdapter: generateRequest RouteAdapter-->>+FerrostarCore: RouteRequest FerrostarCore-)Routing API: Network request Routing API--)FerrostarCore: Route response (bytes) FerrostarCore->>+RouteAdapter: parseResponse RouteAdapter->>+FerrostarCore: [Route] or error FerrostarCore->>+Application Code: [Route] or error
Bundled support
Ferrostar includes support for the following APIs out of the box.
Valhalla (Request + Response)
Valhalla APIs are supported out of the box with full request and response parsing.
As a bundled provider, FerrostarCore
in the platform layer exposes
convenience initializers which automatically configure the RouteAdapter
.
If you’re rolling your own integration or curious about implementation details,
the relevant Rust type is ValhallaHttpRequestGenerator
.
For response parsing, we use the OSRM format at this time.
You can construct an instance of ValhallaHttpRequestGenerator
directly
(if you’re using Rust for your application)
or using the convenience method createValhallaRequestGenerator
from Swift or Kotlin.
OSRM (Response only)
OSRM has become something of a de facto linga franca for navigation APIs. Ferrostar comes bundled with support for decoding OSRM responses, including the common extensions developed by Mapbox and offered by many Valhalla servers. This gives drop-in compatibility with a wide variety of backend APIs, including the hosted options from Stadia Maps and Mapbox, as well as many self-hosted servers.
The relevant Rust type is OsrmResponseParser
.
The autogenerated FFI bindings expose a createOsrmResponseParser
method
in case you want to roll your own RouteAdapter
for an API
that uses a different request format but returns OSRM format responses.
Implementing your own RouteAdapter
If you’re working with a routing engine and want to see it directly supported in Ferrostar, we welcome PRs! Have a look at the existing Valhalla (request generator) and OSRM (response parser) implementations for inspiration.
If you’d rather keep the logic to yourself (ex: for an internal API),
you can implement your own in Swift or Kotlin.
Just implement/conform to one or both of
RouteRequestGenerator
and RouteResponseParser
in your Swift and Kotlin code.
You may only need to implement one half or the other.
For example, to integrate with an API that returns OSRM responses
but has a different request format, you only need a RouteRequestGenerator
;
you can re-use the OSRM response parser.
Refer to the core test code on GitHub for examples which mock both halves. TODO: Examples here after 1.0.
CustomRouteProvider
Custom route providers are most commonly used for local route generation, but they can be used for just about anything. Rather than imposing a clean (but rigid) request+response model with opinionated use cases like HTTP in mind, the custom route provider is just a single-method interface (SAM for you Java or Kotlin devs) for getting routes asynchronously.
Here’s a sequence diagram illustrating the flow of data between components
when using a CustomRouteProvider
.
sequenceDiagram FerrostarCore-)CustomRouteProvider: getRoutes CustomRouteProvider--)FerrostarCore: [Route] or error
Using a RouteProvider
The parts of Ferrostar concerned with routing are managed
by the FerrostarCore
class in the platform layer.
After you create a RouteProvider
and pass it off to FerrostarCore
(or use a convenience constructor which does it all for you!),
you’re done.
Developers do not need to interact with the RouteProvider
after construction.
FerrostarCore
provides a high-level interface for getting routes,
which is as close as you usually get to them.
sequenceDiagram Your Application-)FerrostarCore: getRoutes FerrostarCore-)RouteProvider: (Hidden complexity) RouteProvider--)FerrostarCore: [Route] or error FerrostarCore--)Your Application: [Route] or error
This part of the platform layer follows the Hollywood principle. This provides elegant ways of configuring rerouting, which we cover in Configuring the Navigation Controller.