Skip to content

Commit

Permalink
Merge pull request #104 from Max-Leopold/introduce-valhalla-costing-o…
Browse files Browse the repository at this point in the history
…ptions

Introduce Valhalla costing options
  • Loading branch information
ianthetechie committed May 12, 2024
2 parents 392b264 + fad3c16 commit c730df7
Show file tree
Hide file tree
Showing 18 changed files with 373 additions and 52 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ if useLocalFramework {
path: "./common/target/ios/libferrostar-rs.xcframework"
)
} else {
let releaseTag = "0.0.31"
let releaseTag = "0.0.32"
let releaseChecksum = "a21146211e43922850a287b7cb713e3515d9b4f3863c2a1066a3f4d8af1d302c"
binaryTarget = .binaryTarget(
name: "FerrostarCoreRS",
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ publishing {

allprojects {
group = "com.stadiamaps.ferrostar"
version = "0.0.31"
version = "0.0.32"
}
1 change: 1 addition & 0 deletions android/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.moshi:moshi:1.15.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ class ValhallaCoreTest {
MockInterceptor().apply {
rule(post, url eq valhallaEndpointUrl) { respond(simpleRoute, MEDIATYPE_JSON) }

rule(get) { respond { throw IllegalStateException("an IO error") } }
rule(get) { respond { throw IllegalStateException("Expected only one request") } }
}
val core =
FerrostarCore(
Expand Down Expand Up @@ -276,4 +276,35 @@ class ValhallaCoreTest {
routes.first().geometry)
}
}

@Test
fun valhallaRequestWithCostingOptions(): TestResult {
val interceptor =
MockInterceptor().apply {
rule(post, url eq valhallaEndpointUrl) { respond(simpleRoute, MEDIATYPE_JSON) }

rule(get) { respond { throw IllegalStateException("Expected only one request") } }
}
val core =
FerrostarCore(
valhallaEndpointURL = URL(valhallaEndpointUrl),
profile = "auto",
httpClient = OkHttpClient.Builder().addInterceptor(interceptor).build(),
locationProvider = SimulatedLocationProvider(),
costingOptions = mapOf("auto" to mapOf("useTolls" to 0)))

return runTest {
val routes =
core.getRoutes(
UserLocation(
GeographicCoordinate(60.5347155, -149.543469), 12.0, null, Instant.now(), null),
waypoints =
listOf(
Waypoint(
coordinate = GeographicCoordinate(60.5349908, -149.5485806),
kind = WaypointKind.BREAK)))

assertEquals(routes.count(), 1)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.stadiamaps.ferrostar.core

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import java.net.URL
import java.util.concurrent.Executors
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -28,6 +31,10 @@ data class FerrostarCoreState(
val isCalculatingNewRoute: Boolean
)

private val moshi: Moshi = Moshi.Builder().build()
@OptIn(ExperimentalStdlibApi::class)
private val jsonAdapter: JsonAdapter<Map<String, Any>> = moshi.adapter<Map<String, Any>>()

/**
* This is the entrypoint for end users of Ferrostar on Android, and is responsible for "driving"
* the navigation with location updates and other events.
Expand Down Expand Up @@ -107,9 +114,11 @@ 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, jsonAdapter.toJson(costingOptions))),
httpClient,
locationProvider,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class MainActivity : ComponentActivity(), AndroidTtsStatusListener {
profile = "bicycle",
httpClient = httpClient,
locationProvider = locationProvider,
)
costingOptions = mapOf("bicycle" to mapOf("use_roads" to 0.2)))

private lateinit var ttsObserver: AndroidTtsObserver

Expand Down
7 changes: 4 additions & 3 deletions apple/DemoApp/Demo/DemoNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ struct DemoNavigationView: View {
let simulated = SimulatedLocationProvider(location: initialLocation)
simulated.warpFactor = 2
locationProvider = simulated
ferrostarCore = FerrostarCore(
ferrostarCore = try! FerrostarCore(
valhallaEndpointUrl: URL(
string: "https://api.stadiamaps.com/route/v1?api_key=\(APIKeys.shared.stadiaMapsAPIKey)"
)!,
profile: "pedestrian",
locationProvider: locationProvider
profile: "bicycle",
locationProvider: locationProvider,
costingOptions: ["bicycle": ["use_roads": 0.2]]
)
// NOTE: Not all applications will need a delegate. Read the NavigationDelegate documentation for details.
ferrostarCore.delegate = navigationDelegate
Expand Down
15 changes: 12 additions & 3 deletions apple/Sources/FerrostarCore/FerrostarCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,20 @@ public protocol FerrostarCoreDelegate: AnyObject {
valhallaEndpointUrl: URL,
profile: String,
locationProvider: LocationProviding,
costingOptions: [String: Any] = [:],
networkSession: URLRequestLoading = URLSession.shared
) {
let adapter = RouteAdapter.newValhallaHttp(
) throws {
guard let jsonCostingOptions = try String(
data: JSONSerialization.data(withJSONObject: costingOptions),
encoding: .utf8
) else {
throw InstantiationError.JsonError
}

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

public static func newValhallaHttp(endpointUrl: String, profile: String) -> RouteAdapter {
RouteAdapter(unsafeFromRawPointer: try! rustCall {
public static func newValhallaHttp(endpointUrl: String, profile: String,
costingOptionsJson: String?) throws -> RouteAdapter
{
try RouteAdapter(unsafeFromRawPointer: rustCallWithError(FfiConverterTypeInstantiationError.lift) {
uniffi_ferrostar_fn_constructor_routeadapter_new_valhalla_http(
FfiConverterString.lower(endpointUrl),
FfiConverterString.lower(profile), $0
FfiConverterString.lower(profile),
FfiConverterOptionString.lower(costingOptionsJson), $0
)
})
}
Expand Down Expand Up @@ -2470,6 +2473,38 @@ public func FfiConverterTypeWaypoint_lower(_ value: Waypoint) -> RustBuffer {
FfiConverterTypeWaypoint.lower(value)
}

public enum InstantiationError {
case JsonError

fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error {
try FfiConverterTypeInstantiationError.lift(error)
}
}

public struct FfiConverterTypeInstantiationError: FfiConverterRustBuffer {
typealias SwiftType = InstantiationError

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

default: throw UniffiInternalError.unexpectedEnumCase
}
}

public static func write(_ value: InstantiationError, into buf: inout [UInt8]) {
switch value {
case .JsonError:
writeInt(&buf, Int32(1))
}
}
}

extension InstantiationError: Equatable, Hashable {}

extension InstantiationError: Error {}

// 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 @@ -3726,12 +3761,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 {
try! FfiConverterTypeRouteRequestGenerator.lift(
try! rustCall {
public func createValhallaRequestGenerator(endpointUrl: String, profile: String,
costingOptionsJson: String?) throws -> RouteRequestGenerator
{
try FfiConverterTypeRouteRequestGenerator.lift(
rustCallWithError(FfiConverterTypeInstantiationError.lift) {
uniffi_ferrostar_fn_func_create_valhalla_request_generator(
FfiConverterString.lower(endpointUrl),
FfiConverterString.lower(profile), $0
FfiConverterString.lower(profile),
FfiConverterOptionString.lower(costingOptionsJson), $0
)
}
)
Expand Down Expand Up @@ -3828,7 +3866,7 @@ private var initializationResult: InitializationResult {
if uniffi_ferrostar_checksum_func_create_osrm_response_parser() != 46856 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_func_create_valhalla_request_generator() != 27528 {
if uniffi_ferrostar_checksum_func_create_valhalla_request_generator() != 24001 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ferrostar_checksum_func_get_route_polyline() != 53320 {
Expand Down Expand Up @@ -3873,7 +3911,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() != 22565 {
return InitializationResult.apiChecksumMismatch
}

Expand Down
33 changes: 33 additions & 0 deletions apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,39 @@ final class FerrostarCoreTests: XCTestCase {
assertSnapshot(of: routes, as: .dump)
}

@MainActor
func testValhalalCostingOptionsJSON() async throws {
let mockSession = MockURLSession()
mockSession.registerMock(
forURL: valhallaEndpointUrl,
withData: sampleRouteData,
andResponse: successfulJSONResponse
)

// The main feature of this test is that it uses this constructor,
// which can throw, and similarly getRoutes may not always work with invalid input
let core = try FerrostarCore(
valhallaEndpointUrl: valhallaEndpointUrl,
profile: "low_speed_vehicle",
locationProvider: SimulatedLocationProvider(),
costingOptions: ["low_speed_vehicle": ["vehicle_type": "golf_cart"]],
networkSession: mockSession
)

// Tests that the core generates a request and then the mocked parser returns the expected routes
let routes = try await core.getRoutes(
initialLocation: UserLocation(
coordinates: GeographicCoordinate(lat: 60.5347155, lng: -149.543469),
horizontalAccuracy: 0,
courseOverGround: nil,
timestamp: Date(),
speed: nil
),
waypoints: [Waypoint(coordinate: GeographicCoordinate(lat: 60.5349908, lng: -149.5485806), kind: .break)]
)
assertSnapshot(of: routes, as: .dump)
}

@MainActor
func testCustomRouteProvider() async throws {
let expectation = expectation(description: "The custom route provider should be called once")
Expand Down
2 changes: 1 addition & 1 deletion apple/Tests/FerrostarCoreTests/ValhallaCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class ValhallaCoreTests: XCTestCase {
andResponse: successfulJSONResponse
)

let core = FerrostarCore(
let core = try FerrostarCore(
valhallaEndpointUrl: valhallaEndpointUrl,
profile: "auto",
locationProvider: SimulatedLocationProvider(),
Expand Down
Loading

0 comments on commit c730df7

Please sign in to comment.