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: (0.0, 0.0),
73 max: (100.0, 0.0),
74 },
75 original_boundary: Boundary {
76 min: (-1.0, -1.0),
77 max: (1.0, 1.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}