Skip to main content

sumo_types/additional/
writer.rs

1//! Writing an [`Additional`] to a `.add.xml` file: converts layer 2 into
2//! layer 1 (the private `schema` module) via [`super::schema_writer`] and
3//! serializes it in one step, mirroring `additional`'s own `reader`.
4
5use super::domain::Additional;
6use crate::Result;
7use crate::schema;
8use crate::xml::{write_document, write_document_at};
9use std::io::Write;
10use std::path::Path;
11
12/// Writes `additional` to the `.add.xml` file at `path`, creating or
13/// truncating it.
14///
15/// ```no_run
16/// use sumo_types::additional::domain::{Additional, DetectorId, E1Detector, LanePosition, LaneRef};
17/// use sumo_types::uom::si::f64::Length;
18/// use sumo_types::uom::si::length::meter;
19/// use std::path::Path;
20///
21/// let additional = Additional {
22///     induction_loops: vec![E1Detector {
23///         id: DetectorId("loop0".into()),
24///         lane: LaneRef("e0_0".into()),
25///         position: LanePosition::FromStart(Length::new::<meter>(12.5)),
26///         file: "out/loop0.xml".into(),
27///         period: None,
28///         length: None,
29///         name: None,
30///         friendly_position: None,
31///     }],
32///     ..Additional::default()
33/// };
34/// sumo_types::additional::write_additional(Path::new("city.add.xml"), &additional)?;
35/// # Ok::<(), sumo_types::Error>(())
36/// ```
37///
38/// # Errors
39///
40/// Returns an error if `path` can't be created, or if writing to it fails.
41pub fn write_additional(path: &Path, additional: &Additional) -> Result<()> {
42    write_document_at::<schema::AdditionalType, _>(
43        additional,
44        "additional",
45        "SUMO additional file",
46        path,
47    )
48}
49
50/// Same as [`write_additional`], for callers that want the `.add.xml` bytes
51/// somewhere other than a file on disk (an in-memory buffer, a socket, ...).
52///
53/// # Errors
54///
55/// Same as [`write_additional`], minus the failure to create a file.
56pub fn write_additional_to(additional: &Additional, sink: impl Write) -> Result<()> {
57    write_document::<schema::AdditionalType, _, _>(
58        additional,
59        "additional",
60        "SUMO additional file",
61        sink,
62    )
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::additional::domain::{
69        DetectorGate, DetectorId, E1Detector, E2Detector, E3Detector, LaneCoverage, LanePosition,
70        LaneRef, PersonMode, Point,
71    };
72    use crate::additional::reader::read_additional_from;
73    use uom::si::f64::Length;
74    use uom::si::length::meter;
75
76    fn sample_additional() -> Additional {
77        Additional {
78            induction_loops: vec![E1Detector {
79                id: DetectorId("loop0".into()),
80                lane: LaneRef("e0_0".into()),
81                position: LanePosition::FromStart(Length::new::<meter>(12.5)),
82                file: "out/loop0.xml".into(),
83                period: None,
84                length: None,
85                name: None,
86                friendly_position: None,
87            }],
88            lane_area_detectors: vec![E2Detector {
89                id: DetectorId("area0".into()),
90                coverage: Some(LaneCoverage::SingleLane(LaneRef("e0_0".into()))),
91                position: None,
92                end_position: Some(LanePosition::FromEnd(Length::new::<meter>(5.0))),
93                length: None,
94                file: "out/area0.xml".into(),
95                period: None,
96                name: None,
97                friendly_position: None,
98                speed_threshold: None,
99                time_threshold: None,
100                jam_threshold: None,
101            }],
102            entry_exit_detectors: vec![E3Detector {
103                id: DetectorId("zone0".into()),
104                entries: vec![DetectorGate {
105                    lane: LaneRef("e0_0".into()),
106                    position: LanePosition::FromStart(Length::new::<meter>(0.0)),
107                    friendly_position: None,
108                }],
109                exits: vec![DetectorGate {
110                    lane: LaneRef("e1_0".into()),
111                    position: LanePosition::FromStart(Length::new::<meter>(100.0)),
112                    friendly_position: Some(true),
113                }],
114                file: "out/zone0.xml".into(),
115                detect_persons: vec![PersonMode::Walk],
116                period: None,
117                name: None,
118                icon_position: Some(Point {
119                    x: 42.0,
120                    y: 7.0,
121                    z: 0.0,
122                }),
123                speed_threshold: None,
124                time_threshold: None,
125                open_entry: None,
126            }],
127        }
128    }
129
130    #[test]
131    fn writes_a_document_a_correctly_rooted_reader_accepts() {
132        let mut buf = Vec::new();
133        write_additional_to(&sample_additional(), &mut buf).unwrap();
134
135        let xml = String::from_utf8(buf).unwrap();
136        assert!(xml.starts_with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
137        assert!(xml.contains("<additional"));
138
139        let read_back = read_additional_from(xml.as_bytes()).unwrap();
140        assert_eq!(read_back, sample_additional());
141    }
142}