geospatial_types/
traffic.rs1use crate::coordinate::GeographicCoordinate;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum TrafficIncidentType {
10 Construction,
12 Event,
14 Accident,
16 Weather,
18 Closure,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum TrafficIncidentSeverity {
26 Low,
28 Medium,
30 High,
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct TrafficIncident {
37 pub identifier: String,
39 pub incident_type: TrafficIncidentType,
41 pub severity: TrafficIncidentSeverity,
43 pub short_description_text: String,
45 pub start_coordinate: GeographicCoordinate,
47 pub end_coordinate: Option<GeographicCoordinate>,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53#[serde(rename_all = "snake_case")]
54pub enum TrafficFlowCongestionLevel {
55 FreeFlow,
57 Slow,
59 Congested,
61 Blocked,
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct TrafficFlowData {
70 pub shape_coordinates: Vec<GeographicCoordinate>,
72 pub current_speed_kilometers_per_hour: Option<f64>,
74 pub free_flow_speed_kilometers_per_hour: Option<f64>,
76 pub congestion_level: TrafficFlowCongestionLevel,
78}
79
80impl TrafficFlowData {
81 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 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 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}