wkt/types/
linestring.rs

1// Copyright 2014-2015 The GeoRust Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//	http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use geo_traits::LineStringTrait;
16
17use crate::to_wkt::write_linestring;
18use crate::tokenizer::PeekableTokens;
19use crate::types::coord::Coord;
20use crate::types::Dimension;
21use crate::{FromTokens, Wkt, WktNum};
22use std::fmt;
23use std::str::FromStr;
24
25/// A parsed LineString.
26#[derive(Clone, Debug, Default, PartialEq)]
27pub struct LineString<T: WktNum = f64> {
28    pub(crate) coords: Vec<Coord<T>>,
29    pub(crate) dim: Dimension,
30}
31
32impl<T: WktNum> LineString<T> {
33    /// Create a new LineString from a sequence of [Coord] and known [Dimension].
34    pub fn new(coords: Vec<Coord<T>>, dim: Dimension) -> Self {
35        LineString { dim, coords }
36    }
37
38    /// Create a new empty LineString.
39    pub fn empty(dim: Dimension) -> Self {
40        Self::new(vec![], dim)
41    }
42
43    /// Create a new LineString from a non-empty sequence of [Coord].
44    ///
45    /// This will infer the dimension from the first coordinate, and will not validate that all
46    /// coordinates have the same dimension.
47    ///
48    /// Returns `None` if the input iterator is empty.
49    ///
50    /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting
51    /// to an [empty][Self::empty] geometry with specified dimension.
52    pub fn from_coords(coords: impl IntoIterator<Item = Coord<T>>) -> Option<Self> {
53        let coords = coords.into_iter().collect::<Vec<_>>();
54        if coords.is_empty() {
55            None
56        } else {
57            let dim = coords[0].dimension();
58            Some(Self::new(coords, dim))
59        }
60    }
61
62    /// Return the [Dimension] of this geometry.
63    pub fn dimension(&self) -> Dimension {
64        self.dim
65    }
66
67    /// Access the coordinates of this LineString.
68    pub fn coords(&self) -> &[Coord<T>] {
69        &self.coords
70    }
71
72    /// Consume self and return the inner parts.
73    pub fn into_inner(self) -> (Vec<Coord<T>>, Dimension) {
74        (self.coords, self.dim)
75    }
76}
77
78impl<T> From<LineString<T>> for Wkt<T>
79where
80    T: WktNum,
81{
82    fn from(value: LineString<T>) -> Self {
83        Wkt::LineString(value)
84    }
85}
86
87impl<T> FromTokens<T> for LineString<T>
88where
89    T: WktNum + FromStr + Default,
90{
91    fn from_tokens(tokens: &mut PeekableTokens<T>, dim: Dimension) -> Result<Self, &'static str> {
92        let result = FromTokens::comma_many(<Coord<T> as FromTokens<T>>::from_tokens, tokens, dim);
93        result.map(|coords| LineString { coords, dim })
94    }
95
96    fn new_empty(dim: Dimension) -> Self {
97        Self::empty(dim)
98    }
99}
100
101impl<T> fmt::Display for LineString<T>
102where
103    T: WktNum + fmt::Display,
104{
105    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
106        Ok(write_linestring(f, self)?)
107    }
108}
109
110impl<T: WktNum> LineStringTrait for LineString<T> {
111    type CoordType<'a>
112        = &'a Coord<T>
113    where
114        Self: 'a;
115
116    fn num_coords(&self) -> usize {
117        self.coords.len()
118    }
119
120    unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> {
121        self.coords.get_unchecked(i)
122    }
123}
124
125impl<'a, T: WktNum> LineStringTrait for &'a LineString<T> {
126    type CoordType<'b>
127        = &'a Coord<T>
128    where
129        Self: 'b;
130
131    fn num_coords(&self) -> usize {
132        self.coords.len()
133    }
134
135    unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> {
136        self.coords.get_unchecked(i)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::{Coord, LineString};
143    use crate::types::Dimension;
144    use crate::Wkt;
145    use std::str::FromStr;
146
147    #[test]
148    fn basic_linestring() {
149        let wkt: Wkt<f64> = Wkt::from_str("LINESTRING (10 -20, -0 -0.5)").ok().unwrap();
150        let coords = match wkt {
151            Wkt::LineString(LineString { coords, dim }) => {
152                assert_eq!(dim, Dimension::XY);
153                coords
154            }
155            _ => unreachable!(),
156        };
157        assert_eq!(2, coords.len());
158
159        assert_eq!(10.0, coords[0].x);
160        assert_eq!(-20.0, coords[0].y);
161        assert_eq!(None, coords[0].z);
162        assert_eq!(None, coords[0].m);
163
164        assert_eq!(0.0, coords[1].x);
165        assert_eq!(-0.5, coords[1].y);
166        assert_eq!(None, coords[1].z);
167        assert_eq!(None, coords[1].m);
168    }
169
170    #[test]
171    fn basic_linestring_z() {
172        let wkt = Wkt::from_str("LINESTRING Z (-117 33 2, -116 34 4)")
173            .ok()
174            .unwrap();
175        let coords = match wkt {
176            Wkt::LineString(LineString { coords, dim }) => {
177                assert_eq!(dim, Dimension::XYZ);
178                coords
179            }
180            _ => unreachable!(),
181        };
182        assert_eq!(2, coords.len());
183
184        assert_eq!(-117.0, coords[0].x);
185        assert_eq!(33.0, coords[0].y);
186        assert_eq!(Some(2.0), coords[0].z);
187        assert_eq!(None, coords[0].m);
188
189        assert_eq!(-116.0, coords[1].x);
190        assert_eq!(34.0, coords[1].y);
191        assert_eq!(Some(4.0), coords[1].z);
192        assert_eq!(None, coords[1].m);
193    }
194
195    #[test]
196    fn basic_linestring_m() {
197        let wkt = Wkt::from_str("LINESTRING M (-117 33 2, -116 34 4)")
198            .ok()
199            .unwrap();
200        let coords = match wkt {
201            Wkt::LineString(LineString { coords, dim }) => {
202                assert_eq!(dim, Dimension::XYM);
203                coords
204            }
205            _ => unreachable!(),
206        };
207        assert_eq!(2, coords.len());
208
209        assert_eq!(-117.0, coords[0].x);
210        assert_eq!(33.0, coords[0].y);
211        assert_eq!(None, coords[0].z);
212        assert_eq!(Some(2.0), coords[0].m);
213
214        assert_eq!(-116.0, coords[1].x);
215        assert_eq!(34.0, coords[1].y);
216        assert_eq!(None, coords[1].z);
217        assert_eq!(Some(4.0), coords[1].m);
218    }
219
220    #[test]
221    fn basic_linestring_zm() {
222        let wkt = Wkt::from_str("LINESTRING ZM (-117 33 2 3, -116 34 4 5)")
223            .ok()
224            .unwrap();
225        let coords = match wkt {
226            Wkt::LineString(LineString { coords, dim }) => {
227                assert_eq!(dim, Dimension::XYZM);
228                coords
229            }
230            _ => unreachable!(),
231        };
232        assert_eq!(2, coords.len());
233
234        assert_eq!(-117.0, coords[0].x);
235        assert_eq!(33.0, coords[0].y);
236        assert_eq!(Some(2.0), coords[0].z);
237        assert_eq!(Some(3.0), coords[0].m);
238
239        assert_eq!(-116.0, coords[1].x);
240        assert_eq!(34.0, coords[1].y);
241        assert_eq!(Some(4.0), coords[1].z);
242        assert_eq!(Some(5.0), coords[1].m);
243    }
244
245    #[test]
246    fn basic_linestring_zm_one_word() {
247        let wkt = Wkt::from_str("LINESTRINGZM (-117 33 2 3, -116 34 4 5)")
248            .ok()
249            .unwrap();
250        let coords = match wkt {
251            Wkt::LineString(LineString { coords, dim }) => {
252                assert_eq!(dim, Dimension::XYZM);
253                coords
254            }
255            _ => unreachable!(),
256        };
257        assert_eq!(2, coords.len());
258
259        assert_eq!(-117.0, coords[0].x);
260        assert_eq!(33.0, coords[0].y);
261        assert_eq!(Some(2.0), coords[0].z);
262        assert_eq!(Some(3.0), coords[0].m);
263
264        assert_eq!(-116.0, coords[1].x);
265        assert_eq!(34.0, coords[1].y);
266        assert_eq!(Some(4.0), coords[1].z);
267        assert_eq!(Some(5.0), coords[1].m);
268    }
269
270    #[test]
271    fn parse_empty_linestring() {
272        let wkt: Wkt<f64> = Wkt::from_str("LINESTRING EMPTY").ok().unwrap();
273        match wkt {
274            Wkt::LineString(LineString { coords, dim }) => {
275                assert!(coords.is_empty());
276                assert_eq!(dim, Dimension::XY);
277            }
278            _ => unreachable!(),
279        };
280
281        let wkt: Wkt<f64> = Wkt::from_str("LINESTRING Z EMPTY").ok().unwrap();
282        match wkt {
283            Wkt::LineString(LineString { coords, dim }) => {
284                assert!(coords.is_empty());
285                assert_eq!(dim, Dimension::XYZ);
286            }
287            _ => unreachable!(),
288        };
289
290        let wkt: Wkt<f64> = Wkt::from_str("LINESTRING M EMPTY").ok().unwrap();
291        match wkt {
292            Wkt::LineString(LineString { coords, dim }) => {
293                assert!(coords.is_empty());
294                assert_eq!(dim, Dimension::XYM);
295            }
296            _ => unreachable!(),
297        };
298
299        let wkt: Wkt<f64> = Wkt::from_str("LINESTRING ZM EMPTY").ok().unwrap();
300        match wkt {
301            Wkt::LineString(LineString { coords, dim }) => {
302                assert!(coords.is_empty());
303                assert_eq!(dim, Dimension::XYZM);
304            }
305            _ => unreachable!(),
306        };
307    }
308
309    #[test]
310    fn write_empty_linestring() {
311        let linestring: LineString<f64> = LineString::empty(Dimension::XY);
312        assert_eq!("LINESTRING EMPTY", format!("{}", linestring));
313
314        let linestring: LineString<f64> = LineString::empty(Dimension::XYZ);
315        assert_eq!("LINESTRING Z EMPTY", format!("{}", linestring));
316
317        let linestring: LineString<f64> = LineString::empty(Dimension::XYM);
318        assert_eq!("LINESTRING M EMPTY", format!("{}", linestring));
319
320        let linestring: LineString<f64> = LineString::empty(Dimension::XYZM);
321        assert_eq!("LINESTRING ZM EMPTY", format!("{}", linestring));
322    }
323
324    #[test]
325    fn write_linestring() {
326        let linestring = LineString::from_coords([
327            Coord {
328                x: 10.1,
329                y: 20.2,
330                z: None,
331                m: None,
332            },
333            Coord {
334                x: 30.3,
335                y: 40.4,
336                z: None,
337                m: None,
338            },
339        ])
340        .unwrap();
341
342        assert_eq!("LINESTRING(10.1 20.2,30.3 40.4)", format!("{}", linestring));
343    }
344}