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