wkt/types/
polygon.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::PolygonTrait;
16
17use crate::to_wkt::write_polygon;
18use crate::tokenizer::PeekableTokens;
19use crate::types::{Dimension, LineString};
20use crate::{FromTokens, Wkt, WktNum};
21use std::fmt;
22use std::str::FromStr;
23
24/// A parsed Polygon.
25#[derive(Clone, Debug, Default, PartialEq)]
26pub struct Polygon<T: WktNum = f64> {
27    pub(crate) rings: Vec<LineString<T>>,
28    pub(crate) dim: Dimension,
29}
30
31impl<T: WktNum> Polygon<T> {
32    /// Create a new Polygon from a sequence of [LineString] and known [Dimension].
33    pub fn new(rings: Vec<LineString<T>>, dim: Dimension) -> Self {
34        Polygon { dim, rings }
35    }
36
37    /// Create a new empty polygon.
38    pub fn empty(dim: Dimension) -> Self {
39        Self::new(vec![], dim)
40    }
41
42    /// Create a new polygon from a non-empty sequence of [LineString].
43    ///
44    /// This will infer the dimension from the first line string, and will not validate that all
45    /// line strings have the same dimension.
46    ///
47    /// Returns `None` if the input iterator is empty.
48    ///
49    /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting
50    /// to an [empty][Self::empty] geometry with specified dimension.
51    pub fn from_rings(rings: impl IntoIterator<Item = LineString<T>>) -> Option<Self> {
52        let rings = rings.into_iter().collect::<Vec<_>>();
53        if rings.is_empty() {
54            None
55        } else {
56            let dim = rings[0].dimension();
57            Some(Self::new(rings, dim))
58        }
59    }
60
61    /// Return the [Dimension] of this geometry.
62    pub fn dimension(&self) -> Dimension {
63        self.dim
64    }
65
66    /// Access the inner rings.
67    ///
68    /// The first ring is defined to be the exterior ring, and the rest are interior rings.
69    pub fn rings(&self) -> &[LineString<T>] {
70        &self.rings
71    }
72
73    /// Consume self and return the inner parts.
74    pub fn into_inner(self) -> (Vec<LineString<T>>, Dimension) {
75        (self.rings, self.dim)
76    }
77}
78
79impl<T> From<Polygon<T>> for Wkt<T>
80where
81    T: WktNum,
82{
83    fn from(value: Polygon<T>) -> Self {
84        Wkt::Polygon(value)
85    }
86}
87
88impl<T> fmt::Display for Polygon<T>
89where
90    T: WktNum + fmt::Display,
91{
92    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
93        Ok(write_polygon(f, self)?)
94    }
95}
96
97impl<T> FromTokens<T> for Polygon<T>
98where
99    T: WktNum + FromStr + Default,
100{
101    fn from_tokens(tokens: &mut PeekableTokens<T>, dim: Dimension) -> Result<Self, &'static str> {
102        let result = FromTokens::comma_many(
103            <LineString<T> as FromTokens<T>>::from_tokens_with_parens,
104            tokens,
105            dim,
106        );
107        result.map(|rings| Polygon { rings, dim })
108    }
109
110    fn new_empty(dim: Dimension) -> Self {
111        Self::empty(dim)
112    }
113}
114
115impl<T: WktNum> PolygonTrait for Polygon<T> {
116    type RingType<'a>
117        = &'a LineString<T>
118    where
119        Self: 'a;
120
121    fn exterior(&self) -> Option<Self::RingType<'_>> {
122        self.rings.first()
123    }
124
125    fn num_interiors(&self) -> usize {
126        self.rings.len().saturating_sub(1)
127    }
128
129    unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> {
130        self.rings.get_unchecked(i + 1)
131    }
132}
133
134impl<T: WktNum> PolygonTrait for &Polygon<T> {
135    type RingType<'a>
136        = &'a LineString<T>
137    where
138        Self: 'a;
139
140    fn exterior(&self) -> Option<Self::RingType<'_>> {
141        self.rings.first()
142    }
143
144    fn num_interiors(&self) -> usize {
145        self.rings.len().saturating_sub(1)
146    }
147
148    unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> {
149        self.rings.get_unchecked(i + 1)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::{LineString, Polygon};
156    use crate::types::{Coord, Dimension};
157    use crate::Wkt;
158    use std::str::FromStr;
159
160    #[test]
161    fn basic_polygon() {
162        let wkt: Wkt<f64> = Wkt::from_str("POLYGON ((8 4, 4 0, 0 4, 8 4), (7 3, 4 1, 1 4, 7 3))")
163            .ok()
164            .unwrap();
165        let rings = match wkt {
166            Wkt::Polygon(Polygon { rings, dim }) => {
167                assert_eq!(dim, Dimension::XY);
168                rings
169            }
170            _ => unreachable!(),
171        };
172        assert_eq!(2, rings.len());
173    }
174
175    #[test]
176    fn parse_empty_polygon() {
177        let wkt: Wkt<f64> = Wkt::from_str("POLYGON EMPTY").ok().unwrap();
178        match wkt {
179            Wkt::Polygon(Polygon { rings, dim }) => {
180                assert!(rings.is_empty());
181                assert_eq!(dim, Dimension::XY);
182            }
183            _ => unreachable!(),
184        };
185
186        let wkt: Wkt<f64> = Wkt::from_str("POLYGON Z EMPTY").ok().unwrap();
187        match wkt {
188            Wkt::Polygon(Polygon { rings, dim }) => {
189                assert!(rings.is_empty());
190                assert_eq!(dim, Dimension::XYZ);
191            }
192            _ => unreachable!(),
193        };
194
195        let wkt: Wkt<f64> = Wkt::from_str("POLYGON M EMPTY").ok().unwrap();
196        match wkt {
197            Wkt::Polygon(Polygon { rings, dim }) => {
198                assert!(rings.is_empty());
199                assert_eq!(dim, Dimension::XYM);
200            }
201            _ => unreachable!(),
202        };
203
204        let wkt: Wkt<f64> = Wkt::from_str("POLYGON ZM EMPTY").ok().unwrap();
205        match wkt {
206            Wkt::Polygon(Polygon { rings, dim }) => {
207                assert!(rings.is_empty());
208                assert_eq!(dim, Dimension::XYZM);
209            }
210            _ => unreachable!(),
211        };
212    }
213
214    #[test]
215    fn write_empty_polygon() {
216        let polygon: Polygon<f64> = Polygon::empty(Dimension::XY);
217        assert_eq!("POLYGON EMPTY", format!("{}", polygon));
218
219        let polygon: Polygon<f64> = Polygon::empty(Dimension::XYZ);
220        assert_eq!("POLYGON Z EMPTY", format!("{}", polygon));
221
222        let polygon: Polygon<f64> = Polygon::empty(Dimension::XYM);
223        assert_eq!("POLYGON M EMPTY", format!("{}", polygon));
224
225        let polygon: Polygon<f64> = Polygon::empty(Dimension::XYZM);
226        assert_eq!("POLYGON ZM EMPTY", format!("{}", polygon));
227    }
228
229    #[test]
230    fn write_polygon() {
231        let polygon = Polygon::from_rings([
232            LineString::from_coords([
233                Coord {
234                    x: 0.,
235                    y: 0.,
236                    z: None,
237                    m: None,
238                },
239                Coord {
240                    x: 20.,
241                    y: 40.,
242                    z: None,
243                    m: None,
244                },
245                Coord {
246                    x: 40.,
247                    y: 0.,
248                    z: None,
249                    m: None,
250                },
251                Coord {
252                    x: 0.,
253                    y: 0.,
254                    z: None,
255                    m: None,
256                },
257            ])
258            .unwrap(),
259            LineString::from_coords([
260                Coord {
261                    x: 5.,
262                    y: 5.,
263                    z: None,
264                    m: None,
265                },
266                Coord {
267                    x: 20.,
268                    y: 30.,
269                    z: None,
270                    m: None,
271                },
272                Coord {
273                    x: 30.,
274                    y: 5.,
275                    z: None,
276                    m: None,
277                },
278                Coord {
279                    x: 5.,
280                    y: 5.,
281                    z: None,
282                    m: None,
283                },
284            ])
285            .unwrap(),
286        ])
287        .unwrap();
288
289        assert_eq!(
290            "POLYGON((0 0,20 40,40 0,0 0),(5 5,20 30,30 5,5 5))",
291            format!("{}", polygon)
292        );
293    }
294}