geoarrow_array/scalar/
polygon.rs

1use arrow_buffer::OffsetBuffer;
2use geo_traits::PolygonTrait;
3use geoarrow_schema::Dimension;
4
5use crate::array::CoordBuffer;
6use crate::eq::polygon_eq;
7use crate::scalar::LineString;
8use crate::util::OffsetBufferUtils;
9
10/// An Arrow equivalent of a Polygon
11///
12/// This implements [PolygonTrait], which you can use to extract data.
13#[derive(Debug, Clone)]
14pub struct Polygon<'a> {
15    pub(crate) coords: &'a CoordBuffer,
16
17    /// Offsets into the ring array where each geometry starts
18    pub(crate) geom_offsets: &'a OffsetBuffer<i32>,
19
20    /// Offsets into the coordinate array where each ring starts
21    pub(crate) ring_offsets: &'a OffsetBuffer<i32>,
22
23    pub(crate) geom_index: usize,
24
25    start_offset: usize,
26}
27
28impl<'a> Polygon<'a> {
29    pub(crate) fn new(
30        coords: &'a CoordBuffer,
31        geom_offsets: &'a OffsetBuffer<i32>,
32        ring_offsets: &'a OffsetBuffer<i32>,
33        geom_index: usize,
34    ) -> Self {
35        let (start_offset, _) = geom_offsets.start_end(geom_index);
36        Self {
37            coords,
38            geom_offsets,
39            ring_offsets,
40            geom_index,
41            start_offset,
42        }
43    }
44
45    pub(crate) fn native_dim(&self) -> Dimension {
46        self.coords.dim()
47    }
48}
49
50impl<'a> PolygonTrait for Polygon<'a> {
51    type RingType<'b>
52        = LineString<'a>
53    where
54        Self: 'b;
55
56    fn exterior(&self) -> Option<Self::RingType<'_>> {
57        let (start, end) = self.geom_offsets.start_end(self.geom_index);
58        if start == end {
59            None
60        } else {
61            Some(LineString::new(self.coords, self.ring_offsets, start))
62        }
63    }
64
65    fn num_interiors(&self) -> usize {
66        let (start, end) = self.geom_offsets.start_end(self.geom_index);
67        // Note: we need to use saturating_sub in the case of an empty polygon, where start == end
68        (end - start).saturating_sub(1)
69    }
70
71    unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> {
72        LineString::new(self.coords, self.ring_offsets, self.start_offset + 1 + i)
73    }
74}
75
76impl<'a> PolygonTrait for &'a Polygon<'a> {
77    type RingType<'b>
78        = LineString<'a>
79    where
80        Self: 'b;
81
82    fn exterior(&self) -> Option<Self::RingType<'_>> {
83        let (start, end) = self.geom_offsets.start_end(self.geom_index);
84        if start == end {
85            None
86        } else {
87            Some(LineString::new(self.coords, self.ring_offsets, start))
88        }
89    }
90
91    fn num_interiors(&self) -> usize {
92        let (start, end) = self.geom_offsets.start_end(self.geom_index);
93        // Note: we need to use saturating_sub in the case of an empty polygon, where start == end
94        (end - start).saturating_sub(1)
95    }
96
97    unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> {
98        LineString::new(self.coords, self.ring_offsets, self.start_offset + 1 + i)
99    }
100}
101
102impl<G: PolygonTrait<T = f64>> PartialEq<G> for Polygon<'_> {
103    fn eq(&self, other: &G) -> bool {
104        polygon_eq(self, other)
105    }
106}
107
108#[cfg(test)]
109mod test {
110    use geo::HasDimensions;
111    use geo_traits::to_geo::ToGeoPolygon;
112    use geoarrow_schema::{Dimension, PolygonType};
113    use wkt::wkt;
114
115    use crate::GeoArrowArrayAccessor;
116    use crate::builder::PolygonBuilder;
117
118    /// Test Eq where the current index is true but another index is false
119    #[test]
120    fn test_access_empty_polygon() {
121        let empty_polygon: wkt::types::Polygon<f64> = wkt! { POLYGON EMPTY };
122        let typ = PolygonType::new(Dimension::XY, Default::default());
123        let polygon_array = PolygonBuilder::from_polygons(&[empty_polygon], typ).finish();
124
125        let geo_polygon = polygon_array.value(0).unwrap().to_polygon();
126        assert!(geo_polygon.is_empty());
127    }
128}