#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
use geo::{Coord, LineString, Point, Rect};
#[cfg(feature = "uniffi")]
use polyline::encode_coordinates;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[cfg(all(feature = "std", not(feature = "web-time")))]
use std::time::SystemTime;
#[cfg(feature = "web-time")]
use web_time::SystemTime;
#[cfg(feature = "wasm-bindgen")]
use tsify::Tsify;
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use uuid::Uuid;
use crate::algorithms::get_linestring;
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
pub enum ModelError {
#[error("Failed to generate a polyline from route coordinates: {error}.")]
PolylineGenerationError { error: String },
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct GeographicCoordinate {
pub lat: f64,
pub lng: f64,
}
impl From<Coord> for GeographicCoordinate {
fn from(value: Coord) -> Self {
Self {
lat: value.y,
lng: value.x,
}
}
}
impl From<Point> for GeographicCoordinate {
fn from(value: Point) -> Self {
Self {
lat: value.y(),
lng: value.x(),
}
}
}
impl From<GeographicCoordinate> for Coord {
fn from(value: GeographicCoordinate) -> Self {
Self {
x: value.lng,
y: value.lat,
}
}
}
impl From<GeographicCoordinate> for Point {
fn from(value: GeographicCoordinate) -> Self {
Self(value.into())
}
}
#[derive(Clone, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Waypoint {
pub coordinate: GeographicCoordinate,
pub kind: WaypointKind,
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub properties: Option<Vec<u8>>,
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, 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))]
pub enum WaypointKind {
Break,
Via,
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct BoundingBox {
pub sw: GeographicCoordinate,
pub ne: GeographicCoordinate,
}
impl From<Rect> for BoundingBox {
fn from(value: Rect) -> Self {
Self {
sw: value.min().into(),
ne: value.max().into(),
}
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Heading {
pub true_heading: u16,
pub accuracy: u16,
pub timestamp: SystemTime,
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct CourseOverGround {
pub degrees: u16,
pub accuracy: Option<u16>,
}
impl CourseOverGround {
pub fn new(degrees: f64, accuracy: Option<u16>) -> Self {
debug_assert!(degrees >= 0.0 && degrees < 360.0);
Self {
degrees: degrees.round() as u16,
accuracy,
}
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Speed {
pub value: f64,
pub accuracy: Option<f64>,
}
mod system_time_format {
use serde::{Deserialize, Deserializer, Serializer, de::Error as _};
#[cfg(all(feature = "std", not(feature = "web-time")))]
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[cfg(feature = "web-time")]
use web_time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Deserialize)]
#[serde(untagged)]
enum SystemTimeRepresentation {
Milliseconds(u64),
Legacy {
secs_since_epoch: u64,
nanos_since_epoch: u32,
},
}
pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let duration = time
.duration_since(UNIX_EPOCH)
.map_err(serde::ser::Error::custom)?;
let millis = duration
.as_secs()
.checked_mul(1_000)
.and_then(|millis| millis.checked_add(u64::from(duration.subsec_millis())))
.ok_or_else(|| serde::ser::Error::custom("system time exceeds epoch milliseconds"))?;
serializer.serialize_u64(millis)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
where
D: Deserializer<'de>,
{
let millis = match SystemTimeRepresentation::deserialize(deserializer)? {
SystemTimeRepresentation::Milliseconds(millis) => millis,
SystemTimeRepresentation::Legacy {
secs_since_epoch,
nanos_since_epoch,
} => {
if nanos_since_epoch >= 1_000_000_000 {
return Err(D::Error::custom(
"nanos_since_epoch must be less than 1000000000",
));
}
secs_since_epoch
.checked_mul(1_000)
.and_then(|millis| millis.checked_add(u64::from(nanos_since_epoch / 1_000_000)))
.ok_or_else(|| D::Error::custom("system time exceeds epoch milliseconds"))?
}
};
UNIX_EPOCH
.checked_add(Duration::from_millis(millis))
.ok_or_else(|| D::Error::custom("system time is outside the supported range"))
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct UserLocation {
pub coordinates: GeographicCoordinate,
#[serde(alias = "horizontal_accuracy")]
pub horizontal_accuracy: f64,
#[serde(alias = "course_over_ground")]
pub course_over_ground: Option<CourseOverGround>,
#[serde(with = "system_time_format")]
pub timestamp: SystemTime,
pub speed: Option<Speed>,
}
impl From<UserLocation> for Point {
fn from(val: UserLocation) -> Point {
Point::new(val.coordinates.lng, val.coordinates.lat)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Route {
pub geometry: Vec<GeographicCoordinate>,
pub bbox: BoundingBox,
pub distance: f64,
pub waypoints: Vec<Waypoint>,
pub steps: Vec<RouteStep>,
}
impl Route {
pub fn get_linestring(&self) -> LineString {
get_linestring(&self.geometry)
}
}
#[cfg(feature = "uniffi")]
#[uniffi::export]
fn get_route_polyline(route: &Route, precision: u32) -> Result<String, ModelError> {
encode_coordinates(route.geometry.iter().map(|c| Coord::from(*c)), precision).map_err(|error| {
ModelError::PolylineGenerationError {
error: error.to_string(),
}
})
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct RouteStep {
pub geometry: Vec<GeographicCoordinate>,
pub distance: f64,
pub duration: f64,
#[serde(alias = "road_name")]
pub road_name: Option<String>,
pub exits: Vec<String>,
pub instruction: String,
#[serde(alias = "visual_instructions")]
pub visual_instructions: Vec<VisualInstruction>,
#[serde(alias = "spoken_instructions")]
pub spoken_instructions: Vec<SpokenInstruction>,
pub annotations: Option<Vec<String>>,
pub incidents: Vec<Incident>,
#[serde(alias = "driving_side")]
pub driving_side: Option<DrivingSide>,
#[serde(alias = "roundabout_exit_number")]
pub roundabout_exit_number: Option<u8>,
}
impl RouteStep {
pub(crate) fn get_linestring(&self) -> LineString {
get_linestring(&self.geometry)
}
pub fn get_active_visual_instruction(
&self,
distance_to_end_of_step: f64,
) -> Option<&VisualInstruction> {
self.visual_instructions.iter().rev().find(|instruction| {
distance_to_end_of_step - instruction.trigger_distance_before_maneuver <= 5.0
})
}
pub fn get_current_spoken_instruction(
&self,
distance_to_end_of_step: f64,
) -> Option<&SpokenInstruction> {
self.spoken_instructions.iter().rev().find(|instruction| {
distance_to_end_of_step - instruction.trigger_distance_before_maneuver <= 5.0
})
}
pub fn get_annotation_at_current_index(&self, at_coordinate_index: u64) -> Option<String> {
self.annotations
.as_ref()
.and_then(|annotations| annotations.get(at_coordinate_index as usize).cloned())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi))]
pub struct SpokenInstruction {
pub text: String,
pub ssml: Option<String>,
#[serde(alias = "trigger_distance_before_maneuver")]
pub trigger_distance_before_maneuver: f64,
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "string"))]
#[serde(alias = "utterance_id")]
pub utterance_id: Uuid,
}
#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[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))]
#[serde(rename_all = "lowercase")]
pub enum ManeuverType {
Turn,
#[serde(rename = "new name")]
NewName,
Depart,
Arrive,
Merge,
#[serde(rename = "on ramp")]
OnRamp,
#[serde(rename = "off ramp")]
OffRamp,
Fork,
#[serde(rename = "end of road")]
EndOfRoad,
Continue,
Roundabout,
Rotary,
#[serde(rename = "roundabout turn")]
RoundaboutTurn,
Notification,
#[serde(rename = "exit roundabout")]
ExitRoundabout,
#[serde(rename = "exit rotary")]
ExitRotary,
}
#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[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))]
#[serde(rename_all = "lowercase")]
pub enum ManeuverModifier {
#[serde(rename = "uturn")]
UTurn,
#[serde(rename = "sharp right")]
SharpRight,
Right,
#[serde(rename = "slight right")]
SlightRight,
Straight,
#[serde(rename = "slight left")]
SlightLeft,
Left,
#[serde(rename = "sharp left")]
SharpLeft,
}
#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[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))]
#[serde(rename_all = "lowercase")]
pub enum DrivingSide {
Left,
Right,
}
#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[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))]
#[serde(rename_all = "snake_case")]
pub enum IncidentType {
Accident,
Congestion,
Construction,
DisabledVehicle,
LaneRestriction,
MassTransit,
Miscellaneous,
OtherNews,
PlannedEvent,
RoadClosure,
RoadHazard,
Weather,
}
#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[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))]
#[serde(rename_all = "lowercase")]
pub enum Impact {
Unknown,
Critical,
Major,
Minor,
Low,
}
#[derive(Debug, Copy, Clone, Eq, 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))]
#[serde(rename_all = "lowercase")]
pub enum BlockedLane {
Left,
#[serde(rename = "left center")]
LeftCenter,
#[serde(rename = "left turn lane")]
LeftTurnLane,
Center,
Right,
#[serde(rename = "right center")]
RightCenter,
#[serde(rename = "right turn lane")]
RightTurnLane,
#[serde(rename = "hov")]
HOV,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Congestion {
pub value: u8,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Incident {
pub id: String,
#[serde(alias = "incident_type")]
pub incident_type: IncidentType,
pub description: Option<String>,
#[serde(alias = "long_description")]
pub long_description: Option<String>,
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date | null"))]
#[serde(alias = "creation_time")]
pub creation_time: Option<DateTime<Utc>>,
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date | null"))]
#[serde(alias = "start_time")]
pub start_time: Option<DateTime<Utc>>,
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date | null"))]
#[serde(alias = "end_time")]
pub end_time: Option<DateTime<Utc>>,
pub impact: Option<Impact>,
#[serde(alias = "lanes_blocked")]
pub lanes_blocked: Vec<BlockedLane>,
pub congestion: Option<Congestion>,
pub closed: Option<bool>,
#[serde(alias = "geometry_index_start")]
pub geometry_index_start: u64,
#[serde(alias = "geometry_index_end")]
pub geometry_index_end: Option<u64>,
#[serde(alias = "sub_type")]
pub sub_type: Option<String>,
#[serde(alias = "sub_type_description")]
pub sub_type_description: Option<String>,
#[serde(alias = "iso_3166_1_alpha2")]
pub iso_3166_1_alpha2: Option<String>,
#[serde(alias = "iso_3166_1_alpha3")]
pub iso_3166_1_alpha3: Option<String>,
#[serde(alias = "affected_road_names")]
pub affected_road_names: Vec<String>,
pub bbox: Option<BoundingBox>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct LaneInfo {
pub active: bool,
pub directions: Vec<String>,
#[serde(alias = "active_direction")]
pub active_direction: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct VisualInstructionContent {
pub text: String,
#[serde(alias = "maneuver_type")]
pub maneuver_type: Option<ManeuverType>,
#[serde(alias = "maneuver_modifier")]
pub maneuver_modifier: Option<ManeuverModifier>,
#[serde(alias = "roundabout_exit_degrees")]
pub roundabout_exit_degrees: Option<u16>,
#[serde(alias = "lane_info")]
pub lane_info: Option<Vec<LaneInfo>>,
#[serde(alias = "exit_numbers")]
pub exit_numbers: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm-bindgen", derive(Tsify))]
#[cfg_attr(feature = "wasm-bindgen", tsify(into_wasm_abi, from_wasm_abi))]
pub struct VisualInstruction {
#[serde(alias = "primary_content")]
pub primary_content: VisualInstructionContent,
#[serde(alias = "secondary_content")]
pub secondary_content: Option<VisualInstructionContent>,
#[serde(alias = "sub_content")]
pub sub_content: Option<VisualInstructionContent>,
#[serde(alias = "trigger_distance_before_maneuver")]
pub trigger_distance_before_maneuver: f64,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub struct AnyAnnotationValue {
#[serde(flatten)]
pub value: HashMap<String, Value>,
}
#[cfg(test)]
#[cfg(feature = "uniffi")]
mod tests {
use super::*;
#[test]
fn test_polyline_encode() {
let sw = GeographicCoordinate { lng: 0.0, lat: 0.0 };
let ne = GeographicCoordinate { lng: 1.0, lat: 1.0 };
let route = Route {
geometry: vec![sw, ne],
bbox: BoundingBox { sw, ne },
distance: 0.0,
waypoints: vec![],
steps: vec![],
};
let polyline5 = get_route_polyline(&route, 5).expect("Unable to encode polyline for route");
insta::assert_yaml_snapshot!(polyline5);
let polyline6 = get_route_polyline(&route, 6).expect("Unable to encode polyline for route");
insta::assert_yaml_snapshot!(polyline6);
}
}