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