Skip to main content

egml_io/geometry/primitives/
line_string.rs

1use 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
77/// Serializes a [`LineString`] to a GML XML string.
78///
79/// # Errors
80///
81/// Returns [`Error::XmlSe`] if serialization fails.
82pub 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}