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.

RouteProvider

There are two types of route providers: one more suited to HTTP servers, and another designed for other use cases like local route generation. 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 too! Both Valhalla and Mapbox have OSRM serializers that add a few extra fields which are useful for navigation applications. The OSRM parser in the core of Ferrostar handles all of these “flavors” gracefully.

OSRM parser in hand, all that we need to do to support these different APIs is a RouteRequestGenerator for each. All three speak HTTP, but they have different request formats. Writing a RouteRequestGenerator is pretty easy though. Request generators return a sum type (enum/sealed class) indicating the type of request to make (only HTTP POST is supported at this time) and associated data like the URL, headers, and request body.

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

The Ferrostar crate includes support for the following out of the box.

Valhalla

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

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

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.

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’ll cover next.