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    pub(crate) fn try_push_polygon_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
280        self.polygon_offsets.try_push_usize(offsets_length)?;
281        Ok(())
282    }
283
284    /// Push a raw offset to the underlying ring offsets buffer.
285    ///
286    /// # Invariants
287    ///
288    /// Care must be taken to ensure that pushing raw offsets
289    /// upholds the necessary invariants of the array.
290    #[inline]
291    pub(crate) fn try_push_ring_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
292        self.ring_offsets.try_push_usize(offsets_length)?;
293        Ok(())
294    }
295
296    /// Push a raw coordinate to the underlying coordinate array.
297    ///
298    /// # Safety
299    ///
300    /// This is marked as unsafe because care must be taken to ensure that pushing raw coordinates
301    /// to the array upholds the necessary invariants of the array.
302    #[inline]
303    pub unsafe fn push_coord(&mut self, coord: &impl CoordTrait<T = f64>) -> GeoArrowResult<()> {
304        self.coords.push_coord(coord);
305        Ok(())
306    }
307
308    #[inline]
309    pub(crate) fn push_empty(&mut self) -> GeoArrowResult<()> {
310        self.geom_offsets.try_push_usize(0)?;
311        self.validity.append(true);
312        Ok(())
313    }
314
315    #[inline]
316    pub(crate) fn push_null(&mut self) {
317        // NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
318        // point to the same polygon array location
319        // Note that we don't use self.try_push_geom_offset because that sets validity to true
320        self.geom_offsets.extend_constant(1);
321        self.validity.append(false);
322    }
323
324    /// Construct a new builder, pre-filling it with the provided geometries
325    pub fn from_multi_polygons(
326        geoms: &[impl MultiPolygonTrait<T = f64>],
327        typ: MultiPolygonType,
328    ) -> Self {
329        let capacity = MultiPolygonCapacity::from_multi_polygons(geoms.iter().map(Some));
330        let mut array = Self::with_capacity(typ, capacity);
331        array.extend_from_iter(geoms.iter().map(Some));
332        array
333    }
334
335    /// Construct a new builder, pre-filling it with the provided geometries
336    pub fn from_nullable_multi_polygons(
337        geoms: &[Option<impl MultiPolygonTrait<T = f64>>],
338        typ: MultiPolygonType,
339    ) -> Self {
340        let capacity = MultiPolygonCapacity::from_multi_polygons(geoms.iter().map(|x| x.as_ref()));
341        let mut array = Self::with_capacity(typ, capacity);
342        array.extend_from_iter(geoms.iter().map(|x| x.as_ref()));
343        array
344    }
345
346    /// Construct a new builder, pre-filling it with the provided geometries
347    pub fn from_nullable_geometries(
348        geoms: &[Option<impl GeometryTrait<T = f64>>],
349        typ: MultiPolygonType,
350    ) -> GeoArrowResult<Self> {
351        let capacity = MultiPolygonCapacity::from_geometries(geoms.iter().map(|x| x.as_ref()))?;
352        let mut array = Self::with_capacity(typ, capacity);
353        array.extend_from_geometry_iter(geoms.iter().map(|x| x.as_ref()))?;
354        Ok(array)
355    }
356}
357
358impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, MultiPolygonType)> for MultiPolygonBuilder {
359    type Error = GeoArrowError;
360
361    fn try_from((value, typ): (GenericWkbArray<O>, MultiPolygonType)) -> GeoArrowResult<Self> {
362        let wkb_objects = value
363            .iter()
364            .map(|x| x.transpose())
365            .collect::<GeoArrowResult<Vec<_>>>()?;
366        Self::from_nullable_geometries(&wkb_objects, typ)
367    }
368}
369
370impl GeoArrowArrayBuilder for MultiPolygonBuilder {
371    fn len(&self) -> usize {
372        self.geom_offsets.len_proxy()
373    }
374
375    fn push_null(&mut self) {
376        self.push_null();
377    }
378
379    fn push_geometry(
380        &mut self,
381        geometry: Option<&impl GeometryTrait<T = f64>>,
382    ) -> GeoArrowResult<()> {
383        self.push_geometry(geometry)
384    }
385
386    fn finish(self) -> Arc<dyn GeoArrowArray> {
387        Arc::new(self.finish())
388    }
389}