Skip to main content

sumo_types/net/
writer.rs

1//! Writing a [`Network`] to a `.net.xml` file: converts layer 2 into layer 1
2//! (the private `schema` module) via [`super::schema_writer`] and
3//! serializes it in one step, mirroring `net`'s own `reader`.
4
5use super::domain::Network;
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 `network` to the `.net.xml` file at `path`, creating or
13/// truncating it.
14///
15/// ```no_run
16/// let network = sumo_types::read_network(std::path::Path::new("city.net.xml"))?;
17/// sumo_types::write_network(std::path::Path::new("city-copy.net.xml"), &network)?;
18/// # Ok::<(), sumo_types::Error>(())
19/// ```
20///
21/// # Errors
22///
23/// Returns an error if `path` can't be created, or if writing to it fails.
24pub fn write_network(path: &Path, network: &Network) -> Result<()> {
25    write_document_at::<schema::NetType, _>(network, "net", "SUMO network", path)
26}
27
28/// Same as [`write_network`], for callers that want the `.net.xml` bytes
29/// somewhere other than a file on disk (an in-memory buffer, a socket, ...).
30///
31/// # Errors
32///
33/// Same as [`write_network`], minus the failure to create a file.
34pub fn write_network_to(network: &Network, sink: impl Write) -> Result<()> {
35    write_document::<schema::NetType, _, _>(network, "net", "SUMO network", sink)
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use crate::domain::{
42        Boundary, Edge, EdgeFunction, EdgeId, Junction, JunctionId, JunctionKind, Lane, LaneId,
43        LaneIndex, Location, Phase, Point, Projection, Shape, TrafficLightId, TrafficLightKind,
44        TrafficLightOffset, TrafficLightProgram,
45    };
46    use crate::net::reader::read_network_from;
47    use uom::si::f64::{Length, Time, Velocity};
48    use uom::si::length::meter;
49    use uom::si::time::second;
50    use uom::si::velocity::meter_per_second;
51
52    /// `speed`/`length`/`width` use values that are exact in `f32`
53    /// (`13.75`, `3.25`) rather than realistic-looking ones (`13.89`,
54    /// `3.2`): `.net.xml`'s `speed`/`width` are real `xsd:float`s, so
55    /// `schema_writer` narrows this crate's `f64` `uom` quantities down to
56    /// `f32` on the way out — the same narrowing `schema_mapper` already
57    /// does widening back up on the way in (see
58    /// `converts_lane_speed_and_length_with_real_units` in
59    /// `net/schema_mapper.rs`, which tolerance-compares for the same
60    /// reason). Picking `f32`-exact literals here keeps this test an exact
61    /// `assert_eq!` on the whole `Network` instead of a field-by-field
62    /// tolerance check.
63    fn sample_network() -> Network {
64        Network {
65            location: Location {
66                net_offset: Point {
67                    x: 0.0,
68                    y: 0.0,
69                    z: 0.0,
70                },
71                converted_boundary: Boundary {
72                    min: Point { x: 0.0, y: 0.0, z: 0.0 },
73                    max: Point { x: 100.0, y: 0.0, z: 0.0 },
74                },
75                original_boundary: Boundary {
76                    min: Point { x: -1.0, y: -1.0, z: 0.0 },
77                    max: Point { x: 1.0, y: 1.0, z: 0.0 },
78                },
79                projection: Projection::None,
80            },
81            edges: vec![Edge {
82                id: EdgeId("e0".into()),
83                function: EdgeFunction::Normal,
84                from: Some(JunctionId("j0".into())),
85                to: Some(JunctionId("j1".into())),
86                name: None,
87                priority: None,
88                length: None,
89                shape: None,
90                spread_type: None,
91                lanes: vec![Lane {
92                    id: LaneId("e0_0".into()),
93                    index: LaneIndex(0),
94                    speed: Velocity::new::<meter_per_second>(13.75),
95                    length: Length::new::<meter>(100.0),
96                    width: Length::new::<meter>(3.25),
97                    end_offset: Length::new::<meter>(0.0),
98                    shape: Shape(vec![
99                        Point {
100                            x: 0.0,
101                            y: 0.0,
102                            z: 0.0,
103                        },
104                        Point {
105                            x: 100.0,
106                            y: 0.0,
107                            z: 0.0,
108                        },
109                    ]),
110                    allow: vec![],
111                    disallow: vec![],
112                }],
113            }],
114            junctions: vec![
115                Junction {
116                    id: JunctionId("j0".into()),
117                    position: Point {
118                        x: 0.0,
119                        y: 0.0,
120                        z: 0.0,
121                    },
122                    kind: JunctionKind::DeadEnd,
123                    incoming_lanes: vec![],
124                    internal_lanes: vec![],
125                    shape: None,
126                    name: None,
127                },
128                Junction {
129                    id: JunctionId("j1".into()),
130                    position: Point {
131                        x: 100.0,
132                        y: 0.0,
133                        z: 0.0,
134                    },
135                    kind: JunctionKind::TrafficLight,
136                    incoming_lanes: vec![LaneId("e0_0".into())],
137                    internal_lanes: vec![],
138                    shape: None,
139                    name: None,
140                },
141            ],
142            connections: vec![],
143            roundabouts: vec![],
144            traffic_light_programs: vec![TrafficLightProgram {
145                id: TrafficLightId("j1".into()),
146                program_id: "0".into(),
147                kind: Some(TrafficLightKind::Static),
148                offset: Some(TrafficLightOffset::Fixed(Time::new::<second>(0.0))),
149                phases: vec![
150                    Phase {
151                        duration: Time::new::<second>(42.0),
152                        state: "G".into(),
153                    },
154                    Phase {
155                        duration: Time::new::<second>(3.0),
156                        state: "r".into(),
157                    },
158                ],
159            }],
160        }
161    }
162
163    #[test]
164    fn writes_a_document_a_correctly_rooted_reader_accepts() {
165        let mut buf = Vec::new();
166        write_network_to(&sample_network(), &mut buf).unwrap();
167
168        let xml = String::from_utf8(buf).unwrap();
169        assert!(xml.starts_with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
170        assert!(xml.contains("<net"));
171
172        let read_back = read_network_from(xml.as_bytes()).unwrap();
173        assert_eq!(read_back, sample_network());
174    }
175}