1use 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#[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 pub fn new(rings: Vec<LineString<T>>, dim: Dimension) -> Self {
34 Polygon { dim, rings }
35 }
36
37 pub fn empty(dim: Dimension) -> Self {
39 Self::new(vec![], dim)
40 }
41
42 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 pub fn dimension(&self) -> Dimension {
63 self.dim
64 }
65
66 pub fn rings(&self) -> &[LineString<T>] {
70 &self.rings
71 }
72
73 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}