Skip to main content

geospatial_types/
route.rs

1//! Route, leg, step, maneuver, lane guidance and distance-matrix records.
2
3use crate::bounding_box::BoundingBox;
4use crate::coordinate::GeographicCoordinate;
5use serde::{Deserialize, Serialize};
6
7/// Mode of transport used to calculate a route.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum TransportationMode {
11    /// Private car routing.
12    Automobile,
13    /// Bicycle routing.
14    Bicycle,
15    /// Walking routing.
16    Pedestrian,
17}
18
19/// Length and speed conventions used when displaying a route.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[serde(rename_all = "snake_case")]
22pub enum UnitSystem {
23    /// Kilometers and kilometers per hour.
24    Metric,
25    /// Miles and miles per hour.
26    Imperial,
27}
28
29/// Road features a routing request can ask the provider to avoid.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
31pub struct AvoidOptions {
32    /// Avoid toll roads.
33    pub toll_roads: bool,
34    /// Avoid limited-access highways and motorways.
35    pub limited_access_highways: bool,
36    /// Avoid ferry crossings.
37    pub ferries: bool,
38    /// Avoid unpaved roads.
39    pub unpaved_roads: bool,
40}
41
42/// What a routing request optimizes for.
43///
44/// Providers rank candidate roads by a cost function; the strategy selects
45/// between travel time and physical length (MapQuest `routeType`
46/// fastest vs shortest). Non-automobile modes have no time-based cost model
47/// in the current providers, so the strategy only applies to automobile
48/// routing and rerouting must reuse the request's strategy.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum RouteStrategy {
52    /// Minimize expected travel time.
53    #[default]
54    Fastest,
55    /// Minimize traveled distance.
56    Shortest,
57}
58
59/// Options controlling how a route is calculated.
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub struct RouteOptions {
62    /// Mode of transport.
63    pub transportation_mode: TransportationMode,
64    /// Whether routing optimizes travel time or distance.
65    pub route_strategy: RouteStrategy,
66    /// Display unit convention the provider should annotate.
67    pub unit_system: UnitSystem,
68    /// Features to avoid; all disabled by default.
69    pub avoid_options: AvoidOptions,
70    /// Departure instant as seconds since the Unix epoch, or `None` for
71    /// "depart now".
72    pub departure_timestamp_seconds: Option<u64>,
73}
74
75impl Default for RouteOptions {
76    fn default() -> Self {
77        Self {
78            transportation_mode: TransportationMode::Automobile,
79            route_strategy: RouteStrategy::Fastest,
80            unit_system: UnitSystem::Metric,
81            avoid_options: AvoidOptions::default(),
82            departure_timestamp_seconds: None,
83        }
84    }
85}
86
87/// Turn direction reported by a maneuver or a lane.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum TurnDirection {
91    /// Continue straight ahead.
92    Straight,
93    /// Sharp left turn.
94    SharpLeft,
95    /// Standard left turn.
96    Left,
97    /// Gentle left turn.
98    SlightLeft,
99    /// Gentle right turn.
100    SlightRight,
101    /// Standard right turn.
102    Right,
103    /// Sharp right turn.
104    SharpRight,
105    /// Full reversal of direction.
106    UTurn,
107    /// Leave the current road.
108    LeavingRoad,
109}
110
111/// High-level maneuver classification carried by a route step.
112///
113/// The classification serializes as its [`Self::stable_label`] — the single
114/// maneuver spelling across the SDK (the FFI route records carry the same
115/// labels, and the recorded trace fixtures speak them) — so a route fixture
116/// written from Rust and a foreign route record rebuilt from it cannot
117/// disagree on spelling.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
119pub enum ManeuverType {
120    /// Route start.
121    Departure,
122    /// Route end.
123    Arrival,
124    /// Road-name or road-class change without a turn.
125    Continue,
126    /// Left/right/slight/sharp turn, see [`TurnDirection`].
127    Turn(TurnDirection),
128    /// Merge onto another road.
129    Merge,
130    /// Motorway on-ramp.
131    OnRamp,
132    /// Motorway off-ramp.
133    OffRamp,
134    /// Fork in the road.
135    Fork,
136    /// Roundabout traversal; carries the exit index starting at one.
137    Roundabout(u8),
138    /// Transition to a ferry.
139    Ferry,
140}
141
142/// The label prefix of turn maneuvers in the stable spelling.
143const TURN_STABLE_LABEL_PREFIX: &str = "turn";
144/// The label prefix of roundabout maneuvers in the stable spelling.
145const ROUNDABOUT_STABLE_LABEL_PREFIX: &str = "roundabout";
146/// The hyphen joining a maneuver label to its embedded parameter.
147const STABLE_LABEL_PARAMETER_SEPARATOR: char = '-';
148/// The lowest one-based exit index a roundabout label may carry.
149const STABLE_LABEL_ROUNDABOUT_MINIMUM_EXIT_INDEX: u8 = 1;
150/// The serde error expectation naming the stable spelling for a decoder.
151const STABLE_LABEL_SERDE_EXPECTATION: &str = "a stable maneuver label such as \
152    \"departure\", \"turn-left\" or \"roundabout-2\"";
153
154/// The kebab-case parameter segment a turn direction contributes to its
155/// maneuver label (`left`, `sharp-left`, `u-turn`, …).
156fn turn_direction_label_segment(turn_direction: TurnDirection) -> &'static str {
157    match turn_direction {
158        TurnDirection::Straight => "straight",
159        TurnDirection::SharpLeft => "sharp-left",
160        TurnDirection::Left => "left",
161        TurnDirection::SlightLeft => "slight-left",
162        TurnDirection::SlightRight => "slight-right",
163        TurnDirection::Right => "right",
164        TurnDirection::SharpRight => "sharp-right",
165        TurnDirection::UTurn => "u-turn",
166        TurnDirection::LeavingRoad => "leaving-road",
167    }
168}
169
170/// Builds the turn direction a kebab-case parameter segment names; the
171/// inverse of [`turn_direction_label_segment`].
172fn turn_direction_from_label_segment(label_segment: &str) -> Option<TurnDirection> {
173    Some(match label_segment {
174        "straight" => TurnDirection::Straight,
175        "sharp-left" => TurnDirection::SharpLeft,
176        "left" => TurnDirection::Left,
177        "slight-left" => TurnDirection::SlightLeft,
178        "slight-right" => TurnDirection::SlightRight,
179        "right" => TurnDirection::Right,
180        "sharp-right" => TurnDirection::SharpRight,
181        "u-turn" => TurnDirection::UTurn,
182        "leaving-road" => TurnDirection::LeavingRoad,
183        _ => return None,
184    })
185}
186
187impl ManeuverType {
188    /// The stable snake-case label of this maneuver: `departure`,
189    /// `turn-left`, `roundabout-2`, …. This is the SDK's single maneuver
190    /// spelling — the FFI route records carry it and the serde
191    /// serialization emits it.
192    pub fn stable_label(&self) -> String {
193        match self {
194            Self::Departure => "departure".to_string(),
195            Self::Arrival => "arrival".to_string(),
196            Self::Continue => "continue".to_string(),
197            Self::Turn(turn_direction) => format!(
198                "{TURN_STABLE_LABEL_PREFIX}{STABLE_LABEL_PARAMETER_SEPARATOR}{}",
199                turn_direction_label_segment(*turn_direction)
200            ),
201            Self::Merge => "merge".to_string(),
202            Self::OnRamp => "on-ramp".to_string(),
203            Self::OffRamp => "off-ramp".to_string(),
204            Self::Fork => "fork".to_string(),
205            Self::Roundabout(exit_index) => format!(
206                "{ROUNDABOUT_STABLE_LABEL_PREFIX}{STABLE_LABEL_PARAMETER_SEPARATOR}{exit_index}"
207            ),
208            Self::Ferry => "ferry".to_string(),
209        }
210    }
211
212    /// Builds the maneuver a stable label (see [`Self::stable_label`]) names;
213    /// `None` for unknown or malformed labels — refused, never guessed.
214    pub fn from_stable_label(stable_label: &str) -> Option<Self> {
215        match stable_label {
216            "departure" => Some(Self::Departure),
217            "arrival" => Some(Self::Arrival),
218            "continue" => Some(Self::Continue),
219            "merge" => Some(Self::Merge),
220            "on-ramp" => Some(Self::OnRamp),
221            "off-ramp" => Some(Self::OffRamp),
222            "fork" => Some(Self::Fork),
223            "ferry" => Some(Self::Ferry),
224            _ => parameterized_from_stable_label(stable_label),
225        }
226    }
227}
228
229/// Builds the parameterized maneuvers (`Turn(…)`, `Roundabout(…)`) a stable
230/// label names; `None` for anything that is not a well-formed parameterized
231/// label of those kinds.
232fn parameterized_from_stable_label(stable_label: &str) -> Option<ManeuverType> {
233    let label_parameter = |label_prefix: &str| {
234        stable_label
235            .strip_prefix(label_prefix)
236            .and_then(|rest| rest.strip_prefix(STABLE_LABEL_PARAMETER_SEPARATOR))
237    };
238    if let Some(turn_direction) =
239        label_parameter(TURN_STABLE_LABEL_PREFIX).and_then(turn_direction_from_label_segment)
240    {
241        return Some(ManeuverType::Turn(turn_direction));
242    }
243    label_parameter(ROUNDABOUT_STABLE_LABEL_PREFIX)
244        .and_then(|exit_index_text| exit_index_text.parse::<u8>().ok())
245        .filter(|exit_index| *exit_index >= STABLE_LABEL_ROUNDABOUT_MINIMUM_EXIT_INDEX)
246        .map(ManeuverType::Roundabout)
247}
248
249/// Manoeuvres serialize as their stable label.
250impl Serialize for ManeuverType {
251    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
252        serializer.serialize_str(&self.stable_label())
253    }
254}
255
256/// Manoeuvres deserialize from their stable label; any other form is a
257/// decoding error, not a guess.
258impl<'de> Deserialize<'de> for ManeuverType {
259    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
260        struct StableLabelVisitor;
261
262        impl serde::de::Visitor<'_> for StableLabelVisitor {
263            type Value = ManeuverType;
264
265            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266                formatter.write_str(STABLE_LABEL_SERDE_EXPECTATION)
267            }
268
269            fn visit_str<E: serde::de::Error>(self, label: &str) -> Result<Self::Value, E> {
270                ManeuverType::from_stable_label(label).ok_or_else(|| {
271                    E::invalid_value(
272                        serde::de::Unexpected::Str(label),
273                        &STABLE_LABEL_SERDE_EXPECTATION,
274                    )
275                })
276            }
277        }
278
279        deserializer.deserialize_str(StableLabelVisitor)
280    }
281}
282
283/// Recommended lane(s) for an upcoming maneuver.
284#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
285pub struct LaneGuidance {
286    /// Zero-based lane index across the road cross-section.
287    pub lane_index: u8,
288    /// Directions this lane can legally lead to.
289    pub permitted_directions: Vec<TurnDirection>,
290    /// Whether this lane is among the recommended ones.
291    pub is_recommended: bool,
292}
293
294/// One maneuver within a route leg.
295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
296pub struct RouteStep {
297    /// Distance of this step in meters.
298    pub distance_meters: f64,
299    /// Travel time of this step in seconds.
300    pub duration_seconds: f64,
301    /// Maneuver classification.
302    pub maneuver_type: ManeuverType,
303    /// Human-readable instruction in the display language.
304    pub instruction_display_text: String,
305    /// Instruction tuned for text-to-speech engines.
306    pub instruction_speech_text: String,
307    /// Step geometry from the previous step end to this maneuver point.
308    pub shape: Vec<GeographicCoordinate>,
309    /// Lane guidance available at the maneuver, oldest lane first.
310    pub lane_guidances: Vec<LaneGuidance>,
311}
312
313/// Leg between two consecutive waypoints.
314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
315pub struct RouteLeg {
316    /// Leg distance in meters.
317    pub distance_meters: f64,
318    /// Leg travel time in seconds.
319    pub duration_seconds: f64,
320    /// Maneuver steps in traversal order.
321    pub steps: Vec<RouteStep>,
322}
323
324/// A complete calculated route.
325#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
326pub struct RouteData {
327    /// Total distance across all legs, in meters.
328    pub distance_meters: f64,
329    /// Total travel time across all legs, in seconds.
330    pub duration_seconds: f64,
331    /// Geographic bounds of the full route geometry.
332    pub bounding_box: BoundingBox,
333    /// Legs, one per consecutive waypoint pair.
334    pub legs: Vec<RouteLeg>,
335}
336
337/// One origin-destination cell of a distance matrix.
338#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
339pub struct DistanceMatrixEntry {
340    /// Origin index into the request's origins.
341    pub origin_index: u32,
342    /// Destination index into the request's destinations.
343    pub destination_index: u32,
344    /// Distance for this cell in meters.
345    pub distance_meters: f64,
346    /// Travel time for this cell in seconds.
347    pub duration_seconds: f64,
348}
349
350/// Travel-time matrix for origins × destinations.
351#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
352pub struct DistanceMatrix {
353    /// Entries in row-major origin-major order.
354    pub entries: Vec<DistanceMatrixEntry>,
355}