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