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