ferrostar 0.49.0

The core of modern turn-by-turn navigation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
//! State and configuration data models.

use super::step_advance::conditions::ManualStepCondition;
use super::step_advance::{SerializableStepAdvanceCondition, StepAdvanceCondition};
use crate::algorithms::distance_between_locations;
use crate::deviation_detection::{RouteDeviation, RouteDeviationTracking};
use crate::models::{RouteStep, SpokenInstruction, UserLocation, VisualInstruction, Waypoint};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[cfg(feature = "wasm-bindgen")]
use tsify::Tsify;

/// The navigation state.
///
/// This is typically created from an initial trip state
/// and conditions for advancing navigation to the next step.
/// Any internal navigation state is packed in here so that
/// the navigation controller can remain functionally pure.
#[derive(Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct NavState {
    trip_state: TripState,
    // This has to be here because we actually do need to update the internal state that changes throughout navigation.
    step_advance_condition: Arc<dyn StepAdvanceCondition>,
}

impl NavState {
    /// Creates a new navigation state with the provided trip state and step advance condition.
    pub fn new(
        trip_state: TripState,
        step_advance_condition: Arc<dyn StepAdvanceCondition>,
    ) -> Self {
        Self {
            trip_state,
            step_advance_condition,
        }
    }

    /// Creates a new idle navigation state (no trip currently in progress, but still tracking the user's location).
    pub fn idle(user_location: Option<UserLocation>) -> Self {
        Self {
            trip_state: TripState::Idle { user_location },
            step_advance_condition: Arc::new(ManualStepCondition {}), // No op condition.
        }
    }

    /// Creates a navigation state indicating the trip is complete (arrived at the destination but still tracking the user's location).
    ///
    /// The summary is retained as a snapshot (the caller should have this from the last known state).
    pub fn complete(user_location: UserLocation, last_summary: TripSummary) -> Self {
        Self {
            trip_state: TripState::Complete {
                user_location,
                summary: TripSummary {
                    ended_at: Some(Utc::now()),
                    ..last_summary
                },
            },
            step_advance_condition: Arc::new(ManualStepCondition {}), // No op condition.
        }
    }

    #[inline]
    pub fn trip_state(&self) -> TripState {
        self.trip_state.clone()
    }

    #[inline]
    pub fn step_advance_condition(&self) -> Arc<dyn StepAdvanceCondition> {
        self.step_advance_condition.clone()
    }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct SerializableNavState {
    pub(crate) trip_state: TripState,
    // This has to be here because we actually do need to update the internal state that changes throughout navigation.
    pub(crate) step_advance_condition: SerializableStepAdvanceCondition,
}

impl From<SerializableNavState> for NavState {
    fn from(value: SerializableNavState) -> Self {
        Self {
            trip_state: value.trip_state,
            step_advance_condition: value.step_advance_condition.into(),
        }
    }
}

impl From<NavState> for SerializableNavState {
    fn from(value: NavState) -> Self {
        Self {
            trip_state: value.trip_state,
            step_advance_condition: value.step_advance_condition.to_js(),
        }
    }
}

/// High-level state describing progress through a route.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(any(feature = "wasm-bindgen", test), serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct TripProgress {
    /// The distance to the next maneuver, in meters.
    pub distance_to_next_maneuver: f64,
    /// The total distance remaining in the trip, in meters.
    ///
    /// This is the sum of the distance remaining in the current step and the distance remaining in all subsequent steps.
    pub distance_remaining: f64,
    /// The total duration remaining in the trip, in seconds.
    pub duration_remaining: f64,
}

/// Information pertaining to the user's full navigation trip. This includes
/// simple stats like total duration and distance.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(any(feature = "wasm-bindgen", test), serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct TripSummary {
    /// The total raw distance traveled in the trip, in meters.
    pub distance_traveled: f64,
    /// The total snapped distance traveled in the trip, in meters.
    pub snapped_distance_traveled: f64,
    /// When the trip was started.
    #[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date"))]
    pub started_at: DateTime<Utc>,
    /// When the trip was completed or canceled.
    #[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date | null"))]
    pub ended_at: Option<DateTime<Utc>>,
}

impl TripSummary {
    pub(crate) fn update(
        &self,
        previous_location: &UserLocation,
        current_location: &UserLocation,
        previous_snapped_location: &UserLocation,
        current_snapped_location: &UserLocation,
    ) -> Self {
        // Calculate distance increment between the user locations.
        let distance_increment = distance_between_locations(previous_location, current_location);
        let snapped_distance_increment =
            distance_between_locations(previous_snapped_location, current_snapped_location);

        TripSummary {
            distance_traveled: self.distance_traveled + distance_increment,
            snapped_distance_traveled: self.snapped_distance_traveled + snapped_distance_increment,
            started_at: self.started_at,
            ended_at: self.ended_at,
        }
    }
}

/// The state of a navigation session.
///
/// This is produced by [`NavigationController`](super::NavigationController) methods
/// including [`get_initial_state`](super::NavigationController::get_initial_state)
/// and [`update_user_location`](super::NavigationController::update_user_location).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
#[allow(clippy::large_enum_variant)]
pub enum TripState {
    /// The navigation controller is idle and there is no active trip.
    Idle { user_location: Option<UserLocation> },
    #[cfg_attr(feature = "wasm-bindgen", serde(rename_all = "camelCase"))]
    /// The navigation controller is actively navigating a trip.
    Navigating {
        /// The index of the closest coordinate to the user's snapped location.
        ///
        /// This index is relative to the *current* [`RouteStep`]'s geometry.
        current_step_geometry_index: Option<u64>,
        /// The user's raw location.
        ///
        /// This is more useful than the snapped location when the user is off route,
        /// or in special situations like pedestrian navigation.
        user_location: UserLocation,
        /// The user's location as if they were exactly on the route.
        ///
        /// This is derived by snapping the latitude and longitude to the closest point on the route line,
        /// regardless of where they actually are.
        /// This is desirable as it makes the navigation experience better for vehicular navigation,
        /// removing GPS noise as long as the user is deemed to be on the route.
        ///
        /// All other properties from the [`UserLocation`], including speed and course,
        /// are not affected by snapping.
        snapped_user_location: UserLocation,
        /// 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.
        remaining_steps: Vec<RouteStep>,
        /// Remaining waypoints to visit on the route.
        ///
        /// The waypoint at the front of the list is always the *next* waypoint "goal."
        /// Unlike the current step, there is no value in tracking the "current" waypoint,
        /// as the main use of waypoints is recalculation when the user deviates from the route.
        /// (In most use cases, a route will have only two waypoints, but more complex use cases
        /// may have multiple intervening points that are visited along the route.)
        /// This list is updated as the user advances through the route.
        remaining_waypoints: Vec<Waypoint>,
        /// The trip progress includes information that is useful for showing the
        /// user's progress along the full navigation trip, the route and its components.
        progress: TripProgress,
        /// Information pertaining to the user's full navigation trip. This includes
        /// simple stats like total duration, and distance.
        summary: TripSummary,
        /// The route deviation status: is the user following the route or not?
        deviation: RouteDeviation,
        /// The visual instruction that should be displayed in the user interface.
        visual_instruction: Option<VisualInstruction>,
        /// The most recent spoken instruction that should be synthesized using TTS.
        ///
        /// Note it is the responsibility of the platform layer to ensure that utterances are not synthesized multiple times. This property simply reports the current spoken instruction.
        spoken_instruction: Option<SpokenInstruction>,
        /// Annotation data at the current location.
        /// This is represented as a json formatted byte array to allow for flexible encoding of custom annotations.
        annotation_json: Option<String>,
    },
    /// The navigation controller has reached the end of the trip.
    Complete {
        user_location: UserLocation,
        /// Information pertaining to the user's full navigation trip. This includes
        /// simple stats like total duration, and distance.
        summary: TripSummary,
    },
}

impl TripState {
    pub(crate) fn user_location(&self) -> Option<UserLocation> {
        match self {
            TripState::Navigating { user_location, .. } => Some(*user_location),
            _ => None,
        }
    }

    pub(crate) fn snapped_user_location(&self) -> Option<UserLocation> {
        match self {
            TripState::Navigating {
                snapped_user_location,
                ..
            } => Some(*snapped_user_location),
            _ => None,
        }
    }

    pub(crate) fn current_step(&self) -> Option<RouteStep> {
        match self {
            TripState::Navigating {
                remaining_steps, ..
            } => remaining_steps.first().cloned(),
            _ => None,
        }
    }

    pub(crate) fn next_step(&self) -> Option<RouteStep> {
        self.get_step(1)
    }

    pub(crate) fn get_step(&self, index: usize) -> Option<RouteStep> {
        match self {
            TripState::Navigating {
                remaining_steps, ..
            } => remaining_steps.get(index).cloned(),
            _ => None,
        }
    }

    pub(crate) fn deviation(&self) -> Option<RouteDeviation> {
        match self {
            TripState::Navigating { deviation, .. } => Some(*deviation),
            _ => None,
        }
    }
}

#[allow(clippy::large_enum_variant)]
pub enum StepAdvanceStatus {
    /// Navigation has advanced, and the information on the next step is embedded.
    Advanced { step: RouteStep },
    /// Navigation has reached the end of the route.
    EndOfRoute,
}

/// Controls filtering/post-processing of user course by the [`NavigationController`].
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(from_wasm_abi))]
pub enum CourseFiltering {
    /// Snap the user's course to the current step's linestring using the next index in the step's geometry.
    ///
    // TODO: We could make this more flexible by allowing the user to specify number of indices to average, etc.
    SnapToRoute,

    /// Use the raw course as reported by the location provider with no processing.
    Raw,
}

/// Controls when a waypoint should be marked as complete.
///
/// While a route may consist of thousands of points, waypoints are special.
/// A simple trip will have only one waypoint: the final destination.
/// A more complex trip may have several intermediate stops.
/// Just as the navigation state keeps track of which steps remain in the route,
/// it also tracks which waypoints are still remaining.
///
/// Tracking waypoints enables Ferrostar to reroute users when they stray off the route line.
/// The waypoint advance mode specifies how the framework decides
/// that a waypoint has been visited (and is removed from the list).
///
/// NOTE: Advancing to the next *step* and advancing to the next *waypoint*
/// are separate processes.
/// This will not normally cause any issues, but keep in mind that
/// manually advancing to the next step does not *necessarily* imply
/// that the waypoint will be marked as complete!
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(from_wasm_abi))]
pub enum WaypointAdvanceMode {
    /// Advance when the waypoint is within a certain range of meters from the user's location.
    ///
    /// This condition is potentially more rigorous, requiring the user to actually visit within
    /// a range of every waypoint regardless of step advance.
    WaypointWithinRange(f64),
    /// Advance when a waypoint is within a certain range of meters of any point on the advancing step.
    ///
    /// This condition considers the step being advanced, not the user's location. As a result,
    /// it can recover when your step advance conditions allow the user to skip forward on the route.
    WaypointAlongAdvancingStep(f64),
}

#[derive(Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct NavigationControllerConfig {
    /// Configures when navigation advances to the next waypoint in the route.
    pub waypoint_advance: WaypointAdvanceMode,
    /// Configures when navigation advances to the next step in the route.
    pub step_advance_condition: Arc<dyn StepAdvanceCondition>,
    /// A special advance condition used for the final 2 route steps (last and arrival).
    ///
    /// This exists because several of our step advance conditions require entry and
    /// exit from a step's geometry. The end of the route/arrival doesn't always accommodate
    /// the expected location updates for the core step advance condition.
    pub arrival_step_advance_condition: Arc<dyn StepAdvanceCondition>,
    /// Configures when the user is deemed to be off course.
    ///
    /// NOTE: This is distinct from the action that is taken.
    /// It is only the determination that the user has deviated from the expected route.
    pub route_deviation_tracking: RouteDeviationTracking,
    /// Configures how the heading component of the snapped location is reported in [`TripState`].
    pub snapped_location_course_filtering: CourseFiltering,
}

#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "wasm-bindgen", tsify(from_wasm_abi))]
pub struct SerializableNavigationControllerConfig {
    /// Configures when navigation advances to the next waypoint in the route.
    pub waypoint_advance: WaypointAdvanceMode,
    /// Configures when navigation advances to the next step in the route.
    pub step_advance_condition: SerializableStepAdvanceCondition,
    /// A special advance condition used for the final 2 route steps (last and arrival).
    ///
    /// This exists because several of our step advance conditions require entry and
    /// exit from a step's geometry. The end of the route/arrival doesn't always accommodate
    /// the expected location updates for the core step advance condition.
    pub arrival_step_advance_condition: SerializableStepAdvanceCondition,
    /// Configures when the user is deemed to be off course.
    ///
    /// NOTE: This is distinct from the action that is taken.
    /// It is only the determination that the user has deviated from the expected route.
    pub route_deviation_tracking: RouteDeviationTracking,
    /// Configures how the heading component of the snapped location is reported in [`TripState`].
    pub snapped_location_course_filtering: CourseFiltering,
}

impl From<SerializableNavigationControllerConfig> for NavigationControllerConfig {
    fn from(js_config: SerializableNavigationControllerConfig) -> Self {
        Self {
            waypoint_advance: js_config.waypoint_advance,
            step_advance_condition: js_config.step_advance_condition.into(),
            arrival_step_advance_condition: js_config.arrival_step_advance_condition.into(),
            route_deviation_tracking: js_config.route_deviation_tracking,
            snapped_location_course_filtering: js_config.snapped_location_course_filtering,
        }
    }
}

impl From<NavigationControllerConfig> for SerializableNavigationControllerConfig {
    fn from(config: NavigationControllerConfig) -> Self {
        Self {
            waypoint_advance: config.waypoint_advance,
            step_advance_condition: config.step_advance_condition.to_js(),
            arrival_step_advance_condition: config.arrival_step_advance_condition.to_js(),
            route_deviation_tracking: config.route_deviation_tracking,
            snapped_location_course_filtering: config.snapped_location_course_filtering,
        }
    }
}