Skip to content
New issue

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

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

Already on GitHub? # 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