egml_io/geometry/primitives/
line_string.rs1use crate::geometry::direct_position_list::GmlDirectPositionList;
2use crate::{Error, GmlDirectPosition};
3use egml_core::model::base::{AsAbstractGml, AsAbstractGmlMut};
4use egml_core::model::geometry::DirectPosition;
5use egml_core::model::geometry::primitives::{AbstractCurve, LineString};
6use quick_xml::se;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
10pub struct GmlLineString {
11 #[serde(
12 rename(serialize = "@gml:id", deserialize = "@id"),
13 skip_serializing_if = "Option::is_none"
14 )]
15 pub id: Option<String>,
16
17 #[serde(rename = "$value", skip_serializing_if = "Option::is_none")]
18 pub content: Option<GmlLineStringContent>,
19}
20
21#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
22pub enum GmlLineStringContent {
23 #[serde(rename(serialize = "gml:posList", deserialize = "posList"))]
24 PosList(GmlDirectPositionList),
25
26 #[serde(rename(serialize = "gml:pos", deserialize = "pos"))]
27 Pos(Vec<GmlDirectPosition>),
28}
29
30impl TryFrom<GmlLineStringContent> for Vec<DirectPosition> {
31 type Error = Error;
32
33 fn try_from(value: GmlLineStringContent) -> Result<Self, Self::Error> {
34 match value {
35 GmlLineStringContent::PosList(x) => x.try_into(),
36 GmlLineStringContent::Pos(x) => {
37 let points: Vec<DirectPosition> = x
38 .into_iter()
39 .map(|p| p.try_into())
40 .collect::<Result<Vec<_>, _>>()?;
41
42 Ok(points)
43 }
44 }
45 }
46}
47
48impl TryFrom<GmlLineString> for LineString {
49 type Error = Error;
50
51 fn try_from(value: GmlLineString) -> Result<Self, Self::Error> {
52 let id = value.id.map(|id| id.try_into()).transpose()?;
53 let mut abstract_curve = AbstractCurve::default();
54 abstract_curve.set_id(id);
55
56 let points: Vec<DirectPosition> = value
57 .content
58 .ok_or(Error::ElementNotFound("No element found".to_string()))?
59 .try_into()?;
60
61 let linear_ring = LineString::new(abstract_curve, points)?;
62 Ok(linear_ring)
63 }
64}
65
66impl From<&LineString> for GmlLineString {
67 fn from(line: &LineString) -> Self {
68 Self {
69 id: line.id().map(|id| id.to_string()),
70 content: Some(GmlLineStringContent::PosList(GmlDirectPositionList::from(
71 line.points(),
72 ))),
73 }
74 }
75}
76
77pub fn serialize_line_string(line: &LineString) -> Result<String, Error> {
83 let gml = GmlLineString::from(line);
84 Ok(se::to_string_with_root("gml:LineString", &gml)?)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::GmlLineString;
90 use crate::primitives::line_string::serialize_line_string;
91 use egml_core::model::geometry::DirectPosition;
92 use egml_core::model::geometry::primitives::{AbstractCurve, LineString};
93 use quick_xml::de;
94
95 fn make_line_string() -> LineString {
96 let points = vec![
97 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
98 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
99 DirectPosition::new(2.0, 0.0, 0.0).unwrap(),
100 ];
101 LineString::new(AbstractCurve::default(), points).unwrap()
102 }
103
104 #[test]
105 fn deserialize_line_string() {
106 let xml_document = b"<gml:LineString>
107 <gml:posList srsDimension=\"3\">0.0 0.0 0.0 1.0 1.0 1.0 2.0 2.0 2.0</gml:posList>
108 </gml:LineString>";
109
110 let parsed_geometry: GmlLineString = de::from_reader(xml_document.as_ref()).expect("");
111 let line_string: LineString = parsed_geometry.try_into().unwrap();
112 assert_eq!(line_string.points().len(), 3);
113 }
114
115 #[test]
116 fn serialize_line_string_writes_gml_tags() {
117 let line = make_line_string();
118 let xml = serialize_line_string(&line).unwrap();
119
120 assert!(xml.contains("<gml:LineString"));
121 assert!(xml.contains("<gml:posList"));
122 assert!(!xml.contains("id="));
123 }
124
125 #[test]
126 fn round_trip_line_string_preserves_points() {
127 let line = make_line_string();
128 let xml = serialize_line_string(&line).unwrap();
129
130 let gml: GmlLineString = de::from_reader(xml.as_bytes()).unwrap();
131 let recovered: LineString = gml.try_into().unwrap();
132
133 assert_eq!(recovered.points().len(), line.points().len());
134 for (a, b) in recovered.points().iter().zip(line.points().iter()) {
135 assert_eq!(a.x(), b.x());
136 assert_eq!(a.y(), b.y());
137 assert_eq!(a.z(), b.z());
138 }
139 }
140
141 #[test]
142 fn round_trip_line_string_from_xml() {
143 let input_xml = "<gml:LineString>\
144 <gml:posList srsDimension=\"3\">0 0 0 1 0 0 2 0 0</gml:posList>\
145 </gml:LineString>";
146
147 let gml: GmlLineString = de::from_reader(input_xml.as_bytes()).unwrap();
148 let line: LineString = gml.try_into().unwrap();
149 let output_xml = serialize_line_string(&line).unwrap();
150
151 assert_eq!(input_xml, output_xml);
152 }
153
154 #[test]
155 fn round_trip_line_string_preserves_float_precision() {
156 let points = vec![
157 DirectPosition::new(678058.447, 5403817.567, 424.209).unwrap(),
158 DirectPosition::new(678058.275, 5403817.484, 424.209).unwrap(),
159 DirectPosition::new(678058.689, 5403816.628, 424.209).unwrap(),
160 ];
161 let line = LineString::new(AbstractCurve::default(), points.clone()).unwrap();
162
163 let xml = serialize_line_string(&line).unwrap();
164 let gml: GmlLineString = de::from_reader(xml.as_bytes()).unwrap();
165 let recovered: LineString = gml.try_into().unwrap();
166
167 for (a, b) in recovered.points().iter().zip(points.iter()) {
168 assert_eq!(a.x(), b.x());
169 assert_eq!(a.y(), b.y());
170 assert_eq!(a.z(), b.z());
171 }
172 }
173}