geoarrow_array/builder/
multipolygon.rs

1use std::sync::Arc;
2
3use arrow_array::OffsetSizeTrait;
4use arrow_buffer::NullBufferBuilder;
5use geo_traits::{
6    CoordTrait, GeometryTrait, GeometryType, LineStringTrait, MultiPolygonTrait, PolygonTrait,
7};
8use geoarrow_schema::MultiPolygonType;
9use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
10
11use crate::GeoArrowArray;
12use crate::array::{GenericWkbArray, MultiPolygonArray};
13use crate::builder::geo_trait_wrappers::RectWrapper;
14use crate::builder::{CoordBufferBuilder, OffsetsBuilder};
15use crate::capacity::MultiPolygonCapacity;
16use crate::trait_::{GeoArrowArrayAccessor, GeoArrowArrayBuilder};
17use crate::util::GeometryTypeName;
18
19/// The GeoArrow equivalent to `Vec<Option<MultiPolygon>>`: a mutable collection of MultiPolygons.
20///
21/// Converting an [`MultiPolygonBuilder`] into a [`MultiPolygonArray`] is `O(1)`.
22#[derive(Debug)]
23pub struct MultiPolygonBuilder {
24    data_type: MultiPolygonType,
25
26    pub(crate) coords: CoordBufferBuilder,
27
28    /// OffsetsBuilder into the polygon array where each geometry starts
29    pub(crate) geom_offsets: OffsetsBuilder<i32>,
30
31    /// OffsetsBuilder into the ring array where each polygon starts
32    pub(crate) polygon_offsets: OffsetsBuilder<i32>,
33
34    /// OffsetsBuilder into the coordinate array where each ring starts
35    pub(crate) ring_offsets: OffsetsBuilder<i32>,
36
37    /// Validity is only defined at the geometry level
38    pub(crate) validity: NullBufferBuilder,
39}
40
41impl MultiPolygonBuilder {
42    /// Creates a new empty [`MultiPolygonBuilder`].
43    pub fn new(typ: MultiPolygonType) -> Self {
44        Self::with_capacity(typ, Default::default())
45    }
46
47    /// Creates a new [`MultiPolygonBuilder`] with a capacity.
48    pub fn with_capacity(typ: MultiPolygonType, capacity: MultiPolygonCapacity) -> Self {
49        let coords = CoordBufferBuilder::with_capacity(
50            capacity.coord_capacity,
51            typ.coord_type(),
52            typ.dimension(),
53        );
54        Self {
55            coords,
56            geom_offsets: OffsetsBuilder::with_capacity(capacity.geom_capacity),
57            polygon_offsets: OffsetsBuilder::with_capacity(capacity.polygon_capacity),
58            ring_offsets: OffsetsBuilder::with_capacity(capacity.ring_capacity),
59            validity: NullBufferBuilder::new(capacity.geom_capacity),
60            data_type: typ,
61        }
62    }
63
64    /// Reserves capacity for at least `additional` more MultiPolygons.
65    ///
66    /// The collection may reserve more space to speculatively avoid frequent reallocations. After
67    /// calling `reserve`, capacity will be greater than or equal to `self.len() + additional`.
68    /// Does nothing if capacity is already sufficient.
69    pub fn reserve(&mut self, additional: MultiPolygonCapacity) {
70        self.coords.reserve(additional.coord_capacity);
71        self.ring_offsets.reserve(additional.ring_capacity);
72        self.polygon_offsets.reserve(additional.polygon_capacity);
73        self.geom_offsets.reserve(additional.geom_capacity);
74    }
75
76    /// Reserves the minimum capacity for at least `additional` more MultiPolygons.
77    ///
78    /// Unlike [`reserve`], this will not deliberately over-allocate to speculatively avoid
79    /// frequent allocations. After calling `reserve_exact`, capacity will be greater than or equal
80    /// to `self.len() + additional`. Does nothing if the capacity is already sufficient.
81    ///
82    /// Note that the allocator may give the collection more space than it
83    /// requests. Therefore, capacity can not be relied upon to be precisely
84    /// minimal. Prefer [`reserve`] if future insertions are expected.
85    ///
86    /// [`reserve`]: Self::reserve
87    pub fn reserve_exact(&mut self, additional: MultiPolygonCapacity) {
88        self.coords.reserve_exact(additional.coord_capacity);
89        self.ring_offsets.reserve_exact(additional.ring_capacity);
90        self.polygon_offsets
91            .reserve_exact(additional.polygon_capacity);
92        self.geom_offsets.reserve_exact(additional.geom_capacity);
93    }
94
95    /// Shrinks the capacity of self to fit.
96    pub fn shrink_to_fit(&mut self) {
97        self.coords.shrink_to_fit();
98        self.ring_offsets.shrink_to_fit();
99        self.polygon_offsets.shrink_to_fit();
100        self.geom_offsets.shrink_to_fit();
101        // self.validity.shrink_to_fit();
102    }
103
104    /// Consume the builder and convert to an immutable [`MultiPolygonArray`]
105    pub fn finish(mut self) -> MultiPolygonArray {
106        let validity = self.validity.finish();
107
108        MultiPolygonArray::new(
109            self.coords.finish(),
110            self.geom_offsets.finish(),
111            self.polygon_offsets.finish(),
112            self.ring_offsets.finish(),
113            validity,
114            self.data_type.metadata().clone(),
115        )
116    }
117
118    /// Add a new Polygon to the end of this array.
119    ///
120    /// # Errors
121    ///
122    /// This function errors iff the new last item is larger than what O supports.
123    #[inline]
124    pub fn push_polygon(
125        &mut self,
126        value: Option<&impl PolygonTrait<T = f64>>,
127    ) -> GeoArrowResult<()> {
128        if let Some(polygon) = value {
129            let exterior_ring = polygon.exterior();
130            if exterior_ring.is_none() {
131                self.push_empty()?;
132                return Ok(());
133            }
134
135            if let Some(ext_ring) = polygon.exterior() {
136                // Total number of polygons in this MultiPolygon
137                let num_polygons = 1;
138                self.geom_offsets.try_push_usize(num_polygons)?;
139
140                for coord in ext_ring.coords() {
141                    self.coords.push_coord(&coord);
142                }
143
144                // Total number of rings in this Multipolygon
145                self.polygon_offsets
146                    .try_push_usize(polygon.num_interiors() + 1)?;
147
148                // Number of coords for each ring
149                self.ring_offsets.try_push_usize(ext_ring.num_coords())?;
150
151                for int_ring in polygon.interiors() {
152                    self.ring_offsets.try_push_usize(int_ring.num_coords())?;
153
154                    for coord in int_ring.coords() {
155                        self.coords.push_coord(&coord);
156                    }
157                }
158            } else {
159                let num_polygons = 0;
160                self.geom_offsets.try_push_usize(num_polygons)?;
161            }
162        } else {
163            self.push_null();
164        };
165        Ok(())
166    }
167
168    /// Add a new MultiPolygon to the end of this array.
169    ///
170    /// # Errors
171    ///
172    /// This function errors iff the new last item is larger than what O supports.
173    #[inline]
174    pub fn push_multi_polygon(
175        &mut self,
176        value: Option<&impl MultiPolygonTrait<T = f64>>,
177    ) -> GeoArrowResult<()> {
178        if let Some(multi_polygon) = value {
179            // Total number of polygons in this MultiPolygon
180            let num_polygons = multi_polygon.num_polygons();
181            self.try_push_geom_offset(num_polygons)?;
182
183            // Iterate over polygons
184            for polygon in multi_polygon.polygons() {
185                // Here we unwrap the exterior ring because a polygon inside a multi polygon should
186                // never be empty.
187                let ext_ring = polygon.exterior().unwrap();
188                for coord in ext_ring.coords() {
189                    self.coords.push_coord(&coord);
190                }
191
192                // Total number of rings in this Multipolygon
193                self.polygon_offsets
194                    .try_push_usize(polygon.num_interiors() + 1)?;
195
196                // Number of coords for each ring
197                self.ring_offsets.try_push_usize(ext_ring.num_coords())?;
198
199                for int_ring in polygon.interiors() {
200                    self.ring_offsets.try_push_usize(int_ring.num_coords())?;
201
202                    for coord in int_ring.coords() {
203                        self.coords.push_coord(&coord);
204                    }
205                }
206            }
207        } else {
208            self.push_null();
209        };
210        Ok(())
211    }
212
213    /// Add a new geometry to this builder
214    ///
215    /// This will error if the geometry type is not Polygon or MultiPolygon.
216    #[inline]
217    pub fn push_geometry(
218        &mut self,
219        value: Option<&impl GeometryTrait<T = f64>>,
220    ) -> GeoArrowResult<()> {
221        if let Some(value) = value {
222            match value.as_type() {
223                GeometryType::Polygon(g) => self.push_polygon(Some(g))?,
224                GeometryType::MultiPolygon(g) => self.push_multi_polygon(Some(g))?,
225                GeometryType::Rect(g) => self.push_polygon(Some(&RectWrapper::try_new(g)?))?,
226                gt => {
227                    return Err(GeoArrowError::IncorrectGeometryType(format!(
228                        "Expected MultiPolygon compatible geometry, got {}",
229                        gt.name()
230                    )));
231                }
232            }
233        } else {
234            self.push_null();
235        };
236        Ok(())
237    }
238
239    /// Extend this builder with the given geometries
240    pub fn extend_from_iter<'a>(
241        &mut self,
242        geoms: impl Iterator<Item = Option<&'a (impl MultiPolygonTrait<T = f64> + 'a)>>,
243    ) {
244        geoms
245            .into_iter()
246            .try_for_each(|maybe_multi_polygon| self.push_multi_polygon(maybe_multi_polygon))
247            .unwrap();
248    }
249
250    /// Extend this builder with the given geometries
251    pub fn extend_from_geometry_iter<'a>(
252        &mut self,
253        geoms: impl Iterator<Item = Option<&'a (impl GeometryTrait<T = f64> + 'a)>>,
254    ) -> GeoArrowResult<()> {
255        geoms.into_iter().try_for_each(|g| self.push_geometry(g))?;
256        Ok(())
257    }
258
259    /// Push a raw offset to the underlying geometry offsets buffer.
260    ///
261    /// # Invariants
262    ///
263    /// Care must be taken to ensure that pushing raw offsets
264    /// upholds the necessary invariants of the array.
265    #[inline]
266    pub(crate) fn try_push_geom_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
267        self.geom_offsets.try_push_usize(offsets_length)?;
268        self.validity.append(true);
269        Ok(())
270    }
271
272    /// Push a raw offset to the underlying polygon offsets buffer.
273    ///
274    /// # Invariants
275    ///
276    /// Care must be taken to ensure that pushing raw offsets
277    /// upholds the necessary invariants of the array.
278    #[inline]
279    #[allow(dead_code)]
280    pub(crate) fn try_push_polygon_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
281        self.polygon_offsets.try_push_usize(offsets_length)?;
282        Ok(())
283    }
284
285    /// Push a raw offset to the underlying ring offsets buffer.
286    ///
287    /// # Invariants
288    ///
289    /// Care must be taken to ensure that pushing raw offsets
290    /// upholds the necessary invariants of the array.
291    #[inline]
292    #[allow(dead_code)]
293    pub(crate) fn try_push_ring_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
294        self.ring_offsets.try_push_usize(offsets_length)?;
295        Ok(())
296    }
297
298    /// Push a raw coordinate to the underlying coordinate array.
299    ///
300    /// # Safety
301    ///
302    /// This is marked as unsafe because care must be taken to ensure that pushing raw coordinates
303    /// to the array upholds the necessary invariants of the array.
304    #[inline]
305    pub unsafe fn push_coord(&mut self, coord: &impl CoordTrait<T = f64>) -> GeoArrowResult<()> {
306        self.coords.push_coord(coord);
307        Ok(())
308    }
309
310    #[inline]
311    pub(crate) fn push_empty(&mut self) -> GeoArrowResult<()> {
312        self.geom_offsets.try_push_usize(0)?;
313        self.validity.append(true);
314        Ok(())
315    }
316
317    #[inline]
318    pub(crate) fn push_null(&mut self) {
319        // NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
320        // point to the same polygon array location
321        // Note that we don't use self.try_push_geom_offset because that sets validity to true
322        self.geom_offsets.extend_constant(1);
323        self.validity.append(false);
324    }
325
326    /// Construct a new builder, pre-filling it with the provided geometries
327    pub fn from_multi_polygons(
328        geoms: &[impl MultiPolygonTrait<T = f64>],
329        typ: MultiPolygonType,
330    ) -> Self {
331        let capacity = MultiPolygonCapacity::from_multi_polygons(geoms.iter().map(Some));
332        let mut array = Self::with_capacity(typ, capacity);
333        array.extend_from_iter(geoms.iter().map(Some));
334        array
335    }
336
337    /// Construct a new builder, pre-filling it with the provided geometries
338    pub fn from_nullable_multi_polygons(
339        geoms: &[Option<impl MultiPolygonTrait<T = f64>>],
340        typ: MultiPolygonType,
341    ) -> Self {
342        let capacity = MultiPolygonCapacity::from_multi_polygons(geoms.iter().map(|x| x.as_ref()));
343        let mut array = Self::with_capacity(typ, capacity);
344        array.extend_from_iter(geoms.iter().map(|x| x.as_ref()));
345        array
346    }
347
348    /// Construct a new builder, pre-filling it with the provided geometries
349    pub fn from_nullable_geometries(
350        geoms: &[Option<impl GeometryTrait<T = f64>>],
351        typ: MultiPolygonType,
352    ) -> GeoArrowResult<Self> {
353        let capacity = MultiPolygonCapacity::from_geometries(geoms.iter().map(|x| x.as_ref()))?;
354        let mut array = Self::with_capacity(typ, capacity);
355        array.extend_from_geometry_iter(geoms.iter().map(|x| x.as_ref()))?;
356        Ok(array)
357    }
358}
359
360impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, MultiPolygonType)> for MultiPolygonBuilder {
361    type Error = GeoArrowError;
362
363    fn try_from((value, typ): (GenericWkbArray<O>, MultiPolygonType)) -> GeoArrowResult<Self> {
364        let wkb_objects = value
365            .iter()
366            .map(|x| x.transpose())
367            .collect::<GeoArrowResult<Vec<_>>>()?;
368        Self::from_nullable_geometries(&wkb_objects, typ)
369    }
370}
371
372impl GeoArrowArrayBuilder for MultiPolygonBuilder {
373    fn len(&self) -> usize {
374        self.geom_offsets.len_proxy()
375    }
376
377    fn push_null(&mut self) {
378        self.push_null();
379    }
380
381    fn push_geometry(
382        &mut self,
383        geometry: Option<&impl GeometryTrait<T = f64>>,
384    ) -> GeoArrowResult<()> {
385        self.push_geometry(geometry)
386    }
387
388    fn finish(self) -> Arc<dyn GeoArrowArray> {
389        Arc::new(self.finish())
390    }
391}