use super::domain::Additional;
use crate::Result;
use crate::schema;
use crate::xml::{read_document, read_document_at};
use std::io::BufRead;
use std::path::Path;
pub fn read_additional(path: &Path) -> Result<Additional> {
read_document_at::<schema::AdditionalType, _>(path, "additional", "SUMO additional file")
}
pub fn read_additional_from(source: impl BufRead) -> Result<Additional> {
read_document::<schema::AdditionalType, _, _>(source, "additional", "SUMO additional file")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::additional::domain::{DetectorId, LaneCoverage, LanePosition, LaneRef, Point};
use uom::si::f64::Length;
use uom::si::length::meter;
use uom::si::time::second;
const SAMPLE_ADDITIONAL: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<additional>
<e1Detector id="loop0" lane="e0_0" pos="12.50" period="60.00" file="out/loop0.xml"/>
<e2Detector id="area0" lane="e0_0" pos="0.00" endPos="50.00" file="out/area0.xml"/>
<e3Detector id="zone0" file="out/zone0.xml" pos="42.00,7.00">
<detEntry lane="e0_0" pos="0.00"/>
<detEntry lane="e1_0" pos="0.00"/>
<detExit lane="e0_0" pos="100.00"/>
</e3Detector>
</additional>
"#;
#[test]
fn reads_one_of_each_detector_kind_end_to_end() {
let additional = read_additional_from(SAMPLE_ADDITIONAL.as_bytes()).unwrap();
assert_eq!(additional.induction_loops.len(), 1);
let loop0 = &additional.induction_loops[0];
assert_eq!(loop0.id, DetectorId("loop0".into()));
assert_eq!(loop0.lane, LaneRef("e0_0".into()));
assert_eq!(
loop0.position,
LanePosition::FromStart(Length::new::<meter>(12.5))
);
assert_eq!(loop0.period.unwrap().get::<second>(), 60.0);
assert_eq!(additional.lane_area_detectors.len(), 1);
let area0 = &additional.lane_area_detectors[0];
assert_eq!(
area0.coverage,
Some(LaneCoverage::SingleLane(LaneRef("e0_0".into())))
);
assert_eq!(
area0.end_position,
Some(LanePosition::FromStart(Length::new::<meter>(50.0)))
);
assert_eq!(additional.entry_exit_detectors.len(), 1);
let zone0 = &additional.entry_exit_detectors[0];
assert_eq!(zone0.entries.len(), 2);
assert_eq!(zone0.exits.len(), 1);
assert_eq!(
zone0.exits[0].position,
LanePosition::FromStart(Length::new::<meter>(100.0))
);
assert_eq!(
zone0.icon_position,
Some(Point {
x: 42.0,
y: 7.0,
z: 0.0
})
);
}
#[test]
fn treats_sumos_alternative_element_names_as_the_same_detector() {
let xml = r#"<additional>
<inductionLoop id="loop0" lane="e0_0" pos="1.00" file="a.xml"/>
<laneAreaDetector id="area0" lane="e0_0" file="b.xml"/>
<entryExitDetector id="zone0" file="c.xml"><detEntry lane="e0_0" pos="0.00"/></entryExitDetector>
</additional>"#;
let additional = read_additional_from(xml.as_bytes()).unwrap();
assert_eq!(additional.induction_loops.len(), 1);
assert_eq!(additional.lane_area_detectors.len(), 1);
assert_eq!(additional.entry_exit_detectors.len(), 1);
}
#[test]
fn ignores_element_kinds_this_crate_does_not_model() {
let xml = r#"<additional>
<busStop id="stop0" lane="e0_0" startPos="0.00" endPos="20.00"/>
<e1Detector id="loop0" lane="e0_0" pos="1.00" file="a.xml"/>
</additional>"#;
let additional = read_additional_from(xml.as_bytes()).unwrap();
assert_eq!(additional.induction_loops.len(), 1);
assert!(additional.lane_area_detectors.is_empty());
}
#[test]
fn rejects_unrelated_root_elements() {
let error = read_additional_from(b"<not-additional/>".as_slice()).unwrap_err();
assert!(
error.to_string().contains("<not-additional>"),
"error should name the offending root element, got: {error}"
);
}
#[test]
fn accepts_an_empty_but_correctly_rooted_file() {
let additional = read_additional_from(b"<additional/>".as_slice()).unwrap();
assert_eq!(additional, Additional::default());
}
#[test]
fn rejects_a_non_finite_position() {
let xml =
r#"<additional><e1Detector id="l" lane="e0_0" pos="NaN" file="a.xml"/></additional>"#;
assert!(read_additional_from(xml.as_bytes()).is_err());
}
}