geoarrow_array/array/
geometry.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use arrow_array::cast::AsArray;
5use arrow_array::{Array, ArrayRef, OffsetSizeTrait, UnionArray};
6use arrow_buffer::{NullBuffer, ScalarBuffer};
7use arrow_schema::{ArrowError, DataType, Field, UnionMode};
8use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
9use geoarrow_schema::{
10    CoordType, Dimension, GeoArrowType, GeometryCollectionType, GeometryType, LineStringType,
11    Metadata, MultiLineStringType, MultiPointType, MultiPolygonType, PointType, PolygonType,
12};
13
14use crate::array::*;
15use crate::builder::*;
16use crate::capacity::GeometryCapacity;
17use crate::scalar::Geometry;
18use crate::trait_::{GeoArrowArray, GeoArrowArrayAccessor, IntoArrow};
19
20/// An immutable array of geometries of unknown geometry type and dimension.
21///
22// # Invariants
23//
24// - All arrays must have the same dimension
25// - All arrays must have the same coordinate layout (interleaved or separated)
26//
27// - 1: Point
28// - 2: LineString
29// - 3: Polygon
30// - 4: MultiPoint
31// - 5: MultiLineString
32// - 6: MultiPolygon
33// - 7: GeometryCollection
34// - 11: Point Z
35// - 12: LineString Z
36// - 13: Polygon Z
37// - 14: MultiPoint Z
38// - 15: MultiLineString Z
39// - 16: MultiPolygon Z
40// - 17: GeometryCollection Z
41// - 21: Point M
42// - 22: LineString M
43// - 23: Polygon M
44// - 24: MultiPoint M
45// - 25: MultiLineString M
46// - 26: MultiPolygon M
47// - 27: GeometryCollection M
48// - 31: Point ZM
49// - 32: LineString ZM
50// - 33: Polygon ZM
51// - 34: MultiPoint ZM
52// - 35: MultiLineString ZM
53// - 36: MultiPolygon ZM
54// - 37: GeometryCollection ZM
55#[derive(Debug, Clone)]
56pub struct GeometryArray {
57    pub(crate) data_type: GeometryType,
58
59    /// Invariant: every item in `type_ids` is `> 0 && < fields.len()` if `type_ids` are not
60    /// provided. If `type_ids` exist in the NativeType, then every item in `type_ids` is `> 0 && `
61    pub(crate) type_ids: ScalarBuffer<i8>,
62
63    /// Invariant: `offsets.len() == type_ids.len()`
64    pub(crate) offsets: ScalarBuffer<i32>,
65
66    /// An array of PointArray, ordered XY, XYZ, XYM, XYZM
67    pub(crate) points: [PointArray; 4],
68    pub(crate) line_strings: [LineStringArray; 4],
69    pub(crate) polygons: [PolygonArray; 4],
70    pub(crate) mpoints: [MultiPointArray; 4],
71    pub(crate) mline_strings: [MultiLineStringArray; 4],
72    pub(crate) mpolygons: [MultiPolygonArray; 4],
73    pub(crate) gcs: [GeometryCollectionArray; 4],
74}
75
76impl GeometryArray {
77    /// Create a new GeometryArray from parts
78    ///
79    /// # Implementation
80    ///
81    /// This function is `O(1)`.
82    ///
83    /// # Panics
84    ///
85    /// - if the validity is not `None` and its length is different from the number of geometries
86    /// - if the largest geometry offset does not match the number of coordinates
87    #[allow(clippy::too_many_arguments)]
88    pub fn new(
89        type_ids: ScalarBuffer<i8>,
90        offsets: ScalarBuffer<i32>,
91        points: [PointArray; 4],
92        line_strings: [LineStringArray; 4],
93        polygons: [PolygonArray; 4],
94        mpoints: [MultiPointArray; 4],
95        mline_strings: [MultiLineStringArray; 4],
96        mpolygons: [MultiPolygonArray; 4],
97        gcs: [GeometryCollectionArray; 4],
98        metadata: Arc<Metadata>,
99    ) -> Self {
100        // Validate that all arrays have the same coord type.
101        let mut coord_types = HashSet::new();
102        points.iter().for_each(|arr| {
103            coord_types.insert(arr.data_type.coord_type());
104        });
105        line_strings.iter().for_each(|arr| {
106            coord_types.insert(arr.data_type.coord_type());
107        });
108        polygons.iter().for_each(|arr| {
109            coord_types.insert(arr.data_type.coord_type());
110        });
111        mpoints.iter().for_each(|arr| {
112            coord_types.insert(arr.data_type.coord_type());
113        });
114        mline_strings.iter().for_each(|arr| {
115            coord_types.insert(arr.data_type.coord_type());
116        });
117        mpolygons.iter().for_each(|arr| {
118            coord_types.insert(arr.data_type.coord_type());
119        });
120
121        assert!(coord_types.len() == 1);
122        let coord_type = coord_types.into_iter().next().unwrap();
123
124        Self {
125            data_type: GeometryType::new(metadata).with_coord_type(coord_type),
126            type_ids,
127            offsets,
128            points,
129            line_strings,
130            polygons,
131            mpoints,
132            mline_strings,
133            mpolygons,
134            gcs,
135        }
136    }
137
138    /// The lengths of each buffer contained in this array.
139    pub fn buffer_lengths(&self) -> GeometryCapacity {
140        GeometryCapacity::new(
141            0,
142            core::array::from_fn(|i| self.points[i].buffer_lengths()),
143            core::array::from_fn(|i| self.line_strings[i].buffer_lengths()),
144            core::array::from_fn(|i| self.polygons[i].buffer_lengths()),
145            core::array::from_fn(|i| self.mpoints[i].buffer_lengths()),
146            core::array::from_fn(|i| self.mline_strings[i].buffer_lengths()),
147            core::array::from_fn(|i| self.mpolygons[i].buffer_lengths()),
148            core::array::from_fn(|i| self.gcs[i].buffer_lengths()),
149        )
150    }
151
152    /// Returns the `type_ids` buffer for this array
153    pub fn type_ids(&self) -> &ScalarBuffer<i8> {
154        &self.type_ids
155    }
156
157    /// Returns the `offsets` buffer for this array
158    pub fn offsets(&self) -> &ScalarBuffer<i32> {
159        &self.offsets
160    }
161
162    // TODO: handle slicing
163    pub(crate) fn has_points(&self, dim: Dimension) -> bool {
164        !self.points[dim.order()].is_empty()
165    }
166
167    pub(crate) fn has_line_strings(&self, dim: Dimension) -> bool {
168        !self.line_strings[dim.order()].is_empty()
169    }
170
171    pub(crate) fn has_polygons(&self, dim: Dimension) -> bool {
172        !self.polygons[dim.order()].is_empty()
173    }
174
175    pub(crate) fn has_multi_points(&self, dim: Dimension) -> bool {
176        !self.mpoints[dim.order()].is_empty()
177    }
178
179    pub(crate) fn has_multi_line_strings(&self, dim: Dimension) -> bool {
180        !self.mline_strings[dim.order()].is_empty()
181    }
182
183    pub(crate) fn has_multi_polygons(&self, dim: Dimension) -> bool {
184        !self.mpolygons[dim.order()].is_empty()
185    }
186
187    #[allow(dead_code)]
188    pub(crate) fn has_geometry_collections(&self, dim: Dimension) -> bool {
189        !self.gcs[dim.order()].is_empty()
190    }
191
192    /// Return `true` if this array holds at least one non-empty array of the given dimension
193    pub fn has_dimension(&self, dim: Dimension) -> bool {
194        self.has_points(dim)
195            || self.has_line_strings(dim)
196            || self.has_polygons(dim)
197            || self.has_multi_points(dim)
198            || self.has_multi_line_strings(dim)
199            || self.has_multi_polygons(dim)
200    }
201
202    /// Return `true` if this array holds at least one geometry array of the given dimension and no
203    /// arrays of any other dimension.
204    pub fn has_only_dimension(&self, dim: Dimension) -> bool {
205        use Dimension::*;
206        let existant_dims = [
207            self.has_dimension(XY),
208            self.has_dimension(XYZ),
209            self.has_dimension(XYM),
210            self.has_dimension(XYZM),
211        ];
212        existant_dims.iter().map(|b| *b as u8).sum::<u8>() == 1 && existant_dims[dim.order()]
213    }
214
215    /// The number of bytes occupied by this array.
216    pub fn num_bytes(&self) -> usize {
217        self.buffer_lengths().num_bytes()
218    }
219
220    /// Slice this [`GeometryArray`].
221    ///
222    /// # Implementation
223    ///
224    /// This operation is `O(F)` where `F` is the number of fields.
225    ///
226    /// # Panic
227    ///
228    /// This function panics iff `offset + length > self.len()`.
229    #[inline]
230    pub fn slice(&self, offset: usize, length: usize) -> Self {
231        assert!(
232            offset + length <= self.len(),
233            "offset + length may not exceed length of array"
234        );
235        Self {
236            data_type: self.data_type.clone(),
237            type_ids: self.type_ids.slice(offset, length),
238            offsets: self.offsets.slice(offset, length),
239
240            points: self.points.clone(),
241            line_strings: self.line_strings.clone(),
242            polygons: self.polygons.clone(),
243            mpoints: self.mpoints.clone(),
244            mline_strings: self.mline_strings.clone(),
245            mpolygons: self.mpolygons.clone(),
246            gcs: self.gcs.clone(),
247        }
248    }
249
250    /// Change the [`CoordType`] of this array.
251    pub fn into_coord_type(self, coord_type: CoordType) -> Self {
252        Self {
253            data_type: self.data_type.with_coord_type(coord_type),
254            points: self.points.map(|arr| arr.into_coord_type(coord_type)),
255            line_strings: self.line_strings.map(|arr| arr.into_coord_type(coord_type)),
256            polygons: self.polygons.map(|arr| arr.into_coord_type(coord_type)),
257            mpoints: self.mpoints.map(|arr| arr.into_coord_type(coord_type)),
258            mline_strings: self
259                .mline_strings
260                .map(|arr| arr.into_coord_type(coord_type)),
261            mpolygons: self.mpolygons.map(|arr| arr.into_coord_type(coord_type)),
262            gcs: self.gcs.map(|arr| arr.into_coord_type(coord_type)),
263            ..self
264        }
265    }
266
267    /// Change the [`Metadata`] of this array.
268    pub fn with_metadata(self, metadata: Arc<Metadata>) -> Self {
269        Self {
270            data_type: self.data_type.with_metadata(metadata),
271            ..self
272        }
273    }
274
275    // TODO: recursively expand the types from the geometry collection array
276    #[allow(dead_code)]
277    pub(crate) fn contained_types(&self) -> HashSet<GeoArrowType> {
278        let mut types = HashSet::new();
279        self.points.iter().for_each(|arr| {
280            if !arr.is_empty() {
281                types.insert(arr.data_type());
282            }
283        });
284        self.line_strings.iter().for_each(|arr| {
285            if !arr.is_empty() {
286                types.insert(arr.data_type());
287            }
288        });
289        self.polygons.iter().for_each(|arr| {
290            if !arr.is_empty() {
291                types.insert(arr.data_type());
292            }
293        });
294        self.mpoints.iter().for_each(|arr| {
295            if !arr.is_empty() {
296                types.insert(arr.data_type());
297            }
298        });
299        self.mline_strings.iter().for_each(|arr| {
300            if !arr.is_empty() {
301                types.insert(arr.data_type());
302            }
303        });
304        self.mpolygons.iter().for_each(|arr| {
305            if !arr.is_empty() {
306                types.insert(arr.data_type());
307            }
308        });
309        self.gcs.iter().for_each(|arr| {
310            if !arr.is_empty() {
311                types.insert(arr.data_type());
312            }
313        });
314
315        types
316    }
317}
318
319impl GeoArrowArray for GeometryArray {
320    fn as_any(&self) -> &dyn std::any::Any {
321        self
322    }
323
324    fn into_array_ref(self) -> ArrayRef {
325        Arc::new(self.into_arrow())
326    }
327
328    fn to_array_ref(&self) -> ArrayRef {
329        self.clone().into_array_ref()
330    }
331
332    #[inline]
333    fn len(&self) -> usize {
334        // Note that `type_ids` is sliced as usual, and thus always has the correct length.
335        self.type_ids.len()
336    }
337
338    #[inline]
339    fn logical_nulls(&self) -> Option<NullBuffer> {
340        self.to_array_ref().logical_nulls()
341    }
342
343    #[inline]
344    fn logical_null_count(&self) -> usize {
345        self.to_array_ref().logical_null_count()
346    }
347
348    #[inline]
349    fn is_null(&self, i: usize) -> bool {
350        let type_id = self.type_ids[i];
351        let offset = self.offsets[i] as usize;
352        let dim = (type_id / 10) as usize;
353        match type_id % 10 {
354            1 => self.points[dim].is_null(offset),
355            2 => self.line_strings[dim].is_null(offset),
356            3 => self.polygons[dim].is_null(offset),
357            4 => self.mpoints[dim].is_null(offset),
358            5 => self.mline_strings[dim].is_null(offset),
359            6 => self.mpolygons[dim].is_null(offset),
360            7 => self.gcs[dim].is_null(offset),
361            _ => unreachable!("unknown type_id {}", type_id),
362        }
363    }
364
365    fn data_type(&self) -> GeoArrowType {
366        GeoArrowType::Geometry(self.data_type.clone())
367    }
368
369    fn slice(&self, offset: usize, length: usize) -> Arc<dyn GeoArrowArray> {
370        Arc::new(self.slice(offset, length))
371    }
372
373    fn with_metadata(self, metadata: Arc<Metadata>) -> Arc<dyn GeoArrowArray> {
374        Arc::new(self.with_metadata(metadata))
375    }
376}
377
378impl<'a> GeoArrowArrayAccessor<'a> for GeometryArray {
379    type Item = Geometry<'a>;
380
381    unsafe fn value_unchecked(&'a self, index: usize) -> GeoArrowResult<Self::Item> {
382        let type_id = self.type_ids[index];
383        let offset = self.offsets[index] as usize;
384
385        let dim = (type_id / 10) as usize;
386
387        let result = match type_id % 10 {
388            1 => Geometry::Point(self.points[dim].value(offset)?),
389            2 => Geometry::LineString(self.line_strings[dim].value(offset)?),
390            3 => Geometry::Polygon(self.polygons[dim].value(offset)?),
391            4 => Geometry::MultiPoint(self.mpoints[dim].value(offset)?),
392            5 => Geometry::MultiLineString(self.mline_strings[dim].value(offset)?),
393            6 => Geometry::MultiPolygon(self.mpolygons[dim].value(offset)?),
394            7 => Geometry::GeometryCollection(self.gcs[dim].value(offset)?),
395            _ => unreachable!("unknown type_id {}", type_id),
396        };
397        Ok(result)
398    }
399}
400
401impl IntoArrow for GeometryArray {
402    type ArrowArray = UnionArray;
403    type ExtensionType = GeometryType;
404
405    fn into_arrow(self) -> Self::ArrowArray {
406        let union_fields = match self.data_type.data_type() {
407            DataType::Union(union_fields, _) => union_fields,
408            _ => unreachable!(),
409        };
410
411        // https://stackoverflow.com/a/34406459/7319250
412        let mut child_arrays: Vec<Option<ArrayRef>> = vec![None; 28];
413        for (i, arr) in self.points.into_iter().enumerate() {
414            child_arrays[i * 7] = Some(arr.into_array_ref());
415        }
416        for (i, arr) in self.line_strings.into_iter().enumerate() {
417            child_arrays[i * 7 + 1] = Some(arr.into_array_ref());
418        }
419        for (i, arr) in self.polygons.into_iter().enumerate() {
420            child_arrays[i * 7 + 2] = Some(arr.into_array_ref());
421        }
422        for (i, arr) in self.mpoints.into_iter().enumerate() {
423            child_arrays[i * 7 + 3] = Some(arr.into_array_ref());
424        }
425        for (i, arr) in self.mline_strings.into_iter().enumerate() {
426            child_arrays[i * 7 + 4] = Some(arr.into_array_ref());
427        }
428        for (i, arr) in self.mpolygons.into_iter().enumerate() {
429            child_arrays[i * 7 + 5] = Some(arr.into_array_ref());
430        }
431        for (i, arr) in self.gcs.into_iter().enumerate() {
432            child_arrays[i * 7 + 6] = Some(arr.into_array_ref());
433        }
434
435        UnionArray::try_new(
436            union_fields,
437            self.type_ids,
438            Some(self.offsets),
439            child_arrays.into_iter().map(|x| x.unwrap()).collect(),
440        )
441        .unwrap()
442    }
443
444    fn extension_type(&self) -> &Self::ExtensionType {
445        &self.data_type
446    }
447}
448
449impl TryFrom<(&UnionArray, GeometryType)> for GeometryArray {
450    type Error = GeoArrowError;
451
452    fn try_from((value, typ): (&UnionArray, GeometryType)) -> GeoArrowResult<Self> {
453        let mut points: [Option<PointArray>; 4] = Default::default();
454        let mut line_strings: [Option<LineStringArray>; 4] = Default::default();
455        let mut polygons: [Option<PolygonArray>; 4] = Default::default();
456        let mut mpoints: [Option<MultiPointArray>; 4] = Default::default();
457        let mut mline_strings: [Option<MultiLineStringArray>; 4] = Default::default();
458        let mut mpolygons: [Option<MultiPolygonArray>; 4] = Default::default();
459        let mut gcs: [Option<GeometryCollectionArray>; 4] = Default::default();
460
461        let coord_type = typ.coord_type();
462
463        match value.data_type() {
464            DataType::Union(fields, mode) => {
465                if !matches!(mode, UnionMode::Dense) {
466                    return Err(ArrowError::SchemaError("Expected dense union".to_string()).into());
467                }
468
469                for (type_id, _field) in fields.iter() {
470                    let dim = Dimension::from_order((type_id / 10) as _)?;
471                    let index = dim.order();
472
473                    match type_id % 10 {
474                        1 => {
475                            points[index] = Some(
476                                (
477                                    value.child(type_id).as_ref(),
478                                    PointType::new(dim, Default::default())
479                                        .with_coord_type(coord_type),
480                                )
481                                    .try_into()?,
482                            );
483                        }
484                        2 => {
485                            line_strings[index] = Some(
486                                (
487                                    value.child(type_id).as_ref(),
488                                    LineStringType::new(dim, Default::default())
489                                        .with_coord_type(coord_type),
490                                )
491                                    .try_into()?,
492                            );
493                        }
494                        3 => {
495                            polygons[index] = Some(
496                                (
497                                    value.child(type_id).as_ref(),
498                                    PolygonType::new(dim, Default::default())
499                                        .with_coord_type(coord_type),
500                                )
501                                    .try_into()?,
502                            );
503                        }
504                        4 => {
505                            mpoints[index] = Some(
506                                (
507                                    value.child(type_id).as_ref(),
508                                    MultiPointType::new(dim, Default::default())
509                                        .with_coord_type(coord_type),
510                                )
511                                    .try_into()?,
512                            );
513                        }
514                        5 => {
515                            mline_strings[index] = Some(
516                                (
517                                    value.child(type_id).as_ref(),
518                                    MultiLineStringType::new(dim, Default::default())
519                                        .with_coord_type(coord_type),
520                                )
521                                    .try_into()?,
522                            );
523                        }
524                        6 => {
525                            mpolygons[index] = Some(
526                                (
527                                    value.child(type_id).as_ref(),
528                                    MultiPolygonType::new(dim, Default::default())
529                                        .with_coord_type(coord_type),
530                                )
531                                    .try_into()?,
532                            );
533                        }
534                        7 => {
535                            gcs[index] = Some(
536                                (
537                                    value.child(type_id).as_ref(),
538                                    GeometryCollectionType::new(dim, Default::default())
539                                        .with_coord_type(coord_type),
540                                )
541                                    .try_into()?,
542                            );
543                        }
544                        _ => {
545                            return Err(GeoArrowError::InvalidGeoArrow(format!(
546                                "Unexpected type_id when converting to GeometryArray {type_id}",
547                            )));
548                        }
549                    }
550                }
551            }
552            _ => {
553                return Err(GeoArrowError::InvalidGeoArrow(
554                    "expected union type when converting to GeometryArray".to_string(),
555                ));
556            }
557        };
558
559        let type_ids = value.type_ids().clone();
560        // This is after checking for dense union
561        let offsets = value.offsets().unwrap().clone();
562
563        // We need to convert the array [Option<PointArray>; 4] into `[PointArray; 4]`.
564        // But we also need to ensure the underlying PointArray has the correct `Dimension` for the
565        // given array index.
566        // In order to do this, we need the index of the array, which `map` doesn't give us. And
567        // using `core::array::from_fn` doesn't let us move out of the existing array.
568        // So we mutate the existing array of `[Option<PointArray>; 4]` to ensure all values are
569        // `Some`, and then later we call `unwrap` on all array values in a `map`.
570        points.iter_mut().enumerate().for_each(|(i, arr)| {
571            let new_val = if let Some(arr) = arr.take() {
572                arr
573            } else {
574                PointBuilder::new(
575                    PointType::new(Dimension::from_order(i).unwrap(), Default::default())
576                        .with_coord_type(coord_type),
577                )
578                .finish()
579            };
580            arr.replace(new_val);
581        });
582        line_strings.iter_mut().enumerate().for_each(|(i, arr)| {
583            let new_val = if let Some(arr) = arr.take() {
584                arr
585            } else {
586                LineStringBuilder::new(
587                    LineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
588                        .with_coord_type(coord_type),
589                )
590                .finish()
591            };
592            arr.replace(new_val);
593        });
594        polygons.iter_mut().enumerate().for_each(|(i, arr)| {
595            let new_val = if let Some(arr) = arr.take() {
596                arr
597            } else {
598                PolygonBuilder::new(
599                    PolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
600                        .with_coord_type(coord_type),
601                )
602                .finish()
603            };
604            arr.replace(new_val);
605        });
606        mpoints.iter_mut().enumerate().for_each(|(i, arr)| {
607            let new_val = if let Some(arr) = arr.take() {
608                arr
609            } else {
610                MultiPointBuilder::new(
611                    MultiPointType::new(Dimension::from_order(i).unwrap(), Default::default())
612                        .with_coord_type(coord_type),
613                )
614                .finish()
615            };
616            arr.replace(new_val);
617        });
618        mline_strings.iter_mut().enumerate().for_each(|(i, arr)| {
619            let new_val = if let Some(arr) = arr.take() {
620                arr
621            } else {
622                MultiLineStringBuilder::new(
623                    MultiLineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
624                        .with_coord_type(coord_type),
625                )
626                .finish()
627            };
628            arr.replace(new_val);
629        });
630        mpolygons.iter_mut().enumerate().for_each(|(i, arr)| {
631            let new_val = if let Some(arr) = arr.take() {
632                arr
633            } else {
634                MultiPolygonBuilder::new(
635                    MultiPolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
636                        .with_coord_type(coord_type),
637                )
638                .finish()
639            };
640            arr.replace(new_val);
641        });
642        gcs.iter_mut().enumerate().for_each(|(i, arr)| {
643            let new_val = if let Some(arr) = arr.take() {
644                arr
645            } else {
646                GeometryCollectionBuilder::new(
647                    GeometryCollectionType::new(
648                        Dimension::from_order(i).unwrap(),
649                        Default::default(),
650                    )
651                    .with_coord_type(coord_type),
652                )
653                .finish()
654            };
655            arr.replace(new_val);
656        });
657
658        Ok(Self::new(
659            type_ids,
660            offsets,
661            points.map(|x| x.unwrap()),
662            line_strings.map(|x| x.unwrap()),
663            polygons.map(|x| x.unwrap()),
664            mpoints.map(|x| x.unwrap()),
665            mline_strings.map(|x| x.unwrap()),
666            mpolygons.map(|x| x.unwrap()),
667            gcs.map(|x| x.unwrap()),
668            Default::default(),
669        ))
670    }
671}
672
673impl TryFrom<(&dyn Array, GeometryType)> for GeometryArray {
674    type Error = GeoArrowError;
675
676    fn try_from((value, typ): (&dyn Array, GeometryType)) -> GeoArrowResult<Self> {
677        match value.data_type() {
678            DataType::Union(_, _) => (value.as_union(), typ).try_into(),
679            dt => Err(GeoArrowError::InvalidGeoArrow(format!(
680                "Unexpected GeometryArray DataType: {dt:?}",
681            ))),
682        }
683    }
684}
685
686impl TryFrom<(&dyn Array, &Field)> for GeometryArray {
687    type Error = GeoArrowError;
688
689    fn try_from((arr, field): (&dyn Array, &Field)) -> GeoArrowResult<Self> {
690        let typ = field.try_extension_type::<GeometryType>()?;
691        (arr, typ).try_into()
692    }
693}
694
695impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, GeometryType)> for GeometryArray {
696    type Error = GeoArrowError;
697
698    fn try_from(value: (GenericWkbArray<O>, GeometryType)) -> GeoArrowResult<Self> {
699        let mut_arr: GeometryBuilder = value.try_into()?;
700        Ok(mut_arr.finish())
701    }
702}
703
704pub(crate) trait DimensionIndex: Sized {
705    /// Get the positional index of the internal array for the given dimension.
706    fn order(&self) -> usize;
707
708    fn from_order(index: usize) -> GeoArrowResult<Self>;
709}
710
711impl DimensionIndex for Dimension {
712    fn order(&self) -> usize {
713        match self {
714            Self::XY => 0,
715            Self::XYZ => 1,
716            Self::XYM => 2,
717            Self::XYZM => 3,
718        }
719    }
720
721    fn from_order(index: usize) -> GeoArrowResult<Self> {
722        match index {
723            0 => Ok(Self::XY),
724            1 => Ok(Self::XYZ),
725            2 => Ok(Self::XYM),
726            3 => Ok(Self::XYZM),
727            i => {
728                Err(ArrowError::SchemaError(format!("unsupported index in from_order: {i}")).into())
729            }
730        }
731    }
732}
733
734impl PartialEq for GeometryArray {
735    fn eq(&self, other: &Self) -> bool {
736        self.type_ids == other.type_ids
737            && self.offsets == other.offsets
738            && self.points == other.points
739            && self.line_strings == other.line_strings
740            && self.polygons == other.polygons
741            && self.mpoints == other.mpoints
742            && self.mline_strings == other.mline_strings
743            && self.mpolygons == other.mpolygons
744            && self.gcs == other.gcs
745    }
746}
747
748impl TypeId for PointArray {
749    const ARRAY_TYPE_OFFSET: i8 = 1;
750}
751impl TypeId for LineStringArray {
752    const ARRAY_TYPE_OFFSET: i8 = 2;
753}
754impl TypeId for PolygonArray {
755    const ARRAY_TYPE_OFFSET: i8 = 3;
756}
757impl TypeId for MultiPointArray {
758    const ARRAY_TYPE_OFFSET: i8 = 4;
759}
760impl TypeId for MultiLineStringArray {
761    const ARRAY_TYPE_OFFSET: i8 = 5;
762}
763impl TypeId for MultiPolygonArray {
764    const ARRAY_TYPE_OFFSET: i8 = 6;
765}
766impl TypeId for GeometryCollectionArray {
767    const ARRAY_TYPE_OFFSET: i8 = 7;
768}
769
770type ChildrenArrays = (
771    [PointArray; 4],
772    [LineStringArray; 4],
773    [PolygonArray; 4],
774    [MultiPointArray; 4],
775    [MultiLineStringArray; 4],
776    [MultiPolygonArray; 4],
777    [GeometryCollectionArray; 4],
778);
779
780/// Initialize empty children with the given coord type.
781///
782/// This is used in the impls like `From<PointArray> for GeometryArray`. This lets us initialize
783/// all empty children and then just swap in the one array that's valid.
784fn empty_children(coord_type: CoordType) -> ChildrenArrays {
785    (
786        core::array::from_fn(|i| {
787            PointBuilder::new(
788                PointType::new(Dimension::from_order(i).unwrap(), Default::default())
789                    .with_coord_type(coord_type),
790            )
791            .finish()
792        }),
793        core::array::from_fn(|i| {
794            LineStringBuilder::new(
795                LineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
796                    .with_coord_type(coord_type),
797            )
798            .finish()
799        }),
800        core::array::from_fn(|i| {
801            PolygonBuilder::new(
802                PolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
803                    .with_coord_type(coord_type),
804            )
805            .finish()
806        }),
807        core::array::from_fn(|i| {
808            MultiPointBuilder::new(
809                MultiPointType::new(Dimension::from_order(i).unwrap(), Default::default())
810                    .with_coord_type(coord_type),
811            )
812            .finish()
813        }),
814        core::array::from_fn(|i| {
815            MultiLineStringBuilder::new(
816                MultiLineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
817                    .with_coord_type(coord_type),
818            )
819            .finish()
820        }),
821        core::array::from_fn(|i| {
822            MultiPolygonBuilder::new(
823                MultiPolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
824                    .with_coord_type(coord_type),
825            )
826            .finish()
827        }),
828        core::array::from_fn(|i| {
829            GeometryCollectionBuilder::new(
830                GeometryCollectionType::new(Dimension::from_order(i).unwrap(), Default::default())
831                    .with_coord_type(coord_type),
832            )
833            .finish()
834        }),
835    )
836}
837
838macro_rules! impl_primitive_cast {
839    ($source_array:ty, $value_edit:tt) => {
840        impl From<$source_array> for GeometryArray {
841            fn from(value: $source_array) -> Self {
842                let coord_type = value.data_type.coord_type();
843                let dim = value.data_type.dimension();
844                let metadata = value.data_type.metadata().clone();
845
846                let type_ids = vec![value.type_id(dim); value.len()].into();
847                let offsets = ScalarBuffer::from_iter(0..value.len() as i32);
848                let data_type = GeometryType::new(metadata).with_coord_type(coord_type);
849                let mut children = empty_children(coord_type);
850
851                children.$value_edit[dim.order()] = value;
852                Self {
853                    data_type,
854                    type_ids,
855                    offsets,
856                    points: children.0,
857                    line_strings: children.1,
858                    polygons: children.2,
859                    mpoints: children.3,
860                    mline_strings: children.4,
861                    mpolygons: children.5,
862                    gcs: children.6,
863                }
864            }
865        }
866    };
867}
868
869impl_primitive_cast!(PointArray, 0);
870impl_primitive_cast!(LineStringArray, 1);
871impl_primitive_cast!(PolygonArray, 2);
872impl_primitive_cast!(MultiPointArray, 3);
873impl_primitive_cast!(MultiLineStringArray, 4);
874impl_primitive_cast!(MultiPolygonArray, 5);
875impl_primitive_cast!(GeometryCollectionArray, 6);
876
877#[cfg(test)]
878mod test {
879    use geo_traits::to_geo::ToGeoGeometry;
880    use geoarrow_test::raw;
881
882    use super::*;
883    use crate::test::{linestring, multilinestring, multipoint, multipolygon, point, polygon};
884
885    fn geoms() -> Vec<geo_types::Geometry> {
886        vec![
887            point::p0().into(),
888            point::p1().into(),
889            point::p2().into(),
890            linestring::ls0().into(),
891            linestring::ls1().into(),
892            polygon::p0().into(),
893            polygon::p1().into(),
894            multipoint::mp0().into(),
895            multipoint::mp1().into(),
896            multilinestring::ml0().into(),
897            multilinestring::ml1().into(),
898            multipolygon::mp0().into(),
899            multipolygon::mp1().into(),
900        ]
901    }
902
903    fn geom_array(coord_type: CoordType) -> GeometryArray {
904        let geoms = geoms().into_iter().map(Some).collect::<Vec<_>>();
905        let typ = GeometryType::new(Default::default()).with_coord_type(coord_type);
906        GeometryBuilder::from_nullable_geometries(&geoms, typ)
907            .unwrap()
908            .finish()
909    }
910
911    #[test]
912    fn test_2d() {
913        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
914            let geoms = geoms();
915            let geometry_array = geom_array(coord_type);
916            let geoms_again = geometry_array
917                .iter_values()
918                .map(|g| g.unwrap().to_geometry())
919                .collect::<Vec<_>>();
920            assert_eq!(geoms, geoms_again);
921        }
922    }
923
924    #[test]
925    fn test_2d_roundtrip_arrow() {
926        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
927            let geoms = geoms();
928            let geometry_array = geom_array(coord_type);
929            let field = geometry_array.data_type.to_field("geometry", true);
930            let union_array = geometry_array.into_arrow();
931
932            let geometry_array_again =
933                GeometryArray::try_from((&union_array as _, &field)).unwrap();
934            let geoms_again = geometry_array_again
935                .iter_values()
936                .map(|g| g.unwrap().to_geometry())
937                .collect::<Vec<_>>();
938            assert_eq!(geoms, geoms_again);
939        }
940    }
941
942    #[test]
943    fn try_from_arrow() {
944        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
945            for prefer_multi in [true, false] {
946                let geo_arr = crate::test::geometry::array(coord_type, prefer_multi);
947
948                let point_type = geo_arr.extension_type().clone();
949                let field = point_type.to_field("geometry", true);
950
951                let arrow_arr = geo_arr.to_array_ref();
952
953                let geo_arr2: GeometryArray = (arrow_arr.as_ref(), point_type).try_into().unwrap();
954                let geo_arr3: GeometryArray = (arrow_arr.as_ref(), &field).try_into().unwrap();
955
956                assert_eq!(geo_arr, geo_arr2);
957                assert_eq!(geo_arr, geo_arr3);
958            }
959        }
960    }
961
962    #[test]
963    fn test_nullability() {
964        let geoms = raw::geometry::geoms();
965        let null_idxs = geoms
966            .iter()
967            .enumerate()
968            .filter_map(|(i, geom)| if geom.is_none() { Some(i) } else { None })
969            .collect::<Vec<_>>();
970
971        let typ = GeometryType::new(Default::default());
972        let geo_arr = GeometryBuilder::from_nullable_geometries(&geoms, typ)
973            .unwrap()
974            .finish();
975
976        for null_idx in &null_idxs {
977            assert!(geo_arr.is_null(*null_idx));
978        }
979    }
980
981    #[test]
982    fn test_logical_nulls() {
983        let geoms = raw::geometry::geoms();
984        let expected_nulls = NullBuffer::from_iter(geoms.iter().map(|g| g.is_some()));
985
986        let typ = GeometryType::new(Default::default());
987        let geo_arr = GeometryBuilder::from_nullable_geometries(&geoms, typ)
988            .unwrap()
989            .finish();
990
991        assert_eq!(geo_arr.logical_nulls().unwrap(), expected_nulls);
992    }
993
994    #[test]
995    fn into_coord_type() {
996        for prefer_multi in [true, false] {
997            let geo_arr = crate::test::geometry::array(CoordType::Interleaved, prefer_multi);
998            let geo_arr2 = geo_arr
999                .clone()
1000                .into_coord_type(CoordType::Separated)
1001                .into_coord_type(CoordType::Interleaved);
1002
1003            assert_eq!(geo_arr, geo_arr2);
1004        }
1005    }
1006
1007    #[test]
1008    fn partial_eq() {
1009        for prefer_multi in [true, false] {
1010            let arr1 = crate::test::geometry::array(CoordType::Interleaved, prefer_multi);
1011            let arr2 = crate::test::geometry::array(CoordType::Separated, prefer_multi);
1012
1013            assert_eq!(arr1, arr1);
1014            assert_eq!(arr2, arr2);
1015            assert_eq!(arr1, arr2);
1016
1017            assert_ne!(arr1, arr2.slice(0, 2));
1018        }
1019    }
1020}
1021
1022// #[cfg(test)]
1023// mod test {
1024//     use super::*;
1025//     use crate::test::{linestring, multilinestring, multipoint, multipolygon, point, polygon};
1026
1027//     #[test]
1028//     fn geo_roundtrip_accurate_points() {
1029//         let geoms: Vec<geo::Geometry> = vec![
1030//             geo::Geometry::Point(point::p0()),
1031//             geo::Geometry::Point(point::p1()),
1032//             geo::Geometry::Point(point::p2()),
1033//         ];
1034
1035//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1036//             geoms.as_slice(),
1037//             CoordType::Interleaved,
1038//             Default::default(),
1039//             false,
1040//         )
1041//         .unwrap()
1042//         .finish();
1043
1044//         assert_eq!(arr.value_as_geo(0), geo::Geometry::Point(point::p0()));
1045//         assert_eq!(arr.value_as_geo(1), geo::Geometry::Point(point::p1()));
1046//         assert_eq!(arr.value_as_geo(2), geo::Geometry::Point(point::p2()));
1047//     }
1048
1049//     #[test]
1050//     fn geo_roundtrip_accurate_multi_points() {
1051//         let geoms: Vec<geo::Geometry> = vec![
1052//             geo::Geometry::Point(point::p0()),
1053//             geo::Geometry::Point(point::p1()),
1054//             geo::Geometry::Point(point::p2()),
1055//         ];
1056//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1057//             geoms.as_slice(),
1058//             CoordType::Interleaved,
1059//             Default::default(),
1060//             true,
1061//         )
1062//         .unwrap()
1063//         .finish();
1064
1065//         assert_eq!(
1066//             arr.value_as_geo(0),
1067//             geo::Geometry::MultiPoint(geo::MultiPoint(vec![point::p0()]))
1068//         );
1069//         assert_eq!(
1070//             arr.value_as_geo(1),
1071//             geo::Geometry::MultiPoint(geo::MultiPoint(vec![point::p1()]))
1072//         );
1073//         assert_eq!(
1074//             arr.value_as_geo(2),
1075//             geo::Geometry::MultiPoint(geo::MultiPoint(vec![point::p2()]))
1076//         );
1077//     }
1078
1079//     #[test]
1080//     fn geo_roundtrip_accurate_all() {
1081//         let geoms: Vec<geo::Geometry> = vec![
1082//             geo::Geometry::Point(point::p0()),
1083//             geo::Geometry::LineString(linestring::ls0()),
1084//             geo::Geometry::Polygon(polygon::p0()),
1085//             geo::Geometry::MultiPoint(multipoint::mp0()),
1086//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1087//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1088//         ];
1089
1090//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1091//             geoms.as_slice(),
1092//             CoordType::Interleaved,
1093//             Default::default(),
1094//             false,
1095//         )
1096//         .unwrap()
1097//         .finish();
1098
1099//         assert_eq!(arr.value_as_geo(0), geoms[0]);
1100//         assert_eq!(arr.value_as_geo(1), geoms[1]);
1101//         assert_eq!(arr.value_as_geo(2), geoms[2]);
1102//         assert_eq!(arr.value_as_geo(3), geoms[3]);
1103//         assert_eq!(arr.value_as_geo(4), geoms[4]);
1104//         assert_eq!(arr.value_as_geo(5), geoms[5]);
1105//     }
1106
1107//     #[test]
1108//     fn arrow_roundtrip() {
1109//         let geoms: Vec<geo::Geometry> = vec![
1110//             geo::Geometry::Point(point::p0()),
1111//             geo::Geometry::LineString(linestring::ls0()),
1112//             geo::Geometry::Polygon(polygon::p0()),
1113//             geo::Geometry::MultiPoint(multipoint::mp0()),
1114//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1115//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1116//         ];
1117
1118//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1119//             geoms.as_slice(),
1120//             CoordType::Interleaved,
1121//             Default::default(),
1122//             false,
1123//         )
1124//         .unwrap()
1125//         .finish();
1126
1127//         // Round trip to/from arrow-rs
1128//         let arrow_array = arr.into_arrow();
1129//         let round_trip_arr: GeometryArray = (&arrow_array).try_into().unwrap();
1130
1131//         assert_eq!(round_trip_arr.value_as_geo(0), geoms[0]);
1132//         assert_eq!(round_trip_arr.value_as_geo(1), geoms[1]);
1133//         assert_eq!(round_trip_arr.value_as_geo(2), geoms[2]);
1134//         assert_eq!(round_trip_arr.value_as_geo(3), geoms[3]);
1135//         assert_eq!(round_trip_arr.value_as_geo(4), geoms[4]);
1136//         assert_eq!(round_trip_arr.value_as_geo(5), geoms[5]);
1137//     }
1138
1139//     #[test]
1140//     fn arrow_roundtrip_not_all_types() {
1141//         let geoms: Vec<geo::Geometry> = vec![
1142//             geo::Geometry::MultiPoint(multipoint::mp0()),
1143//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1144//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1145//         ];
1146
1147//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1148//             geoms.as_slice(),
1149//             CoordType::Interleaved,
1150//             Default::default(),
1151//             false,
1152//         )
1153//         .unwrap()
1154//         .finish();
1155
1156//         // Round trip to/from arrow-rs
1157//         let arrow_array = arr.into_arrow();
1158//         let round_trip_arr: GeometryArray = (&arrow_array).try_into().unwrap();
1159
1160//         assert_eq!(round_trip_arr.value_as_geo(0), geoms[0]);
1161//         assert_eq!(round_trip_arr.value_as_geo(1), geoms[1]);
1162//         assert_eq!(round_trip_arr.value_as_geo(2), geoms[2]);
1163//     }
1164
1165//     #[test]
1166//     fn arrow_roundtrip_not_all_types2() {
1167//         let geoms: Vec<geo::Geometry> = vec![
1168//             geo::Geometry::MultiPoint(multipoint::mp0()),
1169//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1170//         ];
1171
1172//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1173//             geoms.as_slice(),
1174//             CoordType::Interleaved,
1175//             Default::default(),
1176//             false,
1177//         )
1178//         .unwrap()
1179//         .finish();
1180
1181//         // Round trip to/from arrow-rs
1182//         let arrow_array = arr.into_arrow();
1183//         let round_trip_arr: GeometryArray = (&arrow_array).try_into().unwrap();
1184
1185//         assert_eq!(round_trip_arr.value_as_geo(0), geoms[0]);
1186//         assert_eq!(round_trip_arr.value_as_geo(1), geoms[1]);
1187//     }
1188
1189//     #[test]
1190//     fn test_slicing() {
1191//         let geoms: Vec<geo::Geometry> = vec![
1192//             geo::Geometry::Point(point::p0()),
1193//             geo::Geometry::LineString(linestring::ls0()),
1194//             geo::Geometry::Polygon(polygon::p0()),
1195//             geo::Geometry::MultiPoint(multipoint::mp0()),
1196//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1197//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1198//         ];
1199
1200//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1201//             geoms.as_slice(),
1202//             CoordType::Interleaved,
1203//             Default::default(),
1204//             false,
1205//         )
1206//         .unwrap()
1207//         .finish();
1208
1209//         assert_eq!(arr.slice(1, 2).value_as_geo(0), geoms[1]);
1210//         assert_eq!(arr.slice(1, 2).value_as_geo(1), geoms[2]);
1211//         assert_eq!(arr.slice(3, 3).value_as_geo(2), geoms[5]);
1212//     }
1213// }