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