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#[derive(Debug, Clone)]
24pub struct MultiPointArray {
25 pub(crate) data_type: MultiPointType,
26
27 pub(crate) coords: CoordBuffer,
28
29 pub(crate) geom_offsets: OffsetBuffer<i32>,
31
32 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() {
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 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 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 pub fn coords(&self) -> &CoordBuffer {
109 &self.coords
110 }
111
112 pub fn geom_offsets(&self) -> &OffsetBuffer<i32> {
114 &self.geom_offsets
115 }
116
117 pub fn buffer_lengths(&self) -> MultiPointCapacity {
119 MultiPointCapacity::new(*self.geom_offsets.last() as usize, self.len())
120 }
121
122 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 #[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 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 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 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 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}