Skip to main content

geospatial_types/
traffic.rs

1//! Traffic incident and traffic flow records.
2
3use crate::coordinate::GeographicCoordinate;
4use serde::{Deserialize, Serialize};
5
6/// Class of disruption reported in a traffic incident.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum TrafficIncidentType {
10    /// Road construction.
11    Construction,
12    /// Traffic-impacting event.
13    Event,
14    /// Collision.
15    Accident,
16    /// Weather-related disruption.
17    Weather,
18    /// Lane or road closure.
19    Closure,
20}
21
22/// Severity ranking of a traffic incident.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum TrafficIncidentSeverity {
26    /// Minimal delay expected.
27    Low,
28    /// Noticeable delay expected.
29    Medium,
30    /// Significant delay expected.
31    High,
32}
33
34/// One reported disruption inside a queried bounding box.
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct TrafficIncident {
37    /// Provider-stable incident identifier.
38    pub identifier: String,
39    /// Class of disruption.
40    pub incident_type: TrafficIncidentType,
41    /// Severity ranking.
42    pub severity: TrafficIncidentSeverity,
43    /// Provider display text for this incident.
44    pub short_description_text: String,
45    /// Position where the incident starts.
46    pub start_coordinate: GeographicCoordinate,
47    /// Position where the incident ends, if reported.
48    pub end_coordinate: Option<GeographicCoordinate>,
49}
50
51/// Congestion classification of a road segment.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53#[serde(rename_all = "snake_case")]
54pub enum TrafficFlowCongestionLevel {
55    /// At or above free-flow speed.
56    FreeFlow,
57    /// Moderately below free-flow speed.
58    Slow,
59    /// Stop-and-go traffic.
60    Congested,
61    /// Roadway effectively closed.
62    Blocked,
63}
64
65/// Flow condition for one road segment (or polyline of road segments)
66/// inside the queried box, in kilometers per hour when the provider
67/// reports speeds and as a congestion classification otherwise.
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct TrafficFlowData {
70    /// Segment geometry, ordered along the road.
71    pub shape_coordinates: Vec<GeographicCoordinate>,
72    /// Currently observed speed in kilometers per hour, when reported.
73    pub current_speed_kilometers_per_hour: Option<f64>,
74    /// Unconstrained (free-flow) speed in kilometers per hour, when reported.
75    pub free_flow_speed_kilometers_per_hour: Option<f64>,
76    /// Congestion classification from the speed ratio or provider buckets.
77    pub congestion_level: TrafficFlowCongestionLevel,
78}
79
80impl TrafficFlowData {
81    /// Builds one single-point flow observation, deriving the congestion
82    /// classification from the current-to-free-flow speed ratio.
83    pub fn from_speeds(
84        midpoint_coordinate: GeographicCoordinate,
85        current_speed_kilometers_per_hour: f64,
86        free_flow_speed_kilometers_per_hour: f64,
87    ) -> Self {
88        let congestion_level = Self::classify_congestion_level(
89            current_speed_kilometers_per_hour,
90            free_flow_speed_kilometers_per_hour,
91        );
92        Self {
93            shape_coordinates: vec![midpoint_coordinate],
94            current_speed_kilometers_per_hour: Some(current_speed_kilometers_per_hour),
95            free_flow_speed_kilometers_per_hour: Some(free_flow_speed_kilometers_per_hour),
96            congestion_level,
97        }
98    }
99
100    /// Builds one polyline flow observation where the provider reports only
101    /// congestion buckets and no speeds (MapQuest JSON flow, for example).
102    pub fn from_congestion_level(
103        shape_coordinates: Vec<GeographicCoordinate>,
104        congestion_level: TrafficFlowCongestionLevel,
105    ) -> Self {
106        Self {
107            shape_coordinates,
108            current_speed_kilometers_per_hour: None,
109            free_flow_speed_kilometers_per_hour: None,
110            congestion_level,
111        }
112    }
113
114    /// Classifies congestion from the current-to-free-flow speed ratio.
115    fn classify_congestion_level(
116        current_speed_kilometers_per_hour: f64,
117        free_flow_speed_kilometers_per_hour: f64,
118    ) -> TrafficFlowCongestionLevel {
119        if free_flow_speed_kilometers_per_hour <= 0.0 {
120            return TrafficFlowCongestionLevel::Blocked;
121        }
122        let speed_ratio = current_speed_kilometers_per_hour / free_flow_speed_kilometers_per_hour;
123        if speed_ratio >= 0.9 {
124            TrafficFlowCongestionLevel::FreeFlow
125        } else if speed_ratio >= 0.5 {
126            TrafficFlowCongestionLevel::Slow
127        } else {
128            TrafficFlowCongestionLevel::Congested
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::TrafficFlowCongestionLevel;
136    use super::TrafficFlowData;
137    use crate::coordinate::GeographicCoordinate;
138
139    #[test]
140    fn classifies_congestion_from_speed_ratio() {
141        let coordinate = GeographicCoordinate::from_degrees(0.0, 0.0).unwrap();
142        let from_speeds = TrafficFlowData::from_speeds(coordinate, 55.0, 100.0);
143        assert_eq!(from_speeds.shape_coordinates, vec![coordinate]);
144        assert_eq!(from_speeds.current_speed_kilometers_per_hour, Some(55.0));
145        assert_eq!(
146            from_speeds.congestion_level,
147            TrafficFlowCongestionLevel::Slow
148        );
149    }
150
151    #[test]
152    fn carries_provider_congestion_buckets_without_speeds() {
153        let shape = vec![
154            GeographicCoordinate::from_degrees(40.73, -73.99).unwrap(),
155            GeographicCoordinate::from_degrees(40.72, -74.01).unwrap(),
156        ];
157        let polyline =
158            TrafficFlowData::from_congestion_level(shape, TrafficFlowCongestionLevel::Blocked);
159        assert_eq!(polyline.shape_coordinates.len(), 2);
160        assert_eq!(polyline.current_speed_kilometers_per_hour, None);
161        assert_eq!(polyline.free_flow_speed_kilometers_per_hour, None);
162        assert_eq!(
163            polyline.congestion_level,
164            TrafficFlowCongestionLevel::Blocked
165        );
166    }
167}