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    if *geom_offsets.last() as usize != ring_offsets.len_proxy() {
60        return Err(GeoArrowError::InvalidGeoArrow(
61            "largest geometry offset must match ring offsets length".to_string(),
62        ));
63    }
64
65    Ok(())
66}
67
68impl PolygonArray {
69    /// Create a new PolygonArray from parts
70    ///
71    /// # Implementation
72    ///
73    /// This function is `O(1)`.
74    ///
75    /// # Panics
76    ///
77    /// - if the nulls is not `None` and its length is different from the number of geometries
78    /// - if the largest ring offset does not match the number of coordinates
79    /// - if the largest geometry offset does not match the size of ring offsets
80    pub fn new(
81        coords: CoordBuffer,
82        geom_offsets: OffsetBuffer<i32>,
83        ring_offsets: OffsetBuffer<i32>,
84        nulls: Option<NullBuffer>,
85        metadata: Arc<Metadata>,
86    ) -> Self {
87        Self::try_new(coords, geom_offsets, ring_offsets, nulls, metadata).unwrap()
88    }
89
90    /// Create a new PolygonArray from parts
91    ///
92    /// # Implementation
93    ///
94    /// This function is `O(1)`.
95    ///
96    /// # Errors
97    ///
98    /// - if the nulls is not `None` and its length is different from the number of geometries
99    /// - if the largest ring offset does not match the number of coordinates
100    /// - if the largest geometry offset does not match the size of ring offsets
101    pub fn try_new(
102        coords: CoordBuffer,
103        geom_offsets: OffsetBuffer<i32>,
104        ring_offsets: OffsetBuffer<i32>,
105        nulls: Option<NullBuffer>,
106        metadata: Arc<Metadata>,
107    ) -> GeoArrowResult<Self> {
108        check(
109            &coords,
110            &geom_offsets,
111            &ring_offsets,
112            nulls.as_ref().map(|v| v.len()),
113        )?;
114        Ok(Self {
115            data_type: PolygonType::new(coords.dim(), metadata)
116                .with_coord_type(coords.coord_type()),
117            coords,
118            geom_offsets,
119            ring_offsets,
120            nulls,
121        })
122    }
123
124    fn vertices_field(&self) -> Arc<Field> {
125        Field::new("vertices", self.coords.storage_type(), false).into()
126    }
127
128    fn rings_field(&self) -> Arc<Field> {
129        let name = "rings";
130        Field::new_list(name, self.vertices_field(), false).into()
131    }
132
133    /// Access the underlying coordinate buffer
134    pub fn coords(&self) -> &CoordBuffer {
135        &self.coords
136    }
137
138    /// Access the underlying geometry offsets buffer
139    pub fn geom_offsets(&self) -> &OffsetBuffer<i32> {
140        &self.geom_offsets
141    }
142
143    /// Access the underlying ring offsets buffer
144    pub fn ring_offsets(&self) -> &OffsetBuffer<i32> {
145        &self.ring_offsets
146    }
147
148    /// The lengths of each buffer contained in this array.
149    pub fn buffer_lengths(&self) -> PolygonCapacity {
150        PolygonCapacity::new(
151            *self.ring_offsets.last() as usize,
152            *self.geom_offsets.last() as usize,
153            self.len(),
154        )
155    }
156
157    /// The number of bytes occupied by this array.
158    pub fn num_bytes(&self) -> usize {
159        let validity_len = self.nulls.as_ref().map(|v| v.buffer().len()).unwrap_or(0);
160        validity_len + self.buffer_lengths().num_bytes(self.data_type.dimension())
161    }
162
163    /// Slice this [`PolygonArray`].
164    ///
165    /// # Panic
166    /// This function panics iff `offset + length > self.len()`.
167    #[inline]
168    pub fn slice(&self, offset: usize, length: usize) -> Self {
169        assert!(
170            offset + length <= self.len(),
171            "offset + length may not exceed length of array"
172        );
173        // Note: we **only** slice the geom_offsets and not any actual data or other offsets.
174        // Otherwise the offsets would be in the wrong location.
175        Self {
176            data_type: self.data_type.clone(),
177            coords: self.coords.clone(),
178            geom_offsets: self.geom_offsets.slice(offset, length),
179            ring_offsets: self.ring_offsets.clone(),
180            nulls: self.nulls.as_ref().map(|v| v.slice(offset, length)),
181        }
182    }
183
184    /// Change the [`CoordType`] of this array.
185    pub fn into_coord_type(self, coord_type: CoordType) -> Self {
186        Self {
187            data_type: self.data_type.with_coord_type(coord_type),
188            coords: self.coords.into_coord_type(coord_type),
189            ..self
190        }
191    }
192
193    /// Change the [`Metadata`] of this array.
194    pub fn with_metadata(self, metadata: Arc<Metadata>) -> Self {
195        Self {
196            data_type: self.data_type.with_metadata(metadata),
197            ..self
198        }
199    }
200}
201
202impl GeoArrowArray for PolygonArray {
203    fn as_any(&self) -> &dyn std::any::Any {
204        self
205    }
206
207    fn into_array_ref(self) -> ArrayRef {
208        Arc::new(self.into_arrow())
209    }
210
211    fn to_array_ref(&self) -> ArrayRef {
212        self.clone().into_array_ref()
213    }
214
215    #[inline]
216    fn len(&self) -> usize {
217        self.geom_offsets.len_proxy()
218    }
219
220    #[inline]
221    fn logical_nulls(&self) -> Option<NullBuffer> {
222        self.nulls.clone()
223    }
224
225    #[inline]
226    fn logical_null_count(&self) -> usize {
227        self.nulls.as_ref().map(|v| v.null_count()).unwrap_or(0)
228    }
229
230    #[inline]
231    fn is_null(&self, i: usize) -> bool {
232        self.nulls
233            .as_ref()
234            .map(|n| n.is_null(i))
235            .unwrap_or_default()
236    }
237
238    fn data_type(&self) -> GeoArrowType {
239        GeoArrowType::Polygon(self.data_type.clone())
240    }
241
242    fn slice(&self, offset: usize, length: usize) -> Arc<dyn GeoArrowArray> {
243        Arc::new(self.slice(offset, length))
244    }
245
246    fn with_metadata(self, metadata: Arc<Metadata>) -> Arc<dyn GeoArrowArray> {
247        Arc::new(self.with_metadata(metadata))
248    }
249}
250
251impl<'a> GeoArrowArrayAccessor<'a> for PolygonArray {
252    type Item = Polygon<'a>;
253
254    unsafe fn value_unchecked(&'a self, index: usize) -> GeoArrowResult<Self::Item> {
255        Ok(Polygon::new(
256            &self.coords,
257            &self.geom_offsets,
258            &self.ring_offsets,
259            index,
260        ))
261    }
262}
263
264impl IntoArrow for PolygonArray {
265    type ArrowArray = GenericListArray<i32>;
266    type ExtensionType = PolygonType;
267
268    fn into_arrow(self) -> Self::ArrowArray {
269        let vertices_field = self.vertices_field();
270        let rings_field = self.rings_field();
271        let nulls = self.nulls;
272        let coord_array = self.coords.into();
273        let ring_array = Arc::new(GenericListArray::new(
274            vertices_field,
275            self.ring_offsets,
276            coord_array,
277            None,
278        ));
279        GenericListArray::new(rings_field, self.geom_offsets, ring_array, nulls)
280    }
281
282    fn extension_type(&self) -> &Self::ExtensionType {
283        &self.data_type
284    }
285}
286
287impl TryFrom<(&GenericListArray<i32>, PolygonType)> for PolygonArray {
288    type Error = GeoArrowError;
289
290    fn try_from((geom_array, typ): (&GenericListArray<i32>, PolygonType)) -> GeoArrowResult<Self> {
291        let geom_offsets = geom_array.offsets();
292        let nulls = geom_array.nulls();
293
294        let rings_dyn_array = geom_array.values();
295        let rings_array = rings_dyn_array.as_list::<i32>();
296
297        let ring_offsets = rings_array.offsets();
298        let coords = CoordBuffer::from_arrow(rings_array.values().as_ref(), typ.dimension())?;
299
300        Ok(Self::new(
301            coords,
302            geom_offsets.clone(),
303            ring_offsets.clone(),
304            nulls.cloned(),
305            typ.metadata().clone(),
306        ))
307    }
308}
309
310impl TryFrom<(&GenericListArray<i64>, PolygonType)> for PolygonArray {
311    type Error = GeoArrowError;
312
313    fn try_from((geom_array, typ): (&GenericListArray<i64>, PolygonType)) -> GeoArrowResult<Self> {
314        let geom_offsets = offsets_buffer_i64_to_i32(geom_array.offsets())?;
315        let nulls = geom_array.nulls();
316
317        let rings_dyn_array = geom_array.values();
318        let rings_array = rings_dyn_array.as_list::<i64>();
319
320        let ring_offsets = offsets_buffer_i64_to_i32(rings_array.offsets())?;
321        let coords = CoordBuffer::from_arrow(rings_array.values().as_ref(), typ.dimension())?;
322
323        Ok(Self::new(
324            coords,
325            geom_offsets,
326            ring_offsets,
327            nulls.cloned(),
328            typ.metadata().clone(),
329        ))
330    }
331}
332impl TryFrom<(&dyn Array, PolygonType)> for PolygonArray {
333    type Error = GeoArrowError;
334
335    fn try_from((value, typ): (&dyn Array, PolygonType)) -> GeoArrowResult<Self> {
336        match value.data_type() {
337            DataType::List(_) => (value.as_list::<i32>(), typ).try_into(),
338            DataType::LargeList(_) => (value.as_list::<i64>(), typ).try_into(),
339            dt => Err(GeoArrowError::InvalidGeoArrow(format!(
340                "Unexpected Polygon DataType: {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
404impl GeometryTypeId for PolygonArray {
405    const GEOMETRY_TYPE_OFFSET: i8 = 3;
406
407    fn dimension(&self) -> Dimension {
408        self.data_type.dimension()
409    }
410}
411
412#[cfg(test)]
413mod test {
414    use geo_traits::to_geo::ToGeoPolygon;
415    use geoarrow_schema::{CoordType, Dimension};
416
417    use super::*;
418    use crate::test::polygon;
419
420    #[test]
421    fn geo_round_trip() {
422        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
423            let geoms = [Some(polygon::p0()), None, Some(polygon::p1()), None];
424            let typ =
425                PolygonType::new(Dimension::XY, Default::default()).with_coord_type(coord_type);
426            let geo_arr = PolygonBuilder::from_nullable_polygons(&geoms, typ).finish();
427
428            for (i, g) in geo_arr.iter().enumerate() {
429                assert_eq!(geoms[i], g.transpose().unwrap().map(|g| g.to_polygon()));
430            }
431
432            // Test sliced
433            for (i, g) in geo_arr.slice(2, 2).iter().enumerate() {
434                assert_eq!(geoms[i + 2], g.transpose().unwrap().map(|g| g.to_polygon()));
435            }
436        }
437    }
438
439    #[test]
440    fn geo_round_trip2() {
441        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
442            let geo_arr = polygon::array(coord_type, Dimension::XY);
443            let geo_geoms = geo_arr
444                .iter()
445                .map(|x| x.transpose().unwrap().map(|g| g.to_polygon()))
446                .collect::<Vec<_>>();
447
448            let typ =
449                PolygonType::new(Dimension::XY, Default::default()).with_coord_type(coord_type);
450            let geo_arr2 = PolygonBuilder::from_nullable_polygons(&geo_geoms, typ).finish();
451            assert_eq!(geo_arr, geo_arr2);
452        }
453    }
454
455    #[test]
456    fn try_from_arrow() {
457        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
458            for dim in [
459                Dimension::XY,
460                Dimension::XYZ,
461                Dimension::XYM,
462                Dimension::XYZM,
463            ] {
464                let geo_arr = polygon::array(coord_type, dim);
465
466                let extension_type = geo_arr.extension_type().clone();
467                let field = extension_type.to_field("geometry", true);
468
469                let arrow_arr = geo_arr.to_array_ref();
470
471                let geo_arr2: PolygonArray =
472                    (arrow_arr.as_ref(), extension_type).try_into().unwrap();
473                let geo_arr3: PolygonArray = (arrow_arr.as_ref(), &field).try_into().unwrap();
474
475                assert_eq!(geo_arr, geo_arr2);
476                assert_eq!(geo_arr, geo_arr3);
477            }
478        }
479    }
480
481    #[test]
482    fn partial_eq() {
483        let arr1 = polygon::p_array(CoordType::Interleaved);
484        let arr2 = polygon::p_array(CoordType::Separated);
485        assert_eq!(arr1, arr1);
486        assert_eq!(arr2, arr2);
487        assert_eq!(arr1, arr2);
488
489        assert_ne!(arr1, arr2.slice(0, 2));
490    }
491}