egml_io/geometry/primitives/
linear_ring.rs1use crate::GmlDirectPosition;
2use crate::error::Error;
3use crate::geometry::direct_position_list::GmlDirectPositionList;
4use egml_core::model::base::{AsAbstractGml, AsAbstractGmlMut};
5use egml_core::model::geometry::DirectPosition;
6use egml_core::model::geometry::primitives::{AbstractRing, LinearRing};
7use quick_xml::se;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
11pub struct GmlLinearRing {
12 #[serde(
13 rename(serialize = "@gml:id", deserialize = "@id"),
14 skip_serializing_if = "Option::is_none"
15 )]
16 pub id: Option<String>,
17
18 #[serde(rename = "$value", skip_serializing_if = "Option::is_none")]
19 pub content: Option<GmlLinearRingContent>,
20}
21
22#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
23pub enum GmlLinearRingContent {
24 #[serde(rename(serialize = "gml:posList", deserialize = "posList"))]
25 PosList(GmlDirectPositionList),
26
27 #[serde(rename = "pos")]
28 Pos(Vec<GmlDirectPosition>),
29}
30
31impl TryFrom<GmlLinearRingContent> for Vec<DirectPosition> {
32 type Error = Error;
33
34 fn try_from(value: GmlLinearRingContent) -> Result<Self, Self::Error> {
35 match value {
36 GmlLinearRingContent::PosList(x) => x.try_into(),
37 GmlLinearRingContent::Pos(x) => {
38 let points: Vec<DirectPosition> = x
39 .into_iter()
40 .map(|p| p.try_into())
41 .collect::<Result<Vec<_>, _>>()?;
42
43 Ok(points)
44 }
45 }
46 }
47}
48
49impl TryFrom<GmlLinearRing> for LinearRing {
50 type Error = Error;
51
52 fn try_from(value: GmlLinearRing) -> Result<Self, Self::Error> {
53 let id = value.id.map(|id| id.try_into()).transpose()?;
54 let mut abstract_ring = AbstractRing::default();
55 abstract_ring.set_id(id);
56
57 let mut points: Vec<DirectPosition> = value
58 .content
59 .ok_or(Error::ElementNotFound("No element found".to_string()))?
60 .try_into()?;
61
62 points.dedup();
63 if points.first().unwrap() == points.last().unwrap() {
64 points.pop();
65 }
66
67 let linear_ring = LinearRing::new(abstract_ring, points)?;
68 Ok(linear_ring)
69 }
70}
71
72impl From<&LinearRing> for GmlLinearRing {
73 fn from(ring: &LinearRing) -> Self {
74 let mut points: Vec<DirectPosition> = ring.points().to_vec();
77 if let Some(&first) = ring.points().first() {
78 points.push(first);
79 }
80
81 Self {
82 id: ring.id().map(|id| id.to_string()),
83 content: Some(GmlLinearRingContent::PosList(GmlDirectPositionList::from(
84 points.as_slice(),
85 ))),
86 }
87 }
88}
89
90pub fn serialize_linear_ring(ring: &LinearRing) -> Result<String, Error> {
96 let gml_ring = GmlLinearRing::from(ring);
97 Ok(se::to_string_with_root("gml:LinearRing", &gml_ring)?)
98}
99
100#[cfg(test)]
101mod tests {
102 use super::GmlLinearRingContent;
103 use crate::Error;
104 use crate::primitives::{GmlLinearRing, serialize_linear_ring};
105 use egml_core::model::base::Id;
106 use egml_core::model::geometry::DirectPosition;
107 use egml_core::model::geometry::primitives::{AbstractRing, LinearRing};
108 use quick_xml::de;
109
110 fn make_triangle() -> LinearRing {
111 let points = vec![
112 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
113 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
114 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
115 ];
116 LinearRing::new(AbstractRing::default(), points).unwrap()
117 }
118
119 #[test]
120 fn deserialize_linear_ring_with_pos_list_and_id() {
121 let xml_document = b"<gml:LinearRing gml:id=\"4018115_LR.d2yyBHNssydL8g3B8MvW\">
122<gml:posList srsDimension=\"3\">
123678058.447 5403817.567 424.209
124678058.275 5403817.484 424.209
125678058.689 5403816.628 424.209
126678058.871 5403816.718 424.209
127678058.447 5403817.567 424.209
128</gml:posList>
129</gml:LinearRing>";
130
131 let parsed_geometry: GmlLinearRing = de::from_reader(xml_document.as_ref()).expect("");
132 let l: LinearRing = parsed_geometry.try_into().unwrap();
133 use egml_core::model::base::AsAbstractGml;
134 assert!(l.id().is_some());
135 }
136
137 #[test]
138 fn deserialize_linear_ring_with_pos_list() {
139 let xml_document = b"<gml:LinearRing>
140 <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>
141 </gml:LinearRing>";
142
143 let parsed_geometry: GmlLinearRing = de::from_reader(xml_document.as_ref()).expect("");
144 let result: Result<LinearRing, Error> = parsed_geometry.try_into();
145
146 assert!(result.is_ok());
147 let linear_ring = result.unwrap();
148 assert_eq!(linear_ring.points().len(), 3);
149 }
150
151 #[test]
152 fn deserialize_linear_ring_with_duplicate_points_returns_error() {
153 let xml_document = b"<gml:LinearRing gml:id=\"DEBY_LOD2_4959457_LR.EEAbfUPItTlOGZGH7VDv\">
154 <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>
155 </gml:LinearRing>";
156
157 let parsed_geometry: GmlLinearRing = de::from_reader(xml_document.as_ref()).expect("");
158 let result: Result<LinearRing, Error> = parsed_geometry.try_into();
159
160 assert!(result.is_err());
161 }
162
163 #[test]
164 fn serialize_linear_ring_writes_gml_tags() {
165 let ring = make_triangle();
166 let xml = serialize_linear_ring(&ring).unwrap();
167
168 assert!(xml.contains("<gml:LinearRing"));
169 assert!(xml.contains("<gml:posList"));
170 assert!(!xml.contains("id="));
171 }
172
173 #[test]
174 fn serialize_linear_ring_appends_closing_vertex() {
175 let ring = make_triangle(); let gml = GmlLinearRing::from(&ring);
179 let positions: Vec<DirectPosition> = match gml.content.unwrap() {
180 GmlLinearRingContent::PosList(pos_list) => pos_list.try_into().unwrap(),
181 GmlLinearRingContent::Pos(_) => panic!("expected PosList"),
182 };
183 assert_eq!(positions.len(), 4); assert_eq!(positions.first(), positions.last()); }
186
187 #[test]
188 fn serialize_linear_ring_with_id() {
189 use egml_core::model::base::AsAbstractGmlMut;
190 let points = vec![
191 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
192 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
193 DirectPosition::new(0.0, 0.0, 1.0).unwrap(),
194 ];
195 let mut abstract_ring = AbstractRing::default();
196 abstract_ring.set_id(Some(Id::from_hashed_string("test-ring")));
197 let ring = LinearRing::new(abstract_ring, points).unwrap();
198
199 let xml = serialize_linear_ring(&ring).unwrap();
200 assert!(xml.contains("id="));
201 }
202
203 #[test]
204 fn round_trip_linear_ring_preserves_points() {
205 let ring = make_triangle();
206 let xml = serialize_linear_ring(&ring).unwrap();
207
208 let gml: GmlLinearRing = de::from_reader(xml.as_bytes()).unwrap();
209 let recovered: LinearRing = gml.try_into().unwrap();
210
211 assert_eq!(recovered.points().len(), ring.points().len());
212 for (a, b) in recovered.points().iter().zip(ring.points().iter()) {
213 assert_eq!(a.x(), b.x());
214 assert_eq!(a.y(), b.y());
215 assert_eq!(a.z(), b.z());
216 }
217 }
218
219 #[test]
220 fn round_trip_linear_ring_preserves_float_precision() {
221 let points = vec![
222 DirectPosition::new(678058.447, 5403817.567, 424.209).unwrap(),
223 DirectPosition::new(678058.275, 5403817.484, 424.209).unwrap(),
224 DirectPosition::new(678058.689, 5403816.628, 424.209).unwrap(),
225 ];
226 let ring = LinearRing::new(AbstractRing::default(), points.clone()).unwrap();
227
228 let xml = serialize_linear_ring(&ring).unwrap();
229 let gml: GmlLinearRing = de::from_reader(xml.as_bytes()).unwrap();
230 let recovered: LinearRing = gml.try_into().unwrap();
231
232 for (a, b) in recovered.points().iter().zip(points.iter()) {
233 assert_eq!(a.x(), b.x());
234 assert_eq!(a.y(), b.y());
235 assert_eq!(a.z(), b.z());
236 }
237 }
238
239 #[test]
240 fn round_trip_linear_ring_from_xml() {
241 let input_xml = "<gml:LinearRing gml:id=\"PolyID7350_878_759628_120742_0\">\
242 <gml:posList srsDimension=\"3\">0 0 0 1 0 0 0 1 0 0 0 0</gml:posList>\
243 </gml:LinearRing>";
244
245 let gml: GmlLinearRing = de::from_reader(input_xml.as_ref()).unwrap();
246 let ring: LinearRing = gml.try_into().unwrap();
247 let output_xml = serialize_linear_ring(&ring).unwrap();
248
249 assert_eq!(input_xml, output_xml);
250 }
251
252 #[test]
253 fn deserialize_linear_ring_with_pos_elements() {
254 let xml_document = b"<gml:LinearRing gml:id=\"PolyID7350_878_759628_120742_0\">
255 <gml:pos>457842.0 5439088.0 118.317691453624</gml:pos>
256 <gml:pos>457842.0 5439093.0 115.430940107676</gml:pos>
257 <gml:pos>457842.0 5439093.0 111.8</gml:pos>
258 <gml:pos>457842.0 5439083.0 111.8</gml:pos>
259 <gml:pos>457842.0 5439083.0 115.430940107676</gml:pos>
260 <gml:pos>457842.0 5439088.0 118.317691453624</gml:pos>
261 </gml:LinearRing>";
262
263 let parsed_geometry: GmlLinearRing = de::from_reader(xml_document.as_ref()).expect("");
264 let result: Result<LinearRing, Error> = parsed_geometry.try_into();
265
266 assert!(result.is_ok());
267 let linear_ring = result.unwrap();
268 assert_eq!(linear_ring.points().len(), 5);
269 }
270}