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() {
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 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 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 pub fn coords(&self) -> &CoordBuffer {
108 &self.coords
109 }
110
111 pub fn geom_offsets(&self) -> &OffsetBuffer<i32> {
113 &self.geom_offsets
114 }
115
116 pub fn buffer_lengths(&self) -> MultiPointCapacity {
118 MultiPointCapacity::new(*self.geom_offsets.last() as usize, self.len())
119 }
120
121 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 #[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 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 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 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 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}