#[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>,
}
#[cfg(feature = "wasm-bindgen")]
mod system_time_format {
use serde::{self, Deserialize, Deserializer, Serializer};
#[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};
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() * 1000 + duration.subsec_millis() as u64;
serializer.serialize_u64(millis)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
where
D: Deserializer<'de>,
{
let millis = u64::deserialize(deserializer)?;
Ok(UNIX_EPOCH + Duration::from_millis(millis))
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", 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,
pub horizontal_accuracy: f64,
pub course_over_ground: Option<CourseOverGround>,
#[cfg_attr(test, serde(skip_serializing))]
#[cfg_attr(feature = "wasm-bindgen", 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))]
#[cfg_attr(feature = "wasm-bindgen", 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,
pub road_name: Option<String>,
pub exits: Vec<String>,
pub instruction: String,
pub visual_instructions: Vec<VisualInstruction>,
pub spoken_instructions: Vec<SpokenInstruction>,
pub annotations: Option<Vec<String>>,
pub incidents: Vec<Incident>,
pub driving_side: Option<DrivingSide>,
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))]
#[cfg_attr(feature = "wasm-bindgen", 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>,
pub trigger_distance_before_maneuver: f64,
#[cfg_attr(test, serde(skip_serializing))]
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "string"))]
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))]
#[cfg_attr(feature = "wasm-bindgen", 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))]
#[cfg_attr(feature = "wasm-bindgen", 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,
pub incident_type: IncidentType,
pub description: Option<String>,
pub long_description: Option<String>,
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date | null"))]
pub creation_time: Option<DateTime<Utc>>,
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date | null"))]
pub start_time: Option<DateTime<Utc>>,
#[cfg_attr(feature = "wasm-bindgen", tsify(type = "Date | null"))]
pub end_time: Option<DateTime<Utc>>,
pub impact: Option<Impact>,
pub lanes_blocked: Vec<BlockedLane>,
pub congestion: Option<Congestion>,
pub closed: Option<bool>,
pub geometry_index_start: u64,
pub geometry_index_end: Option<u64>,
pub sub_type: Option<String>,
pub sub_type_description: Option<String>,
pub iso_3166_1_alpha2: Option<String>,
pub iso_3166_1_alpha3: Option<String>,
pub affected_road_names: Vec<String>,
pub bbox: Option<BoundingBox>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", 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>,
pub active_direction: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", 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,
pub maneuver_type: Option<ManeuverType>,
pub maneuver_modifier: Option<ManeuverModifier>,
pub roundabout_exit_degrees: Option<u16>,
pub lane_info: Option<Vec<LaneInfo>>,
pub exit_numbers: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm-bindgen", 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 {
pub primary_content: VisualInstructionContent,
pub secondary_content: Option<VisualInstructionContent>,
pub sub_content: Option<VisualInstructionContent>,
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);
}
}