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    #[allow(dead_code)]
105    pub(crate) fn try_push_geom_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
106        self.geom_offsets.try_push_usize(offsets_length)?;
107        self.validity.append(true);
108        Ok(())
109    }
110
111    /// Push a raw offset to the underlying ring offsets buffer.
112    ///
113    /// # Invariants
114    ///
115    /// Care must be taken to ensure that pushing raw offsets
116    /// upholds the necessary invariants of the array.
117    #[inline]
118    #[allow(dead_code)]
119    pub(crate) fn try_push_ring_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
120        self.ring_offsets.try_push_usize(offsets_length)?;
121        Ok(())
122    }
123
124    /// Consume the builder and convert to an immutable [`PolygonArray`]
125    pub fn finish(mut self) -> PolygonArray {
126        let validity = self.validity.finish();
127
128        PolygonArray::new(
129            self.coords.finish(),
130            self.geom_offsets.finish(),
131            self.ring_offsets.finish(),
132            validity,
133            self.data_type.metadata().clone(),
134        )
135    }
136
137    /// Add a new Polygon to the end of this array.
138    ///
139    /// # Errors
140    ///
141    /// This function errors iff the new last item is larger than what O supports.
142    #[inline]
143    pub fn push_polygon(
144        &mut self,
145        value: Option<&impl PolygonTrait<T = f64>>,
146    ) -> GeoArrowResult<()> {
147        if let Some(polygon) = value {
148            let exterior_ring = polygon.exterior();
149            if exterior_ring.is_none() {
150                self.push_empty();
151                return Ok(());
152            }
153
154            // - Get exterior ring
155            // - Add exterior ring's # of coords self.ring_offsets
156            // - Push ring's coords to self.coords
157            let ext_ring = polygon.exterior().unwrap();
158            self.ring_offsets.try_push_usize(ext_ring.num_coords())?;
159            for coord in ext_ring.coords() {
160                self.coords.push_coord(&coord);
161            }
162
163            // Total number of rings in this polygon
164            let num_interiors = polygon.num_interiors();
165            self.geom_offsets.try_push_usize(num_interiors + 1)?;
166
167            // For each interior ring:
168            // - Get ring
169            // - Add ring's # of coords to self.ring_offsets
170            // - Push ring's coords to self.coords
171            for int_ring in polygon.interiors() {
172                self.ring_offsets.try_push_usize(int_ring.num_coords())?;
173                for coord in int_ring.coords() {
174                    self.coords.push_coord(&coord);
175                }
176            }
177
178            self.validity.append(true);
179        } else {
180            self.push_null();
181        }
182        Ok(())
183    }
184
185    /// Add a new Rect to this builder
186    #[inline]
187    pub fn push_rect(&mut self, value: Option<&impl RectTrait<T = f64>>) -> GeoArrowResult<()> {
188        if let Some(rect) = value {
189            let rect_wrapper = RectWrapper::try_new(rect)?;
190            self.push_polygon(Some(&rect_wrapper))?;
191        } else {
192            self.push_null();
193        }
194        Ok(())
195    }
196
197    /// Add a new geometry to this builder
198    ///
199    /// This will error if the geometry type is not Polygon, a MultiPolygon of length 1, or Rect.
200    #[inline]
201    pub fn push_geometry(
202        &mut self,
203        value: Option<&impl GeometryTrait<T = f64>>,
204    ) -> GeoArrowResult<()> {
205        if let Some(value) = value {
206            match value.as_type() {
207                GeometryType::Polygon(g) => self.push_polygon(Some(g))?,
208                GeometryType::MultiPolygon(mp) => {
209                    let num_polygons = mp.num_polygons();
210                    if num_polygons == 0 {
211                        self.push_empty();
212                    } else if num_polygons == 1 {
213                        self.push_polygon(Some(&mp.polygon(0).unwrap()))?
214                    } else {
215                        return Err(GeoArrowError::IncorrectGeometryType(format!(
216                            "Expected MultiPolygon with only one polygon in PolygonBuilder, got {num_polygons} polygons",
217                        )));
218                    }
219                }
220                GeometryType::Rect(g) => self.push_rect(Some(g))?,
221                GeometryType::Triangle(tri) => self.push_polygon(Some(&TriangleWrapper(tri)))?,
222                gt => {
223                    return Err(GeoArrowError::IncorrectGeometryType(format!(
224                        "Expected Polygon compatible geometry, got {}",
225                        gt.name()
226                    )));
227                }
228            }
229        } else {
230            self.push_null();
231        };
232        Ok(())
233    }
234
235    /// Extend this builder with the given geometries
236    pub fn extend_from_iter<'a>(
237        &mut self,
238        geoms: impl Iterator<Item = Option<&'a (impl PolygonTrait<T = f64> + 'a)>>,
239    ) {
240        geoms
241            .into_iter()
242            .try_for_each(|maybe_polygon| self.push_polygon(maybe_polygon))
243            .unwrap();
244    }
245
246    /// Extend this builder with the given geometries
247    pub fn extend_from_geometry_iter<'a>(
248        &mut self,
249        geoms: impl Iterator<Item = Option<&'a (impl GeometryTrait<T = f64> + 'a)>>,
250    ) -> GeoArrowResult<()> {
251        geoms.into_iter().try_for_each(|g| self.push_geometry(g))?;
252        Ok(())
253    }
254
255    /// Push a raw coordinate to the underlying coordinate array.
256    ///
257    /// # Invariants
258    ///
259    /// Care must be taken to ensure that pushing raw coordinates to the array upholds the
260    /// necessary invariants of the array.
261    #[inline]
262    pub(crate) fn push_coord(&mut self, coord: &impl CoordTrait<T = f64>) -> GeoArrowResult<()> {
263        self.coords.push_coord(coord);
264        Ok(())
265    }
266
267    #[inline]
268    pub(crate) fn push_empty(&mut self) {
269        self.geom_offsets.extend_constant(1);
270        self.validity.append(true);
271    }
272
273    #[inline]
274    pub(crate) fn push_null(&mut self) {
275        // NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
276        // point to the same ring array location
277        self.geom_offsets.extend_constant(1);
278        self.validity.append(false);
279    }
280
281    /// Construct a new builder, pre-filling it with the provided geometries
282    pub fn from_polygons(geoms: &[impl PolygonTrait<T = f64>], typ: PolygonType) -> Self {
283        let capacity = PolygonCapacity::from_polygons(geoms.iter().map(Some));
284        let mut array = Self::with_capacity(typ, capacity);
285        array.extend_from_iter(geoms.iter().map(Some));
286        array
287    }
288
289    /// Construct a new builder, pre-filling it with the provided geometries
290    pub fn from_nullable_polygons(
291        geoms: &[Option<impl PolygonTrait<T = f64>>],
292        typ: PolygonType,
293    ) -> Self {
294        let capacity = PolygonCapacity::from_polygons(geoms.iter().map(|x| x.as_ref()));
295        let mut array = Self::with_capacity(typ, capacity);
296        array.extend_from_iter(geoms.iter().map(|x| x.as_ref()));
297        array
298    }
299
300    /// Construct a new builder, pre-filling it with the provided geometries
301    pub fn from_nullable_geometries(
302        geoms: &[Option<impl GeometryTrait<T = f64>>],
303        typ: PolygonType,
304    ) -> GeoArrowResult<Self> {
305        let capacity = PolygonCapacity::from_geometries(geoms.iter().map(|x| x.as_ref()))?;
306        let mut array = Self::with_capacity(typ, capacity);
307        array.extend_from_geometry_iter(geoms.iter().map(|x| x.as_ref()))?;
308        Ok(array)
309    }
310}
311
312impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, PolygonType)> for PolygonBuilder {
313    type Error = GeoArrowError;
314
315    fn try_from((value, typ): (GenericWkbArray<O>, PolygonType)) -> GeoArrowResult<Self> {
316        let wkb_objects = value
317            .iter()
318            .map(|x| x.transpose())
319            .collect::<GeoArrowResult<Vec<_>>>()?;
320        Self::from_nullable_geometries(&wkb_objects, typ)
321    }
322}
323
324impl GeoArrowArrayBuilder for PolygonBuilder {
325    fn len(&self) -> usize {
326        self.geom_offsets.len_proxy()
327    }
328
329    fn push_null(&mut self) {
330        self.push_null();
331    }
332
333    fn push_geometry(
334        &mut self,
335        geometry: Option<&impl GeometryTrait<T = f64>>,
336    ) -> GeoArrowResult<()> {
337        self.push_geometry(geometry)
338    }
339
340    fn finish(self) -> Arc<dyn GeoArrowArray> {
341        Arc::new(self.finish())
342    }
343}
344
345#[cfg(test)]
346mod test {
347    use geo::BoundingRect;
348    use geo_traits::to_geo::ToGeoPolygon;
349    use geo_types::{Rect, coord};
350    use geoarrow_schema::{Dimension, PolygonType};
351
352    use crate::GeoArrowArrayAccessor;
353    use crate::builder::PolygonBuilder;
354
355    #[test]
356    fn test_push_rect() {
357        let mut builder = PolygonBuilder::new(PolygonType::new(Dimension::XY, Default::default()));
358
359        let rect = Rect::new(coord! { x: 10., y: 20. }, coord! { x: 30., y: 10. });
360        builder.push_rect(Some(&rect)).unwrap();
361        let array = builder.finish();
362
363        let polygon = array.value(0).unwrap().to_polygon();
364        let bounding_rect = polygon.bounding_rect().unwrap();
365        assert_eq!(rect, bounding_rect);
366    }
367}