geoarrow_array/array/
polygon.rs

1use std::sync::Arc;
2
3use arrow_array::cast::AsArray;
4use arrow_array::{Array, ArrayRef, GenericListArray, OffsetSizeTrait};
5use arrow_buffer::{NullBuffer, OffsetBuffer};
6use arrow_schema::{DataType, Field};
7use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
8use geoarrow_schema::{CoordType, GeoArrowType, Metadata, PolygonType};
9
10use crate::array::{CoordBuffer, GenericWkbArray, RectArray};
11use crate::builder::PolygonBuilder;
12use crate::capacity::PolygonCapacity;
13use crate::eq::offset_buffer_eq;
14use crate::scalar::Polygon;
15use crate::trait_::{GeoArrowArray, GeoArrowArrayAccessor, IntoArrow};
16use crate::util::{OffsetBufferUtils, offsets_buffer_i64_to_i32};
17
18/// An immutable array of Polygon geometries using GeoArrow's in-memory representation.
19///
20/// All polygons must have the same dimension.
21///
22/// This is semantically equivalent to `Vec<Option<Polygon>>` due to the internal validity bitmap.
23#[derive(Debug, Clone)]
24// #[derive(Debug, Clone, PartialEq)]
25pub struct PolygonArray {
26    pub(crate) data_type: PolygonType,
27
28    pub(crate) coords: CoordBuffer,
29
30    /// Offsets into the ring array where each geometry starts
31    pub(crate) geom_offsets: OffsetBuffer<i32>,
32
33    /// Offsets into the coordinate array where each ring starts
34    pub(crate) ring_offsets: OffsetBuffer<i32>,
35
36    /// Validity bitmap
37    pub(crate) nulls: Option<NullBuffer>,
38}
39
40pub(super) fn check(
41    coords: &CoordBuffer,
42    geom_offsets: &OffsetBuffer<i32>,
43    ring_offsets: &OffsetBuffer<i32>,
44    validity_len: Option<usize>,
45) -> GeoArrowResult<()> {
46    if validity_len.is_some_and(|len| len != geom_offsets.len_proxy()) {
47        return Err(GeoArrowError::InvalidGeoArrow(
48            "nulls mask length must match the number of values".to_string(),
49        ));
50    }
51
52    if *ring_offsets.last() as usize != coords.len() {
53        return Err(GeoArrowError::InvalidGeoArrow(
54            "largest ring offset must match coords length".to_string(),
55        ));
56    }
57
58    if *geom_offsets.last() as usize != ring_offsets.len_proxy() {
59        return Err(GeoArrowError::InvalidGeoArrow(
60            "largest geometry offset must match ring offsets length".to_string(),
61        ));
62    }
63
64    Ok(())
65}
66
67impl PolygonArray {
68    /// Create a new PolygonArray from parts
69    ///
70    /// # Implementation
71    ///
72    /// This function is `O(1)`.
73    ///
74    /// # Panics
75    ///
76    /// - if the nulls is not `None` and its length is different from the number of geometries
77    /// - if the largest ring offset does not match the number of coordinates
78    /// - if the largest geometry offset does not match the size of ring offsets
79    pub fn new(
80        coords: CoordBuffer,
81        geom_offsets: OffsetBuffer<i32>,
82        ring_offsets: OffsetBuffer<i32>,
83        nulls: Option<NullBuffer>,
84        metadata: Arc<Metadata>,
85    ) -> Self {
86        Self::try_new(coords, geom_offsets, ring_offsets, nulls, metadata).unwrap()
87    }
88
89    /// Create a new PolygonArray from parts
90    ///
91    /// # Implementation
92    ///
93    /// This function is `O(1)`.
94    ///
95    /// # Errors
96    ///
97    /// - if the nulls is not `None` and its length is different from the number of geometries
98    /// - if the largest ring offset does not match the number of coordinates
99    /// - if the largest geometry offset does not match the size of ring offsets
100    pub fn try_new(
101        coords: CoordBuffer,
102        geom_offsets: OffsetBuffer<i32>,
103        ring_offsets: OffsetBuffer<i32>,
104        nulls: Option<NullBuffer>,
105        metadata: Arc<Metadata>,
106    ) -> GeoArrowResult<Self> {
107        check(
108            &coords,
109            &geom_offsets,
110            &ring_offsets,
111            nulls.as_ref().map(|v| v.len()),
112        )?;
113        Ok(Self {
114            data_type: PolygonType::new(coords.dim(), metadata)
115                .with_coord_type(coords.coord_type()),
116            coords,
117            geom_offsets,
118            ring_offsets,
119            nulls,
120        })
121    }
122
123    fn vertices_field(&self) -> Arc<Field> {
124        Field::new("vertices", self.coords.storage_type(), false).into()
125    }
126
127    fn rings_field(&self) -> Arc<Field> {
128        let name = "rings";
129        Field::new_list(name, self.vertices_field(), false).into()
130    }
131
132    /// Access the underlying coordinate buffer
133    pub fn coords(&self) -> &CoordBuffer {
134        &self.coords
135    }
136
137    /// Access the underlying geometry offsets buffer
138    pub fn geom_offsets(&self) -> &OffsetBuffer<i32> {
139        &self.geom_offsets
140    }
141
142    /// Access the underlying ring offsets buffer
143    pub fn ring_offsets(&self) -> &OffsetBuffer<i32> {
144        &self.ring_offsets
145    }
146
147    /// The lengths of each buffer contained in this array.
148    pub fn buffer_lengths(&self) -> PolygonCapacity {
149        PolygonCapacity::new(
150            *self.ring_offsets.last() as usize,
151            *self.geom_offsets.last() as usize,
152            self.len(),
153        )
154    }
155
156    /// The number of bytes occupied by this array.
157    pub fn num_bytes(&self) -> usize {
158        let validity_len = self.nulls.as_ref().map(|v| v.buffer().len()).unwrap_or(0);
159        validity_len + self.buffer_lengths().num_bytes()
160    }
161
162    /// Slice this [`PolygonArray`].
163    ///
164    /// # Panic
165    /// This function panics iff `offset + length > self.len()`.
166    #[inline]
167    pub fn slice(&self, offset: usize, length: usize) -> Self {
168        assert!(
169            offset + length <= self.len(),
170            "offset + length may not exceed length of array"
171        );
172        // Note: we **only** slice the geom_offsets and not any actual data or other offsets.
173        // Otherwise the offsets would be in the wrong location.
174        Self {
175            data_type: self.data_type.clone(),
176            coords: self.coords.clone(),
177            geom_offsets: self.geom_offsets.slice(offset, length),
178            ring_offsets: self.ring_offsets.clone(),
179            nulls: self.nulls.as_ref().map(|v| v.slice(offset, length)),
180        }
181    }
182
183    /// Change the [`CoordType`] of this array.
184    pub fn into_coord_type(self, coord_type: CoordType) -> Self {
185        Self {
186            data_type: self.data_type.with_coord_type(coord_type),
187            coords: self.coords.into_coord_type(coord_type),
188            ..self
189        }
190    }
191
192    /// Change the [`Metadata`] of this array.
193    pub fn with_metadata(self, metadata: Arc<Metadata>) -> Self {
194        Self {
195            data_type: self.data_type.with_metadata(metadata),
196            ..self
197        }
198    }
199}
200
201impl GeoArrowArray for PolygonArray {
202    fn as_any(&self) -> &dyn std::any::Any {
203        self
204    }
205
206    fn into_array_ref(self) -> ArrayRef {
207        Arc::new(self.into_arrow())
208    }
209
210    fn to_array_ref(&self) -> ArrayRef {
211        self.clone().into_array_ref()
212    }
213
214    #[inline]
215    fn len(&self) -> usize {
216        self.geom_offsets.len_proxy()
217    }
218
219    #[inline]
220    fn logical_nulls(&self) -> Option<NullBuffer> {
221        self.nulls.clone()
222    }
223
224    #[inline]
225    fn logical_null_count(&self) -> usize {
226        self.nulls.as_ref().map(|v| v.null_count()).unwrap_or(0)
227    }
228
229    #[inline]
230    fn is_null(&self, i: usize) -> bool {
231        self.nulls
232            .as_ref()
233            .map(|n| n.is_null(i))
234            .unwrap_or_default()
235    }
236
237    fn data_type(&self) -> GeoArrowType {
238        GeoArrowType::Polygon(self.data_type.clone())
239    }
240
241    fn slice(&self, offset: usize, length: usize) -> Arc<dyn GeoArrowArray> {
242        Arc::new(self.slice(offset, length))
243    }
244
245    fn with_metadata(self, metadata: Arc<Metadata>) -> Arc<dyn GeoArrowArray> {
246        Arc::new(self.with_metadata(metadata))
247    }
248}
249
250impl<'a> GeoArrowArrayAccessor<'a> for PolygonArray {
251    type Item = Polygon<'a>;
252
253    unsafe fn value_unchecked(&'a self, index: usize) -> GeoArrowResult<Self::Item> {
254        Ok(Polygon::new(
255            &self.coords,
256            &self.geom_offsets,
257            &self.ring_offsets,
258            index,
259        ))
260    }
261}
262
263impl IntoArrow for PolygonArray {
264    type ArrowArray = GenericListArray<i32>;
265    type ExtensionType = PolygonType;
266
267    fn into_arrow(self) -> Self::ArrowArray {
268        let vertices_field = self.vertices_field();
269        let rings_field = self.rings_field();
270        let nulls = self.nulls;
271        let coord_array = self.coords.into();
272        let ring_array = Arc::new(GenericListArray::new(
273            vertices_field,
274            self.ring_offsets,
275            coord_array,
276            None,
277        ));
278        GenericListArray::new(rings_field, self.geom_offsets, ring_array, nulls)
279    }
280
281    fn extension_type(&self) -> &Self::ExtensionType {
282        &self.data_type
283    }
284}
285
286impl TryFrom<(&GenericListArray<i32>, PolygonType)> for PolygonArray {
287    type Error = GeoArrowError;
288
289    fn try_from((geom_array, typ): (&GenericListArray<i32>, PolygonType)) -> GeoArrowResult<Self> {
290        let geom_offsets = geom_array.offsets();
291        let nulls = geom_array.nulls();
292
293        let rings_dyn_array = geom_array.values();
294        let rings_array = rings_dyn_array.as_list::<i32>();
295
296        let ring_offsets = rings_array.offsets();
297        let coords = CoordBuffer::from_arrow(rings_array.values().as_ref(), typ.dimension())?;
298
299        Ok(Self::new(
300            coords,
301            geom_offsets.clone(),
302            ring_offsets.clone(),
303            nulls.cloned(),
304            typ.metadata().clone(),
305        ))
306    }
307}
308
309impl TryFrom<(&GenericListArray<i64>, PolygonType)> for PolygonArray {
310    type Error = GeoArrowError;
311
312    fn try_from((geom_array, typ): (&GenericListArray<i64>, PolygonType)) -> GeoArrowResult<Self> {
313        let geom_offsets = offsets_buffer_i64_to_i32(geom_array.offsets())?;
314        let nulls = geom_array.nulls();
315
316        let rings_dyn_array = geom_array.values();
317        let rings_array = rings_dyn_array.as_list::<i64>();
318
319        let ring_offsets = offsets_buffer_i64_to_i32(rings_array.offsets())?;
320        let coords = CoordBuffer::from_arrow(rings_array.values().as_ref(), typ.dimension())?;
321
322        Ok(Self::new(
323            coords,
324            geom_offsets,
325            ring_offsets,
326            nulls.cloned(),
327            typ.metadata().clone(),
328        ))
329    }
330}
331impl TryFrom<(&dyn Array, PolygonType)> for PolygonArray {
332    type Error = GeoArrowError;
333
334    fn try_from((value, typ): (&dyn Array, PolygonType)) -> GeoArrowResult<Self> {
335        match value.data_type() {
336            DataType::List(_) => (value.as_list::<i32>(), typ).try_into(),
337            DataType::LargeList(_) => (value.as_list::<i64>(), typ).try_into(),
338            dt => Err(GeoArrowError::InvalidGeoArrow(format!(
339                "Unexpected Polygon DataType: {:?}",
340                dt
341            ))),
342        }
343    }
344}
345
346impl TryFrom<(&dyn Array, &Field)> for PolygonArray {
347    type Error = GeoArrowError;
348
349    fn try_from((arr, field): (&dyn Array, &Field)) -> GeoArrowResult<Self> {
350        let typ = field.try_extension_type::<PolygonType>()?;
351        (arr, typ).try_into()
352    }
353}
354
355impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, PolygonType)> for PolygonArray {
356    type Error = GeoArrowError;
357
358    fn try_from(value: (GenericWkbArray<O>, PolygonType)) -> GeoArrowResult<Self> {
359        let mut_arr: PolygonBuilder = value.try_into()?;
360        Ok(mut_arr.finish())
361    }
362}
363
364impl From<RectArray> for PolygonArray {
365    fn from(value: RectArray) -> Self {
366        let polygon_type = PolygonType::new(
367            value.data_type.dimension(),
368            value.data_type.metadata().clone(),
369        )
370        .with_coord_type(CoordType::Separated);
371
372        // The number of output geoms is the same as the input
373        let geom_capacity = value.len();
374
375        // Each output polygon is a simple polygon with only one ring
376        let ring_capacity = geom_capacity;
377
378        // Each output polygon has exactly 5 coordinates
379        // Don't reserve capacity for null entries
380        let coord_capacity = (value.len() - value.logical_null_count()) * 5;
381
382        let capacity = PolygonCapacity::new(coord_capacity, ring_capacity, geom_capacity);
383        let mut output_array = PolygonBuilder::with_capacity(polygon_type, capacity);
384
385        value.iter().for_each(|maybe_g| {
386            output_array
387                .push_rect(maybe_g.transpose().unwrap().as_ref())
388                .unwrap()
389        });
390
391        output_array.finish()
392    }
393}
394
395impl PartialEq for PolygonArray {
396    fn eq(&self, other: &Self) -> bool {
397        self.nulls == other.nulls
398            && offset_buffer_eq(&self.geom_offsets, &other.geom_offsets)
399            && offset_buffer_eq(&self.ring_offsets, &other.ring_offsets)
400            && self.coords == other.coords
401    }
402}
403
404#[cfg(test)]
405mod test {
406    use geo_traits::to_geo::ToGeoPolygon;
407    use geoarrow_schema::{CoordType, Dimension};
408
409    use super::*;
410    use crate::test::polygon;
411
412    #[test]
413    fn geo_round_trip() {
414        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
415            let geoms = [Some(polygon::p0()), None, Some(polygon::p1()), None];
416            let typ =
417                PolygonType::new(Dimension::XY, Default::default()).with_coord_type(coord_type);
418            let geo_arr = PolygonBuilder::from_nullable_polygons(&geoms, typ).finish();
419
420            for (i, g) in geo_arr.iter().enumerate() {
421                assert_eq!(geoms[i], g.transpose().unwrap().map(|g| g.to_polygon()));
422            }
423
424            // Test sliced
425            for (i, g) in geo_arr.slice(2, 2).iter().enumerate() {
426                assert_eq!(geoms[i + 2], g.transpose().unwrap().map(|g| g.to_polygon()));
427            }
428        }
429    }
430
431    #[test]
432    fn geo_round_trip2() {
433        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
434            let geo_arr = polygon::array(coord_type, Dimension::XY);
435            let geo_geoms = geo_arr
436                .iter()
437                .map(|x| x.transpose().unwrap().map(|g| g.to_polygon()))
438                .collect::<Vec<_>>();
439
440            let typ =
441                PolygonType::new(Dimension::XY, Default::default()).with_coord_type(coord_type);
442            let geo_arr2 = PolygonBuilder::from_nullable_polygons(&geo_geoms, typ).finish();
443            assert_eq!(geo_arr, geo_arr2);
444        }
445    }
446
447    #[test]
448    fn try_from_arrow() {
449        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
450            for dim in [
451                Dimension::XY,
452                Dimension::XYZ,
453                Dimension::XYM,
454                Dimension::XYZM,
455            ] {
456                let geo_arr = polygon::array(coord_type, dim);
457
458                let extension_type = geo_arr.extension_type().clone();
459                let field = extension_type.to_field("geometry", true);
460
461                let arrow_arr = geo_arr.to_array_ref();
462
463                let geo_arr2: PolygonArray =
464                    (arrow_arr.as_ref(), extension_type).try_into().unwrap();
465                let geo_arr3: PolygonArray = (arrow_arr.as_ref(), &field).try_into().unwrap();
466
467                assert_eq!(geo_arr, geo_arr2);
468                assert_eq!(geo_arr, geo_arr3);
469            }
470        }
471    }
472
473    #[test]
474    fn partial_eq() {
475        let arr1 = polygon::p_array(CoordType::Interleaved);
476        let arr2 = polygon::p_array(CoordType::Separated);
477        assert_eq!(arr1, arr1);
478        assert_eq!(arr2, arr2);
479        assert_eq!(arr1, arr2);
480
481        assert_ne!(arr1, arr2.slice(0, 2));
482    }
483}