galileo_types/
geometry.rs

1//! Abstract geometry types:
2//! * [`Geometry`] trait for operations that are common for all geometry types
3//! * [`CartesianGeometry2d`] for projected geometries.
4//! * [`Geom`] enum that includes all geometry types to allow functions to operation on all of them
5
6use serde::{Deserialize, Serialize};
7
8use crate::cartesian::{CartesianPoint2d, Rect};
9use crate::geo::Projection;
10use crate::geometry_type::{CartesianSpace2d, GeometryType, PointGeometryType};
11use crate::impls::{Contour, MultiContour, MultiPoint, MultiPolygon, Polygon};
12
13/// Enum of different geometry types. This enum implements the [`Geometry`] trait so you can use any generic geometry
14/// method without knowing a specific geometry type you are working with.
15#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Deserialize, Serialize)]
16pub enum Geom<P> {
17    /// Point geometry.
18    Point(P),
19    /// MultiPoint geometry.
20    MultiPoint(MultiPoint<P>),
21    /// Contour geometry.
22    Contour(Contour<P>),
23    /// MultiContour geometry.
24    MultiContour(MultiContour<P>),
25    /// Polygon geometry.
26    Polygon(Polygon<P>),
27    /// MultiPolygon geometry.
28    MultiPolygon(MultiPolygon<P>),
29}
30
31impl<P: GeometryType + Copy> Geometry for Geom<P> {
32    type Point = P;
33
34    fn project<Proj>(&self, projection: &Proj) -> Option<Geom<Proj::OutPoint>>
35    where
36        Proj: Projection<InPoint = <Self as Geometry>::Point> + ?Sized,
37    {
38        match &self {
39            Geom::Point(v) => Some(Geom::Point(projection.project(v)?)),
40            Geom::MultiPoint(v) => v.project(projection),
41            Geom::Contour(v) => v.project(projection),
42            Geom::MultiContour(v) => v.project(projection),
43            Geom::Polygon(v) => v.project(projection),
44            Geom::MultiPolygon(v) => v.project(projection),
45        }
46    }
47}
48
49impl<P> CartesianGeometry2d<P> for Geom<P>
50where
51    P: CartesianPoint2d + GeometryType<Type = PointGeometryType, Space = CartesianSpace2d> + Copy,
52{
53    fn is_point_inside<Other: CartesianPoint2d<Num = P::Num>>(
54        &self,
55        point: &Other,
56        tolerance: P::Num,
57    ) -> bool {
58        match self {
59            Geom::Point(v) => v.is_point_inside(point, tolerance),
60            Geom::MultiPoint(v) => v.is_point_inside(point, tolerance),
61            Geom::Contour(v) => v.is_point_inside(point, tolerance),
62            Geom::MultiContour(v) => v.is_point_inside(point, tolerance),
63            Geom::Polygon(v) => v.is_point_inside(point, tolerance),
64            Geom::MultiPolygon(v) => v.is_point_inside(point, tolerance),
65        }
66    }
67
68    fn bounding_rectangle(&self) -> Option<Rect<P::Num>> {
69        match self {
70            Geom::Point(v) => v.bounding_rectangle(),
71            Geom::MultiPoint(v) => v.bounding_rectangle(),
72            Geom::Contour(v) => v.bounding_rectangle(),
73            Geom::MultiContour(v) => v.bounding_rectangle(),
74            Geom::Polygon(v) => v.bounding_rectangle(),
75            Geom::MultiPolygon(v) => v.bounding_rectangle(),
76        }
77    }
78}
79
80/// Generic geometry.
81///
82/// This trait can be implemented manually for all geometry structs you use, or [`GeometryType`] trait can be used
83/// for auto-implementation.
84pub trait Geometry {
85    /// Type of points this geometry consists of.
86    type Point;
87    /// Project the geometry using the given projection. Implementation of this method may choose to change type or
88    /// properties of a geometry. For example a strait line in a projected CRS can be projected as curved line along
89    /// the shortest path on the ellipsoid when projected into geographic coordinates.
90    ///
91    /// If the geometry cannot be projected with the given projection, `None` is returned.
92    fn project<Proj>(&self, projection: &Proj) -> Option<Geom<Proj::OutPoint>>
93    where
94        Proj: Projection<InPoint = Self::Point> + ?Sized;
95}
96
97/// Geometry with cartesian *XY* coordinates.
98pub trait CartesianGeometry2d<P: CartesianPoint2d>: Geometry<Point = P> {
99    /// Checks if the given `point` is *inside* the geometry with the given tolerance.
100    fn is_point_inside<Other: CartesianPoint2d<Num = P::Num>>(
101        &self,
102        point: &Other,
103        tolerance: P::Num,
104    ) -> bool;
105    /// Returns bounding rectangle of the geometry.
106    fn bounding_rectangle(&self) -> Option<Rect<P::Num>>;
107}
108
109impl<P> From<P> for Geom<P> {
110    fn from(value: P) -> Self {
111        Self::Point(value)
112    }
113}
114
115impl<P> From<Contour<P>> for Geom<P> {
116    fn from(value: Contour<P>) -> Self {
117        Self::Contour(value)
118    }
119}
120
121impl<P> From<Polygon<P>> for Geom<P> {
122    fn from(value: Polygon<P>) -> Self {
123        Self::Polygon(value)
124    }
125}
126
127impl<P> From<MultiPolygon<P>> for Geom<P> {
128    fn from(value: MultiPolygon<P>) -> Self {
129        Self::MultiPolygon(value)
130    }
131}
132
133/// This trait is used to automatically implement the [`Geometry`] trait using [`GeometryType`] trait.
134pub trait GeometrySpecialization<GT, ST>: GeometryType {
135    /// Type of the point of the geometry.
136    type Point;
137
138    /// See [`Geometry::project`].
139    fn project_spec<Proj>(&self, projection: &Proj) -> Option<Geom<Proj::OutPoint>>
140    where
141        Proj: Projection<InPoint = Self::Point> + ?Sized;
142}
143
144impl<T> Geometry for T
145where
146    T: GeometrySpecialization<<Self as GeometryType>::Type, <Self as GeometryType>::Space>,
147{
148    type Point = <Self as GeometrySpecialization<
149        <Self as GeometryType>::Type,
150        <Self as GeometryType>::Space,
151    >>::Point;
152
153    fn project<Proj>(&self, projection: &Proj) -> Option<Geom<Proj::OutPoint>>
154    where
155        Proj: Projection<InPoint = Self::Point> + ?Sized,
156    {
157        <Self as GeometrySpecialization<
158            <Self as GeometryType>::Type,
159            <Self as GeometryType>::Space,
160        >>::project_spec(self, projection)
161    }
162}
163
164/// This trait is used to automatically implement the [`CartesianGeometry2d`] trait using [`GeometryType`] trait.
165pub trait CartesianGeometry2dSpecialization<P: CartesianPoint2d + Copy, GT>:
166    GeometryType<Space = CartesianSpace2d> + Geometry<Point = P>
167{
168    /// See [`CartesianGeometry2d::is_point_inside`].
169    fn is_point_inside_spec<Other: CartesianPoint2d<Num = P::Num>>(
170        &self,
171        point: &Other,
172        tolerance: P::Num,
173    ) -> bool;
174    /// See [`CartesianGeometry2d::bounding_rectangle`].
175    fn bounding_rectangle_spec(&self) -> Option<Rect<P::Num>>;
176}
177
178impl<P, T> CartesianGeometry2d<P> for T
179where
180    P: CartesianPoint2d + Copy,
181    T: CartesianGeometry2dSpecialization<P, <Self as GeometryType>::Type>,
182{
183    fn is_point_inside<Other: CartesianPoint2d<Num = P::Num>>(
184        &self,
185        point: &Other,
186        tolerance: P::Num,
187    ) -> bool {
188        self.is_point_inside_spec(point, tolerance)
189    }
190
191    fn bounding_rectangle(&self) -> Option<Rect<P::Num>> {
192        self.bounding_rectangle_spec()
193    }
194}