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)]
106#[non_exhaustive]
107pub enum GeometryType {
108 Point,
109 LineString,
110 Polygon,
111 MultiPoint,
112 MultiLineString,
113 MultiPolygon,
114 GeometryCollection,
115}
116
117#[derive(Debug, Clone, PartialEq)]
121#[non_exhaustive]
122pub enum Geometry {
123 Point(Coord),
124 LineString(LineString),
125 Polygon(Polygon),
126 MultiPoint(Vec<Coord>),
127 MultiLineString(Vec<LineString>),
128 MultiPolygon(Vec<Polygon>),
129 GeometryCollection(Vec<Geometry>),
130 Empty(GeometryType),
133}
134
135impl Geometry {
136 pub fn type_of(&self) -> GeometryType {
137 match self {
138 Geometry::Point(_) => GeometryType::Point,
139 Geometry::LineString(_) => GeometryType::LineString,
140 Geometry::Polygon(_) => GeometryType::Polygon,
141 Geometry::MultiPoint(_) => GeometryType::MultiPoint,
142 Geometry::MultiLineString(_) => GeometryType::MultiLineString,
143 Geometry::MultiPolygon(_) => GeometryType::MultiPolygon,
144 Geometry::GeometryCollection(_) => GeometryType::GeometryCollection,
145 Geometry::Empty(t) => *t,
146 }
147 }
148
149 pub fn is_empty(&self) -> bool {
150 match self {
151 Geometry::Empty(_) => true,
152 Geometry::Point(_) => false,
153 Geometry::LineString(ls) => ls.is_empty(),
154 Geometry::Polygon(p) => p.is_empty(),
155 Geometry::MultiPoint(v) => v.is_empty(),
156 Geometry::MultiLineString(v) => v.iter().all(LineString::is_empty),
157 Geometry::MultiPolygon(v) => v.iter().all(Polygon::is_empty),
158 Geometry::GeometryCollection(v) => v.iter().all(Geometry::is_empty),
159 }
160 }
161
162 pub fn coord_count(&self) -> usize {
170 match self {
171 Geometry::Empty(_) => 0,
172 Geometry::Point(_) => 1,
173 Geometry::LineString(ls) => ls.coords.len(),
174 Geometry::Polygon(p) => {
175 p.exterior.coords.len()
176 + p.holes.iter().map(|h| h.coords.len()).sum::<usize>()
177 }
178 Geometry::MultiPoint(v) => v.len(),
179 Geometry::MultiLineString(v) => v.iter().map(|ls| ls.coords.len()).sum(),
180 Geometry::MultiPolygon(v) => v
181 .iter()
182 .map(|p| {
183 p.exterior.coords.len()
184 + p.holes.iter().map(|h| h.coords.len()).sum::<usize>()
185 })
186 .sum(),
187 Geometry::GeometryCollection(v) => v.iter().map(Geometry::coord_count).sum(),
188 }
189 }
190
191 pub fn has_z(&self) -> bool {
193 match self {
194 Geometry::Empty(_) => false,
195 Geometry::Point(c) => c.has_z(),
196 Geometry::LineString(ls) => ls.coords.iter().any(Coord::has_z),
197 Geometry::Polygon(p) => {
198 p.exterior.coords.iter().any(Coord::has_z)
199 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_z))
200 }
201 Geometry::MultiPoint(v) => v.iter().any(Coord::has_z),
202 Geometry::MultiLineString(v) => v.iter().any(|ls| ls.coords.iter().any(Coord::has_z)),
203 Geometry::MultiPolygon(v) => v.iter().any(|p| {
204 p.exterior.coords.iter().any(Coord::has_z)
205 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_z))
206 }),
207 Geometry::GeometryCollection(v) => v.iter().any(Geometry::has_z),
208 }
209 }
210
211 pub fn bbox(&self) -> Option<[f64; 4]> {
217 let mut acc: Option<[f64; 4]> = None;
218 for_each_xy(self, &mut |x, y| {
219 if !x.is_finite() || !y.is_finite() {
220 return;
221 }
222 match &mut acc {
223 None => acc = Some([x, y, x, y]),
224 Some(b) => {
225 if x < b[0] {
226 b[0] = x;
227 }
228 if y < b[1] {
229 b[1] = y;
230 }
231 if x > b[2] {
232 b[2] = x;
233 }
234 if y > b[3] {
235 b[3] = y;
236 }
237 }
238 }
239 });
240 acc
241 }
242
243 pub fn has_m(&self) -> bool {
245 match self {
246 Geometry::Empty(_) => false,
247 Geometry::Point(c) => c.has_m(),
248 Geometry::LineString(ls) => ls.coords.iter().any(Coord::has_m),
249 Geometry::Polygon(p) => {
250 p.exterior.coords.iter().any(Coord::has_m)
251 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_m))
252 }
253 Geometry::MultiPoint(v) => v.iter().any(Coord::has_m),
254 Geometry::MultiLineString(v) => v.iter().any(|ls| ls.coords.iter().any(Coord::has_m)),
255 Geometry::MultiPolygon(v) => v.iter().any(|p| {
256 p.exterior.coords.iter().any(Coord::has_m)
257 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_m))
258 }),
259 Geometry::GeometryCollection(v) => v.iter().any(Geometry::has_m),
260 }
261 }
262}
263
264fn for_each_xy(g: &Geometry, f: &mut dyn FnMut(f64, f64)) {
267 match g {
268 Geometry::Empty(_) => {}
269 Geometry::Point(c) => f(c.x, c.y),
270 Geometry::LineString(ls) => {
271 for c in &ls.coords {
272 f(c.x, c.y);
273 }
274 }
275 Geometry::Polygon(p) => {
276 for c in &p.exterior.coords {
277 f(c.x, c.y);
278 }
279 for h in &p.holes {
280 for c in &h.coords {
281 f(c.x, c.y);
282 }
283 }
284 }
285 Geometry::MultiPoint(v) => {
286 for c in v {
287 f(c.x, c.y);
288 }
289 }
290 Geometry::MultiLineString(v) => {
291 for ls in v {
292 for c in &ls.coords {
293 f(c.x, c.y);
294 }
295 }
296 }
297 Geometry::MultiPolygon(v) => {
298 for p in v {
299 for c in &p.exterior.coords {
300 f(c.x, c.y);
301 }
302 for h in &p.holes {
303 for c in &h.coords {
304 f(c.x, c.y);
305 }
306 }
307 }
308 }
309 Geometry::GeometryCollection(v) => {
310 for inner in v {
311 for_each_xy(inner, f);
312 }
313 }
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn coord_constructors_track_dimensions() {
323 assert!(!Coord::xy(1.0, 2.0).has_z());
324 assert!(!Coord::xy(1.0, 2.0).has_m());
325
326 let c = Coord::xyz(1.0, 2.0, 3.0);
327 assert!(c.has_z() && !c.has_m());
328 assert_eq!(c.z, Some(3.0));
329
330 let c = Coord::xym(1.0, 2.0, 99.0);
331 assert!(!c.has_z() && c.has_m());
332 assert_eq!(c.m, Some(99.0));
333
334 let c = Coord::xyzm(1.0, 2.0, 3.0, 99.0);
335 assert!(c.has_z() && c.has_m());
336 }
337
338 #[test]
339 fn geometry_type_of_matches_variant() {
340 assert_eq!(
341 Geometry::Point(Coord::xy(0.0, 0.0)).type_of(),
342 GeometryType::Point
343 );
344 assert_eq!(
345 Geometry::Polygon(Polygon::default()).type_of(),
346 GeometryType::Polygon
347 );
348 assert_eq!(
349 Geometry::Empty(GeometryType::MultiPolygon).type_of(),
350 GeometryType::MultiPolygon
351 );
352 }
353
354 #[test]
355 fn empty_detection() {
356 assert!(Geometry::Empty(GeometryType::Point).is_empty());
357 assert!(Geometry::LineString(LineString::default()).is_empty());
358 assert!(!Geometry::Point(Coord::xy(0.0, 0.0)).is_empty());
359
360 let ls = LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xy(1.0, 1.0)]);
361 assert!(!Geometry::LineString(ls).is_empty());
362 }
363
364 #[test]
365 fn bbox_of_polygon_with_hole() {
366 let p = Polygon::new(
367 LineString::new(vec![
368 Coord::xy(0.0, 0.0),
369 Coord::xy(10.0, 0.0),
370 Coord::xy(10.0, 10.0),
371 Coord::xy(0.0, 10.0),
372 Coord::xy(0.0, 0.0),
373 ]),
374 vec![LineString::new(vec![
375 Coord::xy(2.0, 2.0),
376 Coord::xy(4.0, 2.0),
377 Coord::xy(4.0, 4.0),
378 Coord::xy(2.0, 4.0),
379 Coord::xy(2.0, 2.0),
380 ])],
381 );
382 let bbox = Geometry::Polygon(p).bbox().unwrap();
383 assert_eq!(bbox, [0.0, 0.0, 10.0, 10.0]);
384 }
385
386 #[test]
387 fn bbox_of_empty_geometry_is_none() {
388 assert!(Geometry::Empty(GeometryType::Polygon).bbox().is_none());
389 assert!(Geometry::LineString(LineString::default()).bbox().is_none());
390 }
391
392 #[test]
393 fn bbox_skips_nan() {
394 let g = Geometry::MultiPoint(vec![
395 Coord::xy(1.0, 2.0),
396 Coord::xy(f64::NAN, f64::NAN),
397 Coord::xy(5.0, 6.0),
398 ]);
399 assert_eq!(g.bbox(), Some([1.0, 2.0, 5.0, 6.0]));
400 }
401
402 #[test]
403 fn bbox_of_geometry_collection() {
404 let g = Geometry::GeometryCollection(vec![
405 Geometry::Point(Coord::xy(-1.0, -1.0)),
406 Geometry::Point(Coord::xy(5.0, 5.0)),
407 ]);
408 assert_eq!(g.bbox(), Some([-1.0, -1.0, 5.0, 5.0]));
409 }
410
411 #[test]
412 fn has_z_m_recursion() {
413 let ls = LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xyz(1.0, 1.0, 5.0)]);
414 let g = Geometry::LineString(ls);
415 assert!(g.has_z());
416 assert!(!g.has_m());
417
418 let mls = Geometry::MultiLineString(vec![LineString::new(vec![Coord::xym(0.0, 0.0, 7.0)])]);
419 assert!(!mls.has_z());
420 assert!(mls.has_m());
421
422 let collection =
423 Geometry::GeometryCollection(vec![Geometry::Point(Coord::xyzm(0.0, 0.0, 1.0, 2.0))]);
424 assert!(collection.has_z());
425 assert!(collection.has_m());
426 }
427}