egml_io/codec/geometry/primitives/
linear_ring.rs1use crate::codec::geometry::GmlDirectPosition;
2use crate::codec::geometry::direct_position_list::GmlDirectPositionList;
3use crate::codec::geometry::primitives::abstract_ring::{
4 deserialize_abstract_ring, serialize_abstract_ring,
5};
6use crate::error::Error;
7use crate::util::{
8 Formatting, GmlElement, XmlNode, XmlNodeContent, dedup_adjacent_positions,
9 extract_xml_element_spans, serialize_inner,
10};
11use egml_core::model::geometry::DirectPosition;
12use egml_core::model::geometry::primitives::{AsAbstractRing, LinearRing};
13use quick_xml::de;
14use serde::{Deserialize, Serialize};
15
16pub fn deserialize_linear_ring(xml_document: &[u8]) -> Result<LinearRing, Error> {
17 let spans = extract_xml_element_spans(xml_document)?;
18 let abstract_ring = deserialize_abstract_ring(xml_document, &spans)?;
19
20 let parsed: GmlLinearRing = de::from_reader(xml_document)?;
21 let mut points: Vec<DirectPosition> = parsed.content.unwrap().try_into()?;
22 dedup_adjacent_positions(&mut points, "LinearRing");
23 if points.first().unwrap() == points.last().unwrap() {
24 points.pop();
25 }
26
27 let linear_ring = LinearRing::from_abstract_ring(abstract_ring, points)?;
28 Ok(linear_ring)
29}
30
31pub fn serialize_linear_ring(
32 linear_ring: &LinearRing,
33 formatting: Formatting,
34) -> Result<XmlNode, Error> {
35 let mut xml_node_parts = serialize_abstract_ring(linear_ring.abstract_ring(), formatting)?;
36
37 if let Some(raw) = serialize_inner(GmlLinearRing::from(linear_ring), formatting)? {
38 xml_node_parts.content.push(XmlNodeContent::Raw(raw));
39 }
40
41 Ok(XmlNode::new(GmlElement::LinearRing.into(), xml_node_parts))
42}
43
44#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
45pub struct GmlLinearRing {
46 #[serde(rename = "$value", skip_serializing_if = "Option::is_none")]
47 pub content: Option<GmlLinearRingContent>,
48}
49
50#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
51pub enum GmlLinearRingContent {
52 #[serde(rename(serialize = "gml:posList", deserialize = "posList"))]
53 PosList(GmlDirectPositionList),
54
55 #[serde(rename = "pos")]
56 Pos(Vec<GmlDirectPosition>),
57}
58
59impl TryFrom<GmlLinearRingContent> for Vec<DirectPosition> {
60 type Error = Error;
61
62 fn try_from(value: GmlLinearRingContent) -> Result<Self, Self::Error> {
63 match value {
64 GmlLinearRingContent::PosList(x) => x.try_into(),
65 GmlLinearRingContent::Pos(x) => {
66 let points: Vec<DirectPosition> = x
67 .into_iter()
68 .map(|p| p.try_into())
69 .collect::<Result<Vec<_>, _>>()?;
70
71 Ok(points)
72 }
73 }
74 }
75}
76
77impl TryFrom<GmlLinearRing> for LinearRing {
78 type Error = Error;
79
80 fn try_from(value: GmlLinearRing) -> Result<Self, Self::Error> {
81 let points: Vec<DirectPosition> = value
82 .content
83 .ok_or(Error::ElementNotFound("No element found".to_string()))?
84 .try_into()?;
85
86 let linear_ring = LinearRing::new(points)?;
87 Ok(linear_ring)
88 }
89}
90
91impl From<&LinearRing> for GmlLinearRing {
92 fn from(ring: &LinearRing) -> Self {
93 let mut points: Vec<DirectPosition> = ring.points().to_vec();
96 if let Some(&first) = ring.points().first() {
97 points.push(first);
98 }
99
100 Self {
101 content: Some(GmlLinearRingContent::PosList(GmlDirectPositionList::from(
102 points.as_slice(),
103 ))),
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::GmlLinearRingContent;
111 use crate::codec::geometry::primitives::{
112 GmlLinearRing, deserialize_linear_ring, serialize_linear_ring,
113 };
114 use crate::util::{Formatting, extract_xml_element_spans};
115 use egml_core::model::base::Id;
116 use egml_core::model::geometry::DirectPosition;
117 use egml_core::model::geometry::primitives::LinearRing;
118 use quick_xml::de;
119
120 fn make_triangle() -> LinearRing {
121 let points = vec![
122 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
123 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
124 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
125 ];
126 LinearRing::new(points).unwrap()
127 }
128
129 #[test]
130 fn deserialize_linear_ring_with_pos_list_and_id() {
131 let xml_document = b"<gml:LinearRing gml:id=\"4018115_LR.d2yyBHNssydL8g3B8MvW\">
132<gml:posList srsDimension=\"3\">
133678058.447 5403817.567 424.209
134678058.275 5403817.484 424.209
135678058.689 5403816.628 424.209
136678058.871 5403816.718 424.209
137678058.447 5403817.567 424.209
138</gml:posList>
139</gml:LinearRing>";
140
141 let linear_ring: LinearRing =
142 deserialize_linear_ring(xml_document).expect("should deserialize");
143 use egml_core::model::base::AsAbstractGml;
144 assert!(linear_ring.id().is_some());
145 }
146
147 #[test]
148 fn deserialize_linear_ring_with_pos_list() {
149 let xml_document = b"<gml:LinearRing>
150 <gml:posList>350.54400634765625 972.9130249023438 0.11999999731779099 350.5414201635045 968.6025425887852 0.11999999731779099 350.54400634765625 968.6025096366793 0.11999999731779099 350.54400634765625 972.9130249023438 0.11999999731779099</gml:posList>
151 </gml:LinearRing>";
152
153 let linear_ring_result = deserialize_linear_ring(xml_document);
154
155 assert!(linear_ring_result.is_ok());
156 let linear_ring = linear_ring_result.unwrap();
157 assert_eq!(linear_ring.points().len(), 3);
158 }
159
160 #[test]
161 fn deserialize_linear_ring_repairs_adjacent_duplicate_points() {
162 let xml_document = b"<gml:LinearRing>
163 <gml:posList>0 0 0 1 0 0 1 0 0 1 1 0 0 0 0</gml:posList>
164 </gml:LinearRing>";
165
166 let linear_ring = deserialize_linear_ring(xml_document).expect("should deserialize");
167
168 assert_eq!(linear_ring.points().len(), 3);
169 }
170
171 #[test]
172 fn deserialize_linear_ring_with_duplicate_points_returns_error() {
173 let xml_document = b"<gml:LinearRing gml:id=\"DEBY_LOD2_4959457_LR.EEAbfUPItTlOGZGH7VDv\">
174 <gml:posList>691040.851 5336002.449 529.908 691040.741 5336002.172 529.908 691040.851 5336002.449 529.908 691040.851 5336002.449 529.908</gml:posList>
175 </gml:LinearRing>";
176
177 let linear_ring_result = deserialize_linear_ring(xml_document);
178
179 assert!(linear_ring_result.is_err());
180 }
181
182 #[test]
183 fn serialize_linear_ring_writes_gml_tags() {
184 let linear_ring = make_triangle();
185 let xml_node = serialize_linear_ring(&linear_ring, Formatting::Compact).unwrap();
186 let xml = xml_node
187 .to_string(Formatting::Compact)
188 .expect("should serialize");
189
190 assert!(xml.contains("<gml:LinearRing"));
191 assert!(xml.contains("<gml:posList"));
192 assert!(!xml.contains("id="));
193 }
194
195 #[test]
196 fn serialize_linear_ring_appends_closing_vertex() {
197 let ring = make_triangle(); let gml = GmlLinearRing::from(&ring);
201 let positions: Vec<DirectPosition> = match gml.content.unwrap() {
202 GmlLinearRingContent::PosList(pos_list) => pos_list.try_into().unwrap(),
203 GmlLinearRingContent::Pos(_) => panic!("expected PosList"),
204 };
205 assert_eq!(positions.len(), 4); assert_eq!(positions.first(), positions.last()); }
208
209 #[test]
210 fn serialize_linear_ring_with_id() {
211 use egml_core::model::base::AsAbstractGmlMut;
212 let points = vec![
213 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
214 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
215 DirectPosition::new(0.0, 0.0, 1.0).unwrap(),
216 ];
217 let mut linear_ring = LinearRing::new(points).unwrap();
218 linear_ring.set_id(Id::from_hashed_string("test-ring"));
219
220 let xml_node = serialize_linear_ring(&linear_ring, Formatting::Compact).unwrap();
221 let xml = xml_node
222 .to_string(Formatting::Compact)
223 .expect("should serialize");
224 assert!(xml.contains("id="));
225 }
226
227 #[test]
228 fn round_trip_linear_ring_preserves_points() {
229 let linear_ring = make_triangle();
230 let xml_node = serialize_linear_ring(&linear_ring, Formatting::Compact).unwrap();
231 let xml = xml_node
232 .to_string(Formatting::Compact)
233 .expect("should serialize");
234
235 let recovered_linear_ring: LinearRing =
236 deserialize_linear_ring(xml.as_ref()).expect("should deserialize");
237
238 assert_eq!(
239 recovered_linear_ring.points().len(),
240 linear_ring.points().len()
241 );
242 for (a, b) in recovered_linear_ring
243 .points()
244 .iter()
245 .zip(linear_ring.points().iter())
246 {
247 assert_eq!(a.x(), b.x());
248 assert_eq!(a.y(), b.y());
249 assert_eq!(a.z(), b.z());
250 }
251 }
252
253 #[test]
254 fn round_trip_linear_ring_preserves_float_precision() {
255 let points = vec![
256 DirectPosition::new(678058.447, 5403817.567, 424.209).unwrap(),
257 DirectPosition::new(678058.275, 5403817.484, 424.209).unwrap(),
258 DirectPosition::new(678058.689, 5403816.628, 424.209).unwrap(),
259 ];
260 let linear_ring = LinearRing::new(points.clone()).unwrap();
261
262 let xml_node = serialize_linear_ring(&linear_ring, Formatting::Compact).unwrap();
263 let xml = xml_node
264 .to_string(Formatting::Compact)
265 .expect("should serialize");
266
267 let recovered_linear_ring: LinearRing =
268 deserialize_linear_ring(xml.as_ref()).expect("should deserialize");
269
270 for (a, b) in recovered_linear_ring.points().iter().zip(points.iter()) {
271 assert_eq!(a.x(), b.x());
272 assert_eq!(a.y(), b.y());
273 assert_eq!(a.z(), b.z());
274 }
275 }
276
277 #[test]
278 fn round_trip_linear_ring_from_xml() {
279 let xml_document = "<gml:LinearRing gml:id=\"PolyID7350_878_759628_120742_0\">\
280 <gml:posList srsDimension=\"3\">0 0 0 1 0 0 0 1 0 0 0 0</gml:posList>\
281 </gml:LinearRing>";
282
283 let linear_ring =
284 deserialize_linear_ring(xml_document.as_ref()).expect("should deserialize");
285
286 let xml_node = serialize_linear_ring(&linear_ring, Formatting::Compact).unwrap();
287 let output_xml = xml_node
288 .to_string(Formatting::Compact)
289 .expect("should serialize");
290
291 assert_eq!(xml_document, output_xml);
292 }
293
294 #[test]
295 fn deserialize_linear_ring_with_pos_elements() {
296 let xml_document = b"<gml:LinearRing gml:id=\"PolyID7350_878_759628_120742_0\">
297 <gml:pos>457842.0 5439088.0 118.317691453624</gml:pos>
298 <gml:pos>457842.0 5439093.0 115.430940107676</gml:pos>
299 <gml:pos>457842.0 5439093.0 111.8</gml:pos>
300 <gml:pos>457842.0 5439083.0 111.8</gml:pos>
301 <gml:pos>457842.0 5439083.0 115.430940107676</gml:pos>
302 <gml:pos>457842.0 5439088.0 118.317691453624</gml:pos>
303 </gml:LinearRing>";
304
305 let linear_ring_result = deserialize_linear_ring(xml_document);
306
307 assert!(linear_ring_result.is_ok());
308 let linear_ring = linear_ring_result.unwrap();
309 assert_eq!(linear_ring.points().len(), 5);
310 }
311}