geoarrow_array/array/coord/
combined.rs

1use std::sync::Arc;
2
3use arrow_array::{Array, ArrayRef, FixedSizeListArray, StructArray};
4use arrow_schema::DataType;
5use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
6use geoarrow_schema::{CoordType, Dimension};
7
8use crate::array::{InterleavedCoordBuffer, SeparatedCoordBuffer};
9use crate::builder::{InterleavedCoordBufferBuilder, SeparatedCoordBufferBuilder};
10use crate::scalar::Coord;
11
12/// An Arrow representation of an array of coordinates.
13///
14/// As defined in the GeoArrow spec, coordinates can either be interleaved (i.e. a single array of
15/// XYXYXY) or separated (i.e. two arrays, one XXX and another YYY).
16///
17/// This CoordBuffer abstracts over an `InterleavedCoordBuffer` and a `SeparatedCoordBuffer`.
18///
19/// For now all coordinate buffers support only two dimensions.
20///
21/// This is named `CoordBuffer` instead of `CoordArray` because the buffer does not store its own
22/// validity bitmask. Rather the geometry arrays that build on top of this maintain their own
23/// validity masks.
24#[derive(Debug, Clone)]
25pub enum CoordBuffer {
26    /// Interleaved coordinates
27    Interleaved(InterleavedCoordBuffer),
28    /// Separated coordinates
29    Separated(SeparatedCoordBuffer),
30}
31
32impl CoordBuffer {
33    /// Slice this buffer
34    pub(crate) fn slice(&self, offset: usize, length: usize) -> Self {
35        match self {
36            CoordBuffer::Interleaved(c) => CoordBuffer::Interleaved(c.slice(offset, length)),
37            CoordBuffer::Separated(c) => CoordBuffer::Separated(c.slice(offset, length)),
38        }
39    }
40
41    /// The underlying coordinate type
42    pub fn coord_type(&self) -> CoordType {
43        match self {
44            CoordBuffer::Interleaved(_) => CoordType::Interleaved,
45            CoordBuffer::Separated(_) => CoordType::Separated,
46        }
47    }
48
49    /// The arrow [DataType] for this coordinate buffer.
50    pub(crate) fn storage_type(&self) -> DataType {
51        match self {
52            CoordBuffer::Interleaved(c) => c.storage_type(),
53            CoordBuffer::Separated(c) => c.storage_type(),
54        }
55    }
56
57    /// The length of this coordinate buffer
58    pub fn len(&self) -> usize {
59        match self {
60            CoordBuffer::Interleaved(c) => c.len(),
61            CoordBuffer::Separated(c) => c.len(),
62        }
63    }
64
65    /// Whether this coordinate buffer is empty
66    pub fn is_empty(&self) -> bool {
67        self.len() == 0
68    }
69
70    /// Returns the element at index `i`, not considering validity.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use geo_traits::CoordTrait;
76    /// use geoarrow_array::array::{CoordBuffer, SeparatedCoordBuffer};
77    /// use geoarrow_schema::Dimension;
78    ///
79    /// let coords = [
80    ///     geo_types::coord! { x: 1.0, y: 2.0 },
81    ///     geo_types::coord! { x: 3.0, y: 4.0 },
82    /// ];
83    /// let coord_buffer = CoordBuffer::from(
84    ///     SeparatedCoordBuffer::from_coords(coords.iter(), Dimension::XY).unwrap()
85    /// );
86    /// let coord = coord_buffer.value(0);
87    /// assert_eq!(coord.x(), 1.0);
88    /// assert_eq!(coord.y(), 2.0);
89    /// ```
90    ///
91    /// # Panics
92    ///
93    /// Panics if the value is outside the bounds of the buffer.
94    pub fn value(&self, index: usize) -> Coord<'_> {
95        match self {
96            CoordBuffer::Interleaved(c) => Coord::Interleaved(c.value(index)),
97            CoordBuffer::Separated(c) => Coord::Separated(c.value(index)),
98        }
99    }
100
101    /// Returns the element at index `i`, not considering validity.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use geo_traits::CoordTrait;
107    /// use geoarrow_array::array::{CoordBuffer, SeparatedCoordBuffer};
108    /// use geoarrow_schema::Dimension;
109    ///
110    /// let coords = [
111    ///     geo_types::coord! { x: 1.0, y: 2.0 },
112    ///     geo_types::coord! { x: 3.0, y: 4.0 },
113    /// ];
114    /// let coord_buffer = CoordBuffer::from(
115    ///     SeparatedCoordBuffer::from_coords(coords.iter(), Dimension::XY).unwrap()
116    /// );
117    /// let coord = unsafe { coord_buffer.value_unchecked(0) };
118    /// assert_eq!(coord.x(), 1.0);
119    /// assert_eq!(coord.y(), 2.0);
120    /// ```
121    ///
122    /// # Safety
123    ///
124    /// Caller is responsible for ensuring that the index is within the bounds of the buffer.
125    pub unsafe fn value_unchecked(&self, index: usize) -> Coord<'_> {
126        match self {
127            CoordBuffer::Interleaved(c) => Coord::Interleaved(unsafe { c.value_unchecked(index) }),
128            CoordBuffer::Separated(c) => Coord::Separated(unsafe { c.value_unchecked(index) }),
129        }
130    }
131
132    pub(crate) fn into_array_ref(self) -> ArrayRef {
133        self.into()
134    }
135
136    /// The dimension of this coordinate buffer
137    pub fn dim(&self) -> Dimension {
138        match self {
139            CoordBuffer::Interleaved(c) => c.dim(),
140            CoordBuffer::Separated(c) => c.dim(),
141        }
142    }
143
144    /// Convert this coordinate array into the given [CoordType]
145    ///
146    /// This is a no-op if the coord_type matches the existing coord type. Otherwise a full clone
147    /// of the underlying coordinate buffers will be performed.
148    pub fn into_coord_type(self, coord_type: CoordType) -> Self {
149        let dim = self.dim();
150        match (self, coord_type) {
151            (CoordBuffer::Interleaved(cb), CoordType::Interleaved) => CoordBuffer::Interleaved(cb),
152            (CoordBuffer::Interleaved(cb), CoordType::Separated) => {
153                let mut new_buffer = SeparatedCoordBufferBuilder::with_capacity(cb.len(), dim);
154                for i in 0..cb.len() {
155                    let coord = cb.value(i);
156                    new_buffer.push_coord(&coord);
157                }
158                CoordBuffer::Separated(new_buffer.finish())
159            }
160            (CoordBuffer::Separated(cb), CoordType::Separated) => CoordBuffer::Separated(cb),
161            (CoordBuffer::Separated(cb), CoordType::Interleaved) => {
162                let mut new_buffer = InterleavedCoordBufferBuilder::with_capacity(cb.len(), dim);
163                for i in 0..cb.len() {
164                    let coord = cb.value(i);
165                    new_buffer.push_coord(&coord);
166                }
167                CoordBuffer::Interleaved(new_buffer.finish())
168            }
169        }
170    }
171
172    pub(crate) fn from_arrow(value: &dyn Array, dim: Dimension) -> GeoArrowResult<Self> {
173        match value.data_type() {
174            DataType::Struct(_) => {
175                let downcasted = value.as_any().downcast_ref::<StructArray>().unwrap();
176                Ok(CoordBuffer::Separated(SeparatedCoordBuffer::from_arrow(
177                    downcasted, dim,
178                )?))
179            }
180            DataType::FixedSizeList(_, _) => {
181                let downcasted = value.as_any().downcast_ref::<FixedSizeListArray>().unwrap();
182                Ok(CoordBuffer::Interleaved(
183                    InterleavedCoordBuffer::from_arrow(downcasted, dim)?,
184                ))
185            }
186            _ => Err(GeoArrowError::InvalidGeoArrow(format!(
187                "Unexpected coord buffer type: {:?}",
188                value.data_type()
189            ))),
190        }
191    }
192}
193
194impl From<CoordBuffer> for ArrayRef {
195    fn from(value: CoordBuffer) -> Self {
196        match value {
197            CoordBuffer::Interleaved(c) => Arc::new(FixedSizeListArray::from(c)),
198            CoordBuffer::Separated(c) => Arc::new(StructArray::from(c)),
199        }
200    }
201}
202
203impl PartialEq for CoordBuffer {
204    fn eq(&self, other: &Self) -> bool {
205        match (self, other) {
206            (CoordBuffer::Interleaved(a), CoordBuffer::Interleaved(b)) => PartialEq::eq(a, b),
207            (CoordBuffer::Interleaved(left), CoordBuffer::Separated(right)) => {
208                if left.len() != right.len() {
209                    return false;
210                }
211
212                for i in 0..left.len() {
213                    let left_coord = left.value(i);
214                    let right_coord = right.value(i);
215
216                    if left_coord != right_coord {
217                        return false;
218                    }
219                }
220
221                true
222            }
223            (CoordBuffer::Separated(a), CoordBuffer::Separated(b)) => PartialEq::eq(a, b),
224            (CoordBuffer::Separated(left), CoordBuffer::Interleaved(right)) => {
225                if left.len() != right.len() {
226                    return false;
227                }
228
229                for i in 0..left.len() {
230                    let left_coord = left.value(i);
231                    let right_coord = right.value(i);
232
233                    if left_coord != right_coord {
234                        return false;
235                    }
236                }
237
238                true
239            }
240        }
241    }
242}
243
244impl From<InterleavedCoordBuffer> for CoordBuffer {
245    fn from(value: InterleavedCoordBuffer) -> Self {
246        Self::Interleaved(value)
247    }
248}
249
250impl From<SeparatedCoordBuffer> for CoordBuffer {
251    fn from(value: SeparatedCoordBuffer) -> Self {
252        Self::Separated(value)
253    }
254}
255
256// #[cfg(test)]
257// mod test {
258//     use crate::error::Result;
259
260//     use super::*;
261
262//     #[test]
263//     fn test_eq_both_interleaved() -> Result<()> {
264//         let coords1 = vec![0., 3., 1., 4., 2., 5.];
265//         let buf1 =
266//             CoordBuffer::Interleaved(InterleavedCoordBuffer::from_vec(coords1, Dimension::XY)?);
267
268//         let coords2 = vec![0., 3., 1., 4., 2., 5.];
269//         let buf2 =
270//             CoordBuffer::Interleaved(InterleavedCoordBuffer::from_vec(coords2, Dimension::XY)?);
271
272//         assert_eq!(buf1, buf2);
273//         Ok(())
274//     }
275
276//     #[test]
277//     fn test_eq_across_types() -> Result<()> {
278//         let x1 = vec![0., 1., 2.];
279//         let y1 = vec![3., 4., 5.];
280
281//         let buf1 = CoordBuffer::Separated(SeparatedCoordBuffer::new(
282//             [x1.into(), y1.into(), vec![].into(), vec![].into()],
283//             Dimension::XY,
284//         ));
285
286//         let coords2 = vec![0., 3., 1., 4., 2., 5.];
287//         let buf2 =
288//             CoordBuffer::Interleaved(InterleavedCoordBuffer::new(coords2.into(), Dimension::XY));
289
290//         assert_eq!(buf1, buf2);
291//         Ok(())
292//     }
293
294//     #[test]
295//     fn test_eq_across_types_slicing() -> Result<()> {
296//         let x1 = vec![0., 1., 2.];
297//         let y1 = vec![3., 4., 5.];
298
299//         let buf1 = CoordBuffer::Separated((x1, y1).try_into()?).slice(1, 1);
300
301//         let coords2 = vec![0., 3., 1., 4., 2., 5.];
302//         let buf2 =
303//             CoordBuffer::Interleaved(InterleavedCoordBuffer::new(coords2.into(), Dimension::XY))
304//                 .slice(1, 1);
305
306//         assert_eq!(buf1, buf2);
307//         Ok(())
308//     }
309// }