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    traits::{DistributeGrid, TotalMemory, VertexCount},
13    *,
14};
15
16/// 2D geometry collection.
17#[derive(Debug, Clone, Default, Deref, DerefMut)]
18pub struct Geometries2D(Vec<Rc<Geometry2D>>);
19
20impl Geometries2D {
21    /// New geometry collection.
22    pub fn new(geometries: Vec<Geometry2D>) -> Self {
23        Self(geometries.into_iter().map(Rc::new).collect())
24    }
25
26    /// Append another geometry collection.
27    pub fn append(&mut self, mut geometries: Geometries2D) {
28        self.0.append(&mut geometries.0)
29    }
30
31    /// Apply boolean operation to render into MultiPolygon.
32    pub fn boolean_op(&self, op: &BooleanOp) -> geo2d::MultiPolygon {
33        let multi_polygon_list: Vec<_> = self
34            .0
35            .iter()
36            // Render each geometry into a multipolygon and filter out empty ones
37            .filter_map(|geo| {
38                let multi_polygon = geo.to_multi_polygon();
39                if multi_polygon.is_empty() {
40                    None
41                } else {
42                    Some(multi_polygon)
43                }
44            })
45            .collect();
46
47        if multi_polygon_list.is_empty() {
48            return geo2d::MultiPolygon::empty();
49        }
50
51        multi_polygon_list[1..]
52            .iter()
53            .fold(multi_polygon_list[0].clone(), |acc, geo| {
54                use geo::BooleanOps;
55                acc.boolean_op(geo, op.into())
56            })
57    }
58
59    /// Generate multipolygon.
60    pub fn to_multi_polygon(&self) -> MultiPolygon {
61        let mut polygons = Vec::new();
62        self.iter().for_each(|geo| {
63            polygons.append(&mut (**geo).clone().to_multi_polygon().0);
64        });
65
66        MultiPolygon::new(polygons)
67    }
68
69    /// Apply contex hull operation to geometries.
70    pub fn hull(&self) -> geo2d::Polygon {
71        let mut coords: Vec<_> = self
72            .iter()
73            .flat_map(|geo| match geo.as_ref() {
74                Geometry2D::LineString(line_string) => {
75                    line_string.coords_iter().collect::<Vec<_>>()
76                }
77                Geometry2D::MultiLineString(multi_line_string) => {
78                    multi_line_string.coords_iter().collect::<Vec<_>>()
79                }
80                Geometry2D::Polygon(polygon) => polygon.exterior_coords_iter().collect::<Vec<_>>(),
81                Geometry2D::MultiPolygon(multi_polygon) => {
82                    multi_polygon.exterior_coords_iter().collect::<Vec<_>>()
83                }
84                Geometry2D::Rect(rect) => rect.coords_iter().collect::<Vec<_>>(),
85                Geometry2D::Line(line) => vec![line.0.into(), line.1.into()],
86                Geometry2D::Collection(collection) => {
87                    collection.hull().exterior_coords_iter().collect::<Vec<_>>()
88                }
89            })
90            .collect();
91
92        geo2d::Polygon::new(
93            geo::algorithm::convex_hull::qhull::quick_hull(&mut coords),
94            vec![],
95        )
96    }
97}
98
99impl geo::Buffer for Geometries2D {
100    type Scalar = Scalar;
101
102    fn buffer_with_style(
103        &self,
104        style: geo::buffer::BufferStyle<Self::Scalar>,
105    ) -> MultiPolygon<Self::Scalar> {
106        let mut polygons = Vec::new();
107        self.iter().for_each(|geo| {
108            polygons.append(&mut (**geo).clone().buffer_with_style(style.clone()).0);
109        });
110
111        MultiPolygon::new(polygons)
112    }
113}
114
115impl FromIterator<Rc<Geometry2D>> for Geometries2D {
116    fn from_iter<T: IntoIterator<Item = Rc<Geometry2D>>>(iter: T) -> Self {
117        Geometries2D(iter.into_iter().collect())
118    }
119}
120
121impl CalcBounds2D for Geometries2D {
122    fn calc_bounds_2d(&self) -> Bounds2D {
123        self.0.iter().fold(Bounds2D::default(), |bounds, geometry| {
124            bounds.extend(geometry.calc_bounds_2d())
125        })
126    }
127}
128
129impl Transformed2D for Geometries2D {
130    fn transformed_2d(&self, mat: &Mat3) -> Self {
131        Self(
132            self.iter()
133                .map(|geometry| Rc::new(geometry.transformed_2d(mat)))
134                .collect::<Vec<_>>(),
135        )
136    }
137}
138
139impl From<Geometries2D> for MultiPolygon {
140    fn from(geometries: Geometries2D) -> Self {
141        Self(
142            geometries
143                .iter()
144                .flat_map(|geo| {
145                    let multi_polygon: MultiPolygon = geo.as_ref().clone().into();
146                    multi_polygon.0
147                })
148                .collect(),
149        )
150    }
151}
152
153impl DistributeGrid for Geometries2D {
154    fn distribute_grid(&self, rect: Rect, rows: Integer, columns: Integer) -> Self {
155        Geometries2D(
156            GridCells::new(rect, rows, columns)
157                .zip(self.0.iter())
158                .map(|(cell, geo)| {
159                    let center = geo.calc_bounds_2d().center();
160                    let cell_center: Vec2 = cell.center().x_y().into();
161                    let d = center - cell_center;
162                    Rc::new(geo.transformed_2d(&Mat3::from_translation(d)))
163                })
164                .collect(),
165        )
166    }
167}
168
169impl TotalMemory for Geometries2D {
170    fn heap_memory(&self) -> usize {
171        self.iter().map(|geo| geo.heap_memory()).sum()
172    }
173}
174
175impl VertexCount for Geometries2D {
176    fn vertex_count(&self) -> usize {
177        self.iter().map(|geo| geo.vertex_count()).sum()
178    }
179}