1#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Coord {
11 pub x: f64,
12 pub y: f64,
13 pub z: Option<f64>,
14 pub m: Option<f64>,
15}
16
17impl Coord {
18 pub const fn xy(x: f64, y: f64) -> Self {
19 Self {
20 x,
21 y,
22 z: None,
23 m: None,
24 }
25 }
26
27 pub const fn xyz(x: f64, y: f64, z: f64) -> Self {
28 Self {
29 x,
30 y,
31 z: Some(z),
32 m: None,
33 }
34 }
35
36 pub const fn xym(x: f64, y: f64, m: f64) -> Self {
37 Self {
38 x,
39 y,
40 z: None,
41 m: Some(m),
42 }
43 }
44
45 pub const fn xyzm(x: f64, y: f64, z: f64, m: f64) -> Self {
46 Self {
47 x,
48 y,
49 z: Some(z),
50 m: Some(m),
51 }
52 }
53
54 pub const fn has_z(&self) -> bool {
55 self.z.is_some()
56 }
57
58 pub const fn has_m(&self) -> bool {
59 self.m.is_some()
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Default)]
64pub struct LineString {
65 pub coords: Vec<Coord>,
66}
67
68impl LineString {
69 pub fn new(coords: Vec<Coord>) -> Self {
70 Self { coords }
71 }
72
73 pub fn is_empty(&self) -> bool {
74 self.coords.is_empty()
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Default)]
84pub struct Polygon {
85 pub exterior: LineString,
86 pub holes: Vec<LineString>,
87}
88
89impl Polygon {
90 pub fn new(exterior: LineString, holes: Vec<LineString>) -> Self {
91 Self { exterior, holes }
92 }
93
94 pub fn is_empty(&self) -> bool {
95 self.exterior.is_empty()
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102pub enum GeometryType {
103 Point,
104 LineString,
105 Polygon,
106 MultiPoint,
107 MultiLineString,
108 MultiPolygon,
109 GeometryCollection,
110}
111
112#[derive(Debug, Clone, PartialEq)]
113pub enum Geometry {
114 Point(Coord),
115 LineString(LineString),
116 Polygon(Polygon),
117 MultiPoint(Vec<Coord>),
118 MultiLineString(Vec<LineString>),
119 MultiPolygon(Vec<Polygon>),
120 GeometryCollection(Vec<Geometry>),
121 Empty(GeometryType),
124}
125
126impl Geometry {
127 pub fn type_of(&self) -> GeometryType {
128 match self {
129 Geometry::Point(_) => GeometryType::Point,
130 Geometry::LineString(_) => GeometryType::LineString,
131 Geometry::Polygon(_) => GeometryType::Polygon,
132 Geometry::MultiPoint(_) => GeometryType::MultiPoint,
133 Geometry::MultiLineString(_) => GeometryType::MultiLineString,
134 Geometry::MultiPolygon(_) => GeometryType::MultiPolygon,
135 Geometry::GeometryCollection(_) => GeometryType::GeometryCollection,
136 Geometry::Empty(t) => *t,
137 }
138 }
139
140 pub fn is_empty(&self) -> bool {
141 match self {
142 Geometry::Empty(_) => true,
143 Geometry::Point(_) => false,
144 Geometry::LineString(ls) => ls.is_empty(),
145 Geometry::Polygon(p) => p.is_empty(),
146 Geometry::MultiPoint(v) => v.is_empty(),
147 Geometry::MultiLineString(v) => v.iter().all(LineString::is_empty),
148 Geometry::MultiPolygon(v) => v.iter().all(Polygon::is_empty),
149 Geometry::GeometryCollection(v) => v.iter().all(Geometry::is_empty),
150 }
151 }
152
153 pub fn has_z(&self) -> bool {
155 match self {
156 Geometry::Empty(_) => false,
157 Geometry::Point(c) => c.has_z(),
158 Geometry::LineString(ls) => ls.coords.iter().any(Coord::has_z),
159 Geometry::Polygon(p) => {
160 p.exterior.coords.iter().any(Coord::has_z)
161 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_z))
162 }
163 Geometry::MultiPoint(v) => v.iter().any(Coord::has_z),
164 Geometry::MultiLineString(v) => v.iter().any(|ls| ls.coords.iter().any(Coord::has_z)),
165 Geometry::MultiPolygon(v) => v.iter().any(|p| {
166 p.exterior.coords.iter().any(Coord::has_z)
167 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_z))
168 }),
169 Geometry::GeometryCollection(v) => v.iter().any(Geometry::has_z),
170 }
171 }
172
173 pub fn bbox(&self) -> Option<[f64; 4]> {
179 let mut acc: Option<[f64; 4]> = None;
180 for_each_xy(self, &mut |x, y| {
181 if !x.is_finite() || !y.is_finite() {
182 return;
183 }
184 match &mut acc {
185 None => acc = Some([x, y, x, y]),
186 Some(b) => {
187 if x < b[0] {
188 b[0] = x;
189 }
190 if y < b[1] {
191 b[1] = y;
192 }
193 if x > b[2] {
194 b[2] = x;
195 }
196 if y > b[3] {
197 b[3] = y;
198 }
199 }
200 }
201 });
202 acc
203 }
204
205 pub fn has_m(&self) -> bool {
207 match self {
208 Geometry::Empty(_) => false,
209 Geometry::Point(c) => c.has_m(),
210 Geometry::LineString(ls) => ls.coords.iter().any(Coord::has_m),
211 Geometry::Polygon(p) => {
212 p.exterior.coords.iter().any(Coord::has_m)
213 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_m))
214 }
215 Geometry::MultiPoint(v) => v.iter().any(Coord::has_m),
216 Geometry::MultiLineString(v) => v.iter().any(|ls| ls.coords.iter().any(Coord::has_m)),
217 Geometry::MultiPolygon(v) => v.iter().any(|p| {
218 p.exterior.coords.iter().any(Coord::has_m)
219 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_m))
220 }),
221 Geometry::GeometryCollection(v) => v.iter().any(Geometry::has_m),
222 }
223 }
224}
225
226fn for_each_xy(g: &Geometry, f: &mut dyn FnMut(f64, f64)) {
229 match g {
230 Geometry::Empty(_) => {}
231 Geometry::Point(c) => f(c.x, c.y),
232 Geometry::LineString(ls) => {
233 for c in &ls.coords {
234 f(c.x, c.y);
235 }
236 }
237 Geometry::Polygon(p) => {
238 for c in &p.exterior.coords {
239 f(c.x, c.y);
240 }
241 for h in &p.holes {
242 for c in &h.coords {
243 f(c.x, c.y);
244 }
245 }
246 }
247 Geometry::MultiPoint(v) => {
248 for c in v {
249 f(c.x, c.y);
250 }
251 }
252 Geometry::MultiLineString(v) => {
253 for ls in v {
254 for c in &ls.coords {
255 f(c.x, c.y);
256 }
257 }
258 }
259 Geometry::MultiPolygon(v) => {
260 for p in v {
261 for c in &p.exterior.coords {
262 f(c.x, c.y);
263 }
264 for h in &p.holes {
265 for c in &h.coords {
266 f(c.x, c.y);
267 }
268 }
269 }
270 }
271 Geometry::GeometryCollection(v) => {
272 for inner in v {
273 for_each_xy(inner, f);
274 }
275 }
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn coord_constructors_track_dimensions() {
285 assert!(!Coord::xy(1.0, 2.0).has_z());
286 assert!(!Coord::xy(1.0, 2.0).has_m());
287
288 let c = Coord::xyz(1.0, 2.0, 3.0);
289 assert!(c.has_z() && !c.has_m());
290 assert_eq!(c.z, Some(3.0));
291
292 let c = Coord::xym(1.0, 2.0, 99.0);
293 assert!(!c.has_z() && c.has_m());
294 assert_eq!(c.m, Some(99.0));
295
296 let c = Coord::xyzm(1.0, 2.0, 3.0, 99.0);
297 assert!(c.has_z() && c.has_m());
298 }
299
300 #[test]
301 fn geometry_type_of_matches_variant() {
302 assert_eq!(
303 Geometry::Point(Coord::xy(0.0, 0.0)).type_of(),
304 GeometryType::Point
305 );
306 assert_eq!(
307 Geometry::Polygon(Polygon::default()).type_of(),
308 GeometryType::Polygon
309 );
310 assert_eq!(
311 Geometry::Empty(GeometryType::MultiPolygon).type_of(),
312 GeometryType::MultiPolygon
313 );
314 }
315
316 #[test]
317 fn empty_detection() {
318 assert!(Geometry::Empty(GeometryType::Point).is_empty());
319 assert!(Geometry::LineString(LineString::default()).is_empty());
320 assert!(!Geometry::Point(Coord::xy(0.0, 0.0)).is_empty());
321
322 let ls = LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xy(1.0, 1.0)]);
323 assert!(!Geometry::LineString(ls).is_empty());
324 }
325
326 #[test]
327 fn bbox_of_polygon_with_hole() {
328 let p = Polygon::new(
329 LineString::new(vec![
330 Coord::xy(0.0, 0.0),
331 Coord::xy(10.0, 0.0),
332 Coord::xy(10.0, 10.0),
333 Coord::xy(0.0, 10.0),
334 Coord::xy(0.0, 0.0),
335 ]),
336 vec![LineString::new(vec![
337 Coord::xy(2.0, 2.0),
338 Coord::xy(4.0, 2.0),
339 Coord::xy(4.0, 4.0),
340 Coord::xy(2.0, 4.0),
341 Coord::xy(2.0, 2.0),
342 ])],
343 );
344 let bbox = Geometry::Polygon(p).bbox().unwrap();
345 assert_eq!(bbox, [0.0, 0.0, 10.0, 10.0]);
346 }
347
348 #[test]
349 fn bbox_of_empty_geometry_is_none() {
350 assert!(Geometry::Empty(GeometryType::Polygon).bbox().is_none());
351 assert!(Geometry::LineString(LineString::default()).bbox().is_none());
352 }
353
354 #[test]
355 fn bbox_skips_nan() {
356 let g = Geometry::MultiPoint(vec![
357 Coord::xy(1.0, 2.0),
358 Coord::xy(f64::NAN, f64::NAN),
359 Coord::xy(5.0, 6.0),
360 ]);
361 assert_eq!(g.bbox(), Some([1.0, 2.0, 5.0, 6.0]));
362 }
363
364 #[test]
365 fn bbox_of_geometry_collection() {
366 let g = Geometry::GeometryCollection(vec![
367 Geometry::Point(Coord::xy(-1.0, -1.0)),
368 Geometry::Point(Coord::xy(5.0, 5.0)),
369 ]);
370 assert_eq!(g.bbox(), Some([-1.0, -1.0, 5.0, 5.0]));
371 }
372
373 #[test]
374 fn has_z_m_recursion() {
375 let ls = LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xyz(1.0, 1.0, 5.0)]);
376 let g = Geometry::LineString(ls);
377 assert!(g.has_z());
378 assert!(!g.has_m());
379
380 let mls = Geometry::MultiLineString(vec![LineString::new(vec![Coord::xym(0.0, 0.0, 7.0)])]);
381 assert!(!mls.has_z());
382 assert!(mls.has_m());
383
384 let collection =
385 Geometry::GeometryCollection(vec![Geometry::Point(Coord::xyzm(0.0, 0.0, 1.0, 2.0))]);
386 assert!(collection.has_z());
387 assert!(collection.has_m());
388 }
389}