use std::sync::Arc;
use super::{StepAdvanceCondition, StepAdvanceConditionSerializable, StepAdvanceResult};
use crate::{
algorithms::{
deviation_from_line, get_linestring, is_within_threshold_to_end_of_linestring,
snap_user_location_to_line,
},
deviation_detection::RouteDeviation,
navigation_controller::models::TripState,
};
use geo::Point;
#[cfg(test)]
use proptest::prelude::*;
#[cfg(test)]
use crate::{
navigation_controller::test_helpers::get_navigating_trip_state,
test_utils::{arb_coord, make_user_location},
};
use super::SerializableStepAdvanceCondition;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct ManualStepCondition;
impl StepAdvanceCondition for ManualStepCondition {
#[allow(unused_variables)]
fn should_advance_step(&self, trip_state: TripState) -> StepAdvanceResult {
StepAdvanceResult::continue_with_state(Arc::new(ManualStepCondition))
}
fn new_instance(&self) -> Arc<dyn StepAdvanceCondition> {
Arc::new(ManualStepCondition)
}
}
impl StepAdvanceConditionSerializable for ManualStepCondition {
fn to_js(&self) -> SerializableStepAdvanceCondition {
SerializableStepAdvanceCondition::Manual
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct DistanceToEndOfStepCondition {
pub distance: u16,
pub minimum_horizontal_accuracy: u16,
}
impl StepAdvanceCondition for DistanceToEndOfStepCondition {
fn should_advance_step(&self, trip_state: TripState) -> StepAdvanceResult {
self.should_advance_inner(&trip_state)
.unwrap_or(StepAdvanceResult::continue_with_state(self.new_instance()))
}
fn new_instance(&self) -> Arc<dyn StepAdvanceCondition> {
Arc::new(DistanceToEndOfStepCondition {
distance: self.distance,
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
})
}
}
impl DistanceToEndOfStepCondition {
fn should_advance_inner(&self, trip_state: &TripState) -> Option<StepAdvanceResult> {
let user_location = trip_state.user_location()?;
let current_step = trip_state.current_step()?;
let should_advance =
if user_location.horizontal_accuracy > self.minimum_horizontal_accuracy.into() {
false
} else {
is_within_threshold_to_end_of_linestring(
&user_location.into(),
¤t_step.get_linestring(),
f64::from(self.distance),
)
};
let result = if should_advance {
StepAdvanceResult::advance_to_new_instance(self)
} else {
StepAdvanceResult::continue_with_state(self.new_instance())
};
Some(result)
}
}
impl StepAdvanceConditionSerializable for DistanceToEndOfStepCondition {
fn to_js(&self) -> SerializableStepAdvanceCondition {
SerializableStepAdvanceCondition::DistanceToEndOfStep {
distance: self.distance,
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
}
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct DistanceFromStepCondition {
pub distance: u16,
pub minimum_horizontal_accuracy: u16,
pub calculate_while_off_route: bool,
}
impl StepAdvanceCondition for DistanceFromStepCondition {
fn should_advance_step(&self, trip_state: TripState) -> StepAdvanceResult {
self.should_advance_inner(&trip_state)
.unwrap_or(StepAdvanceResult::continue_with_state(self.new_instance()))
}
fn new_instance(&self) -> Arc<dyn StepAdvanceCondition> {
Arc::new(DistanceFromStepCondition {
distance: self.distance,
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
calculate_while_off_route: self.calculate_while_off_route,
})
}
}
impl DistanceFromStepCondition {
fn should_advance_inner(&self, trip_state: &TripState) -> Option<StepAdvanceResult> {
let deviation = trip_state.deviation()?;
let user_location = trip_state.user_location()?;
let current_step = trip_state.current_step()?;
let should_advance =
if (!self.calculate_while_off_route && deviation != RouteDeviation::NoDeviation)
|| (user_location.horizontal_accuracy > self.minimum_horizontal_accuracy.into()){
false
} else {
let current_position: Point = user_location.into();
let current_step_linestring = current_step.get_linestring();
deviation_from_line(¤t_position, ¤t_step_linestring)
.is_some_and(|deviation| deviation > self.distance.into())
};
let result = if should_advance {
StepAdvanceResult::advance_to_new_instance(self)
} else {
StepAdvanceResult::continue_with_state(self.new_instance())
};
Some(result)
}
}
impl StepAdvanceConditionSerializable for DistanceFromStepCondition {
fn to_js(&self) -> SerializableStepAdvanceCondition {
SerializableStepAdvanceCondition::DistanceFromStep {
distance: self.distance,
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
calculate_while_off_route: self.calculate_while_off_route,
}
}
}
#[derive(Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct OrAdvanceConditions {
pub conditions: Vec<Arc<dyn StepAdvanceCondition>>,
}
impl StepAdvanceCondition for OrAdvanceConditions {
fn should_advance_step(&self, trip_state: TripState) -> StepAdvanceResult {
let mut should_advance = false;
let mut next_conditions = Vec::with_capacity(self.conditions.len());
for condition in &self.conditions {
let result = condition.should_advance_step(trip_state.clone());
should_advance = should_advance || result.should_advance;
next_conditions.push(result.next_iteration);
}
StepAdvanceResult {
should_advance,
next_iteration: if should_advance {
self.new_instance()
} else {
Arc::new(OrAdvanceConditions {
conditions: next_conditions,
})
},
}
}
fn new_instance(&self) -> Arc<dyn StepAdvanceCondition> {
Arc::new(OrAdvanceConditions {
conditions: self
.conditions
.iter()
.map(|condition| condition.new_instance())
.collect(),
})
}
}
impl StepAdvanceConditionSerializable for OrAdvanceConditions {
fn to_js(&self) -> SerializableStepAdvanceCondition {
SerializableStepAdvanceCondition::OrAdvanceConditions {
conditions: self.conditions.iter().map(|c| c.to_js()).collect(),
}
}
}
#[derive(Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct AndAdvanceConditions {
pub conditions: Vec<Arc<dyn StepAdvanceCondition>>,
}
impl StepAdvanceCondition for AndAdvanceConditions {
fn should_advance_step(&self, trip_state: TripState) -> StepAdvanceResult {
let mut should_advance = true;
let mut next_conditions = Vec::with_capacity(self.conditions.len());
for condition in &self.conditions {
let result = condition.should_advance_step(trip_state.clone());
should_advance = should_advance && result.should_advance;
next_conditions.push(result.next_iteration);
}
StepAdvanceResult {
should_advance,
next_iteration: if should_advance {
self.new_instance()
} else {
Arc::new(AndAdvanceConditions {
conditions: next_conditions,
})
},
}
}
fn new_instance(&self) -> Arc<dyn StepAdvanceCondition> {
Arc::new(AndAdvanceConditions {
conditions: self
.conditions
.iter()
.map(|condition| condition.new_instance())
.collect(),
})
}
}
impl StepAdvanceConditionSerializable for AndAdvanceConditions {
fn to_js(&self) -> SerializableStepAdvanceCondition {
SerializableStepAdvanceCondition::AndAdvanceConditions {
conditions: self.conditions.iter().map(|c| c.to_js()).collect(),
}
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct DistanceEntryAndExitCondition {
pub(super) distance_to_end_of_step: u16,
pub(super) distance_after_end_of_step: u16,
pub(super) minimum_horizontal_accuracy: u16,
pub(super) has_reached_end_of_current_step: bool,
}
impl Default for DistanceEntryAndExitCondition {
fn default() -> Self {
Self {
distance_to_end_of_step: 20,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 25,
has_reached_end_of_current_step: false,
}
}
}
#[cfg(test)]
impl DistanceEntryAndExitCondition {
pub fn exact() -> Self {
Self {
distance_to_end_of_step: 0,
distance_after_end_of_step: 0,
minimum_horizontal_accuracy: 0,
has_reached_end_of_current_step: false,
}
}
}
impl StepAdvanceCondition for DistanceEntryAndExitCondition {
fn should_advance_step(&self, trip_state: TripState) -> StepAdvanceResult {
if self.has_reached_end_of_current_step {
let distance_from_end = DistanceFromStepCondition {
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
distance: self.distance_after_end_of_step,
calculate_while_off_route: false,
};
let should_advance = distance_from_end
.should_advance_step(trip_state)
.should_advance;
if should_advance {
StepAdvanceResult::advance_to_new_instance(self)
} else {
StepAdvanceResult::continue_with_state(Arc::new(DistanceEntryAndExitCondition {
distance_to_end_of_step: self.distance_to_end_of_step,
distance_after_end_of_step: self.distance_after_end_of_step,
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
has_reached_end_of_current_step: true,
}))
}
} else {
let distance_to_end = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
distance: self.distance_to_end_of_step,
};
let next_iteration = DistanceEntryAndExitCondition {
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
distance_to_end_of_step: self.distance_to_end_of_step,
distance_after_end_of_step: self.distance_after_end_of_step,
has_reached_end_of_current_step: distance_to_end
.should_advance_step(trip_state)
.should_advance,
};
StepAdvanceResult::continue_with_state(Arc::new(next_iteration))
}
}
fn new_instance(&self) -> Arc<dyn StepAdvanceCondition> {
Arc::new(DistanceEntryAndExitCondition {
distance_to_end_of_step: self.distance_to_end_of_step,
distance_after_end_of_step: self.distance_after_end_of_step,
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
has_reached_end_of_current_step: false, })
}
}
impl StepAdvanceConditionSerializable for DistanceEntryAndExitCondition {
fn to_js(&self) -> SerializableStepAdvanceCondition {
SerializableStepAdvanceCondition::DistanceEntryExit {
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
distance_to_end_of_step: self.distance_to_end_of_step,
distance_after_end_step: self.distance_after_end_of_step,
has_reached_end_of_current_step: self.has_reached_end_of_current_step,
}
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct DistanceEntryAndSnappedExitCondition {
pub(super) distance_to_end_of_step: u16,
pub(super) distance_after_end_of_step: u16,
pub(super) minimum_horizontal_accuracy: u16,
pub(super) has_reached_end_of_current_step: bool,
}
impl Default for DistanceEntryAndSnappedExitCondition {
fn default() -> Self {
Self {
distance_to_end_of_step: 20,
distance_after_end_of_step: 2,
minimum_horizontal_accuracy: 25,
has_reached_end_of_current_step: false,
}
}
}
#[cfg(test)]
impl DistanceEntryAndSnappedExitCondition {
pub fn exact() -> Self {
Self {
distance_to_end_of_step: 0,
distance_after_end_of_step: 0,
minimum_horizontal_accuracy: 0,
has_reached_end_of_current_step: false,
}
}
}
impl StepAdvanceCondition for DistanceEntryAndSnappedExitCondition {
#[allow(unused_variables)]
fn should_advance_step(&self, trip_state: TripState) -> StepAdvanceResult {
let result = if self.has_reached_end_of_current_step {
self.check_exit_result(&trip_state)
} else {
self.check_entry_result(&trip_state)
};
result.unwrap_or(StepAdvanceResult::continue_with_state(self.new_instance()))
}
fn new_instance(&self) -> Arc<dyn StepAdvanceCondition> {
Arc::new(DistanceEntryAndSnappedExitCondition {
has_reached_end_of_current_step: false, ..*self
})
}
}
impl DistanceEntryAndSnappedExitCondition {
fn check_exit_result(&self, trip_state: &TripState) -> Option<StepAdvanceResult> {
let user_location = trip_state.user_location()?;
let current_step = trip_state.current_step()?;
let next_step = trip_state.next_step();
let should_advance = if user_location.horizontal_accuracy
> self.minimum_horizontal_accuracy.into()
{
false
} else if let Some(next) = next_step {
let mut combined_coords = current_step.geometry.clone();
let mut accumulated_distance = next.distance;
let mut step_index = 1;
combined_coords.extend(next.geometry.clone());
let target_distance = (self.distance_after_end_of_step as f64) * 2.0;
while accumulated_distance < target_distance {
if let Some(future_step) = trip_state.get_step(step_index + 1) {
combined_coords.extend(future_step.geometry.clone());
accumulated_distance += future_step.distance;
step_index += 1;
} else {
break;
}
}
let combined_linestring = get_linestring(&combined_coords);
let snapped_to_route = snap_user_location_to_line(user_location, &combined_linestring);
let current_step_linestring = current_step.get_linestring();
let deviation =
deviation_from_line(&Point::from(snapped_to_route), ¤t_step_linestring)
.unwrap_or(0.0);
let effective_exit_distance =
(self.distance_after_end_of_step as f64).min(accumulated_distance);
deviation >= effective_exit_distance
} else {
true
};
if should_advance {
Some(StepAdvanceResult::advance_to_new_instance(self))
} else {
None
}
}
fn check_entry_result(&self, trip_state: &TripState) -> Option<StepAdvanceResult> {
let distance_to_end = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
distance: self.distance_to_end_of_step,
};
let next_iteration = DistanceEntryAndSnappedExitCondition {
has_reached_end_of_current_step: distance_to_end
.should_advance_step(trip_state.clone())
.should_advance,
..*self
};
let result = StepAdvanceResult::continue_with_state(Arc::new(next_iteration));
Some(result)
}
}
impl StepAdvanceConditionSerializable for DistanceEntryAndSnappedExitCondition {
fn to_js(&self) -> SerializableStepAdvanceCondition {
SerializableStepAdvanceCondition::DistanceEntryAndSnappedExit {
minimum_horizontal_accuracy: self.minimum_horizontal_accuracy,
distance_to_end_of_step: self.distance_to_end_of_step,
distance_after_end_step: self.distance_after_end_of_step,
has_reached_end_of_current_step: self.has_reached_end_of_current_step,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{RouteStep, UserLocation};
use crate::navigation_controller::test_helpers::{
gen_route_step_with_coords, get_navigating_trip_state,
};
use crate::test_utils::make_user_location;
use geo::coord;
use std::sync::LazyLock;
static STRAIGHT_LINE_SHORT_ROUTE_STEP: LazyLock<RouteStep> = LazyLock::new(|| {
gen_route_step_with_coords(vec![
coord!(x: 0.0, y: 0.0), coord!(x: 0.001, y: 0.0), ])
});
static LOCATION_NEAR_START_OF_STEP: LazyLock<UserLocation> =
LazyLock::new(|| make_user_location(coord!(x: 0.0001, y: 0.0), 5.0));
static LOCATION_NEAR_END_OF_STEP: LazyLock<UserLocation> =
LazyLock::new(|| make_user_location(coord!(x: 0.00099, y: 0.0), 5.0));
#[test]
fn test_manual_step_advance() {
let condition = ManualStepCondition;
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_START_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = condition.should_advance_step(trip_state);
assert!(
!result.should_advance,
"Should not advance with the manual condition"
);
}
#[test]
fn test_distance_to_end_of_step_doesnt_advance() {
let condition = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 20, };
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_START_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = condition.should_advance_step(trip_state);
assert!(
!result.should_advance,
"Should not advance when far from the end of the step"
);
}
#[test]
fn test_distance_to_end_of_step_advance() {
let condition = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 20, };
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = condition.should_advance_step(trip_state);
assert!(
result.should_advance,
"Should advance when close to the end of the step"
);
}
#[test]
fn test_distance_from_step_advance_with_deviation() {
let user_location = make_user_location(coord!(x: 0.005, y: 0.0005), 5.0);
let condition = DistanceFromStepCondition {
minimum_horizontal_accuracy: 10,
distance: 100, calculate_while_off_route: true,
};
let trip_state = get_navigating_trip_state(
user_location,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::OffRoute {
deviation_from_route_line: 10.0,
},
);
let result = condition.should_advance_step(trip_state);
assert!(
result.should_advance,
"Should advance when far from the route"
);
}
#[test]
fn test_distance_from_step_advance_with_deviation_off() {
let user_location = make_user_location(coord!(x: 0.005, y: 0.0005), 5.0);
let condition = DistanceFromStepCondition {
minimum_horizontal_accuracy: 10,
distance: 100, calculate_while_off_route: false,
};
let trip_state = get_navigating_trip_state(
user_location,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::OffRoute {
deviation_from_route_line: 10.0,
},
);
let result = condition.should_advance_step(trip_state);
assert!(
!result.should_advance,
"Should not advance when far from the route"
);
}
#[test]
fn test_distance_from_step_advance() {
let user_location = make_user_location(coord!(x: 0.005, y: 0.0005), 5.0);
let condition = DistanceFromStepCondition {
minimum_horizontal_accuracy: 10,
distance: 100, calculate_while_off_route: false,
};
let trip_state = get_navigating_trip_state(
user_location,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = condition.should_advance_step(trip_state);
assert!(
result.should_advance,
"Should advance when far from the route"
);
}
#[test]
fn test_or_condition_doesnt_advance() {
let manual_condition1 = ManualStepCondition;
let manual_condition2 = ManualStepCondition;
let or_condition = OrAdvanceConditions {
conditions: vec![Arc::new(manual_condition1), Arc::new(manual_condition2)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_START_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = or_condition.should_advance_step(trip_state);
assert!(
!result.should_advance,
"Should not advance when all OR conditions are false"
);
}
#[test]
fn test_or_condition_advance() {
let manual_condition = ManualStepCondition;
let distance_condition = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 20, };
let or_condition = OrAdvanceConditions {
conditions: vec![Arc::new(manual_condition), Arc::new(distance_condition)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = or_condition.should_advance_step(trip_state);
assert!(
result.should_advance,
"Should advance when at least one OR condition is true"
);
}
#[test]
fn test_and_condition_doesnt_advance() {
let manual_condition = ManualStepCondition;
let distance_condition = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 20, };
let and_condition = AndAdvanceConditions {
conditions: vec![
Arc::new(manual_condition), Arc::new(distance_condition), ],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = and_condition.should_advance_step(trip_state);
assert!(
!result.should_advance,
"Should not advance when at least one AND condition is false"
);
}
#[test]
fn test_and_condition_advance() {
let distance_condition1 = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 30, };
let distance_condition2 = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 20, };
let and_condition = AndAdvanceConditions {
conditions: vec![Arc::new(distance_condition1), Arc::new(distance_condition2)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result = and_condition.should_advance_step(trip_state);
assert!(
result.should_advance,
"Should advance when all AND conditions are true"
);
}
#[test]
fn test_entry_and_exit_condition_doesnt_advance() {
let condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 20,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update even when near end of step"
);
let user_location_still_close = make_user_location(coord!(x: 0.001, y: 0.00002), 5.0);
let next_condition = result1.next_iteration;
let trip_state2 = get_navigating_trip_state(
user_location_still_close,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
!result2.should_advance,
"Should not advance when user hasn't moved far enough from the route"
);
}
#[test]
fn test_entry_and_exit_condition_advance() {
let condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 20,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update even when near end of step"
);
let next_condition = result1.next_iteration;
let user_location_far = make_user_location(coord!(x: 0.001, y: 0.0005), 5.0);
let trip_state2 = get_navigating_trip_state(
user_location_far,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when user has first reached end of step and then moved away"
);
}
#[test]
fn test_and_condition_preserves_state() {
let entry_exit_condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let and_condition = AndAdvanceConditions {
conditions: vec![Arc::new(entry_exit_condition)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = and_condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update"
);
let next_and_condition = result1.next_iteration;
let user_location_far = make_user_location(coord!(x: 0.001, y: 0.0005), 5.0);
let trip_state2 = get_navigating_trip_state(
user_location_far,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_and_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when stateful condition completes in AND"
);
}
#[test]
fn test_entry_and_exit_condition_in_and_composite_advance() {
let entry_exit_condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let distance_condition = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 100, };
let and_condition = AndAdvanceConditions {
conditions: vec![Arc::new(entry_exit_condition), Arc::new(distance_condition)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = and_condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update even when near end of step"
);
let next_condition = result1.next_iteration;
let user_location_far = make_user_location(coord!(x: 0.001, y: 0.0005), 5.0);
let trip_state2 = get_navigating_trip_state(
user_location_far,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when stateful condition completes within AND composite"
);
}
#[test]
fn test_entry_and_exit_condition_in_or_composite_advance() {
let entry_exit_condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let manual_condition = ManualStepCondition;
let or_condition = OrAdvanceConditions {
conditions: vec![Arc::new(entry_exit_condition), Arc::new(manual_condition)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = or_condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update even when near end of step"
);
let next_condition = result1.next_iteration;
let user_location_far = make_user_location(coord!(x: 0.001, y: 0.0005), 5.0);
let trip_state2 = get_navigating_trip_state(
user_location_far,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when stateful condition completes within OR composite"
);
}
#[test]
fn test_entry_and_exit_condition_resets_in_and_composite_when_advancing() {
let entry_exit_condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let distance_condition = DistanceToEndOfStepCondition {
minimum_horizontal_accuracy: 10,
distance: 100, };
let and_condition = AndAdvanceConditions {
conditions: vec![Arc::new(entry_exit_condition), Arc::new(distance_condition)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = and_condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update even when near end of step"
);
let next_condition = result1.next_iteration;
let user_location_far = make_user_location(coord!(x: 0.001, y: 0.0005), 5.0);
let trip_state2 = get_navigating_trip_state(
user_location_far,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when stateful condition completes within AND composite"
);
let reset_condition = result2.next_iteration;
let trip_state3 = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result3 = reset_condition.should_advance_step(trip_state3);
assert!(
!result3.should_advance,
"Should not advance immediately after reset - entry/exit condition should restart its two-phase process"
);
}
#[test]
fn test_route_snapped_entry_and_exit_condition_advance() {
let step1 = gen_route_step_with_coords(vec![
coord!(x: 0.0, y: 0.0), coord!(x: 0.001, y: 0.0), ]);
let step2 = gen_route_step_with_coords(vec![
coord!(x: 0.001, y: 0.0), coord!(x: 0.001, y: 0.001), ]);
let condition = DistanceEntryAndSnappedExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 10,
has_reached_end_of_current_step: false,
};
let location_near_end = make_user_location(coord!(x: 0.00099, y: 0.0), 5.0);
let trip_state = get_navigating_trip_state(
location_near_end,
vec![step1],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update when entering end zone"
);
let location_on_step2 = make_user_location(coord!(x: 0.001, y: 0.0001), 5.0);
let next_condition = result1.next_iteration;
let trip_state2 = get_navigating_trip_state(
location_on_step2,
vec![step2],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when route-snapped position has moved onto next step"
);
}
#[test]
fn test_route_snapped_entry_and_exit_with_short_next_step() {
let step1 = gen_route_step_with_coords(vec![
coord!(x: 0.0, y: 0.0), coord!(x: 0.001, y: 0.0), ]);
let step2 = gen_route_step_with_coords(vec![
coord!(x: 0.001, y: 0.0), coord!(x: 0.001, y: 0.000027), ]);
let step3 = gen_route_step_with_coords(vec![
coord!(x: 0.001, y: 0.000027), coord!(x: 0.001, y: 0.000054), ]);
let condition = DistanceEntryAndSnappedExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 10, minimum_horizontal_accuracy: 10,
has_reached_end_of_current_step: false,
};
let location_near_end = make_user_location(coord!(x: 0.00099, y: 0.0), 5.0);
let trip_state = get_navigating_trip_state(
location_near_end,
vec![step1, step2.clone(), step3.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update when entering end zone"
);
let location_on_step2 = make_user_location(coord!(x: 0.001, y: 0.000072), 5.0);
let next_condition = result1.next_iteration;
let trip_state2 = get_navigating_trip_state(
location_on_step2,
vec![step2, step3],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance with short next steps using min(configured, accumulated) distance"
);
}
#[test]
fn test_route_snapped_entry_and_exit_with_zero_length_via_waypoint() {
let step1 = gen_route_step_with_coords(vec![
coord!(x: 0.0, y: 0.0), coord!(x: 0.001, y: 0.0), ]);
let mut step2 = gen_route_step_with_coords(vec![
coord!(x: 0.001, y: 0.0), coord!(x: 0.001, y: 0.0), ]);
step2.distance = 0.0;
let step3 = gen_route_step_with_coords(vec![
coord!(x: 0.001, y: 0.0), coord!(x: 0.001, y: 0.001), ]);
let condition = DistanceEntryAndSnappedExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 10,
has_reached_end_of_current_step: false,
};
let location_near_end = make_user_location(coord!(x: 0.00099, y: 0.0), 5.0);
let trip_state = get_navigating_trip_state(
location_near_end,
vec![step1, step2.clone(), step3.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update when entering end zone"
);
let location_on_step3 = make_user_location(coord!(x: 0.001, y: 0.0001), 5.0);
let next_condition = result1.next_iteration;
let trip_state2 = get_navigating_trip_state(
location_on_step3,
vec![step2, step3],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when route-snapped position has moved onto step after 0-length via waypoint"
);
}
#[test]
fn test_entry_and_exit_condition_resets_in_or_composite_when_advancing() {
let entry_exit_condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 5,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let manual_condition = ManualStepCondition;
let or_condition = OrAdvanceConditions {
conditions: vec![Arc::new(entry_exit_condition), Arc::new(manual_condition)],
};
let trip_state = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = or_condition.should_advance_step(trip_state);
assert!(
!result1.should_advance,
"Should not advance on first update even when near end of step"
);
let next_condition = result1.next_iteration;
let user_location_far = make_user_location(coord!(x: 0.001, y: 0.0005), 5.0);
let trip_state2 = get_navigating_trip_state(
user_location_far,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
assert!(
result2.should_advance,
"Should advance when stateful condition completes within OR composite"
);
let reset_condition = result2.next_iteration;
let trip_state3 = get_navigating_trip_state(
*LOCATION_NEAR_END_OF_STEP,
vec![STRAIGHT_LINE_SHORT_ROUTE_STEP.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result3 = reset_condition.should_advance_step(trip_state3);
assert!(
!result3.should_advance,
"Should not advance immediately after reset - entry/exit condition should restart its two-phase process"
);
}
}
#[cfg(test)]
proptest! {
#[test]
fn manual_step_never_advances(
c1 in arb_coord(),
c2 in arb_coord(),
accuracy: f64
) {
let route_step =
crate::navigation_controller::test_helpers::gen_route_step_with_coords(vec![c1, c2]);
let user_location = make_user_location(c2, accuracy);
let condition = ManualStepCondition;
let trip_state = get_navigating_trip_state(
user_location,
vec![route_step],
vec![],
RouteDeviation::NoDeviation,
);
let result = condition.should_advance_step(trip_state);
prop_assert!(
!result.should_advance,
"Should not advance with the manual condition"
);
}
#[test]
fn entry_and_exit_never_advances_on_zero_movement(
c1 in arb_coord(),
c2 in arb_coord(),
) {
let route_step =
crate::navigation_controller::test_helpers::gen_route_step_with_coords(vec![c1, c2]);
let user_location = make_user_location(c2, 5.0);
let condition = DistanceEntryAndExitCondition {
distance_to_end_of_step: 10,
distance_after_end_of_step: 20,
minimum_horizontal_accuracy: 5,
has_reached_end_of_current_step: false,
};
let trip_state = get_navigating_trip_state(
user_location,
vec![route_step.clone()],
vec![],
RouteDeviation::NoDeviation,
);
let result1 = condition.should_advance_step(trip_state);
prop_assert!(
!result1.should_advance,
"Should not advance on first update even when at the end of the step"
);
let next_condition = result1.next_iteration;
let trip_state2 = get_navigating_trip_state(
user_location,
vec![route_step],
vec![],
RouteDeviation::NoDeviation,
);
let result2 = next_condition.should_advance_step(trip_state2);
prop_assert!(
!result2.should_advance,
"Should not advance when user hasn't moved far enough from the route"
);
}
}