diff --git a/Package.resolved b/Package.resolved index ea0e3789..9fd55418 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-syntax.git", "state" : { - "revision" : "74203046135342e4a4a627476dd6caf8b28fe11b", - "version" : "509.0.0" + "revision" : "ffa3cd6fc2aa62adbedd31d3efaf7c0d86a9f029", + "version" : "509.0.1" } } ], diff --git a/Package.swift b/Package.swift index d20bff37..6a0912d1 100644 --- a/Package.swift +++ b/Package.swift @@ -15,8 +15,8 @@ if useLocalFramework { path: "./common/target/ios/ferrostar-rs.xcframework" ) } else { - let releaseTag = "0.0.6" - let releaseChecksum = "f122f35cc657ad3758a105ec62516a0a8c2f358e24f32f0e115bbc592961f2ce" + let releaseTag = "0.0.7" + let releaseChecksum = "637d6837bd671791c4a4ef7e315755d39f04bcbcc053cef4498793a58e68f76d" binaryTarget = .binaryTarget( name: "FerrostarCoreRS", url: "https://github.com/stadiamaps/ferrostar/releases/download/\(releaseTag)/ferrostar-rs.xcframework.zip", diff --git a/apple/Sources/FerrostarCore/ModelWrappers.swift b/apple/Sources/FerrostarCore/ModelWrappers.swift index 86af33e9..97bb1a13 100644 --- a/apple/Sources/FerrostarCore/ModelWrappers.swift +++ b/apple/Sources/FerrostarCore/ModelWrappers.swift @@ -51,9 +51,9 @@ public enum StepAdvanceMode { /// /// Distance and horizontal accuracy are measured in meters. case distanceToEndOfStep(distance: UInt16, minimumHorizontalAccuracy: UInt16) - /// Automatically advances when the user's distance to the *next* step's linestring is less - /// than or equal to the distance to the current step's linestring. - case relativeLineStringDistance(minimumHorizontalAccuracy: UInt16) + /// At this (optional) distance, navigation should advance to the next step regardless + /// of which LineString appears closer. + case relativeLineStringDistance(minimumHorizontalAccuracy: UInt16, automaticAdvanceDistance: UInt16?) var ffiValue: UniFFI.StepAdvanceMode { switch self { @@ -61,8 +61,8 @@ public enum StepAdvanceMode { return .manual case .distanceToEndOfStep(distance: let distance, minimumHorizontalAccuracy: let minimumHorizontalAccuracy): return .distanceToEndOfStep(distance: distance, minimumHorizontalAccuracy: minimumHorizontalAccuracy) - case .relativeLineStringDistance(minimumHorizontalAccuracy: let minimumHorizontalAccuracy): - return .relativeLineStringDistance(minimumHorizontalAccuracy: minimumHorizontalAccuracy) + case .relativeLineStringDistance(minimumHorizontalAccuracy: let minimumHorizontalAccuracy, automaticAdvanceDistance: let automaticAdvanceDistance): + return .relativeLineStringDistance(minimumHorizontalAccuracy: minimumHorizontalAccuracy, automaticAdvanceDistance: automaticAdvanceDistance) } } } diff --git a/apple/Sources/UniFFI/ferrostar.swift b/apple/Sources/UniFFI/ferrostar.swift index 5b56c8a5..9918f305 100644 --- a/apple/Sources/UniFFI/ferrostar.swift +++ b/apple/Sources/UniFFI/ferrostar.swift @@ -1536,7 +1536,7 @@ extension RoutingResponseParseError: Error {} public enum StepAdvanceMode { case manual case distanceToEndOfStep(distance: UInt16, minimumHorizontalAccuracy: UInt16) - case relativeLineStringDistance(minimumHorizontalAccuracy: UInt16) + case relativeLineStringDistance(minimumHorizontalAccuracy: UInt16, automaticAdvanceDistance: UInt16?) } public struct FfiConverterTypeStepAdvanceMode: FfiConverterRustBuffer { @@ -1553,7 +1553,8 @@ public struct FfiConverterTypeStepAdvanceMode: FfiConverterRustBuffer { ) case 3: return try .relativeLineStringDistance( - minimumHorizontalAccuracy: FfiConverterUInt16.read(from: &buf) + minimumHorizontalAccuracy: FfiConverterUInt16.read(from: &buf), + automaticAdvanceDistance: FfiConverterOptionUInt16.read(from: &buf) ) default: throw UniffiInternalError.unexpectedEnumCase @@ -1570,9 +1571,10 @@ public struct FfiConverterTypeStepAdvanceMode: FfiConverterRustBuffer { FfiConverterUInt16.write(distance, into: &buf) FfiConverterUInt16.write(minimumHorizontalAccuracy, into: &buf) - case let .relativeLineStringDistance(minimumHorizontalAccuracy): + case let .relativeLineStringDistance(minimumHorizontalAccuracy, automaticAdvanceDistance): writeInt(&buf, Int32(3)) FfiConverterUInt16.write(minimumHorizontalAccuracy, into: &buf) + FfiConverterOptionUInt16.write(automaticAdvanceDistance, into: &buf) } } } diff --git a/common/Cargo.lock b/common/Cargo.lock index 2b398a16..3e0309da 100644 --- a/common/Cargo.lock +++ b/common/Cargo.lock @@ -330,7 +330,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "ferrostar-core" -version = "0.0.6" +version = "0.0.7" dependencies = [ "assert-json-diff", "geo", diff --git a/common/ferrostar-core/Cargo.toml b/common/ferrostar-core/Cargo.toml index 5a4437de..929d0d45 100644 --- a/common/ferrostar-core/Cargo.toml +++ b/common/ferrostar-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ferrostar-core" -version = "0.0.6" +version = "0.0.7" authors = ["Ian Wagner ", "Luke Seelenbinder "] license = "BSD-3-Clause" repository = "https://github.com/stadiamaps/ferrostar" diff --git a/common/ferrostar-core/proptest-regressions/navigation_controller/algorithms.txt b/common/ferrostar-core/proptest-regressions/navigation_controller/algorithms.txt new file mode 100644 index 00000000..32d148f6 --- /dev/null +++ b/common/ferrostar-core/proptest-regressions/navigation_controller/algorithms.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc b960506a2a3ada78b4d042a2ec23695871c1676d7a8e6d4c16053e56197acff9 # shrinks to x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0, x3 = 0.0, y3 = 0.0, error = 0.0009697441544620993, has_next_step = false, distance = 153, minimum_horizontal_accuracy = 0, automatic_advance_distance = Some(0) diff --git a/common/ferrostar-core/src/ferrostar.udl b/common/ferrostar-core/src/ferrostar.udl index 70187253..21dbcdee 100644 --- a/common/ferrostar-core/src/ferrostar.udl +++ b/common/ferrostar-core/src/ferrostar.udl @@ -124,7 +124,7 @@ interface NavigationStateUpdate { interface StepAdvanceMode { Manual(); DistanceToEndOfStep(u16 distance, u16 minimum_horizontal_accuracy); - RelativeLineStringDistance(u16 minimum_horizontal_accuracy); + RelativeLineStringDistance(u16 minimum_horizontal_accuracy, u16? automatic_advance_distance); }; dictionary NavigationControllerConfig { diff --git a/common/ferrostar-core/src/navigation_controller/utils.rs b/common/ferrostar-core/src/navigation_controller/algorithms.rs similarity index 82% rename from common/ferrostar-core/src/navigation_controller/utils.rs rename to common/ferrostar-core/src/navigation_controller/algorithms.rs index 26de2edb..d9d3415c 100644 --- a/common/ferrostar-core/src/navigation_controller/utils.rs +++ b/common/ferrostar-core/src/navigation_controller/algorithms.rs @@ -1,5 +1,8 @@ use crate::models::{GeographicCoordinates, RouteStep, UserLocation}; -use geo::{Closest, ClosestPoint, EuclideanDistance, HaversineDistance, HaversineLength, LineLocatePoint, LineString, Point}; +use geo::{ + Closest, ClosestPoint, EuclideanDistance, HaversineDistance, HaversineLength, LineLocatePoint, + LineString, Point, +}; use super::models::{StepAdvanceMode, StepAdvanceStatus}; use crate::navigation_controller::models::StepAdvanceStatus::{Advanced, EndOfRoute}; @@ -33,6 +36,21 @@ fn snap_point_to_line(point: &Point, line: &LineString) -> Option { } } +fn is_close_enough_to_end_of_step( + current_position: &Point, + current_step_linestring: &LineString, + threshold: f64, +) -> bool { + if let Some(end_coord) = current_step_linestring.coords().last() { + let end_point = Point::from(*end_coord); + let distance_to_end = end_point.haversine_distance(¤t_position); + + distance_to_end <= threshold + } else { + false + } +} + /// Determines whether the navigation controller should complete the current route step /// and move to the next. /// @@ -53,21 +71,32 @@ pub fn should_advance_to_next_step( } => { if user_location.horizontal_accuracy > minimum_horizontal_accuracy.into() { false - } else if let Some(end_coord) = current_step_linestring.coords().last() { - let end_point = Point::from(*end_coord); - let distance_to_end = end_point.haversine_distance(¤t_position); - - distance_to_end <= distance as f64 } else { - false + is_close_enough_to_end_of_step( + ¤t_position, + current_step_linestring, + distance as f64, + ) } } StepAdvanceMode::RelativeLineStringDistance { minimum_horizontal_accuracy, + automatic_advance_distance, } => { if user_location.horizontal_accuracy > minimum_horizontal_accuracy.into() { false } else { + if let Some(distance) = automatic_advance_distance { + // Short-circuit: if we are close to the end of the step, we may advance + if is_close_enough_to_end_of_step( + ¤t_position, + current_step_linestring, + distance as f64, + ) { + return true; + } + } + if let Some(next_step) = next_route_step { // FIXME: This isn't very efficient to keep doing at the moment let next_step_linestring = next_step.get_linestring(); @@ -189,7 +218,8 @@ proptest! { x2 in -180f64..180f64, y2 in -90f64..90f64, x3 in -180f64..180f64, y3 in -90f64..90f64, has_next_step: bool, - distance: u16, minimum_horizontal_accuracy: u16, excess_inaccuracy in 0f64..65535f64 + distance: u16, minimum_horizontal_accuracy: u16, excess_inaccuracy in 0f64..65535f64, + automatic_advance_distance: Option, ) { let current_route_step = gen_dummy_route_step(x1, y1, x2, y2); let next_route_step = if has_next_step { @@ -220,7 +250,8 @@ proptest! { // Same when looking at the relative distances between the two step geometries assert!(should_advance_to_next_step(¤t_route_step.get_linestring(), next_route_step.as_ref(), &exact_user_location, StepAdvanceMode::RelativeLineStringDistance { - minimum_horizontal_accuracy + minimum_horizontal_accuracy, + automatic_advance_distance })); // Should always fail (unless excess_inaccuracy is zero), as the horizontal accuracy is worse than (>) than the desired error threshold @@ -228,7 +259,8 @@ proptest! { distance, minimum_horizontal_accuracy }), excess_inaccuracy == 0.0, "Expected that the navigation would not advance to the next step except when excess_inaccuracy is 0"); assert_eq!(should_advance_to_next_step(¤t_route_step.get_linestring(), next_route_step.as_ref(), &inaccurate_user_location, StepAdvanceMode::RelativeLineStringDistance { - minimum_horizontal_accuracy + minimum_horizontal_accuracy, + automatic_advance_distance }), excess_inaccuracy == 0.0, "Expected that the navigation would not advance to the next step except when excess_inaccuracy is 0"); } @@ -238,7 +270,8 @@ proptest! { x2 in -180f64..180f64, y2 in -90f64..90f64, x3 in -180f64..180f64, y3 in -90f64..90f64, error in -0.003f64..0.003f64, has_next_step: bool, - distance: u16, minimum_horizontal_accuracy in 0u16..250u16 + distance: u16, minimum_horizontal_accuracy in 0u16..250u16, + automatic_advance_distance: Option, ) { let current_route_step = gen_dummy_route_step(x1, y1, x2, y2); let next_route_step = if has_next_step { @@ -259,7 +292,7 @@ proptest! { timestamp: SystemTime::now(), }; let user_location_point = Point::from(user_location); - // let distance_from_end_of_current_step = user_location.into(). + let distance_from_end_of_current_step = user_location_point.haversine_distance(&end_of_step.into()); // Never advance to the next step when StepAdvanceMode is Manual assert!(!should_advance_to_next_step(¤t_route_step.get_linestring(), next_route_step.as_ref(), &user_location, StepAdvanceMode::Manual)); @@ -267,7 +300,17 @@ proptest! { // Assumes that haversine_distance is correct assert_eq!(should_advance_to_next_step(¤t_route_step.get_linestring(), next_route_step.as_ref(), &user_location, StepAdvanceMode::DistanceToEndOfStep { distance, minimum_horizontal_accuracy - }), user_location_point.haversine_distance(&end_of_step.into()) <= distance.into(), "Expected that the step should advance in this case as we are closer to the end of the step than the threshold."); + }), distance_from_end_of_current_step <= distance.into(), "Expected that the step should advance in this case as we are closer to the end of the step than the threshold."); + + // Similar test for automatic advance on the relative line string distance mode + if automatic_advance_distance.map_or(false, |advance_distance| { + distance_from_end_of_current_step <= advance_distance.into() + }) { + assert!(should_advance_to_next_step(¤t_route_step.get_linestring(), next_route_step.as_ref(), &user_location, StepAdvanceMode::RelativeLineStringDistance { + minimum_horizontal_accuracy, + automatic_advance_distance, + }), "Expected that the step should advance any time that the haversine distance to the end of the step is within the automatic advance threshold."); + } // TODO: We can use snap_to_line and, assuming that snap_to_line works and haversine_distance works, we have a valid end to end test } diff --git a/common/ferrostar-core/src/navigation_controller/mod.rs b/common/ferrostar-core/src/navigation_controller/mod.rs index 083dcf49..e9bdb38b 100644 --- a/common/ferrostar-core/src/navigation_controller/mod.rs +++ b/common/ferrostar-core/src/navigation_controller/mod.rs @@ -1,12 +1,14 @@ +mod algorithms; pub mod models; -mod utils; use crate::models::{Route, UserLocation}; -use crate::navigation_controller::utils::{advance_step, distance_to_end_of_step, should_advance_to_next_step}; +use crate::navigation_controller::algorithms::{ + advance_step, distance_to_end_of_step, should_advance_to_next_step, +}; +use algorithms::snap_user_location_to_line; use geo::Coord; use models::*; use std::sync::Mutex; -use utils::snap_user_location_to_line; /// Manages the navigation lifecycle of a single trip, requesting the initial route and updating /// internal state based on inputs like user location updates. @@ -197,7 +199,10 @@ impl NavigationController { // 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(), current_step_linestring); + let distance_to_next_maneuver = distance_to_end_of_step( + &snapped_user_location.into(), + current_step_linestring, + ); NavigationStateUpdate::Navigating { snapped_user_location, diff --git a/common/ferrostar-core/src/navigation_controller/models.rs b/common/ferrostar-core/src/navigation_controller/models.rs index 80d0ce36..6cedb63f 100644 --- a/common/ferrostar-core/src/navigation_controller/models.rs +++ b/common/ferrostar-core/src/navigation_controller/models.rs @@ -68,6 +68,9 @@ pub enum StepAdvanceMode { /// The minimum required horizontal accuracy of the user location. /// Values larger than this cannot trigger a step advance. minimum_horizontal_accuracy: u16, + /// At this (optional) distance, navigation should advance to the next step regardless + /// of which LineString appears closer. + automatic_advance_distance: Option, }, }