1use crate::error::{OxiGdalError, Result};
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "std")]
9use std::vec::Vec;
10
11#[cfg(all(not(feature = "std"), feature = "alloc"))]
12use alloc::vec::Vec;
13
14#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
16pub struct Coordinate {
17 pub x: f64,
19 pub y: f64,
21 pub z: Option<f64>,
23 pub m: Option<f64>,
25}
26
27impl Coordinate {
28 #[must_use]
30 pub const fn new_2d(x: f64, y: f64) -> Self {
31 Self {
32 x,
33 y,
34 z: None,
35 m: None,
36 }
37 }
38
39 #[must_use]
41 pub const fn new_3d(x: f64, y: f64, z: f64) -> Self {
42 Self {
43 x,
44 y,
45 z: Some(z),
46 m: None,
47 }
48 }
49
50 #[must_use]
52 pub const fn new_2dm(x: f64, y: f64, m: f64) -> Self {
53 Self {
54 x,
55 y,
56 z: None,
57 m: Some(m),
58 }
59 }
60
61 #[must_use]
63 pub const fn new_3dm(x: f64, y: f64, z: f64, m: f64) -> Self {
64 Self {
65 x,
66 y,
67 z: Some(z),
68 m: Some(m),
69 }
70 }
71
72 #[must_use]
74 pub const fn has_z(&self) -> bool {
75 self.z.is_some()
76 }
77
78 #[must_use]
80 pub const fn has_m(&self) -> bool {
81 self.m.is_some()
82 }
83
84 #[must_use]
86 pub const fn dimensions(&self) -> u8 {
87 let mut dims = 2;
88 if self.z.is_some() {
89 dims += 1;
90 }
91 if self.m.is_some() {
92 dims += 1;
93 }
94 dims
95 }
96
97 #[must_use]
99 pub const fn x(&self) -> f64 {
100 self.x
101 }
102
103 #[must_use]
105 pub const fn y(&self) -> f64 {
106 self.y
107 }
108
109 #[must_use]
111 pub const fn z(&self) -> Option<f64> {
112 self.z
113 }
114
115 #[must_use]
117 pub const fn m(&self) -> Option<f64> {
118 self.m
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124pub enum Geometry {
125 Point(Point),
127 LineString(LineString),
129 Polygon(Polygon),
131 MultiPoint(MultiPoint),
133 MultiLineString(MultiLineString),
135 MultiPolygon(MultiPolygon),
137 GeometryCollection(GeometryCollection),
139}
140
141impl Geometry {
142 #[must_use]
144 pub const fn geometry_type(&self) -> &'static str {
145 match self {
146 Self::Point(_) => "Point",
147 Self::LineString(_) => "LineString",
148 Self::Polygon(_) => "Polygon",
149 Self::MultiPoint(_) => "MultiPoint",
150 Self::MultiLineString(_) => "MultiLineString",
151 Self::MultiPolygon(_) => "MultiPolygon",
152 Self::GeometryCollection(_) => "GeometryCollection",
153 }
154 }
155
156 #[must_use]
158 pub fn is_empty(&self) -> bool {
159 match self {
160 Self::Point(p) => p.coord.x.is_nan() || p.coord.y.is_nan(),
161 Self::LineString(ls) => ls.coords.is_empty(),
162 Self::Polygon(p) => p.exterior.coords.is_empty(),
163 Self::MultiPoint(mp) => mp.points.is_empty(),
164 Self::MultiLineString(mls) => mls.line_strings.is_empty(),
165 Self::MultiPolygon(mp) => mp.polygons.is_empty(),
166 Self::GeometryCollection(gc) => gc.geometries.is_empty(),
167 }
168 }
169
170 #[must_use]
172 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
173 match self {
174 Self::Point(p) => {
175 if p.coord.x.is_nan() || p.coord.y.is_nan() {
176 None
177 } else {
178 Some((p.coord.x, p.coord.y, p.coord.x, p.coord.y))
179 }
180 }
181 Self::LineString(ls) => ls.bounds(),
182 Self::Polygon(p) => p.bounds(),
183 Self::MultiPoint(mp) => mp.bounds(),
184 Self::MultiLineString(mls) => mls.bounds(),
185 Self::MultiPolygon(mp) => mp.bounds(),
186 Self::GeometryCollection(gc) => gc.bounds(),
187 }
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct Point {
194 pub coord: Coordinate,
196}
197
198impl Point {
199 #[must_use]
201 pub const fn new(x: f64, y: f64) -> Self {
202 Self {
203 coord: Coordinate::new_2d(x, y),
204 }
205 }
206
207 #[must_use]
209 pub const fn new_3d(x: f64, y: f64, z: f64) -> Self {
210 Self {
211 coord: Coordinate::new_3d(x, y, z),
212 }
213 }
214
215 #[must_use]
217 pub const fn from_coord(coord: Coordinate) -> Self {
218 Self { coord }
219 }
220
221 #[must_use]
223 pub const fn x(&self) -> f64 {
224 self.coord.x
225 }
226
227 #[must_use]
229 pub const fn y(&self) -> f64 {
230 self.coord.y
231 }
232
233 #[must_use]
235 pub const fn z(&self) -> Option<f64> {
236 self.coord.z
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242pub struct LineString {
243 pub coords: Vec<Coordinate>,
245}
246
247impl LineString {
248 pub fn new(coords: Vec<Coordinate>) -> Result<Self> {
250 if coords.len() < 2 {
251 return Err(OxiGdalError::InvalidParameter {
252 parameter: "coords",
253 message: "LineString must have at least 2 coordinates".to_string(),
254 });
255 }
256 Ok(Self { coords })
257 }
258
259 #[must_use]
261 pub const fn empty() -> Self {
262 Self { coords: Vec::new() }
263 }
264
265 pub fn push(&mut self, coord: Coordinate) {
267 self.coords.push(coord);
268 }
269
270 #[must_use]
272 pub fn len(&self) -> usize {
273 self.coords.len()
274 }
275
276 #[must_use]
278 pub fn is_empty(&self) -> bool {
279 self.coords.is_empty()
280 }
281
282 #[must_use]
284 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
285 if self.coords.is_empty() {
286 return None;
287 }
288
289 let mut min_x = f64::INFINITY;
290 let mut min_y = f64::INFINITY;
291 let mut max_x = f64::NEG_INFINITY;
292 let mut max_y = f64::NEG_INFINITY;
293
294 for coord in &self.coords {
295 min_x = min_x.min(coord.x);
296 min_y = min_y.min(coord.y);
297 max_x = max_x.max(coord.x);
298 max_y = max_y.max(coord.y);
299 }
300
301 Some((min_x, min_y, max_x, max_y))
302 }
303
304 #[must_use]
306 pub fn coords(&self) -> &[Coordinate] {
307 &self.coords
308 }
309
310 pub fn points(&self) -> impl Iterator<Item = &Coordinate> {
312 self.coords.iter()
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub struct Polygon {
319 pub exterior: LineString,
321 pub interiors: Vec<LineString>,
323}
324
325impl Polygon {
326 pub fn new(exterior: LineString, interiors: Vec<LineString>) -> Result<Self> {
328 if exterior.coords.len() < 4 {
329 return Err(OxiGdalError::InvalidParameter {
330 parameter: "exterior",
331 message: "Polygon exterior ring must have at least 4 coordinates".to_string(),
332 });
333 }
334
335 let first = &exterior.coords[0];
337 let last = &exterior.coords[exterior.coords.len() - 1];
338 if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
339 return Err(OxiGdalError::InvalidParameter {
340 parameter: "exterior",
341 message: "Polygon exterior ring must be closed".to_string(),
342 });
343 }
344
345 for interior in &interiors {
347 if interior.coords.len() < 4 {
348 return Err(OxiGdalError::InvalidParameter {
349 parameter: "interiors",
350 message: "Polygon interior ring must have at least 4 coordinates".to_string(),
351 });
352 }
353
354 let first = &interior.coords[0];
355 let last = &interior.coords[interior.coords.len() - 1];
356 if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
357 return Err(OxiGdalError::InvalidParameter {
358 parameter: "interiors",
359 message: "Polygon interior ring must be closed".to_string(),
360 });
361 }
362 }
363
364 Ok(Self {
365 exterior,
366 interiors,
367 })
368 }
369
370 #[must_use]
372 pub const fn empty() -> Self {
373 Self {
374 exterior: LineString::empty(),
375 interiors: Vec::new(),
376 }
377 }
378
379 #[must_use]
381 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
382 self.exterior.bounds()
383 }
384
385 #[must_use]
387 pub fn exterior(&self) -> &LineString {
388 &self.exterior
389 }
390
391 #[must_use]
393 pub fn interiors(&self) -> &[LineString] {
394 &self.interiors
395 }
396}
397
398#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
400pub struct MultiPoint {
401 pub points: Vec<Point>,
403}
404
405impl MultiPoint {
406 #[must_use]
408 pub const fn new(points: Vec<Point>) -> Self {
409 Self { points }
410 }
411
412 #[must_use]
414 pub const fn empty() -> Self {
415 Self { points: Vec::new() }
416 }
417
418 pub fn push(&mut self, point: Point) {
420 self.points.push(point);
421 }
422
423 #[must_use]
425 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
426 if self.points.is_empty() {
427 return None;
428 }
429
430 let mut min_x = f64::INFINITY;
431 let mut min_y = f64::INFINITY;
432 let mut max_x = f64::NEG_INFINITY;
433 let mut max_y = f64::NEG_INFINITY;
434
435 for point in &self.points {
436 min_x = min_x.min(point.coord.x);
437 min_y = min_y.min(point.coord.y);
438 max_x = max_x.max(point.coord.x);
439 max_y = max_y.max(point.coord.y);
440 }
441
442 Some((min_x, min_y, max_x, max_y))
443 }
444}
445
446#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
448pub struct MultiLineString {
449 pub line_strings: Vec<LineString>,
451}
452
453impl MultiLineString {
454 #[must_use]
456 pub const fn new(line_strings: Vec<LineString>) -> Self {
457 Self { line_strings }
458 }
459
460 #[must_use]
462 pub const fn empty() -> Self {
463 Self {
464 line_strings: Vec::new(),
465 }
466 }
467
468 pub fn push(&mut self, line_string: LineString) {
470 self.line_strings.push(line_string);
471 }
472
473 #[must_use]
475 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
476 if self.line_strings.is_empty() {
477 return None;
478 }
479
480 let mut min_x = f64::INFINITY;
481 let mut min_y = f64::INFINITY;
482 let mut max_x = f64::NEG_INFINITY;
483 let mut max_y = f64::NEG_INFINITY;
484
485 for ls in &self.line_strings {
486 if let Some((x_min, y_min, x_max, y_max)) = ls.bounds() {
487 min_x = min_x.min(x_min);
488 min_y = min_y.min(y_min);
489 max_x = max_x.max(x_max);
490 max_y = max_y.max(y_max);
491 }
492 }
493
494 if min_x.is_infinite() {
495 None
496 } else {
497 Some((min_x, min_y, max_x, max_y))
498 }
499 }
500}
501
502#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
504pub struct MultiPolygon {
505 pub polygons: Vec<Polygon>,
507}
508
509impl MultiPolygon {
510 #[must_use]
512 pub const fn new(polygons: Vec<Polygon>) -> Self {
513 Self { polygons }
514 }
515
516 #[must_use]
518 pub const fn empty() -> Self {
519 Self {
520 polygons: Vec::new(),
521 }
522 }
523
524 pub fn push(&mut self, polygon: Polygon) {
526 self.polygons.push(polygon);
527 }
528
529 #[must_use]
531 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
532 if self.polygons.is_empty() {
533 return None;
534 }
535
536 let mut min_x = f64::INFINITY;
537 let mut min_y = f64::INFINITY;
538 let mut max_x = f64::NEG_INFINITY;
539 let mut max_y = f64::NEG_INFINITY;
540
541 for poly in &self.polygons {
542 if let Some((x_min, y_min, x_max, y_max)) = poly.bounds() {
543 min_x = min_x.min(x_min);
544 min_y = min_y.min(y_min);
545 max_x = max_x.max(x_max);
546 max_y = max_y.max(y_max);
547 }
548 }
549
550 if min_x.is_infinite() {
551 None
552 } else {
553 Some((min_x, min_y, max_x, max_y))
554 }
555 }
556}
557
558#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
560pub struct GeometryCollection {
561 pub geometries: Vec<Geometry>,
563}
564
565impl GeometryCollection {
566 #[must_use]
568 pub const fn new(geometries: Vec<Geometry>) -> Self {
569 Self { geometries }
570 }
571
572 #[must_use]
574 pub const fn empty() -> Self {
575 Self {
576 geometries: Vec::new(),
577 }
578 }
579
580 pub fn push(&mut self, geometry: Geometry) {
582 self.geometries.push(geometry);
583 }
584
585 #[must_use]
587 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
588 if self.geometries.is_empty() {
589 return None;
590 }
591
592 let mut min_x = f64::INFINITY;
593 let mut min_y = f64::INFINITY;
594 let mut max_x = f64::NEG_INFINITY;
595 let mut max_y = f64::NEG_INFINITY;
596
597 for geom in &self.geometries {
598 if let Some((x_min, y_min, x_max, y_max)) = geom.bounds() {
599 min_x = min_x.min(x_min);
600 min_y = min_y.min(y_min);
601 max_x = max_x.max(x_max);
602 max_y = max_y.max(y_max);
603 }
604 }
605
606 if min_x.is_infinite() {
607 None
608 } else {
609 Some((min_x, min_y, max_x, max_y))
610 }
611 }
612}
613
614#[cfg(test)]
615mod tests {
616 use super::*;
617
618 #[test]
619 fn test_coordinate_2d() {
620 let coord = Coordinate::new_2d(1.0, 2.0);
621 assert_eq!(coord.x, 1.0);
622 assert_eq!(coord.y, 2.0);
623 assert!(!coord.has_z());
624 assert!(!coord.has_m());
625 assert_eq!(coord.dimensions(), 2);
626 }
627
628 #[test]
629 fn test_coordinate_3d() {
630 let coord = Coordinate::new_3d(1.0, 2.0, 3.0);
631 assert_eq!(coord.x, 1.0);
632 assert_eq!(coord.y, 2.0);
633 assert_eq!(coord.z, Some(3.0));
634 assert!(coord.has_z());
635 assert!(!coord.has_m());
636 assert_eq!(coord.dimensions(), 3);
637 }
638
639 #[test]
640 fn test_point() {
641 let point = Point::new(1.0, 2.0);
642 assert_eq!(point.coord.x, 1.0);
643 assert_eq!(point.coord.y, 2.0);
644 }
645
646 #[test]
647 fn test_linestring() {
648 let coords = vec![
649 Coordinate::new_2d(0.0, 0.0),
650 Coordinate::new_2d(1.0, 1.0),
651 Coordinate::new_2d(2.0, 0.0),
652 ];
653 let ls = LineString::new(coords).ok();
654 assert!(ls.is_some());
655 let ls = ls.expect("linestring creation failed");
656 assert_eq!(ls.len(), 3);
657 assert!(!ls.is_empty());
658
659 let bounds = ls.bounds();
660 assert!(bounds.is_some());
661 let (min_x, min_y, max_x, max_y) = bounds.expect("bounds calculation failed");
662 assert_eq!(min_x, 0.0);
663 assert_eq!(min_y, 0.0);
664 assert_eq!(max_x, 2.0);
665 assert_eq!(max_y, 1.0);
666 }
667
668 #[test]
669 fn test_linestring_invalid() {
670 let coords = vec![Coordinate::new_2d(0.0, 0.0)];
671 let result = LineString::new(coords);
672 assert!(result.is_err());
673 }
674
675 #[test]
676 fn test_polygon() {
677 let exterior_coords = vec![
678 Coordinate::new_2d(0.0, 0.0),
679 Coordinate::new_2d(1.0, 0.0),
680 Coordinate::new_2d(1.0, 1.0),
681 Coordinate::new_2d(0.0, 1.0),
682 Coordinate::new_2d(0.0, 0.0),
683 ];
684 let exterior = LineString::new(exterior_coords).ok();
685 assert!(exterior.is_some());
686 let exterior = exterior.expect("linestring creation failed");
687
688 let poly = Polygon::new(exterior, vec![]);
689 assert!(poly.is_ok());
690 }
691
692 #[test]
693 fn test_polygon_not_closed() {
694 let exterior_coords = vec![
695 Coordinate::new_2d(0.0, 0.0),
696 Coordinate::new_2d(1.0, 0.0),
697 Coordinate::new_2d(1.0, 1.0),
698 Coordinate::new_2d(0.0, 1.0),
699 ];
700 let exterior = LineString::new(exterior_coords).ok();
701 assert!(exterior.is_some());
702 let exterior = exterior.expect("linestring creation failed");
703
704 let result = Polygon::new(exterior, vec![]);
705 assert!(result.is_err());
706 }
707}