1use 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#[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 pub fn new(coords: Vec<Coord<T>>, dim: Dimension) -> Self {
35 LineString { dim, coords }
36 }
37
38 pub fn empty(dim: Dimension) -> Self {
40 Self::new(vec![], dim)
41 }
42
43 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 pub fn dimension(&self) -> Dimension {
64 self.dim
65 }
66
67 pub fn coords(&self) -> &[Coord<T>] {
69 &self.coords
70 }
71
72 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}