microcad_core/geo2d/
geometry.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use super::*;
5use crate::traits::{Center, TotalMemory, VertexCount};
6use derive_more::From;
7
8use geo::{ConvexHull, MultiPolygon};
9use strum::IntoStaticStr;
10
11/// A 2D Geometry which is independent from resolution.
12#[derive(IntoStaticStr, From, Clone, Debug)]
13pub enum Geometry2D {
14    /// Line string.
15    LineString(LineString),
16    /// Multiple line strings.
17    MultiLineString(MultiLineString),
18    /// Polygon.
19    Polygon(Polygon),
20    /// Multiple polygons.
21    MultiPolygon(MultiPolygon),
22    /// Rectangle.
23    Rect(Rect),
24    /// Line.
25    Line(Line),
26    /// Collection,
27    Collection(Geometries2D),
28}
29
30impl Geometry2D {
31    /// Return name of geometry.
32    pub fn name(&self) -> &'static str {
33        self.into()
34    }
35
36    /// Apply boolean operation.
37    pub fn boolean_op(self, other: Self, op: &BooleanOp) -> geo2d::MultiPolygon {
38        use geo::BooleanOps;
39        self.to_multi_polygon()
40            .boolean_op(&other.to_multi_polygon(), op.into())
41    }
42
43    /// Convert geometry to a multi_polygon.
44    pub fn to_multi_polygon(&self) -> MultiPolygon {
45        match self {
46            Geometry2D::Line(_) | Geometry2D::LineString(_) | Geometry2D::MultiLineString(_) => {
47                MultiPolygon::empty()
48            }
49            Geometry2D::Polygon(polygon) => MultiPolygon(vec![polygon.clone()]),
50            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.clone(),
51            Geometry2D::Rect(rect) => MultiPolygon(vec![rect.to_polygon()]),
52            Geometry2D::Collection(collection) => collection.to_multi_polygon(),
53        }
54    }
55
56    /// Apply hull operation.
57    pub fn hull(&self) -> Self {
58        match self {
59            Geometry2D::LineString(line_string) => Geometry2D::Polygon(line_string.convex_hull()),
60            Geometry2D::MultiLineString(multi_line_string) => {
61                Geometry2D::Polygon(multi_line_string.convex_hull())
62            }
63            Geometry2D::Polygon(polygon) => Geometry2D::Polygon(polygon.convex_hull()),
64            Geometry2D::MultiPolygon(multi_polygon) => {
65                Geometry2D::Polygon(multi_polygon.convex_hull())
66            }
67            Geometry2D::Rect(rect) => Geometry2D::Rect(*rect),
68            Geometry2D::Line(line) => Geometry2D::Polygon(
69                LineString::new(vec![line.0.into(), line.1.into()]).convex_hull(),
70            ),
71            Geometry2D::Collection(collection) => Geometry2D::Polygon(collection.hull()),
72        }
73    }
74
75    /// Returns true if this geometry fills an area (e.g. like a polygon or circle).
76    pub fn is_areal(&self) -> bool {
77        !matches!(
78            self,
79            Geometry2D::LineString(_)
80                | Geometry2D::MultiLineString(_)
81                | Geometry2D::Line(_)
82                | Geometry2D::Collection(_)
83        )
84    }
85
86    /// Return this geometry with calculated bounds.
87    pub fn with_bounds(self) -> WithBounds2D<Geometry2D> {
88        let bounds = self.calc_bounds_2d();
89        WithBounds2D {
90            bounds,
91            inner: self,
92        }
93    }
94}
95
96impl CalcBounds2D for MultiPolygon {
97    fn calc_bounds_2d(&self) -> Bounds2D {
98        use geo::BoundingRect;
99        self.bounding_rect().into()
100    }
101}
102
103impl CalcBounds2D for Geometry2D {
104    fn calc_bounds_2d(&self) -> Bounds2D {
105        use geo::BoundingRect;
106
107        match &self {
108            Geometry2D::LineString(line_string) => line_string.bounding_rect().into(),
109            Geometry2D::MultiLineString(multi_line_string) => {
110                multi_line_string.bounding_rect().into()
111            }
112            Geometry2D::Polygon(polygon) => polygon.bounding_rect().into(),
113            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.calc_bounds_2d(),
114            Geometry2D::Rect(rect) => Some(*rect).into(),
115            Geometry2D::Line(line) => line.calc_bounds_2d(),
116            Geometry2D::Collection(collection) => collection.calc_bounds_2d(),
117        }
118    }
119}
120
121impl Transformed2D for Geometry2D {
122    fn transformed_2d(&self, mat: &Mat3) -> Self {
123        if self.is_areal() {
124            let multi_polygon: MultiPolygon = self.clone().into();
125            Self::MultiPolygon(multi_polygon.transformed_2d(mat))
126        } else {
127            match self {
128                Geometry2D::LineString(line_string) => {
129                    Self::LineString(line_string.transformed_2d(mat))
130                }
131                Geometry2D::MultiLineString(multi_line_string) => {
132                    Self::MultiLineString(multi_line_string.transformed_2d(mat))
133                }
134                Geometry2D::Line(line) => Self::Line(line.transformed_2d(mat)),
135                Geometry2D::Collection(geometries) => {
136                    Self::Collection(geometries.transformed_2d(mat))
137                }
138                _ => unreachable!("Geometry type not supported"),
139            }
140        }
141    }
142}
143
144impl Center for Geometry2D {
145    fn center(&self) -> Self {
146        if let Some(bounds) = self.calc_bounds_2d().rect() {
147            let d: Vec2 = bounds.center().x_y().into();
148            self.transformed_2d(&Mat3::from_translation(-d))
149        } else {
150            self.clone()
151        }
152    }
153}
154
155impl geo::Buffer for Geometry2D {
156    type Scalar = Scalar;
157
158    fn buffer_with_style(
159        &self,
160        style: geo::buffer::BufferStyle<Self::Scalar>,
161    ) -> MultiPolygon<Self::Scalar> {
162        match &self {
163            Geometry2D::LineString(line_string) => line_string.buffer_with_style(style),
164            Geometry2D::MultiLineString(multi_line_string) => {
165                multi_line_string.buffer_with_style(style)
166            }
167            Geometry2D::Polygon(polygon) => polygon.buffer_with_style(style),
168            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.buffer_with_style(style),
169            Geometry2D::Rect(rect) => rect.buffer_with_style(style),
170            Geometry2D::Line(line) => {
171                LineString::new(vec![line.0.into(), line.1.into()]).buffer_with_style(style)
172            }
173            Geometry2D::Collection(collection) => collection.buffer_with_style(style),
174        }
175    }
176}
177
178impl From<Geometry2D> for MultiPolygon {
179    fn from(geo: Geometry2D) -> Self {
180        match geo {
181            Geometry2D::Polygon(polygon) => polygon.into(),
182            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon,
183            Geometry2D::Rect(rect) => MultiPolygon(vec![rect.to_polygon()]),
184            Geometry2D::Collection(collection) => collection.into(),
185            _ => MultiPolygon::empty(),
186        }
187    }
188}
189
190impl TotalMemory for LineString {
191    fn heap_memory(&self) -> usize {
192        self.0.heap_memory()
193    }
194}
195
196impl TotalMemory for MultiLineString {
197    fn heap_memory(&self) -> usize {
198        self.0.iter().map(|l| l.heap_memory()).sum()
199    }
200}
201
202impl TotalMemory for Polygon {
203    fn heap_memory(&self) -> usize {
204        self.exterior().heap_memory()
205            + self
206                .interiors()
207                .iter()
208                .map(|l| l.heap_memory())
209                .sum::<usize>()
210    }
211}
212
213impl TotalMemory for MultiPolygon {
214    fn heap_memory(&self) -> usize {
215        self.0.iter().map(|p| p.heap_memory()).sum()
216    }
217}
218
219impl TotalMemory for Rect {}
220impl TotalMemory for Line {}
221
222impl TotalMemory for Geometry2D {
223    fn heap_memory(&self) -> usize {
224        match &self {
225            Geometry2D::LineString(line_string) => line_string.heap_memory(),
226            Geometry2D::MultiLineString(multi_line_string) => multi_line_string.heap_memory(),
227            Geometry2D::Polygon(polygon) => polygon.heap_memory(),
228            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.heap_memory(),
229            Geometry2D::Rect(rect) => rect.heap_memory(),
230            Geometry2D::Line(line) => line.heap_memory(),
231            Geometry2D::Collection(collection) => collection.heap_memory(),
232        }
233    }
234}
235
236impl VertexCount for LineString {
237    fn vertex_count(&self) -> usize {
238        self.0.len()
239    }
240}
241
242impl VertexCount for MultiLineString {
243    fn vertex_count(&self) -> usize {
244        self.iter().map(|l| l.vertex_count()).sum()
245    }
246}
247
248impl VertexCount for Polygon {
249    fn vertex_count(&self) -> usize {
250        self.exterior().vertex_count()
251            + self
252                .interiors()
253                .iter()
254                .map(|l| l.vertex_count())
255                .sum::<usize>()
256    }
257}
258
259impl VertexCount for MultiPolygon {
260    fn vertex_count(&self) -> usize {
261        self.iter().map(|p| p.vertex_count()).sum()
262    }
263}
264
265impl VertexCount for Rect {
266    fn vertex_count(&self) -> usize {
267        4
268    }
269}
270
271impl VertexCount for Line {
272    fn vertex_count(&self) -> usize {
273        2
274    }
275}
276
277impl VertexCount for Geometry2D {
278    fn vertex_count(&self) -> usize {
279        match &self {
280            Geometry2D::LineString(line_string) => line_string.vertex_count(),
281            Geometry2D::MultiLineString(multi_line_string) => multi_line_string.vertex_count(),
282            Geometry2D::Polygon(polygon) => polygon.vertex_count(),
283            Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.vertex_count(),
284            Geometry2D::Rect(rect) => rect.vertex_count(),
285            Geometry2D::Line(line) => line.vertex_count(),
286            Geometry2D::Collection(collection) => collection.vertex_count(),
287        }
288    }
289}