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