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