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