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

Added Osrm to route builder for custom route adapters #176

Merged
merged 9 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ publishing {

allprojects {
group = "com.stadiamaps.ferrostar"
version = "0.6.1"
version = "0.7.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.stadiamaps.ferrostar.core.extensions

import uniffi.ferrostar.Route
import uniffi.ferrostar.createRouteFromOsrm

/**
* Create a [Route] from OSRM route and waypoint data.
*
* This behavior uses the same internal decoders as the OsrmResponseParser. This function will
* automatically map & combine via and break waypoints from the route and waypoint data objects.
*
* @param route The encoded JSON data for the OSRM route.
* @param waypoints The encoded JSON data for the OSRM waypoints.
* @param polylinePrecision The polyline precision.
* @return The navigation [Route]
*/
fun Route.Companion.fromOsrm(
route: ByteArray,
waypoints: ByteArray,
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
polylinePrecision: UInt
): Route {
return createRouteFromOsrm(routeData = route, waypointData = waypoints, polylinePrecision)
}
21 changes: 21 additions & 0 deletions apple/Sources/FerrostarCore/Extensions/RouteExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import FerrostarCoreFFI
import Foundation

public extension Route {
/// Create a new Route directly from an OSRM route.
///
/// This behavior uses the same internal decoders as the OsrmResponseParser. This function will
/// automatically map & combine via and break waypoints from the route and waypoint data objects.
///
/// - Parameters:
/// - route: The encoded JSON data for the OSRM route.
/// - waypoints: The encoded JSON data for the OSRM waypoints.
/// - precision: The polyline precision.
static func initFromOsrm(route: Data, waypoints: Data, polylinePrecision: UInt32) throws -> Route {
try createRouteFromOsrm(routeData: route, waypointData: waypoints, polylinePrecision: polylinePrecision)
}

func getPolyline(precision: UInt32) throws -> String {
try getRoutePolyline(route: self, precision: precision)
}
}
6 changes: 0 additions & 6 deletions apple/Sources/FerrostarCore/Models/ModelWrappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ import CoreLocation
import FerrostarCoreFFI
import Foundation

public extension Route {
func getPolyline(precision: UInt32) throws -> String {
try getRoutePolyline(route: self, precision: precision)
}
}

private class DetectorImpl: RouteDeviationDetector {
let detectorFunc: (UserLocation, Route, RouteStep) -> RouteDeviation

Expand Down
112 changes: 66 additions & 46 deletions apple/Sources/UniFFI/ferrostar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ open class RouteAdapter:
}

open func parseResponse(response: Data) throws -> [Route] {
try FfiConverterSequenceTypeRoute.lift(rustCallWithError(FfiConverterTypeRoutingResponseParseError.lift) {
try FfiConverterSequenceTypeRoute.lift(rustCallWithError(FfiConverterTypeParsingError.lift) {
uniffi_ferrostar_fn_method_routeadapter_parse_response(self.uniffiClonePointer(),
FfiConverterData.lower(response), $0)
})
Expand Down Expand Up @@ -1284,7 +1284,7 @@ open class RouteResponseParserImpl:
* as this works for all currently conceivable formats (JSON, PBF, etc.).
*/
open func parseResponse(response: Data) throws -> [Route] {
try FfiConverterSequenceTypeRoute.lift(rustCallWithError(FfiConverterTypeRoutingResponseParseError.lift) {
try FfiConverterSequenceTypeRoute.lift(rustCallWithError(FfiConverterTypeParsingError.lift) {
uniffi_ferrostar_fn_method_routeresponseparser_parse_response(self.uniffiClonePointer(),
FfiConverterData.lower(response), $0)
})
Expand Down Expand Up @@ -1318,7 +1318,7 @@ private enum UniffiCallbackInterfaceRouteResponseParser {
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn,
lowerError: FfiConverterTypeRoutingResponseParseError.lower
lowerError: FfiConverterTypeParsingError.lower
)
},
uniffiFree: { (uniffiHandle: UInt64) in
Expand Down Expand Up @@ -2875,6 +2875,47 @@ extension ModelError: Foundation.LocalizedError {
}
}

public enum ParsingError {
case ParseError(error: String)
case UnknownError
}

public struct FfiConverterTypeParsingError: FfiConverterRustBuffer {
typealias SwiftType = ParsingError

public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ParsingError {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return try .ParseError(
error: FfiConverterString.read(from: &buf)
)

case 2: return .UnknownError

default: throw UniffiInternalError.unexpectedEnumCase
}
}

public static func write(_ value: ParsingError, into buf: inout [UInt8]) {
switch value {
case let .ParseError(error):
writeInt(&buf, Int32(1))
FfiConverterString.write(error, into: &buf)

case .UnknownError:
writeInt(&buf, Int32(2))
}
}
}

extension ParsingError: Equatable, Hashable {}

extension ParsingError: Foundation.LocalizedError {
public var errorDescription: String? {
String(reflecting: self)
}
}

// Note that we don't yet support `indirect` for enums.
// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
/**
Expand Down Expand Up @@ -3100,47 +3141,6 @@ extension RoutingRequestGenerationError: Foundation.LocalizedError {
}
}

public enum RoutingResponseParseError {
case ParseError(error: String)
case UnknownError
}

public struct FfiConverterTypeRoutingResponseParseError: FfiConverterRustBuffer {
typealias SwiftType = RoutingResponseParseError

public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RoutingResponseParseError {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return try .ParseError(
error: FfiConverterString.read(from: &buf)
)

case 2: return .UnknownError

default: throw UniffiInternalError.unexpectedEnumCase
}
}

public static func write(_ value: RoutingResponseParseError, into buf: inout [UInt8]) {
switch value {
case let .ParseError(error):
writeInt(&buf, Int32(1))
FfiConverterString.write(error, into: &buf)

case .UnknownError:
writeInt(&buf, Int32(2))
}
}
}

extension RoutingResponseParseError: Equatable, Hashable {}

extension RoutingResponseParseError: Foundation.LocalizedError {
public var errorDescription: String? {
String(reflecting: self)
}
}

public enum SimulationError {
/**
* Errors decoding the polyline string.
Expand Down Expand Up @@ -3879,6 +3879,23 @@ public func createOsrmResponseParser(polylinePrecision: UInt32) -> RouteResponse
})
}

/**
* Creates a [`Route`] from OSRM data.
*
* This uses the same logic as the [`OsrmResponseParser`] and is designed to be fairly flexible,
* supporting both vanilla OSRM and enhanced Valhalla (ex: from Stadia Maps and Mapbox) outputs
* which contain richer information like banners and voice instructions for navigation.
*/
public func createRouteFromOsrm(routeData: Data, waypointData: Data, polylinePrecision: UInt32) throws -> Route {
try FfiConverterTypeRoute.lift(rustCallWithError(FfiConverterTypeParsingError.lift) {
uniffi_ferrostar_fn_func_create_route_from_osrm(
FfiConverterData.lower(routeData),
FfiConverterData.lower(waypointData),
FfiConverterUInt32.lower(polylinePrecision), $0
)
})
}

/**
* Creates a [`RouteRequestGenerator`]
* which generates requests to an arbitrary Valhalla server (using the OSRM response format).
Expand Down Expand Up @@ -3980,6 +3997,9 @@ private var initializationResult: InitializationResult = {
if uniffi_ferrostar_checksum_func_create_osrm_response_parser() != 16550 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_func_create_route_from_osrm() != 42270 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_func_create_valhalla_request_generator() != 62919 {
return InitializationResult.apiChecksumMismatch
}
Expand Down Expand Up @@ -4007,7 +4027,7 @@ private var initializationResult: InitializationResult = {
if uniffi_ferrostar_checksum_method_routeadapter_generate_request() != 59034 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_method_routeadapter_parse_response() != 47311 {
if uniffi_ferrostar_checksum_method_routeadapter_parse_response() != 34481 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_method_routedeviationdetector_check_route_deviation() != 50476 {
Expand All @@ -4016,7 +4036,7 @@ private var initializationResult: InitializationResult = {
if uniffi_ferrostar_checksum_method_routerequestgenerator_generate_request() != 63458 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_method_routeresponseparser_parse_response() != 38851 {
if uniffi_ferrostar_checksum_method_routeresponseparser_parse_response() != 44735 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_constructor_navigationcontroller_new() != 60881 {
Expand Down
2 changes: 1 addition & 1 deletion common/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions common/ferrostar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ lints.workspace = true

[package]
name = "ferrostar"
version = "0.6.1"
version = "0.7.0"
readme = "README.md"
description = "The core of modern turn-by-turn navigation."
keywords = ["navigation", "routing", "valhalla", "osrm"]
Expand All @@ -19,14 +19,20 @@ rust-version.workspace = true
alloc = []
std = ["alloc", "serde_json/std", "proptest/std"]
default = ["std", "uniffi"]
wasm_js = ["std", "getrandom/js", "serde-wasm-bindgen", "wasm-bindgen", "web-time"]
wasm_js = [
"std",
"getrandom/js",
"serde-wasm-bindgen",
"wasm-bindgen",
"web-time",
]

[dependencies]
geo = "0.28.0"
polyline = "0.11.0"
serde = { version = "1.0.162", features = ["derive"] }
serde_json = { version = "1.0.117", default-features = false }
serde-wasm-bindgen = { version = "0.6.5", optional = true}
serde-wasm-bindgen = { version = "0.6.5", optional = true }
thiserror = "1.0.40"
uniffi = { workspace = true, optional = true }
uuid = { version = "1.8.0", features = ["v4", "serde"] }
Expand Down
27 changes: 26 additions & 1 deletion common/ferrostar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ pub mod navigation_controller;
pub mod routing_adapters;
pub mod simulation;

use models::Route;
#[cfg(feature = "uniffi")]
use routing_adapters::{
error::InstantiationError, osrm::OsrmResponseParser, valhalla::ValhallaHttpRequestGenerator,
error::{InstantiationError, ParsingError},
osrm::{
models::{Route as OsrmRoute, Waypoint as OsrmWaypoint},
OsrmResponseParser,
},
valhalla::ValhallaHttpRequestGenerator,
RouteRequestGenerator, RouteResponseParser,
};
#[cfg(feature = "uniffi")]
Expand Down Expand Up @@ -92,3 +98,22 @@ fn create_valhalla_request_generator(
fn create_osrm_response_parser(polyline_precision: u32) -> Arc<dyn RouteResponseParser> {
Arc::new(OsrmResponseParser::new(polyline_precision))
}

// MARK: OSRM Route Conversion

/// Creates a [`Route`] from OSRM data.
///
/// This uses the same logic as the [`OsrmResponseParser`] and is designed to be fairly flexible,
/// supporting both vanilla OSRM and enhanced Valhalla (ex: from Stadia Maps and Mapbox) outputs
/// which contain richer information like banners and voice instructions for navigation.
#[cfg(feature = "uniffi")]
#[uniffi::export]
fn create_route_from_osrm(
route_data: Vec<u8>,
waypoint_data: Vec<u8>,
polyline_precision: u32,
) -> Result<Route, ParsingError> {
let route: OsrmRoute = serde_json::from_slice(&route_data)?;
let waypoints: Vec<OsrmWaypoint> = serde_json::from_slice(&waypoint_data)?;
return Route::from_osrm(&route, &waypoints, polyline_precision);
}
12 changes: 6 additions & 6 deletions common/ferrostar/src/routing_adapters/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl From<serde_json::Error> for RoutingRequestGenerationError {
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
pub enum RoutingResponseParseError {
pub enum ParsingError {
// TODO: Unable to find route and other common errors
#[cfg_attr(feature = "std", error("Failed to parse route response: {error}."))]
ParseError { error: String },
Expand All @@ -65,15 +65,15 @@ pub enum RoutingResponseParseError {
}

#[cfg(feature = "uniffi")]
impl From<uniffi::UnexpectedUniFFICallbackError> for RoutingResponseParseError {
fn from(_: uniffi::UnexpectedUniFFICallbackError) -> RoutingResponseParseError {
RoutingResponseParseError::UnknownError
impl From<uniffi::UnexpectedUniFFICallbackError> for ParsingError {
fn from(_: uniffi::UnexpectedUniFFICallbackError) -> ParsingError {
ParsingError::UnknownError
}
}

impl From<serde_json::Error> for RoutingResponseParseError {
impl From<serde_json::Error> for ParsingError {
fn from(e: serde_json::Error) -> Self {
RoutingResponseParseError::ParseError {
ParsingError::ParseError {
error: e.to_string(),
}
}
Expand Down
9 changes: 3 additions & 6 deletions common/ferrostar/src/routing_adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
use crate::models::Waypoint;
use crate::models::{Route, UserLocation};
use crate::routing_adapters::error::InstantiationError;
use error::{RoutingRequestGenerationError, RoutingResponseParseError};
use error::{ParsingError, RoutingRequestGenerationError};

#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::collections::BTreeMap as HashMap;
Expand Down Expand Up @@ -102,7 +102,7 @@ pub trait RouteResponseParser: Send + Sync {
///
/// We use a sequence of octets as a common interchange format.
/// as this works for all currently conceivable formats (JSON, PBF, etc.).
fn parse_response(&self, response: Vec<u8>) -> Result<Vec<Route>, RoutingResponseParseError>;
fn parse_response(&self, response: Vec<u8>) -> Result<Vec<Route>, ParsingError>;
}

/// The route adapter bridges between the common core and a routing backend where interaction takes place
Expand Down Expand Up @@ -172,10 +172,7 @@ impl RouteAdapter {
.generate_request(user_location, waypoints)
}

pub fn parse_response(
&self,
response: Vec<u8>,
) -> Result<Vec<Route>, RoutingResponseParseError> {
pub fn parse_response(&self, response: Vec<u8>) -> Result<Vec<Route>, ParsingError> {
self.response_parser.parse_response(response)
}
}
Expand Down
Loading
Loading