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 has_z(&self) -> bool {
164 match self {
165 Geometry::Empty(_) => false,
166 Geometry::Point(c) => c.has_z(),
167 Geometry::LineString(ls) => ls.coords.iter().any(Coord::has_z),
168 Geometry::Polygon(p) => {
169 p.exterior.coords.iter().any(Coord::has_z)
170 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_z))
171 }
172 Geometry::MultiPoint(v) => v.iter().any(Coord::has_z),
173 Geometry::MultiLineString(v) => v.iter().any(|ls| ls.coords.iter().any(Coord::has_z)),
174 Geometry::MultiPolygon(v) => v.iter().any(|p| {
175 p.exterior.coords.iter().any(Coord::has_z)
176 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_z))
177 }),
178 Geometry::GeometryCollection(v) => v.iter().any(Geometry::has_z),
179 }
180 }
181
182 pub fn bbox(&self) -> Option<[f64; 4]> {
188 let mut acc: Option<[f64; 4]> = None;
189 for_each_xy(self, &mut |x, y| {
190 if !x.is_finite() || !y.is_finite() {
191 return;
192 }
193 match &mut acc {
194 None => acc = Some([x, y, x, y]),
195 Some(b) => {
196 if x < b[0] {
197 b[0] = x;
198 }
199 if y < b[1] {
200 b[1] = y;
201 }
202 if x > b[2] {
203 b[2] = x;
204 }
205 if y > b[3] {
206 b[3] = y;
207 }
208 }
209 }
210 });
211 acc
212 }
213
214 pub fn has_m(&self) -> bool {
216 match self {
217 Geometry::Empty(_) => false,
218 Geometry::Point(c) => c.has_m(),
219 Geometry::LineString(ls) => ls.coords.iter().any(Coord::has_m),
220 Geometry::Polygon(p) => {
221 p.exterior.coords.iter().any(Coord::has_m)
222 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_m))
223 }
224 Geometry::MultiPoint(v) => v.iter().any(Coord::has_m),
225 Geometry::MultiLineString(v) => v.iter().any(|ls| ls.coords.iter().any(Coord::has_m)),
226 Geometry::MultiPolygon(v) => v.iter().any(|p| {
227 p.exterior.coords.iter().any(Coord::has_m)
228 || p.holes.iter().any(|h| h.coords.iter().any(Coord::has_m))
229 }),
230 Geometry::GeometryCollection(v) => v.iter().any(Geometry::has_m),
231 }
232 }
233}
234
235fn for_each_xy(g: &Geometry, f: &mut dyn FnMut(f64, f64)) {
238 match g {
239 Geometry::Empty(_) => {}
240 Geometry::Point(c) => f(c.x, c.y),
241 Geometry::LineString(ls) => {
242 for c in &ls.coords {
243 f(c.x, c.y);
244 }
245 }
246 Geometry::Polygon(p) => {
247 for c in &p.exterior.coords {
248 f(c.x, c.y);
249 }
250 for h in &p.holes {
251 for c in &h.coords {
252 f(c.x, c.y);
253 }
254 }
255 }
256 Geometry::MultiPoint(v) => {
257 for c in v {
258 f(c.x, c.y);
259 }
260 }
261 Geometry::MultiLineString(v) => {
262 for ls in v {
263 for c in &ls.coords {
264 f(c.x, c.y);
265 }
266 }
267 }
268 Geometry::MultiPolygon(v) => {
269 for p in v {
270 for c in &p.exterior.coords {
271 f(c.x, c.y);
272 }
273 for h in &p.holes {
274 for c in &h.coords {
275 f(c.x, c.y);
276 }
277 }
278 }
279 }
280 Geometry::GeometryCollection(v) => {
281 for inner in v {
282 for_each_xy(inner, f);
283 }
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn coord_constructors_track_dimensions() {
294 assert!(!Coord::xy(1.0, 2.0).has_z());
295 assert!(!Coord::xy(1.0, 2.0).has_m());
296
297 let c = Coord::xyz(1.0, 2.0, 3.0);
298 assert!(c.has_z() && !c.has_m());
299 assert_eq!(c.z, Some(3.0));
300
301 let c = Coord::xym(1.0, 2.0, 99.0);
302 assert!(!c.has_z() && c.has_m());
303 assert_eq!(c.m, Some(99.0));
304
305 let c = Coord::xyzm(1.0, 2.0, 3.0, 99.0);
306 assert!(c.has_z() && c.has_m());
307 }
308
309 #[test]
310 fn geometry_type_of_matches_variant() {
311 assert_eq!(
312 Geometry::Point(Coord::xy(0.0, 0.0)).type_of(),
313 GeometryType::Point
314 );
315 assert_eq!(
316 Geometry::Polygon(Polygon::default()).type_of(),
317 GeometryType::Polygon
318 );
319 assert_eq!(
320 Geometry::Empty(GeometryType::MultiPolygon).type_of(),
321 GeometryType::MultiPolygon
322 );
323 }
324
325 #[test]
326 fn empty_detection() {
327 assert!(Geometry::Empty(GeometryType::Point).is_empty());
328 assert!(Geometry::LineString(LineString::default()).is_empty());
329 assert!(!Geometry::Point(Coord::xy(0.0, 0.0)).is_empty());
330
331 let ls = LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xy(1.0, 1.0)]);
332 assert!(!Geometry::LineString(ls).is_empty());
333 }
334
335 #[test]
336 fn bbox_of_polygon_with_hole() {
337 let p = Polygon::new(
338 LineString::new(vec![
339 Coord::xy(0.0, 0.0),
340 Coord::xy(10.0, 0.0),
341 Coord::xy(10.0, 10.0),
342 Coord::xy(0.0, 10.0),
343 Coord::xy(0.0, 0.0),
344 ]),
345 vec![LineString::new(vec![
346 Coord::xy(2.0, 2.0),
347 Coord::xy(4.0, 2.0),
348 Coord::xy(4.0, 4.0),
349 Coord::xy(2.0, 4.0),
350 Coord::xy(2.0, 2.0),
351 ])],
352 );
353 let bbox = Geometry::Polygon(p).bbox().unwrap();
354 assert_eq!(bbox, [0.0, 0.0, 10.0, 10.0]);
355 }
356
357 #[test]
358 fn bbox_of_empty_geometry_is_none() {
359 assert!(Geometry::Empty(GeometryType::Polygon).bbox().is_none());
360 assert!(Geometry::LineString(LineString::default()).bbox().is_none());
361 }
362
363 #[test]
364 fn bbox_skips_nan() {
365 let g = Geometry::MultiPoint(vec![
366 Coord::xy(1.0, 2.0),
367 Coord::xy(f64::NAN, f64::NAN),
368 Coord::xy(5.0, 6.0),
369 ]);
370 assert_eq!(g.bbox(), Some([1.0, 2.0, 5.0, 6.0]));
371 }
372
373 #[test]
374 fn bbox_of_geometry_collection() {
375 let g = Geometry::GeometryCollection(vec![
376 Geometry::Point(Coord::xy(-1.0, -1.0)),
377 Geometry::Point(Coord::xy(5.0, 5.0)),
378 ]);
379 assert_eq!(g.bbox(), Some([-1.0, -1.0, 5.0, 5.0]));
380 }
381
382 #[test]
383 fn has_z_m_recursion() {
384 let ls = LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xyz(1.0, 1.0, 5.0)]);
385 let g = Geometry::LineString(ls);
386 assert!(g.has_z());
387 assert!(!g.has_m());
388
389 let mls = Geometry::MultiLineString(vec![LineString::new(vec![Coord::xym(0.0, 0.0, 7.0)])]);
390 assert!(!mls.has_z());
391 assert!(mls.has_m());
392
393 let collection =
394 Geometry::GeometryCollection(vec![Geometry::Point(Coord::xyzm(0.0, 0.0, 1.0, 2.0))]);
395 assert!(collection.has_z());
396 assert!(collection.has_m());
397 }
398}