1use super::domain::{
16 Additional, DetectorGate, DetectorId, E1Detector, E2Detector, E3Detector, LaneCoverage,
17 LanePosition, LaneRef, PersonMode, Point,
18};
19use crate::schema;
20use crate::sumo::{parse_bool_opt, parse_finite, split_ids};
21use crate::{Error, Result};
22use uom::si::f64::{Length, Time, Velocity};
23use uom::si::length::meter;
24use uom::si::time::second;
25use uom::si::velocity::meter_per_second;
26
27fn lane_position(raw: &str, context: &'static str) -> Result<LanePosition> {
31 let value = parse_finite(raw, context)?;
32
33 Ok(if value < 0.0 {
34 LanePosition::FromEnd(Length::new::<meter>(-value))
35 } else {
36 LanePosition::FromStart(Length::new::<meter>(value))
37 })
38}
39
40fn icon_position(raw: &str) -> Result<Point> {
45 Point::parse(raw, "detector icon position")
46}
47
48fn seconds(value: Option<f32>) -> Option<Time> {
49 value.map(|s| Time::new::<second>(f64::from(s)))
50}
51
52fn meters(value: Option<f32>) -> Option<Length> {
53 value.map(|m| Length::new::<meter>(f64::from(m)))
54}
55
56fn sampling_period(period: Option<f32>, freq: Option<f32>) -> Option<Time> {
59 seconds(period.or(freq))
60}
61
62impl TryFrom<schema::DetEntryExitType> for DetectorGate {
63 type Error = Error;
64
65 fn try_from(value: schema::DetEntryExitType) -> Result<Self> {
66 Ok(DetectorGate {
67 lane: LaneRef(value.lane),
68 position: lane_position(&value.pos, "detector gate position")?,
69 friendly_position: parse_bool_opt(value.friendly_pos)?,
70 })
71 }
72}
73
74impl TryFrom<schema::E1DetectorType> for E1Detector {
75 type Error = Error;
76
77 fn try_from(value: schema::E1DetectorType) -> Result<Self> {
78 Ok(E1Detector {
79 id: DetectorId(value.id),
80 lane: LaneRef(value.lane),
81 position: lane_position(&value.pos, "e1Detector position")?,
82 file: value.file,
83 period: sampling_period(value.period, value.freq),
84 length: meters(value.length),
85 name: value.name,
86 friendly_position: parse_bool_opt(value.friendly_pos)?,
87 })
88 }
89}
90
91fn lane_coverage(
106 lane: Option<String>,
107 lanes: Option<&str>,
108 length: Option<&str>,
109) -> Result<Option<LaneCoverage>> {
110 match (lane, lanes) {
111 (Some(_), Some(_)) => Err(Error::ConflictingAttributes {
112 element: "e2Detector",
113 first: "lane",
114 second: "lanes",
115 }),
116 (_, Some(_)) if length.is_some() => Err(Error::ConflictingAttributes {
117 element: "e2Detector",
118 first: "lanes",
119 second: "length",
120 }),
121 (Some(lane), None) => Ok(Some(LaneCoverage::SingleLane(LaneRef(lane)))),
122 (None, Some(lanes)) => Ok(Some(LaneCoverage::LaneChain(split_ids(lanes)))),
123 (None, None) => Ok(None),
124 }
125}
126
127impl TryFrom<schema::E2DetectorType> for E2Detector {
128 type Error = Error;
129
130 fn try_from(value: schema::E2DetectorType) -> Result<Self> {
131 Ok(E2Detector {
132 id: DetectorId(value.id),
133 coverage: lane_coverage(value.lane, value.lanes.as_deref(), value.length.as_deref())?,
134 position: value
135 .pos
136 .as_deref()
137 .map(|pos| lane_position(pos, "e2Detector position"))
138 .transpose()?,
139 end_position: value
140 .end_pos
141 .as_deref()
142 .map(|pos| lane_position(pos, "e2Detector end position"))
143 .transpose()?,
144 length: value
145 .length
146 .as_deref()
147 .map(|raw| parse_finite(raw, "e2Detector length"))
148 .transpose()?
149 .map(Length::new::<meter>),
150 file: value.file,
151 period: sampling_period(value.period, value.freq),
152 name: value.name,
153 friendly_position: parse_bool_opt(value.friendly_pos)?,
154 speed_threshold: value
155 .speed_threshold
156 .as_deref()
157 .map(|raw| parse_finite(raw, "e2Detector speedThreshold"))
158 .transpose()?
159 .map(Velocity::new::<meter_per_second>),
160 time_threshold: value
161 .time_threshold
162 .as_deref()
163 .map(|raw| parse_finite(raw, "e2Detector timeThreshold"))
164 .transpose()?
165 .map(Time::new::<second>),
166 jam_threshold: value
167 .jam_threshold
168 .as_deref()
169 .map(|raw| parse_finite(raw, "e2Detector jamThreshold"))
170 .transpose()?
171 .map(Length::new::<meter>),
172 })
173 }
174}
175
176fn parse_person_modes(raw: &str) -> Result<Vec<PersonMode>> {
181 raw.split_whitespace()
182 .map(|token| token.parse::<PersonMode>().map_err(|err| Error::InvalidPersonMode { value: err.0 }))
183 .collect()
184}
185
186impl TryFrom<schema::E3DetectorType> for E3Detector {
187 type Error = Error;
188
189 fn try_from(value: schema::E3DetectorType) -> Result<Self> {
190 let mut entries = Vec::new();
191 let mut exits = Vec::new();
192
193 for item in value.content.det_gate_group {
194 match item.content {
195 schema::E3DetectorDetGateGroupTypeContent::detEntry(gates) => {
196 for gate in gates {
197 entries.push(DetectorGate::try_from(gate)?);
198 }
199 }
200 schema::E3DetectorDetGateGroupTypeContent::detExit(gates) => {
201 for gate in gates {
202 exits.push(DetectorGate::try_from(gate)?);
203 }
204 }
205 }
206 }
207
208 Ok(E3Detector {
209 id: DetectorId(value.id),
210 entries,
211 exits,
212 file: value.file,
213 period: sampling_period(value.period, value.freq),
214 name: value.name,
215 icon_position: value.pos.as_deref().map(icon_position).transpose()?,
216 speed_threshold: value
217 .speed_threshold
218 .map(|s| Velocity::new::<meter_per_second>(f64::from(s))),
219 time_threshold: value
220 .time_threshold
221 .as_deref()
222 .map(|raw| parse_finite(raw, "e3Detector timeThreshold"))
223 .transpose()?
224 .map(Time::new::<second>),
225 open_entry: parse_bool_opt(value.open_entry)?,
226 detect_persons: value.detect_persons.as_deref().map(parse_person_modes).transpose()?.unwrap_or_default(),
227 })
228 }
229}
230
231impl TryFrom<schema::AdditionalType> for Additional {
232 type Error = Error;
233
234 fn try_from(value: schema::AdditionalType) -> Result<Self> {
235 let mut additional = Additional::default();
236
237 for item in value.content {
238 match item {
239 schema::AdditionalTypeContent::e1Detector(Some(detector))
244 | schema::AdditionalTypeContent::inductionLoop(Some(detector)) => {
245 additional
246 .induction_loops
247 .push(E1Detector::try_from(detector)?);
248 }
249 schema::AdditionalTypeContent::e2Detector(Some(detector))
250 | schema::AdditionalTypeContent::laneAreaDetector(Some(detector)) => {
251 additional
252 .lane_area_detectors
253 .push(E2Detector::try_from(detector)?);
254 }
255 schema::AdditionalTypeContent::e3Detector(Some(detector))
256 | schema::AdditionalTypeContent::entryExitDetector(Some(detector)) => {
257 additional
258 .entry_exit_detectors
259 .push(E3Detector::try_from(detector)?);
260 }
261 _ => {}
268 }
269 }
270
271 Ok(additional)
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn reads_the_sign_of_a_position_as_which_end_it_is_measured_from() {
281 assert_eq!(
282 lane_position("12.5", "test").unwrap(),
283 LanePosition::FromStart(Length::new::<meter>(12.5))
284 );
285 assert_eq!(
288 lane_position("-12.5", "test").unwrap(),
289 LanePosition::FromEnd(Length::new::<meter>(12.5))
290 );
291 }
292
293 #[test]
294 fn rejects_the_lane_combinations_sumo_rejects() {
295 assert!(lane_coverage(Some("e0_0".into()), Some("e0_0 e1_0"), None).is_err());
297 assert!(lane_coverage(None, Some("e0_0 e1_0"), Some("50.0")).is_err());
298
299 assert_eq!(
302 lane_coverage(Some("e0_0".into()), None, Some("50.0")).unwrap(),
303 Some(LaneCoverage::SingleLane(LaneRef("e0_0".into())))
304 );
305 assert_eq!(lane_coverage(None, None, Some("50.0")).unwrap(), None);
306 assert_eq!(lane_coverage(None, None, None).unwrap(), None);
307 }
308
309 #[test]
310 fn parses_the_icon_position_with_and_without_z() {
311 assert_eq!(
312 icon_position("42,7").unwrap(),
313 Point {
314 x: 42.0,
315 y: 7.0,
316 z: 0.0
317 }
318 );
319 assert_eq!(
320 icon_position("42,7,1.5").unwrap(),
321 Point {
322 x: 42.0,
323 y: 7.0,
324 z: 1.5
325 }
326 );
327 assert!(icon_position("42").is_err());
328 assert!(icon_position("42,7,1,9").is_err());
329 }
330
331 #[test]
332 fn falls_back_from_period_to_the_deprecated_freq() {
333 use uom::si::time::second;
334
335 assert_eq!(
336 sampling_period(Some(60.0), None).unwrap().get::<second>(),
337 60.0
338 );
339 assert_eq!(
340 sampling_period(None, Some(30.0)).unwrap().get::<second>(),
341 30.0
342 );
343 assert_eq!(
345 sampling_period(Some(60.0), Some(30.0))
346 .unwrap()
347 .get::<second>(),
348 60.0
349 );
350 assert!(sampling_period(None, None).is_none());
351 }
352
353 #[test]
354 fn splits_a_lane_chain() {
355 assert_eq!(
356 split_ids::<LaneRef>("e0_0 e1_0 e2_0"),
357 vec![
358 LaneRef("e0_0".into()),
359 LaneRef("e1_0".into()),
360 LaneRef("e2_0".into())
361 ]
362 );
363 }
364
365 #[test]
366 fn rejects_bool_without_binary_meaning() {
367 assert!(parse_bool_opt(Some(schema::BoolType::x)).is_err());
368 assert_eq!(parse_bool_opt(None).unwrap(), None);
369 assert_eq!(
370 parse_bool_opt(Some(schema::BoolType::True)).unwrap(),
371 Some(true)
372 );
373 }
374}