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