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// }