From dfaec1ab1ece92afbd09091cf8f49bd7235104de Mon Sep 17 00:00:00 2001 From: Ian Wagner Date: Mon, 20 Nov 2023 04:15:21 +0900 Subject: [PATCH] Add spoken instructions to Route model; cleanup+refactoring --- .../ferrostar/maplibreui/BannerView.kt | 6 +- .../ferrostar/core/FerrostarCoreTest.kt | 8 +- .../ferrostar/core/ValhallaCoreTest.kt | 26 +- .../ferrostar/core/FerrostarCore.kt | 4 +- .../com/stadiamaps/ferrostar/core/Location.kt | 10 +- .../core/SimulatedLocationProviderTest.kt | 6 +- .../CoreLocation Extensions.swift | 6 +- .../FerrostarCore/ObservableState.swift | 4 +- .../FerrostarMapLibreUI/BannerView.swift | 6 +- apple/Sources/UniFFI/ferrostar.swift | 260 ++++++++++-------- .../FerrostarCoreTests.swift | 6 +- common/ferrostar/src/models.rs | 32 +-- .../src/navigation_controller/algorithms.rs | 11 +- .../src/navigation_controller/mod.rs | 4 - .../src/navigation_controller/models.rs | 6 +- common/ferrostar/src/routing_adapters/mod.rs | 7 +- .../src/routing_adapters/osrm/mod.rs | 23 +- .../src/routing_adapters/osrm/models.rs | 17 +- ...ers__osrm__tests__parse_valhalla_osrm.snap | 92 +++++++ .../src/routing_adapters/valhalla.rs | 16 +- 20 files changed, 345 insertions(+), 205 deletions(-) diff --git a/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/BannerView.kt b/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/BannerView.kt index db520fc7..b842a128 100644 --- a/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/BannerView.kt +++ b/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/BannerView.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.unit.sp import uniffi.ferrostar.ManeuverModifier import uniffi.ferrostar.ManeuverType import uniffi.ferrostar.VisualInstructionContent -import uniffi.ferrostar.VisualInstructions +import uniffi.ferrostar.VisualInstruction val VisualInstructionContent.maneuverIcon: ImageVector? get() { @@ -47,7 +47,7 @@ val VisualInstructionContent.maneuverIcon: ImageVector? @Composable -fun BannerView(instructions: VisualInstructions, distanceToNextManeuver: Double?) { +fun BannerView(instructions: VisualInstruction, distanceToNextManeuver: Double?) { Column( modifier = Modifier .fillMaxWidth() @@ -85,7 +85,7 @@ fun BannerView(instructions: VisualInstructions, distanceToNextManeuver: Double? @Preview() @Composable fun PreviewBannerView() { - val instructions = VisualInstructions( + val instructions = VisualInstruction( primaryContent = VisualInstructionContent( text = "Hyde Street", maneuverType = ManeuverType.TURN, diff --git a/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/FerrostarCoreTest.kt b/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/FerrostarCoreTest.kt index daeedfc3..8856be92 100644 --- a/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/FerrostarCoreTest.kt +++ b/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/FerrostarCoreTest.kt @@ -14,7 +14,7 @@ import okhttp3.mock.url import org.junit.Assert.assertEquals import org.junit.Assert.fail import org.junit.Test -import uniffi.ferrostar.GeographicCoordinates +import uniffi.ferrostar.GeographicCoordinate import uniffi.ferrostar.Route import uniffi.ferrostar.RouteAdapter import uniffi.ferrostar.RouteRequest @@ -30,7 +30,7 @@ private val valhallaEndpointUrl = "https://api.stadiamaps.com/navigate/v1" class MockRouteRequestGenerator: RouteRequestGenerator { override fun generateRequest( userLocation: UserLocation, - waypoints: List + waypoints: List ): RouteRequest = RouteRequest.HttpPost(valhallaEndpointUrl, mapOf(), byteArrayOf()) } @@ -69,8 +69,8 @@ class FerrostarCoreTest { try { // Tests that the core generates a request and attempts to process it, but throws due to the mocked network layer core.getRoutes( - initialLocation = UserLocation(coordinates = GeographicCoordinates(-149.543469, 60.5347155), 0.0, null, Instant.now()), - waypoints = listOf(GeographicCoordinates(-149.5485806, 60.5349908)) + initialLocation = UserLocation(coordinates = GeographicCoordinate(-149.543469, 60.5347155), 0.0, null, Instant.now()), + waypoints = listOf(GeographicCoordinate(-149.5485806, 60.5349908)) ) fail("Expected the request to fail") } catch (e: InvalidStatusCodeException) { diff --git a/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/ValhallaCoreTest.kt b/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/ValhallaCoreTest.kt index e13143ea..38da16fb 100644 --- a/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/ValhallaCoreTest.kt +++ b/android/core/src/androidTest/java/com/stadiamaps/ferrostar/core/ValhallaCoreTest.kt @@ -22,7 +22,7 @@ import okhttp3.mock.rule import okhttp3.mock.url import org.junit.Assert.assertEquals import org.junit.Test -import uniffi.ferrostar.GeographicCoordinates +import uniffi.ferrostar.GeographicCoordinate import uniffi.ferrostar.UserLocation import java.net.URL import java.time.Instant @@ -254,41 +254,41 @@ class ValhallaCoreTest { return runTest { val routes = core.getRoutes( UserLocation( - GeographicCoordinates(60.5347155, -149.543469), 12.0, null, Instant.now() - ), waypoints = listOf(GeographicCoordinates(60.5349908, -149.5485806)) + GeographicCoordinate(60.5347155, -149.543469), 12.0, null, Instant.now() + ), waypoints = listOf(GeographicCoordinate(60.5349908, -149.5485806)) ) assertEquals(routes.count(), 1) assertEquals( listOf( - GeographicCoordinates( + GeographicCoordinate( -149.543469, 60.534716 ), - GeographicCoordinates( + GeographicCoordinate( -149.543879, 60.534782 ), - GeographicCoordinates( + GeographicCoordinate( -149.544134, 60.534829 ), - GeographicCoordinates( + GeographicCoordinate( -149.5443, 60.534856 ), - GeographicCoordinates( + GeographicCoordinate( -149.544533, 60.534887 ), - GeographicCoordinates( + GeographicCoordinate( -149.544976, 60.534941 ), - GeographicCoordinates( + GeographicCoordinate( -149.545485, 60.534971 ), - GeographicCoordinates( + GeographicCoordinate( -149.546177, 60.535003 ), - GeographicCoordinates( + GeographicCoordinate( -149.546937, 60.535008 ), - GeographicCoordinates( + GeographicCoordinate( -149.548581, 60.534991 ), ), routes.first().geometry diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt index 5ceb834b..16bba071 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt @@ -3,7 +3,7 @@ package com.stadiamaps.ferrostar.core import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody -import uniffi.ferrostar.GeographicCoordinates +import uniffi.ferrostar.GeographicCoordinate import uniffi.ferrostar.NavigationController import uniffi.ferrostar.NavigationControllerConfig import uniffi.ferrostar.Route @@ -46,7 +46,7 @@ public class FerrostarCore( ) suspend fun getRoutes( - initialLocation: UserLocation, waypoints: List + initialLocation: UserLocation, waypoints: List ): List { when (val request = routeAdapter.generateRequest(initialLocation, waypoints)) { is RouteRequest.HttpPost -> { diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/Location.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/Location.kt index d0187260..1256e3ac 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/Location.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/Location.kt @@ -1,13 +1,13 @@ package com.stadiamaps.ferrostar.core import uniffi.ferrostar.CourseOverGround -import uniffi.ferrostar.GeographicCoordinates +import uniffi.ferrostar.GeographicCoordinate import uniffi.ferrostar.UserLocation import java.time.Instant import java.util.concurrent.Executor interface Location { - val coordinates: GeographicCoordinates + val coordinates: GeographicCoordinate val horizontalAccuracy: Double val courseOverGround: CourseOverGround? val timestamp: Instant @@ -21,7 +21,7 @@ interface Location { } data class SimulatedLocation( - override val coordinates: GeographicCoordinates, + override val coordinates: GeographicCoordinate, override val horizontalAccuracy: Double, override val courseOverGround: CourseOverGround?, override val timestamp: Instant @@ -29,13 +29,13 @@ data class SimulatedLocation( // TODO: Decide if we want to have a compile-time dependency on Android data class AndroidLocation( - override val coordinates: GeographicCoordinates, + override val coordinates: GeographicCoordinate, override val horizontalAccuracy: Double, override val courseOverGround: CourseOverGround?, override val timestamp: Instant ) : Location { constructor(location: android.location.Location) : this( - GeographicCoordinates(location.latitude, location.longitude), + GeographicCoordinate(location.latitude, location.longitude), location.accuracy.toDouble(), if (location.hasBearing() && location.hasBearingAccuracy()) { CourseOverGround( diff --git a/android/core/src/test/java/com/stadiamaps/ferrostar/core/SimulatedLocationProviderTest.kt b/android/core/src/test/java/com/stadiamaps/ferrostar/core/SimulatedLocationProviderTest.kt index d58f082a..84399b0a 100644 --- a/android/core/src/test/java/com/stadiamaps/ferrostar/core/SimulatedLocationProviderTest.kt +++ b/android/core/src/test/java/com/stadiamaps/ferrostar/core/SimulatedLocationProviderTest.kt @@ -3,7 +3,7 @@ package com.stadiamaps.ferrostar.core import org.junit.Test import org.junit.Assert.* -import uniffi.ferrostar.GeographicCoordinates +import uniffi.ferrostar.GeographicCoordinate import java.time.Instant import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors @@ -21,7 +21,7 @@ class SimulatedLocationProviderTest { @Test fun `set location`() { val locationProvider = SimulatedLocationProvider() - val location = SimulatedLocation(GeographicCoordinates(42.02, 24.0), 12.0, null, Instant.now()) + val location = SimulatedLocation(GeographicCoordinate(42.02, 24.0), 12.0, null, Instant.now()) locationProvider.lastLocation = location @@ -32,7 +32,7 @@ class SimulatedLocationProviderTest { fun `test listener events`() { val latch = CountDownLatch(1) val locationProvider = SimulatedLocationProvider() - val location = SimulatedLocation(GeographicCoordinates(42.02, 24.0), 12.0, null, Instant.now()) + val location = SimulatedLocation(GeographicCoordinate(42.02, 24.0), 12.0, null, Instant.now()) val listener = object : LocationUpdateListener { override fun onLocationUpdated(location: Location) { diff --git a/apple/Sources/FerrostarCore/CoreLocation Extensions.swift b/apple/Sources/FerrostarCore/CoreLocation Extensions.swift index 45320a8d..e5875f25 100644 --- a/apple/Sources/FerrostarCore/CoreLocation Extensions.swift +++ b/apple/Sources/FerrostarCore/CoreLocation Extensions.swift @@ -2,11 +2,11 @@ import CoreLocation import UniFFI extension CLLocationCoordinate2D { - var geographicCoordinates: UniFFI.GeographicCoordinates { - UniFFI.GeographicCoordinates(lng: longitude, lat: latitude) + var geographicCoordinates: UniFFI.GeographicCoordinate { + UniFFI.GeographicCoordinate(lng: longitude, lat: latitude) } - init(geographicCoordinates: GeographicCoordinates) { + init(geographicCoordinates: GeographicCoordinate) { self.init(latitude: geographicCoordinates.lat, longitude: geographicCoordinates.lng) } } diff --git a/apple/Sources/FerrostarCore/ObservableState.swift b/apple/Sources/FerrostarCore/ObservableState.swift index e880b0d9..e55b4d7e 100644 --- a/apple/Sources/FerrostarCore/ObservableState.swift +++ b/apple/Sources/FerrostarCore/ObservableState.swift @@ -13,7 +13,7 @@ public final class FerrostarObservableState: ObservableObject { @Published public internal(set) var fullRouteShape: [CLLocationCoordinate2D] @Published public internal(set) var remainingWaypoints: [CLLocationCoordinate2D] @Published public internal(set) var currentStep: UniFFI.RouteStep - @Published public internal(set) var visualInstructions: UniFFI.VisualInstructions? + @Published public internal(set) var visualInstructions: UniFFI.VisualInstruction? @Published public internal(set) var spokenInstruction: UniFFI.SpokenInstruction? @Published public internal(set) var distanceToNextManeuver: CLLocationDistance? @@ -32,7 +32,7 @@ public final class FerrostarObservableState: ObservableObject { let remainingWaypoints = Array(samplePedestrianWaypoints.dropFirst(n)) let lastUserLocation = remainingWaypoints.first! - let result = FerrostarObservableState(snappedLocation: CLLocation(latitude: samplePedestrianWaypoints.first!.latitude, longitude: samplePedestrianWaypoints.first!.longitude), fullRoute: samplePedestrianWaypoints, steps: [UniFFI.RouteStep(geometry: [lastUserLocation.geographicCoordinates], distance: 100, roadName: "Jefferson St.", instruction: "Walk west on Jefferson St.", visualInstructions: [UniFFI.VisualInstructions(primaryContent: VisualInstructionContent(text: "Hyde Street", maneuverType: .turn, maneuverModifier: .left, roundaboutExitDegrees: nil), secondaryContent: nil, triggerDistanceBeforeManeuver: 42.0)])]) + let result = FerrostarObservableState(snappedLocation: CLLocation(latitude: samplePedestrianWaypoints.first!.latitude, longitude: samplePedestrianWaypoints.first!.longitude), fullRoute: samplePedestrianWaypoints, steps: [UniFFI.RouteStep(geometry: [lastUserLocation.geographicCoordinates], distance: 100, roadName: "Jefferson St.", instruction: "Walk west on Jefferson St.", visualInstructions: [UniFFI.VisualInstruction(primaryContent: VisualInstructionContent(text: "Hyde Street", maneuverType: .turn, maneuverModifier: .left, roundaboutExitDegrees: nil), secondaryContent: nil, triggerDistanceBeforeManeuver: 42.0)], spokenInstructions: [])]) result.remainingWaypoints = remainingWaypoints result.snappedLocation = CLLocation(latitude: lastUserLocation.latitude, longitude: lastUserLocation.longitude) diff --git a/apple/Sources/FerrostarMapLibreUI/BannerView.swift b/apple/Sources/FerrostarMapLibreUI/BannerView.swift index cd37554a..09352d9e 100644 --- a/apple/Sources/FerrostarMapLibreUI/BannerView.swift +++ b/apple/Sources/FerrostarMapLibreUI/BannerView.swift @@ -45,7 +45,7 @@ extension UniFFI.VisualInstructionContent { } struct BannerView: View { - let instructions: VisualInstructions + let instructions: VisualInstruction let distanceToNextManeuver: CLLocationDistance? let formatter = MKDistanceFormatter() @@ -80,8 +80,8 @@ struct BannerView: View { } #Preview { - let location = GeographicCoordinates(lng: 0, lat: 0) - let instructions = UniFFI.VisualInstructions(primaryContent: VisualInstructionContent(text: "Hyde Street", maneuverType: .turn, maneuverModifier: .left, roundaboutExitDegrees: nil), secondaryContent: nil, triggerDistanceBeforeManeuver: 42.0) + let location = GeographicCoordinate(lng: 0, lat: 0) + let instructions = UniFFI.VisualInstruction(primaryContent: VisualInstructionContent(text: "Hyde Street", maneuverType: .turn, maneuverModifier: .left, roundaboutExitDegrees: nil), secondaryContent: nil, triggerDistanceBeforeManeuver: 42.0) return BannerView(instructions: instructions, distanceToNextManeuver: 42) } diff --git a/apple/Sources/UniFFI/ferrostar.swift b/apple/Sources/UniFFI/ferrostar.swift index 51d60365..6f0bf339 100644 --- a/apple/Sources/UniFFI/ferrostar.swift +++ b/apple/Sources/UniFFI/ferrostar.swift @@ -515,7 +515,7 @@ public func FfiConverterTypeNavigationController_lower(_ value: NavigationContro } public protocol RouteAdapterProtocol: AnyObject { - func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinates]) throws -> RouteRequest + func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinate]) throws -> RouteRequest func parseResponse(response: Data) throws -> [Route] } @@ -553,12 +553,12 @@ public class RouteAdapter: }) } - public func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinates]) throws -> RouteRequest { + public func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinate]) throws -> RouteRequest { return try FfiConverterTypeRouteRequest.lift( rustCallWithError(FfiConverterTypeRoutingRequestGenerationError.lift) { uniffi_ferrostar_fn_method_routeadapter_generate_request(self.pointer, FfiConverterTypeUserLocation.lower(userLocation), - FfiConverterSequenceTypeGeographicCoordinates.lower(waypoints), $0) + FfiConverterSequenceTypeGeographicCoordinate.lower(waypoints), $0) } ) } @@ -612,7 +612,7 @@ public func FfiConverterTypeRouteAdapter_lower(_ value: RouteAdapter) -> UnsafeM } public protocol RouteRequestGenerator: AnyObject { - func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinates]) throws -> RouteRequest + func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinate]) throws -> RouteRequest } public class RouteRequestGeneratorImpl: @@ -631,12 +631,12 @@ public class RouteRequestGeneratorImpl: try! rustCall { uniffi_ferrostar_fn_free_routerequestgenerator(pointer, $0) } } - public func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinates]) throws -> RouteRequest { + public func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinate]) throws -> RouteRequest { return try FfiConverterTypeRouteRequest.lift( rustCallWithError(FfiConverterTypeRoutingRequestGenerationError.lift) { uniffi_ferrostar_fn_method_routerequestgenerator_generate_request(self.pointer, FfiConverterTypeUserLocation.lower(userLocation), - FfiConverterSequenceTypeGeographicCoordinates.lower(waypoints), $0) + FfiConverterSequenceTypeGeographicCoordinate.lower(waypoints), $0) } ) } @@ -716,7 +716,7 @@ private let uniffiCallbackInterfaceRouteRequestGenerator: ForeignCallback = { (h func makeCall() throws -> Int32 { let result = try swiftCallbackInterface.generateRequest( userLocation: FfiConverterTypeUserLocation.read(from: &reader), - waypoints: FfiConverterSequenceTypeGeographicCoordinates.read(from: &reader) + waypoints: FfiConverterSequenceTypeGeographicCoordinate.read(from: &reader) ) var writer = [UInt8]() FfiConverterTypeRouteRequest.write(result, into: &writer) @@ -986,7 +986,7 @@ public func FfiConverterTypeCourseOverGround_lower(_ value: CourseOverGround) -> return FfiConverterTypeCourseOverGround.lower(value) } -public struct GeographicCoordinates { +public struct GeographicCoordinate { public var lng: Double public var lat: Double @@ -998,8 +998,8 @@ public struct GeographicCoordinates { } } -extension GeographicCoordinates: Equatable, Hashable { - public static func == (lhs: GeographicCoordinates, rhs: GeographicCoordinates) -> Bool { +extension GeographicCoordinate: Equatable, Hashable { + public static func == (lhs: GeographicCoordinate, rhs: GeographicCoordinate) -> Bool { if lhs.lng != rhs.lng { return false } @@ -1015,27 +1015,27 @@ extension GeographicCoordinates: Equatable, Hashable { } } -public struct FfiConverterTypeGeographicCoordinates: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> GeographicCoordinates { +public struct FfiConverterTypeGeographicCoordinate: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> GeographicCoordinate { return - try GeographicCoordinates( + try GeographicCoordinate( lng: FfiConverterDouble.read(from: &buf), lat: FfiConverterDouble.read(from: &buf) ) } - public static func write(_ value: GeographicCoordinates, into buf: inout [UInt8]) { + public static func write(_ value: GeographicCoordinate, into buf: inout [UInt8]) { FfiConverterDouble.write(value.lng, into: &buf) FfiConverterDouble.write(value.lat, into: &buf) } } -public func FfiConverterTypeGeographicCoordinates_lift(_ buf: RustBuffer) throws -> GeographicCoordinates { - return try FfiConverterTypeGeographicCoordinates.lift(buf) +public func FfiConverterTypeGeographicCoordinate_lift(_ buf: RustBuffer) throws -> GeographicCoordinate { + return try FfiConverterTypeGeographicCoordinate.lift(buf) } -public func FfiConverterTypeGeographicCoordinates_lower(_ value: GeographicCoordinates) -> RustBuffer { - return FfiConverterTypeGeographicCoordinates.lower(value) +public func FfiConverterTypeGeographicCoordinate_lower(_ value: GeographicCoordinate) -> RustBuffer { + return FfiConverterTypeGeographicCoordinate.lower(value) } public struct NavigationControllerConfig { @@ -1083,14 +1083,14 @@ public func FfiConverterTypeNavigationControllerConfig_lower(_ value: Navigation } public struct Route { - public var geometry: [GeographicCoordinates] + public var geometry: [GeographicCoordinate] public var distance: Double - public var waypoints: [GeographicCoordinates] + public var waypoints: [GeographicCoordinate] public var steps: [RouteStep] // Default memberwise initializers are never public by default, so we // declare one manually. - public init(geometry: [GeographicCoordinates], distance: Double, waypoints: [GeographicCoordinates], steps: [RouteStep]) { + public init(geometry: [GeographicCoordinate], distance: Double, waypoints: [GeographicCoordinate], steps: [RouteStep]) { self.geometry = geometry self.distance = distance self.waypoints = waypoints @@ -1127,17 +1127,17 @@ public struct FfiConverterTypeRoute: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Route { return try Route( - geometry: FfiConverterSequenceTypeGeographicCoordinates.read(from: &buf), + geometry: FfiConverterSequenceTypeGeographicCoordinate.read(from: &buf), distance: FfiConverterDouble.read(from: &buf), - waypoints: FfiConverterSequenceTypeGeographicCoordinates.read(from: &buf), + waypoints: FfiConverterSequenceTypeGeographicCoordinate.read(from: &buf), steps: FfiConverterSequenceTypeRouteStep.read(from: &buf) ) } public static func write(_ value: Route, into buf: inout [UInt8]) { - FfiConverterSequenceTypeGeographicCoordinates.write(value.geometry, into: &buf) + FfiConverterSequenceTypeGeographicCoordinate.write(value.geometry, into: &buf) FfiConverterDouble.write(value.distance, into: &buf) - FfiConverterSequenceTypeGeographicCoordinates.write(value.waypoints, into: &buf) + FfiConverterSequenceTypeGeographicCoordinate.write(value.waypoints, into: &buf) FfiConverterSequenceTypeRouteStep.write(value.steps, into: &buf) } } @@ -1151,20 +1151,22 @@ public func FfiConverterTypeRoute_lower(_ value: Route) -> RustBuffer { } public struct RouteStep { - public var geometry: [GeographicCoordinates] + public var geometry: [GeographicCoordinate] public var distance: Double public var roadName: String? public var instruction: String - public var visualInstructions: [VisualInstructions] + public var visualInstructions: [VisualInstruction] + public var spokenInstructions: [SpokenInstruction] // Default memberwise initializers are never public by default, so we // declare one manually. - public init(geometry: [GeographicCoordinates], distance: Double, roadName: String?, instruction: String, visualInstructions: [VisualInstructions]) { + public init(geometry: [GeographicCoordinate], distance: Double, roadName: String?, instruction: String, visualInstructions: [VisualInstruction], spokenInstructions: [SpokenInstruction]) { self.geometry = geometry self.distance = distance self.roadName = roadName self.instruction = instruction self.visualInstructions = visualInstructions + self.spokenInstructions = spokenInstructions } } @@ -1185,6 +1187,9 @@ extension RouteStep: Equatable, Hashable { if lhs.visualInstructions != rhs.visualInstructions { return false } + if lhs.spokenInstructions != rhs.spokenInstructions { + return false + } return true } @@ -1194,6 +1199,7 @@ extension RouteStep: Equatable, Hashable { hasher.combine(roadName) hasher.combine(instruction) hasher.combine(visualInstructions) + hasher.combine(spokenInstructions) } } @@ -1201,20 +1207,22 @@ public struct FfiConverterTypeRouteStep: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RouteStep { return try RouteStep( - geometry: FfiConverterSequenceTypeGeographicCoordinates.read(from: &buf), + geometry: FfiConverterSequenceTypeGeographicCoordinate.read(from: &buf), distance: FfiConverterDouble.read(from: &buf), roadName: FfiConverterOptionString.read(from: &buf), instruction: FfiConverterString.read(from: &buf), - visualInstructions: FfiConverterSequenceTypeVisualInstructions.read(from: &buf) + visualInstructions: FfiConverterSequenceTypeVisualInstruction.read(from: &buf), + spokenInstructions: FfiConverterSequenceTypeSpokenInstruction.read(from: &buf) ) } public static func write(_ value: RouteStep, into buf: inout [UInt8]) { - FfiConverterSequenceTypeGeographicCoordinates.write(value.geometry, into: &buf) + FfiConverterSequenceTypeGeographicCoordinate.write(value.geometry, into: &buf) FfiConverterDouble.write(value.distance, into: &buf) FfiConverterOptionString.write(value.roadName, into: &buf) FfiConverterString.write(value.instruction, into: &buf) - FfiConverterSequenceTypeVisualInstructions.write(value.visualInstructions, into: &buf) + FfiConverterSequenceTypeVisualInstruction.write(value.visualInstructions, into: &buf) + FfiConverterSequenceTypeSpokenInstruction.write(value.spokenInstructions, into: &buf) } } @@ -1287,14 +1295,14 @@ public func FfiConverterTypeSpokenInstruction_lower(_ value: SpokenInstruction) } public struct UserLocation { - public var coordinates: GeographicCoordinates + public var coordinates: GeographicCoordinate public var horizontalAccuracy: Double public var courseOverGround: CourseOverGround? public var timestamp: Date // Default memberwise initializers are never public by default, so we // declare one manually. - public init(coordinates: GeographicCoordinates, horizontalAccuracy: Double, courseOverGround: CourseOverGround?, timestamp: Date) { + public init(coordinates: GeographicCoordinate, horizontalAccuracy: Double, courseOverGround: CourseOverGround?, timestamp: Date) { self.coordinates = coordinates self.horizontalAccuracy = horizontalAccuracy self.courseOverGround = courseOverGround @@ -1331,7 +1339,7 @@ public struct FfiConverterTypeUserLocation: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UserLocation { return try UserLocation( - coordinates: FfiConverterTypeGeographicCoordinates.read(from: &buf), + coordinates: FfiConverterTypeGeographicCoordinate.read(from: &buf), horizontalAccuracy: FfiConverterDouble.read(from: &buf), courseOverGround: FfiConverterOptionTypeCourseOverGround.read(from: &buf), timestamp: FfiConverterTimestamp.read(from: &buf) @@ -1339,7 +1347,7 @@ public struct FfiConverterTypeUserLocation: FfiConverterRustBuffer { } public static func write(_ value: UserLocation, into buf: inout [UInt8]) { - FfiConverterTypeGeographicCoordinates.write(value.coordinates, into: &buf) + FfiConverterTypeGeographicCoordinate.write(value.coordinates, into: &buf) FfiConverterDouble.write(value.horizontalAccuracy, into: &buf) FfiConverterOptionTypeCourseOverGround.write(value.courseOverGround, into: &buf) FfiConverterTimestamp.write(value.timestamp, into: &buf) @@ -1354,6 +1362,66 @@ public func FfiConverterTypeUserLocation_lower(_ value: UserLocation) -> RustBuf return FfiConverterTypeUserLocation.lower(value) } +public struct VisualInstruction { + public var primaryContent: VisualInstructionContent + public var secondaryContent: VisualInstructionContent? + public var triggerDistanceBeforeManeuver: Double + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(primaryContent: VisualInstructionContent, secondaryContent: VisualInstructionContent?, triggerDistanceBeforeManeuver: Double) { + self.primaryContent = primaryContent + self.secondaryContent = secondaryContent + self.triggerDistanceBeforeManeuver = triggerDistanceBeforeManeuver + } +} + +extension VisualInstruction: Equatable, Hashable { + public static func == (lhs: VisualInstruction, rhs: VisualInstruction) -> Bool { + if lhs.primaryContent != rhs.primaryContent { + return false + } + if lhs.secondaryContent != rhs.secondaryContent { + return false + } + if lhs.triggerDistanceBeforeManeuver != rhs.triggerDistanceBeforeManeuver { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(primaryContent) + hasher.combine(secondaryContent) + hasher.combine(triggerDistanceBeforeManeuver) + } +} + +public struct FfiConverterTypeVisualInstruction: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> VisualInstruction { + return + try VisualInstruction( + primaryContent: FfiConverterTypeVisualInstructionContent.read(from: &buf), + secondaryContent: FfiConverterOptionTypeVisualInstructionContent.read(from: &buf), + triggerDistanceBeforeManeuver: FfiConverterDouble.read(from: &buf) + ) + } + + public static func write(_ value: VisualInstruction, into buf: inout [UInt8]) { + FfiConverterTypeVisualInstructionContent.write(value.primaryContent, into: &buf) + FfiConverterOptionTypeVisualInstructionContent.write(value.secondaryContent, into: &buf) + FfiConverterDouble.write(value.triggerDistanceBeforeManeuver, into: &buf) + } +} + +public func FfiConverterTypeVisualInstruction_lift(_ buf: RustBuffer) throws -> VisualInstruction { + return try FfiConverterTypeVisualInstruction.lift(buf) +} + +public func FfiConverterTypeVisualInstruction_lower(_ value: VisualInstruction) -> RustBuffer { + return FfiConverterTypeVisualInstruction.lower(value) +} + public struct VisualInstructionContent { public var text: String public var maneuverType: ManeuverType? @@ -1422,66 +1490,6 @@ public func FfiConverterTypeVisualInstructionContent_lower(_ value: VisualInstru return FfiConverterTypeVisualInstructionContent.lower(value) } -public struct VisualInstructions { - public var primaryContent: VisualInstructionContent - public var secondaryContent: VisualInstructionContent? - public var triggerDistanceBeforeManeuver: Double - - // Default memberwise initializers are never public by default, so we - // declare one manually. - public init(primaryContent: VisualInstructionContent, secondaryContent: VisualInstructionContent?, triggerDistanceBeforeManeuver: Double) { - self.primaryContent = primaryContent - self.secondaryContent = secondaryContent - self.triggerDistanceBeforeManeuver = triggerDistanceBeforeManeuver - } -} - -extension VisualInstructions: Equatable, Hashable { - public static func == (lhs: VisualInstructions, rhs: VisualInstructions) -> Bool { - if lhs.primaryContent != rhs.primaryContent { - return false - } - if lhs.secondaryContent != rhs.secondaryContent { - return false - } - if lhs.triggerDistanceBeforeManeuver != rhs.triggerDistanceBeforeManeuver { - return false - } - return true - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(primaryContent) - hasher.combine(secondaryContent) - hasher.combine(triggerDistanceBeforeManeuver) - } -} - -public struct FfiConverterTypeVisualInstructions: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> VisualInstructions { - return - try VisualInstructions( - primaryContent: FfiConverterTypeVisualInstructionContent.read(from: &buf), - secondaryContent: FfiConverterOptionTypeVisualInstructionContent.read(from: &buf), - triggerDistanceBeforeManeuver: FfiConverterDouble.read(from: &buf) - ) - } - - public static func write(_ value: VisualInstructions, into buf: inout [UInt8]) { - FfiConverterTypeVisualInstructionContent.write(value.primaryContent, into: &buf) - FfiConverterOptionTypeVisualInstructionContent.write(value.secondaryContent, into: &buf) - FfiConverterDouble.write(value.triggerDistanceBeforeManeuver, into: &buf) - } -} - -public func FfiConverterTypeVisualInstructions_lift(_ buf: RustBuffer) throws -> VisualInstructions { - return try FfiConverterTypeVisualInstructions.lift(buf) -} - -public func FfiConverterTypeVisualInstructions_lower(_ value: VisualInstructions) -> RustBuffer { - return FfiConverterTypeVisualInstructions.lower(value) -} - // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum ManeuverModifier { @@ -1689,7 +1697,7 @@ extension ManeuverType: Equatable, Hashable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum NavigationStateUpdate { - case navigating(snappedUserLocation: UserLocation, remainingWaypoints: [GeographicCoordinates], currentStep: RouteStep, distanceToNextManeuver: Double) + case navigating(snappedUserLocation: UserLocation, remainingWaypoints: [GeographicCoordinate], currentStep: RouteStep, distanceToNextManeuver: Double) case arrived } @@ -1701,7 +1709,7 @@ public struct FfiConverterTypeNavigationStateUpdate: FfiConverterRustBuffer { switch variant { case 1: return try .navigating( snappedUserLocation: FfiConverterTypeUserLocation.read(from: &buf), - remainingWaypoints: FfiConverterSequenceTypeGeographicCoordinates.read(from: &buf), + remainingWaypoints: FfiConverterSequenceTypeGeographicCoordinate.read(from: &buf), currentStep: FfiConverterTypeRouteStep.read(from: &buf), distanceToNextManeuver: FfiConverterDouble.read(from: &buf) ) @@ -1717,7 +1725,7 @@ public struct FfiConverterTypeNavigationStateUpdate: FfiConverterRustBuffer { case let .navigating(snappedUserLocation, remainingWaypoints, currentStep, distanceToNextManeuver): writeInt(&buf, Int32(1)) FfiConverterTypeUserLocation.write(snappedUserLocation, into: &buf) - FfiConverterSequenceTypeGeographicCoordinates.write(remainingWaypoints, into: &buf) + FfiConverterSequenceTypeGeographicCoordinate.write(remainingWaypoints, into: &buf) FfiConverterTypeRouteStep.write(currentStep, into: &buf) FfiConverterDouble.write(distanceToNextManeuver, into: &buf) @@ -2046,23 +2054,23 @@ private struct FfiConverterOptionTypeManeuverType: FfiConverterRustBuffer { } } -private struct FfiConverterSequenceTypeGeographicCoordinates: FfiConverterRustBuffer { - typealias SwiftType = [GeographicCoordinates] +private struct FfiConverterSequenceTypeGeographicCoordinate: FfiConverterRustBuffer { + typealias SwiftType = [GeographicCoordinate] - public static func write(_ value: [GeographicCoordinates], into buf: inout [UInt8]) { + public static func write(_ value: [GeographicCoordinate], into buf: inout [UInt8]) { let len = Int32(value.count) writeInt(&buf, len) for item in value { - FfiConverterTypeGeographicCoordinates.write(item, into: &buf) + FfiConverterTypeGeographicCoordinate.write(item, into: &buf) } } - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [GeographicCoordinates] { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [GeographicCoordinate] { let len: Int32 = try readInt(&buf) - var seq = [GeographicCoordinates]() + var seq = [GeographicCoordinate]() seq.reserveCapacity(Int(len)) for _ in 0 ..< len { - try seq.append(FfiConverterTypeGeographicCoordinates.read(from: &buf)) + try seq.append(FfiConverterTypeGeographicCoordinate.read(from: &buf)) } return seq } @@ -2112,23 +2120,45 @@ private struct FfiConverterSequenceTypeRouteStep: FfiConverterRustBuffer { } } -private struct FfiConverterSequenceTypeVisualInstructions: FfiConverterRustBuffer { - typealias SwiftType = [VisualInstructions] +private struct FfiConverterSequenceTypeSpokenInstruction: FfiConverterRustBuffer { + typealias SwiftType = [SpokenInstruction] + + public static func write(_ value: [SpokenInstruction], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeSpokenInstruction.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [SpokenInstruction] { + let len: Int32 = try readInt(&buf) + var seq = [SpokenInstruction]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + try seq.append(FfiConverterTypeSpokenInstruction.read(from: &buf)) + } + return seq + } +} + +private struct FfiConverterSequenceTypeVisualInstruction: FfiConverterRustBuffer { + typealias SwiftType = [VisualInstruction] - public static func write(_ value: [VisualInstructions], into buf: inout [UInt8]) { + public static func write(_ value: [VisualInstruction], into buf: inout [UInt8]) { let len = Int32(value.count) writeInt(&buf, len) for item in value { - FfiConverterTypeVisualInstructions.write(item, into: &buf) + FfiConverterTypeVisualInstruction.write(item, into: &buf) } } - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [VisualInstructions] { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [VisualInstruction] { let len: Int32 = try readInt(&buf) - var seq = [VisualInstructions]() + var seq = [VisualInstruction]() seq.reserveCapacity(Int(len)) for _ in 0 ..< len { - try seq.append(FfiConverterTypeVisualInstructions.read(from: &buf)) + try seq.append(FfiConverterTypeVisualInstruction.read(from: &buf)) } return seq } @@ -2206,13 +2236,13 @@ private var initializationResult: InitializationResult { if uniffi_ferrostar_checksum_method_navigationcontroller_update_user_location() != 18868 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_checksum_method_routeadapter_generate_request() != 57178 { + if uniffi_ferrostar_checksum_method_routeadapter_generate_request() != 35986 { return InitializationResult.apiChecksumMismatch } if uniffi_ferrostar_checksum_method_routeadapter_parse_response() != 29808 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_checksum_method_routerequestgenerator_generate_request() != 55969 { + if uniffi_ferrostar_checksum_method_routerequestgenerator_generate_request() != 34771 { return InitializationResult.apiChecksumMismatch } if uniffi_ferrostar_checksum_method_routeresponseparser_parse_response() != 30875 { diff --git a/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift b/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift index 6c021cb3..6204e394 100644 --- a/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift +++ b/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift @@ -15,7 +15,7 @@ let errorResponse = HTTPURLResponse(url: valhallaEndpointUrl, statusCode: 401, h private class MockRouteRequestGenerator: RouteRequestGenerator { - func generateRequest(userLocation: UniFFI.UserLocation, waypoints: [UniFFI.GeographicCoordinates]) throws -> UniFFI.RouteRequest { + func generateRequest(userLocation: UniFFI.UserLocation, waypoints: [UniFFI.GeographicCoordinate]) throws -> UniFFI.RouteRequest { return UniFFI.RouteRequest.httpPost(url: valhallaEndpointUrl.absoluteString, headers: [:], body: Data()) } } @@ -56,9 +56,9 @@ final class FerrostarCoreTests: XCTestCase { let mockSession = MockURLSession() mockSession.registerMock(forURL: valhallaEndpointUrl, withData: Data(), andResponse: successfulJSONResponse) - let geom = [GeographicCoordinates(lng: 0, lat: 0), GeographicCoordinates(lng: 1, lat: 1)] + let geom = [GeographicCoordinate(lng: 0, lat: 0), GeographicCoordinate(lng: 1, lat: 1)] let instructionContent = VisualInstructionContent(text: "Sail straight", maneuverType: .depart, maneuverModifier: .straight, roundaboutExitDegrees: nil) - let mockRoute = UniFFI.Route(geometry: geom, distance: 1, waypoints: geom, steps: [RouteStep(geometry: geom, distance: 1, roadName: "foo road", instruction: "Sail straight", visualInstructions: [VisualInstructions(primaryContent: instructionContent, secondaryContent: nil, triggerDistanceBeforeManeuver: 42)])]) + let mockRoute = UniFFI.Route(geometry: geom, distance: 1, waypoints: geom, steps: [RouteStep(geometry: geom, distance: 1, roadName: "foo road", instruction: "Sail straight", visualInstructions: [VisualInstruction(primaryContent: instructionContent, secondaryContent: nil, triggerDistanceBeforeManeuver: 42)], spokenInstructions: [])]) let routeAdapter = RouteAdapter(requestGenerator: MockRouteRequestGenerator(), responseParser: MockRouteResponseParser(routes: [mockRoute])) diff --git a/common/ferrostar/src/models.rs b/common/ferrostar/src/models.rs index 276f51b7..10ed5c93 100644 --- a/common/ferrostar/src/models.rs +++ b/common/ferrostar/src/models.rs @@ -7,12 +7,12 @@ use serde::Serialize; #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] -pub struct GeographicCoordinates { +pub struct GeographicCoordinate { pub lng: f64, pub lat: f64, } -impl From for GeographicCoordinates { +impl From for GeographicCoordinate { fn from(value: Coord) -> Self { Self { lng: value.x, @@ -21,8 +21,8 @@ impl From for GeographicCoordinates { } } -impl From for Coord { - fn from(value: GeographicCoordinates) -> Self { +impl From for Coord { + fn from(value: GeographicCoordinate) -> Self { Self { x: value.lng, y: value.lat, @@ -30,8 +30,8 @@ impl From for Coord { } } -impl From for Point { - fn from(value: GeographicCoordinates) -> Self { +impl From for Point { + fn from(value: GeographicCoordinate) -> Self { Self(value.into()) } } @@ -58,11 +58,10 @@ impl CourseOverGround { /// which can influence navigation logic and UI. #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, uniffi::Record)] pub struct UserLocation { - pub coordinates: GeographicCoordinates, + pub coordinates: GeographicCoordinate, /// The estimated accuracy of the coordinate (in meters) pub horizontal_accuracy: f64, pub course_over_ground: Option, - // TODO: Decide if we want to include heading in the user location, if/how we should factor it in, and how to handle it on Android pub timestamp: SystemTime, } @@ -79,13 +78,13 @@ impl From for Point { #[derive(Debug, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] pub struct Route { - pub geometry: Vec, + pub geometry: Vec, /// The total route distance, in meters. pub distance: f64, /// The ordered list of waypoints to visit, including the starting point. /// Note that this is distinct from the *geometry* which includes all points visited. /// A waypoint represents a start/end point for a route leg. - pub waypoints: Vec, + pub waypoints: Vec, pub steps: Vec, } @@ -98,13 +97,13 @@ pub struct Route { #[derive(Clone, Debug, PartialEq, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] pub struct RouteStep { - pub geometry: Vec, + pub geometry: Vec, /// The distance, in meters, to travel along the route after the maneuver to reach the next step. pub distance: f64, pub road_name: Option, pub instruction: String, - pub visual_instructions: Vec, - // TODO: Spoken instruction + pub visual_instructions: Vec, + pub spoken_instructions: Vec, } impl RouteStep { @@ -117,9 +116,8 @@ impl RouteStep { } } -// TODO: trigger_at doesn't really have to live in the public interface; figure out if we want to have a separate FFI vs internal type - -#[derive(Debug, PartialEq, uniffi::Record)] +#[derive(Debug, Clone, PartialEq, uniffi::Record)] +#[cfg_attr(test, derive(Serialize))] pub struct SpokenInstruction { /// Plain-text instruction which can be synthesized with a TTS engine. pub text: String, @@ -191,7 +189,7 @@ pub struct VisualInstructionContent { #[derive(Debug, Clone, PartialEq, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] -pub struct VisualInstructions { +pub struct VisualInstruction { pub primary_content: VisualInstructionContent, pub secondary_content: Option, /// How far (in meters) from the upcoming maneuver the instruction should start being displayed diff --git a/common/ferrostar/src/navigation_controller/algorithms.rs b/common/ferrostar/src/navigation_controller/algorithms.rs index 6d987090..a399f4a2 100644 --- a/common/ferrostar/src/navigation_controller/algorithms.rs +++ b/common/ferrostar/src/navigation_controller/algorithms.rs @@ -1,4 +1,4 @@ -use crate::models::{GeographicCoordinates, RouteStep, UserLocation}; +use crate::models::{GeographicCoordinate, RouteStep, UserLocation}; use geo::{ Closest, ClosestPoint, EuclideanDistance, HaversineDistance, HaversineLength, LineLocatePoint, LineString, Point, @@ -20,7 +20,7 @@ pub fn snap_user_location_to_line(location: UserLocation, line: &LineString) -> snap_point_to_line(&original_point, line).map_or_else( || location, |snapped| UserLocation { - coordinates: GeographicCoordinates { + coordinates: GeographicCoordinate { lng: snapped.x(), lat: snapped.y(), }, @@ -194,11 +194,11 @@ pub fn distance_to_end_of_step( fn gen_dummy_route_step(start_lng: f64, start_lat: f64, end_lng: f64, end_lat: f64) -> RouteStep { RouteStep { geometry: vec![ - GeographicCoordinates { + GeographicCoordinate { lng: start_lng, lat: start_lat, }, - GeographicCoordinates { + GeographicCoordinate { lng: end_lng, lat: end_lat, }, @@ -207,6 +207,7 @@ fn gen_dummy_route_step(start_lng: f64, start_lat: f64, end_lng: f64, end_lat: f road_name: None, instruction: "".to_string(), visual_instructions: vec![], + spoken_instructions: vec![], } } @@ -283,7 +284,7 @@ proptest! { // Construct a user location that's slightly offset from the transition point with perfect accuracy let end_of_step = *current_route_step.geometry.last().unwrap(); let user_location = UserLocation { - coordinates: GeographicCoordinates { + coordinates: GeographicCoordinate { lng: end_of_step.lng + error, lat: end_of_step.lat + error, }, diff --git a/common/ferrostar/src/navigation_controller/mod.rs b/common/ferrostar/src/navigation_controller/mod.rs index 842f9827..35632135 100644 --- a/common/ferrostar/src/navigation_controller/mod.rs +++ b/common/ferrostar/src/navigation_controller/mod.rs @@ -196,10 +196,6 @@ impl NavigationController { Some(current_step.clone()) }; - // TODO: Calculate distance to the next step - // Hmm... We don't currently store the LineString for the current step... - // let fraction_along_line = route_linestring.line_locate_point(&point!(x: snapped_user_location.coordinates.lng, y: snapped_user_location.coordinates.lat)); - if let Some(step) = current_step { let distance_to_next_maneuver = distance_to_end_of_step( &snapped_user_location.into(), diff --git a/common/ferrostar/src/navigation_controller/models.rs b/common/ferrostar/src/navigation_controller/models.rs index 104b9846..eedadc49 100644 --- a/common/ferrostar/src/navigation_controller/models.rs +++ b/common/ferrostar/src/navigation_controller/models.rs @@ -1,4 +1,4 @@ -use crate::{GeographicCoordinates, Route, RouteStep, UserLocation}; +use crate::{GeographicCoordinate, Route, RouteStep, UserLocation}; use geo::LineString; /// Internal state of the navigation controller. @@ -12,7 +12,7 @@ pub(super) enum TripState { /// The ordered list of waypoints remaining to visit on this trip. Intermediate waypoints on /// the route to the final destination are discarded as they are visited. /// TODO: Do these need additional details like a name/label? - remaining_waypoints: Vec, + remaining_waypoints: Vec, /// The ordered list of steps that remain in the trip. /// The step at the front of the list is always the current step. /// We currently assume that you cannot move backward to a previous step. @@ -31,7 +31,7 @@ pub enum NavigationStateUpdate { snapped_user_location: UserLocation, /// The ordered list of waypoints remaining to visit on this trip. Intermediate waypoints on /// the route to the final destination are discarded as they are visited. - remaining_waypoints: Vec, + remaining_waypoints: Vec, /// The current/active maneuver. Properties such as the distance will be updated live. current_step: RouteStep, /// The distance remaining till the end of the current step (taking the line geometry diff --git a/common/ferrostar/src/routing_adapters/mod.rs b/common/ferrostar/src/routing_adapters/mod.rs index c8e9e4a7..4d340da0 100644 --- a/common/ferrostar/src/routing_adapters/mod.rs +++ b/common/ferrostar/src/routing_adapters/mod.rs @@ -1,6 +1,6 @@ use crate::{ create_osrm_response_parser, create_valhalla_request_generator, - models::{GeographicCoordinates, Route, UserLocation}, + models::{GeographicCoordinate, Route, UserLocation}, }; use error::{RoutingRequestGenerationError, RoutingResponseParseError}; use std::collections::HashMap; @@ -22,7 +22,6 @@ pub enum RouteRequest { // TODO: Generic case for cases like local/offline route generation or other arbitrary foreign code } -// TODO: Conventions on constructor arguments? Setter methods?? /// A trait describing any object capable of generating [RouteRequest]s. /// /// The interface is intentionally generic. Every routing backend has its own set of @@ -42,7 +41,7 @@ pub trait RouteRequestGenerator: Send + Sync { fn generate_request( &self, user_location: UserLocation, - waypoints: Vec, + waypoints: Vec, ) -> Result; // TODO: "Trace attributes" request method? Maybe in a separate trait? @@ -108,7 +107,7 @@ impl RouteAdapter { pub fn generate_request( &self, user_location: UserLocation, - waypoints: Vec, + waypoints: Vec, ) -> Result { self.request_generator .generate_request(user_location, waypoints) diff --git a/common/ferrostar/src/routing_adapters/osrm/mod.rs b/common/ferrostar/src/routing_adapters/osrm/mod.rs index 50e36775..19729bfc 100644 --- a/common/ferrostar/src/routing_adapters/osrm/mod.rs +++ b/common/ferrostar/src/routing_adapters/osrm/mod.rs @@ -1,9 +1,9 @@ pub(crate) mod models; use super::RouteResponseParser; -use crate::models::{GeographicCoordinates, RouteStep}; +use crate::models::{GeographicCoordinate, RouteStep}; use crate::routing_adapters::{osrm::models::RouteResponse, Route, RoutingResponseParseError}; -use crate::{VisualInstructionContent, VisualInstructions}; +use crate::{VisualInstructionContent, VisualInstruction, SpokenInstruction}; use polyline::decode_polyline; /// A response parser for OSRM-compatible routing backends. @@ -27,7 +27,7 @@ impl RouteResponseParser for OsrmResponseParser { let waypoints: Vec<_> = res .waypoints .iter() - .map(|waypoint| GeographicCoordinates { + .map(|waypoint| GeographicCoordinate { lat: waypoint.location.latitude(), lng: waypoint.location.longitude(), }) @@ -45,7 +45,7 @@ impl RouteResponseParser for OsrmResponseParser { })?; let geometry = linestring .coords() - .map(|coord| GeographicCoordinates::from(*coord)) + .map(|coord| GeographicCoordinate::from(*coord)) .collect(); let mut steps = vec![]; @@ -77,13 +77,13 @@ impl RouteStep { // TODO: Trait for this common pattern? let geometry = linestring .coords() - .map(|coord| GeographicCoordinates::from(*coord)) + .map(|coord| GeographicCoordinate::from(*coord)) .collect(); let visual_instructions = value .banner_instructions .iter() - .map(|banner| VisualInstructions { + .map(|banner| VisualInstruction { primary_content: VisualInstructionContent { text: banner.primary.text.clone(), maneuver_type: banner.primary.maneuver_type, @@ -102,6 +102,16 @@ impl RouteStep { }) .collect(); + let spoken_instructions = value + .voice_instructions + .iter() + .map(|instruction| SpokenInstruction { + text: instruction.announcement.clone(), + ssml: instruction.ssml_announcement.clone(), + trigger_distance_before_maneuver: instruction.distance_along_geometry, + }) + .collect(); + Ok(RouteStep { geometry, // TODO: Investigate using the haversine distance or geodesics to normalize. @@ -110,6 +120,7 @@ impl RouteStep { road_name: value.name.clone(), instruction: value.maneuver.get_instruction(), visual_instructions, + spoken_instructions, }) } } diff --git a/common/ferrostar/src/routing_adapters/osrm/models.rs b/common/ferrostar/src/routing_adapters/osrm/models.rs index 13c32768..b3a81afe 100644 --- a/common/ferrostar/src/routing_adapters/osrm/models.rs +++ b/common/ferrostar/src/routing_adapters/osrm/models.rs @@ -132,6 +132,9 @@ pub struct RouteStep { /// Textual instructions that are displayed as a banner; supported by Mapbox and Valhalla #[serde(default, rename = "bannerInstructions")] pub banner_instructions: Vec, + /// Textual instructions that are displayed as a banner; supported by Mapbox and Stadia Maps + #[serde(default, rename = "voiceInstructions")] + pub voice_instructions: Vec, } #[derive(Deserialize, Debug)] @@ -143,6 +146,15 @@ pub struct BannerInstruction { pub secondary: Option, } +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct VoiceInstruction { + pub announcement: String, + pub ssml_announcement: Option, + /// How far (in meters) from the upcoming maneuver the instruction should start being displayed + pub distance_along_geometry: f64, +} + #[derive(Deserialize, Debug)] pub struct BannerContent { pub text: String, @@ -184,8 +196,9 @@ pub struct StepManeuver { impl StepManeuver { // TODO: This is a placeholder implementation. - // The backends that the developers are working with all do instruction synthesis from - // components server-side. + // Most commercial offerings offer server-side synthesis of voice instructions. + // However, we might consider synthesizing these locally too. + // This will be rather cumbersome with localization though. fn synthesize_instruction(&self, locale: &str) -> String { String::from("TODO: OSRM instruction synthesis") } diff --git a/common/ferrostar/src/routing_adapters/osrm/snapshots/ferrostar__routing_adapters__osrm__tests__parse_valhalla_osrm.snap b/common/ferrostar/src/routing_adapters/osrm/snapshots/ferrostar__routing_adapters__osrm__tests__parse_valhalla_osrm.snap index dc897e96..d035794b 100644 --- a/common/ferrostar/src/routing_adapters/osrm/snapshots/ferrostar__routing_adapters__osrm__tests__parse_valhalla_osrm.snap +++ b/common/ferrostar/src/routing_adapters/osrm/snapshots/ferrostar__routing_adapters__osrm__tests__parse_valhalla_osrm.snap @@ -316,6 +316,13 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 111.251 + spoken_instructions: + - text: Walk west on the walkway. + ssml: "Walk west on the walkway." + trigger_distance_before_maneuver: 111.251 + - text: "In 200 feet, Turn left onto the walkway." + ssml: "In 200 feet, Turn left onto the walkway." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.763449 lat: 59.442754 @@ -332,6 +339,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 9 + spoken_instructions: + - text: "In 14 feet, Turn right onto Laeva." + ssml: "In 14 feet, Turn right onto Laeva." + trigger_distance_before_maneuver: 4.5 - geometry: - lng: 24.763423 lat: 59.442671 @@ -348,6 +359,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 16 + spoken_instructions: + - text: "In 26 feet, Bear right." + ssml: "In 26 feet, Bear right." + trigger_distance_before_maneuver: 8 - geometry: - lng: 24.763155 lat: 59.442709 @@ -366,6 +381,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 15 + spoken_instructions: + - text: "In 24 feet, Bear left onto the walkway." + ssml: "In 24 feet, Bear left onto the walkway." + trigger_distance_before_maneuver: 7.5 - geometry: - lng: 24.763 lat: 59.442819 @@ -386,6 +405,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 38 + spoken_instructions: + - text: "In 62 feet, Continue." + ssml: "In 62 feet, Continue." + trigger_distance_before_maneuver: 19 - geometry: - lng: 24.762356 lat: 59.442918 @@ -402,6 +425,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 7 + spoken_instructions: + - text: "In 11 feet, Turn right onto Admiralisild/Admiral Bridge." + ssml: "In 11 feet, Turn right onto Admiralisild/Admiral Bridge." + trigger_distance_before_maneuver: 3.5 - geometry: - lng: 24.762237 lat: 59.442936 @@ -424,6 +451,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 70 + spoken_instructions: + - text: "In 200 feet, Continue on the walkway." + ssml: "In 200 feet, Continue on the walkway." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.761765 lat: 59.443526 @@ -442,6 +473,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 46 + spoken_instructions: + - text: "In 75 feet, Turn left onto the walkway." + ssml: "In 75 feet, Turn left onto the walkway." + trigger_distance_before_maneuver: 23 - geometry: - lng: 24.761432 lat: 59.4439 @@ -458,6 +493,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 131 + spoken_instructions: + - text: "In 200 feet, Turn right onto the walkway." + ssml: "In 200 feet, Turn right onto the walkway." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.759273 lat: 59.443487 @@ -480,6 +519,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 25 + spoken_instructions: + - text: "In 41 feet, Turn left onto the walkway." + ssml: "In 41 feet, Turn left onto the walkway." + trigger_distance_before_maneuver: 12.5 - geometry: - lng: 24.759127 lat: 59.443712 @@ -500,6 +543,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 16 + spoken_instructions: + - text: "In 26 feet, Turn right onto Logi." + ssml: "In 26 feet, Turn right onto Logi." + trigger_distance_before_maneuver: 8 - geometry: - lng: 24.758853 lat: 59.443674 @@ -534,6 +581,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 91 + spoken_instructions: + - text: "In 200 feet, Turn left onto the walkway." + ssml: "In 200 feet, Turn left onto the walkway." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.758392 lat: 59.444448 @@ -550,6 +601,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 8 + spoken_instructions: + - text: "In 13 feet, Turn right onto the walkway." + ssml: "In 13 feet, Turn right onto the walkway." + trigger_distance_before_maneuver: 4 - geometry: - lng: 24.758246 lat: 59.444431 @@ -572,6 +627,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 85 + spoken_instructions: + - text: "In 200 feet, Bear left onto Kultuurikilomeeter." + ssml: "In 200 feet, Bear left onto Kultuurikilomeeter." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.757636 lat: 59.445069 @@ -694,6 +753,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 1254 + spoken_instructions: + - text: "In 200 feet, Turn right onto the walkway." + ssml: "In 200 feet, Turn right onto the walkway." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.739543 lat: 59.44946 @@ -712,6 +775,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 23 + spoken_instructions: + - text: "In 37 feet, Turn left onto the walkway." + ssml: "In 37 feet, Turn left onto the walkway." + trigger_distance_before_maneuver: 11.5 - geometry: - lng: 24.739675 lat: 59.449652 @@ -730,6 +797,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 16 + spoken_instructions: + - text: "In 26 feet, Turn left onto the crosswalk." + ssml: "In 26 feet, Turn left onto the crosswalk." + trigger_distance_before_maneuver: 8 - geometry: - lng: 24.739454 lat: 59.449733 @@ -784,6 +855,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 347 + spoken_instructions: + - text: "In 200 feet, Turn right onto the walkway." + ssml: "In 200 feet, Turn right onto the walkway." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.733721 lat: 59.450765 @@ -800,6 +875,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 2 + spoken_instructions: + - text: "In 3 feet, Turn left onto the walkway." + ssml: "In 3 feet, Turn left onto the walkway." + trigger_distance_before_maneuver: 1 - geometry: - lng: 24.733717 lat: 59.450787 @@ -842,6 +921,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 241 + spoken_instructions: + - text: "In 200 feet, Bear left onto Allveelaeva." + ssml: "In 200 feet, Bear left onto Allveelaeva." + trigger_distance_before_maneuver: 60 - geometry: - lng: 24.730259 lat: 59.451907 @@ -858,6 +941,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 28 + spoken_instructions: + - text: "In 45 feet, Turn right onto Peetri." + ssml: "In 45 feet, Turn right onto Peetri." + trigger_distance_before_maneuver: 14 - geometry: - lng: 24.729829 lat: 59.452026 @@ -876,6 +963,10 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 25.099 + spoken_instructions: + - text: "In 41 feet, You have arrived at your destination." + ssml: "In 41 feet, You have arrived at your destination." + trigger_distance_before_maneuver: 12.5495 - geometry: - lng: 24.730034 lat: 59.452226 @@ -892,4 +983,5 @@ expression: routes roundabout_exit_degrees: ~ secondary_content: ~ trigger_distance_before_maneuver: 0 + spoken_instructions: [] diff --git a/common/ferrostar/src/routing_adapters/valhalla.rs b/common/ferrostar/src/routing_adapters/valhalla.rs index cec4b7f2..f4da878f 100644 --- a/common/ferrostar/src/routing_adapters/valhalla.rs +++ b/common/ferrostar/src/routing_adapters/valhalla.rs @@ -1,5 +1,5 @@ use super::{RouteRequest, RoutingRequestGenerationError}; -use crate::models::{GeographicCoordinates, UserLocation}; +use crate::models::{GeographicCoordinate, UserLocation}; use crate::routing_adapters::RouteRequestGenerator; use serde_json::{json, Value as JsonValue}; use std::collections::HashMap; @@ -33,7 +33,7 @@ impl RouteRequestGenerator for ValhallaHttpRequestGenerator { fn generate_request( &self, user_location: UserLocation, - waypoints: Vec, + waypoints: Vec, ) -> Result { if waypoints.is_empty() { Err(RoutingRequestGenerationError::NotEnoughWaypoints) @@ -97,13 +97,13 @@ mod tests { const ENDPOINT_URL: &str = "https://api.stadiamaps.com/route/v1"; const COSTING: &str = "bicycle"; const USER_LOCATION: UserLocation = UserLocation { - coordinates: GeographicCoordinates { lat: 0.0, lng: 0.0 }, + coordinates: GeographicCoordinate { lat: 0.0, lng: 0.0 }, horizontal_accuracy: 6.0, course_over_ground: None, timestamp: SystemTime::UNIX_EPOCH, }; const USER_LOCATION_WITH_COURSE: UserLocation = UserLocation { - coordinates: GeographicCoordinates { lat: 0.0, lng: 0.0 }, + coordinates: GeographicCoordinate { lat: 0.0, lng: 0.0 }, horizontal_accuracy: 6.0, course_over_ground: Some(CourseOverGround { degrees: 42, @@ -111,9 +111,9 @@ mod tests { }), timestamp: SystemTime::UNIX_EPOCH, }; - const WAYPOINTS: [GeographicCoordinates; 2] = [ - GeographicCoordinates { lat: 0.0, lng: 1.0 }, - GeographicCoordinates { lat: 2.0, lng: 3.0 }, + const WAYPOINTS: [GeographicCoordinate; 2] = [ + GeographicCoordinate { lat: 0.0, lng: 1.0 }, + GeographicCoordinate { lat: 2.0, lng: 3.0 }, ]; #[test] @@ -217,7 +217,7 @@ mod tests { let generator = ValhallaHttpRequestGenerator::new(ENDPOINT_URL.to_string(), COSTING.to_string()); let location = UserLocation { - coordinates: GeographicCoordinates { lat: 0.0, lng: 0.0 }, + coordinates: GeographicCoordinate { lat: 0.0, lng: 0.0 }, horizontal_accuracy: -6.0, course_over_ground: None, timestamp: SystemTime::now(),