wkt/types/
point.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::PointTrait;
16
17use crate::to_wkt::write_point;
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 Point.
26#[derive(Clone, Debug, Default, PartialEq)]
27pub struct Point<T: WktNum = f64> {
28    pub(crate) coord: Option<Coord<T>>,
29    pub(crate) dim: Dimension,
30}
31
32impl<T: WktNum> Point<T> {
33    /// Create a new Point from a coordinate and known [Dimension].
34    pub fn new(coord: Option<Coord<T>>, dim: Dimension) -> Self {
35        Self { coord, dim }
36    }
37
38    /// Create a new point from a valid [Coord].
39    ///
40    /// This infers the dimension from the coordinate.
41    pub fn from_coord(coord: Coord<T>) -> Self {
42        Self {
43            dim: coord.dimension(),
44            coord: Some(coord),
45        }
46    }
47
48    /// Create a new empty point.
49    pub fn empty(dim: Dimension) -> Self {
50        Self::new(None, dim)
51    }
52
53    /// Return the [Dimension] of this geometry.
54    pub fn dimension(&self) -> Dimension {
55        self.dim
56    }
57
58    /// Access the coordinate of this point.
59    pub fn coord(&self) -> Option<&Coord<T>> {
60        self.coord.as_ref()
61    }
62
63    /// Consume self and return the inner parts.
64    pub fn into_inner(self) -> (Option<Coord<T>>, Dimension) {
65        (self.coord, self.dim)
66    }
67}
68
69impl<T> From<Point<T>> for Wkt<T>
70where
71    T: WktNum,
72{
73    fn from(value: Point<T>) -> Self {
74        Wkt::Point(value)
75    }
76}
77
78impl<T> fmt::Display for Point<T>
79where
80    T: WktNum + fmt::Display,
81{
82    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
83        Ok(write_point(f, self)?)
84    }
85}
86
87impl<T> FromTokens<T> for Point<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 = <Coord<T> as FromTokens<T>>::from_tokens(tokens, dim);
93        result.map(|coord| Point {
94            coord: Some(coord),
95            dim,
96        })
97    }
98
99    fn new_empty(dim: Dimension) -> Self {
100        Self::empty(dim)
101    }
102}
103
104impl<T: WktNum> PointTrait for Point<T> {
105    type CoordType<'a>
106        = &'a Coord<T>
107    where
108        Self: 'a;
109
110    fn coord(&self) -> Option<Self::CoordType<'_>> {
111        self.coord.as_ref()
112    }
113}
114
115impl<'a, T: WktNum> PointTrait for &'a Point<T> {
116    type CoordType<'b>
117        = &'a Coord<T>
118    where
119        Self: 'b;
120
121    fn coord(&self) -> Option<Self::CoordType<'_>> {
122        self.coord.as_ref()
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::{Coord, Point};
129    use crate::types::Dimension;
130    use crate::Wkt;
131    use std::str::FromStr;
132
133    #[test]
134    fn basic_point() {
135        let wkt: Wkt<f64> = Wkt::from_str("POINT (10 -20)").ok().unwrap();
136        let (coord, dim) = match wkt {
137            Wkt::Point(Point { coord, dim }) => (coord.unwrap(), dim),
138            _ => unreachable!(),
139        };
140        assert_eq!(10.0, coord.x);
141        assert_eq!(-20.0, coord.y);
142        assert_eq!(None, coord.z);
143        assert_eq!(None, coord.m);
144        assert_eq!(dim, Dimension::XY);
145    }
146
147    #[test]
148    fn basic_point_z() {
149        let wkt = Wkt::from_str("POINT Z(-117 33 10)").ok().unwrap();
150        let (coord, dim) = match wkt {
151            Wkt::Point(Point { coord, dim }) => (coord.unwrap(), dim),
152            _ => unreachable!(),
153        };
154        assert_eq!(-117.0, coord.x);
155        assert_eq!(33.0, coord.y);
156        assert_eq!(Some(10.0), coord.z);
157        assert_eq!(None, coord.m);
158        assert_eq!(dim, Dimension::XYZ);
159    }
160
161    #[test]
162    fn basic_point_z_one_word() {
163        let wkt = Wkt::from_str("POINTZ(-117 33 10)").ok().unwrap();
164        let (coord, dim) = match wkt {
165            Wkt::Point(Point { coord, dim }) => (coord.unwrap(), dim),
166            _ => unreachable!(),
167        };
168        assert_eq!(-117.0, coord.x);
169        assert_eq!(33.0, coord.y);
170        assert_eq!(Some(10.0), coord.z);
171        assert_eq!(None, coord.m);
172        assert_eq!(dim, Dimension::XYZ);
173    }
174
175    #[test]
176    fn basic_point_whitespace() {
177        let wkt: Wkt<f64> = Wkt::from_str(" \n\t\rPOINT \n\t\r( \n\r\t10 \n\t\r-20 \n\t\r) \n\t\r")
178            .ok()
179            .unwrap();
180        let (coord, dim) = match wkt {
181            Wkt::Point(Point { coord, dim }) => (coord.unwrap(), dim),
182            _ => unreachable!(),
183        };
184        assert_eq!(10.0, coord.x);
185        assert_eq!(-20.0, coord.y);
186        assert_eq!(None, coord.z);
187        assert_eq!(None, coord.m);
188        assert_eq!(dim, Dimension::XY);
189    }
190
191    #[test]
192    fn parse_empty_point() {
193        let wkt: Wkt<f64> = Wkt::from_str("POINT EMPTY").ok().unwrap();
194        match wkt {
195            Wkt::Point(Point { coord, dim }) => {
196                assert!(coord.is_none());
197                assert_eq!(dim, Dimension::XY);
198            }
199            _ => unreachable!(),
200        };
201
202        let wkt: Wkt<f64> = Wkt::from_str("POINT Z EMPTY").ok().unwrap();
203        match wkt {
204            Wkt::Point(Point { coord, dim }) => {
205                assert!(coord.is_none());
206                assert_eq!(dim, Dimension::XYZ);
207            }
208            _ => unreachable!(),
209        };
210
211        let wkt: Wkt<f64> = Wkt::from_str("POINT M EMPTY").ok().unwrap();
212        match wkt {
213            Wkt::Point(Point { coord, dim }) => {
214                assert!(coord.is_none());
215                assert_eq!(dim, Dimension::XYM);
216            }
217            _ => unreachable!(),
218        };
219
220        let wkt: Wkt<f64> = Wkt::from_str("POINT ZM EMPTY").ok().unwrap();
221        match wkt {
222            Wkt::Point(Point { coord, dim }) => {
223                assert!(coord.is_none());
224                assert_eq!(dim, Dimension::XYZM);
225            }
226            _ => unreachable!(),
227        };
228    }
229
230    #[test]
231    fn invalid_points() {
232        <Wkt<f64>>::from_str("POINT ()").err().unwrap();
233        <Wkt<f64>>::from_str("POINT (10)").err().unwrap();
234        <Wkt<f64>>::from_str("POINT 10").err().unwrap();
235    }
236
237    #[test]
238    fn write_empty_point() {
239        let point: Point<f64> = Point::empty(Dimension::XY);
240        assert_eq!("POINT EMPTY", format!("{}", point));
241
242        let point: Point<f64> = Point::empty(Dimension::XYZ);
243        assert_eq!("POINT Z EMPTY", format!("{}", point));
244
245        let point: Point<f64> = Point::empty(Dimension::XYM);
246        assert_eq!("POINT M EMPTY", format!("{}", point));
247
248        let point: Point<f64> = Point::empty(Dimension::XYZM);
249        assert_eq!("POINT ZM EMPTY", format!("{}", point));
250    }
251
252    #[test]
253    fn write_2d_point() {
254        let point = Point::from_coord(Coord {
255            x: 10.12345,
256            y: 20.67891,
257            z: None,
258            m: None,
259        });
260
261        assert_eq!("POINT(10.12345 20.67891)", format!("{}", point));
262    }
263
264    #[test]
265    fn write_point_with_z_coord() {
266        let point = Point::from_coord(Coord {
267            x: 10.12345,
268            y: 20.67891,
269            z: Some(-32.56455),
270            m: None,
271        });
272
273        assert_eq!("POINT Z(10.12345 20.67891 -32.56455)", format!("{}", point));
274    }
275
276    #[test]
277    fn write_point_with_m_coord() {
278        let point = Point::from_coord(Coord {
279            x: 10.12345,
280            y: 20.67891,
281            z: None,
282            m: Some(10.),
283        });
284
285        assert_eq!("POINT M(10.12345 20.67891 10)", format!("{}", point));
286    }
287
288    #[test]
289    fn write_point_with_zm_coord() {
290        let point = Point::from_coord(Coord {
291            x: 10.12345,
292            y: 20.67891,
293            z: Some(-32.56455),
294            m: Some(10.),
295        });
296
297        assert_eq!(
298            "POINT ZM(10.12345 20.67891 -32.56455 10)",
299            format!("{}", point)
300        );
301    }
302}