microcad_core/geo2d/
collection.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! 2D Geometry collection
5
6use derive_more::{Deref, DerefMut};
7use geo::{CoordsIter, HasDimensions, MultiPolygon};
8use std::rc::Rc;
9
10use crate::{
11    geo2d::{CalcBounds2D, bounds::Bounds2D},
12    *,
13};
14
15/// 2D geometry collection.
16#[derive(Debug, Clone, Default, Deref, DerefMut)]
17pub struct Geometries2D(Vec<Rc<Geometry2D>>);
18
19impl Geometries2D {
20    /// New geometry collection.
21    pub fn new(geometries: Vec<Geometry2D>) -> Self {
22        Self(geometries.into_iter().map(Rc::new).collect())
23    }
24
25    /// Append another geometry collection.
26    pub fn append(&mut self, mut geometries: Geometries2D) {
27        self.0.append(&mut geometries.0)
28    }
29
30    /// Apply boolean operation to render into MultiPolygon.
31    pub fn boolean_op(&self, op: &BooleanOp) -> geo2d::MultiPolygon {
32        let multi_polygon_list: Vec<_> = self
33            .0
34            .iter()
35            // Render each geometry into a multipolygon and filter out empty ones
36            .filter_map(|geo| {
37                let multi_polygon = geo.to_multi_polygon();
38                if multi_polygon.is_empty() {
39                    None
40                } else {
41                    Some(multi_polygon)
42                }
43            })
44            .collect();
45
46        if multi_polygon_list.is_empty() {
47            return geo2d::MultiPolygon::empty();
48        }
49
50        multi_polygon_list[1..]
51            .iter()
52            .fold(multi_polygon_list[0].clone(), |acc, geo| {
53                use geo::BooleanOps;
54                acc.boolean_op(geo, op.into())
55            })
56    }
57
58    /// Generate multipolygon.
59    pub fn to_multi_polygon(&self) -> MultiPolygon {
60        let mut polygons = Vec::new();
61        self.iter().for_each(|geo| {
62            polygons.append(&mut (**geo).clone().to_multi_polygon().0);
63        });
64
65        MultiPolygon::new(polygons)
66    }
67
68    /// Apply contex hull operation to geometries.
69    pub fn hull(&self) -> geo2d::Polygon {
70        let mut coords = self.iter().fold(Vec::new(), |mut coords, geo| {
71            match geo.as_ref() {
72                Geometry2D::LineString(line_string) => {
73                    coords.append(&mut line_string.coords_iter().collect())
74                }
75                Geometry2D::MultiLineString(multi_line_string) => {
76                    coords.append(&mut multi_line_string.coords_iter().collect())
77                }
78                Geometry2D::Polygon(polygon) => {
79                    coords.append(&mut polygon.exterior_coords_iter().collect())
80                }
81                Geometry2D::MultiPolygon(multi_polygon) => {
82                    coords.append(&mut multi_polygon.exterior_coords_iter().collect())
83                }
84                Geometry2D::Rect(rect) => {
85                    let mut rect_corners: Vec<_> = rect.coords_iter().collect();
86                    coords.append(&mut rect_corners)
87                }
88                Geometry2D::Line(line) => {
89                    coords.push(line.0.into());
90                    coords.push(line.1.into());
91                }
92                Geometry2D::Collection(collection) => {
93                    coords.append(&mut collection.hull().exterior_coords_iter().collect())
94                }
95            }
96            coords
97        });
98
99        geo2d::Polygon::new(
100            geo::algorithm::convex_hull::qhull::quick_hull(&mut coords),
101            vec![],
102        )
103    }
104}
105
106impl FromIterator<Rc<Geometry2D>> for Geometries2D {
107    fn from_iter<T: IntoIterator<Item = Rc<Geometry2D>>>(iter: T) -> Self {
108        Geometries2D(iter.into_iter().collect())
109    }
110}
111
112impl CalcBounds2D for Geometries2D {
113    fn calc_bounds_2d(&self) -> Bounds2D {
114        self.0.iter().fold(Bounds2D::default(), |bounds, geometry| {
115            bounds.extend(geometry.calc_bounds_2d())
116        })
117    }
118}
119
120impl Transformed2D for Geometries2D {
121    fn transformed_2d(&self, mat: &Mat3) -> Self {
122        Self(
123            self.iter()
124                .map(|geometry| Rc::new(geometry.transformed_2d(mat)))
125                .collect::<Vec<_>>(),
126        )
127    }
128}
129
130impl From<Geometries2D> for MultiPolygon {
131    fn from(geometries: Geometries2D) -> Self {
132        Self(
133            geometries
134                .iter()
135                .flat_map(|geo| {
136                    let multi_polygon: MultiPolygon = geo.as_ref().clone().into();
137                    multi_polygon.0
138                })
139                .collect(),
140        )
141    }
142}