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

Introduce Valhalla costing options #104

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions android/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'

implementation "net.java.dev.jna:jna:5.12.0@aar"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
Expand Down Expand Up @@ -107,9 +109,15 @@ class FerrostarCore(
profile: String,
httpClient: OkHttpClient,
locationProvider: LocationProvider,
costingOptions: Map<String, Any> = emptyMap(),
) : this(
RouteProvider.RouteAdapter(
RouteAdapter.newValhallaHttp(valhallaEndpointURL.toString(), profile)),
RouteAdapter.newValhallaHttp(
valhallaEndpointURL.toString(),
profile,
Json.encodeToString(costingOptions)
)
),
httpClient,
locationProvider,
)
Expand Down
17 changes: 15 additions & 2 deletions apple/Sources/FerrostarCore/FerrostarCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,29 @@ public protocol FerrostarCoreDelegate: AnyObject {
// Location provider setup
locationProvider.delegate = self
}

enum ValhallaError: Error {
case invalidCostingOptions
}

public convenience init(
valhallaEndpointUrl: URL,
profile: String,
locationProvider: LocationProviding,
costingOptions: [String: Any] = [:],
networkSession: URLRequestLoading = URLSession.shared
) {
) throws {
guard let jsonCostingOptions = String(
data: try JSONSerialization.data(withJSONObject: costingOptions),
encoding: .utf8
) else {
throw ValhallaError.invalidCostingOptions
}

let adapter = RouteAdapter.newValhallaHttp(
endpointUrl: valhallaEndpointUrl.absoluteString,
profile: profile
profile: profile,
costingOptions: jsonCostingOptions
)
self.init(
routeProvider: .routeAdapter(adapter),
Expand Down
16 changes: 10 additions & 6 deletions apple/Sources/UniFFI/ferrostar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -661,11 +661,12 @@ public class RouteAdapter:
try! rustCall { uniffi_ferrostar_fn_free_routeadapter(pointer, $0) }
}

public static func newValhallaHttp(endpointUrl: String, profile: String) -> RouteAdapter {
public static func newValhallaHttp(endpointUrl: String, profile: String, costingOptions: String) -> RouteAdapter {
RouteAdapter(unsafeFromRawPointer: try! rustCall {
uniffi_ferrostar_fn_constructor_routeadapter_new_valhalla_http(
FfiConverterString.lower(endpointUrl),
FfiConverterString.lower(profile), $0
FfiConverterString.lower(profile),
FfiConverterString.lower(costingOptions), $0
)
})
}
Expand Down Expand Up @@ -3523,12 +3524,15 @@ public func createOsrmResponseParser(polylinePrecision: UInt32) -> RouteResponse
*
* This is provided as a convenience for use from foreign code when creating your own [routing_adapters::RouteAdapter].
*/
public func createValhallaRequestGenerator(endpointUrl: String, profile: String) -> RouteRequestGenerator {
public func createValhallaRequestGenerator(endpointUrl: String, profile: String,
costingOptions: String) -> RouteRequestGenerator
{
try! FfiConverterTypeRouteRequestGenerator.lift(
try! rustCall {
uniffi_ferrostar_fn_func_create_valhalla_request_generator(
FfiConverterString.lower(endpointUrl),
FfiConverterString.lower(profile), $0
FfiConverterString.lower(profile),
FfiConverterString.lower(costingOptions), $0
)
}
)
Expand Down Expand Up @@ -3625,7 +3629,7 @@ private var initializationResult: InitializationResult {
if uniffi_ferrostar_checksum_func_create_osrm_response_parser() != 28097 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_func_create_valhalla_request_generator() != 35701 {
if uniffi_ferrostar_checksum_func_create_valhalla_request_generator() != 3174 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_func_get_route_polyline() != 53320 {
Expand Down Expand Up @@ -3670,7 +3674,7 @@ private var initializationResult: InitializationResult {
if uniffi_ferrostar_checksum_constructor_routeadapter_new() != 15081 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_constructor_routeadapter_new_valhalla_http() != 24553 {
if uniffi_ferrostar_checksum_constructor_routeadapter_new_valhalla_http() != 14445 {
return InitializationResult.apiChecksumMismatch
}

Expand Down
7 changes: 6 additions & 1 deletion common/ferrostar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,13 @@ impl UniffiCustomTypeConverter for Uuid {
fn create_valhalla_request_generator(
endpoint_url: String,
profile: String,
costing_options: String,
) -> Arc<dyn RouteRequestGenerator> {
Arc::new(ValhallaHttpRequestGenerator::new(endpoint_url, profile))
Arc::new(ValhallaHttpRequestGenerator::new(
endpoint_url,
profile,
costing_options,
))
}

/// Creates a [RouteResponseParser] capable of parsing OSRM responses.
Expand Down
9 changes: 7 additions & 2 deletions common/ferrostar/src/routing_adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,13 @@ impl RouteAdapter {
}

#[uniffi::constructor]
pub fn new_valhalla_http(endpoint_url: String, profile: String) -> Self {
let request_generator = create_valhalla_request_generator(endpoint_url, profile);
pub fn new_valhalla_http(
endpoint_url: String,
profile: String,
costing_options: String,
) -> Self {
let request_generator =
create_valhalla_request_generator(endpoint_url, profile, costing_options);
let response_parser = create_osrm_response_parser(6);
Self::new(request_generator, response_parser)
}
Expand Down
108 changes: 82 additions & 26 deletions common/ferrostar/src/routing_adapters/valhalla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ pub struct ValhallaHttpRequestGenerator {
/// Users *may* include a query string with an API key.
endpoint_url: String,
profile: String,
// TODO: more tunable parameters; a dict that gets inserted seems like a bare minimum; we can also allow higher level ones
costing_options: String,
}

impl ValhallaHttpRequestGenerator {
pub fn new(endpoint_url: String, profile: String) -> Self {
pub fn new(endpoint_url: String, profile: String, costing_options: String) -> Self {
Self {
endpoint_url,
profile,
costing_options,
}
}
}
Expand Down Expand Up @@ -63,6 +64,8 @@ impl RouteRequestGenerator for ValhallaHttpRequestGenerator {
})
}))
.collect();

let parsed_costing_options: JsonValue = serde_json::from_str(&self.costing_options)?;
// NOTE: We currently use the OSRM format, as it is the richest one.
// Though it would be nice to use PBF if we can get the required data.
// However, certain info (like banners) are only available in the OSRM format.
Expand All @@ -82,6 +85,7 @@ impl RouteRequestGenerator for ValhallaHttpRequestGenerator {
"voice_instructions": true,
"costing": &self.profile,
"locations": locations,
"costing_options": parsed_costing_options,
});
let body = serde_json::to_vec(&args)?;
Ok(RouteRequest::HttpPost {
Expand Down Expand Up @@ -131,8 +135,11 @@ mod tests {

#[test]
fn not_enough_locations() {
let generator =
ValhallaHttpRequestGenerator::new(ENDPOINT_URL.to_string(), COSTING.to_string());
let generator = ValhallaHttpRequestGenerator::new(
ENDPOINT_URL.to_string(),
COSTING.to_string(),
String::new(),
);

// At least two locations are required
assert!(matches!(
Expand All @@ -141,27 +148,37 @@ mod tests {
));
}

fn generate_body(user_location: UserLocation, waypoints: Vec<Waypoint>) -> JsonValue {
let generator =
ValhallaHttpRequestGenerator::new(ENDPOINT_URL.to_string(), COSTING.to_string());

let RouteRequest::HttpPost {
url: request_url,
headers,
body,
} = generator
.generate_request(user_location, waypoints)
.unwrap();

assert_eq!(ENDPOINT_URL, request_url);
assert_eq!(headers["Content-Type"], "application/json".to_string());
fn generate_body(
user_location: UserLocation,
waypoints: Vec<Waypoint>,
costing_options: String,
) -> JsonValue {
let generator = ValhallaHttpRequestGenerator::new(
ENDPOINT_URL.to_string(),
COSTING.to_string(),
costing_options,
);

from_slice(&body).expect("Failed to parse request body as JSON")
match generator.generate_request(user_location, waypoints) {
Ok(RouteRequest::HttpPost {
url: request_url,
headers,
body,
}) => {
assert_eq!(ENDPOINT_URL, request_url);
assert_eq!(headers["Content-Type"], "application/json".to_string());
from_slice(&body).expect("Failed to parse request body as JSON")
}
Err(e) => {
println!("Failed to generate request: {:?}", e);
json!(null)
}
}
}

#[test]
fn request_body_without_course() {
let body_json = generate_body(USER_LOCATION, WAYPOINTS.to_vec());
let body_json = generate_body(USER_LOCATION, WAYPOINTS.to_vec(), "{}".to_string());

assert_json_include!(
actual: body_json,
Expand All @@ -181,14 +198,18 @@ mod tests {
"lat": 2.0,
"lon": 3.0,
}
]
],
})
);
}

#[test]
fn request_body_with_course() {
let body_json = generate_body(USER_LOCATION_WITH_COURSE, WAYPOINTS.to_vec());
let body_json = generate_body(
USER_LOCATION_WITH_COURSE,
WAYPOINTS.to_vec(),
"{}".to_string(),
);

assert_json_include!(
actual: body_json,
Expand All @@ -209,15 +230,50 @@ mod tests {
"lat": 2.0,
"lon": 3.0,
}
]
],
})
);
}

#[test]
fn request_body_without_costing_options() {
let body_json = generate_body(USER_LOCATION, WAYPOINTS.to_vec(), "{}".to_string());

assert_json_include!(
actual: body_json,
expected: json!({
"costing_options": {},
})
);
}

#[test]
fn request_body_with_costing_options() {
let body_json = generate_body(
USER_LOCATION,
WAYPOINTS.to_vec(),
r#"{"bicycle": {"bicycle_type": "Road"}}"#.to_string(),
);

assert_json_include!(
actual: body_json,
expected: json!({
"costing_options": {
"bicycle": {
"bicycle_type": "Road",
},
},
})
);
}

#[test]
fn request_body_with_invalid_horizontal_accuracy() {
let generator =
ValhallaHttpRequestGenerator::new(ENDPOINT_URL.to_string(), COSTING.to_string());
let generator = ValhallaHttpRequestGenerator::new(
ENDPOINT_URL.to_string(),
COSTING.to_string(),
"{}".to_string(),
);
let location = UserLocation {
coordinates: GeographicCoordinate { lat: 0.0, lng: 0.0 },
horizontal_accuracy: -6.0,
Expand Down Expand Up @@ -256,7 +312,7 @@ mod tests {
"lat": 2.0,
"lon": 3.0,
}
]
],
})
);
}
Expand Down