everymap_core/domains/
isoline.rs1use crate::domains::routing::{DepartureTime, TransportMode};
2use crate::error::EveryMapResult;
3use crate::types::Coordinate;
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
9pub enum RangeType {
10 Distance,
11 Time,
12 Consumption,
13}
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17pub struct IsolineOptions {
18 pub range_type: Option<RangeType>,
20 pub transport_mode: Option<TransportMode>,
22 pub departure_time: Option<DepartureTime>,
24 pub avoid: Vec<crate::domains::routing::AvoidType>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub provider_extra: Option<serde_json::Value>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct IsolineResult {
34 pub polygon: Vec<Coordinate>,
36 pub range: Option<f64>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct IsolineResponse {
43 pub isolines: Vec<IsolineResult>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub raw: Option<serde_json::Value>,
48}
49
50#[async_trait]
51pub trait IsolineProvider: Send + Sync {
52 async fn get_isoline(
53 &self,
54 center: &Coordinate,
55 range: f64,
56 options: &IsolineOptions,
57 ) -> EveryMapResult<IsolineResponse>;
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_isoline_options_default() {
66 let options = IsolineOptions::default();
67 assert!(options.range_type.is_none());
68 assert!(options.transport_mode.is_none());
69 assert!(options.departure_time.is_none());
70 assert!(options.avoid.is_empty());
71 assert!(options.provider_extra.is_none());
72 }
73
74 #[test]
75 fn test_isoline_options_with_fields() {
76 let options = IsolineOptions {
77 range_type: Some(RangeType::Time),
78 transport_mode: Some(crate::domains::routing::TransportMode::Car),
79 departure_time: Some(DepartureTime::Iso8601("2024-01-01T08:00:00".to_string())),
80 avoid: vec![crate::domains::routing::AvoidType::Tolls],
81 provider_extra: Some(serde_json::json!({"routing_mode": "fast"})),
82 };
83 assert_eq!(options.range_type, Some(RangeType::Time));
84 assert!(options.avoid.len() == 1);
85 }
86
87 #[test]
88 fn test_isoline_result_construction() {
89 let result = IsolineResult {
90 polygon: vec![],
91 range: Some(1000.0),
92 };
93 assert_eq!(result.range, Some(1000.0));
94 assert!(result.polygon.is_empty());
95 }
96
97 #[test]
98 fn test_range_type_serialization() {
99 assert_eq!(
100 serde_json::to_string(&RangeType::Distance).unwrap(),
101 "\"Distance\""
102 );
103 let rt: RangeType = serde_json::from_str("\"Time\"").unwrap();
104 assert_eq!(rt, RangeType::Time);
105 }
106
107 #[test]
110 fn test_range_type_all_variants_serde() {
111 let variants = [RangeType::Distance, RangeType::Time, RangeType::Consumption];
112 for v in &variants {
113 let json = serde_json::to_string(v).unwrap();
114 let back: RangeType = serde_json::from_str(&json).unwrap();
115 assert_eq!(*v, back, "Failed roundtrip for {:?}", v);
116 }
117 }
118
119 #[test]
120 fn test_range_type_all_variants_distinct() {
121 let variants = [RangeType::Distance, RangeType::Time, RangeType::Consumption];
122 for i in 0..variants.len() {
123 for j in 0..variants.len() {
124 if i != j {
125 assert_ne!(variants[i], variants[j]);
126 }
127 }
128 }
129 }
130
131 #[test]
134 fn test_isoline_response_serde_roundtrip() {
135 let response = IsolineResponse {
136 isolines: vec![IsolineResult {
137 polygon: vec![
138 Coordinate::new(52.5, 13.4).unwrap(),
139 Coordinate::new(52.6, 13.5).unwrap(),
140 ],
141 range: Some(5000.0),
142 }],
143 raw: Some(serde_json::json!({"center": "52.5,13.4"})),
144 };
145 let json = serde_json::to_string(&response).unwrap();
146 let back: IsolineResponse = serde_json::from_str(&json).unwrap();
147 assert_eq!(back.isolines.len(), 1);
148 assert_eq!(back.isolines[0].polygon.len(), 2);
149 assert_eq!(back.isolines[0].range, Some(5000.0));
150 assert!(back.raw.is_some());
151 }
152
153 #[test]
154 fn test_isoline_response_empty() {
155 let response = IsolineResponse {
156 isolines: vec![],
157 raw: None,
158 };
159 let json = serde_json::to_string(&response).unwrap();
160 let back: IsolineResponse = serde_json::from_str(&json).unwrap();
161 assert!(back.isolines.is_empty());
162 assert!(back.raw.is_none());
163 }
164
165 #[test]
168 fn test_isoline_options_serde_roundtrip() {
169 let options = IsolineOptions {
170 range_type: Some(RangeType::Consumption),
171 transport_mode: Some(crate::domains::routing::TransportMode::Truck),
172 departure_time: Some(DepartureTime::Iso8601("2024-01-01T08:00:00".to_string())),
173 avoid: vec![
174 crate::domains::routing::AvoidType::Tolls,
175 crate::domains::routing::AvoidType::Highways,
176 ],
177 provider_extra: Some(serde_json::json!({"optimize_for": "quality"})),
178 };
179 let json = serde_json::to_string(&options).unwrap();
180 let back: IsolineOptions = serde_json::from_str(&json).unwrap();
181 assert_eq!(back.range_type, Some(RangeType::Consumption));
182 assert_eq!(back.avoid.len(), 2);
183 assert!(back.provider_extra.is_some());
184 }
185
186 #[test]
189 fn test_isoline_result_empty_polygon() {
190 let result = IsolineResult {
191 polygon: vec![],
192 range: None,
193 };
194 assert!(result.polygon.is_empty());
195 assert!(result.range.is_none());
196 }
197
198 #[test]
199 fn test_isoline_result_zero_range() {
200 let result = IsolineResult {
201 polygon: vec![Coordinate::ORIGIN],
202 range: Some(0.0),
203 };
204 assert_eq!(result.range, Some(0.0));
205 }
206}