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        let metadata = typ.metadata().clone();
463
464        // Note: From the spec:
465        //
466        // The child arrays should not themselves contain GeoArrow metadata. Only the top-level
467        // geometry array should contain GeoArrow metadata.
468        match value.data_type() {
469            DataType::Union(fields, mode) => {
470                if !matches!(mode, UnionMode::Dense) {
471                    return Err(ArrowError::SchemaError("Expected dense union".to_string()).into());
472                }
473
474                for (type_id, _field) in fields.iter() {
475                    let dim = Dimension::from_order((type_id / 10) as _)?;
476                    let index = dim.order();
477
478                    match type_id % 10 {
479                        1 => {
480                            points[index] = Some(
481                                (
482                                    value.child(type_id).as_ref(),
483                                    PointType::new(dim, Default::default())
484                                        .with_coord_type(coord_type),
485                                )
486                                    .try_into()?,
487                            );
488                        }
489                        2 => {
490                            line_strings[index] = Some(
491                                (
492                                    value.child(type_id).as_ref(),
493                                    LineStringType::new(dim, Default::default())
494                                        .with_coord_type(coord_type),
495                                )
496                                    .try_into()?,
497                            );
498                        }
499                        3 => {
500                            polygons[index] = Some(
501                                (
502                                    value.child(type_id).as_ref(),
503                                    PolygonType::new(dim, Default::default())
504                                        .with_coord_type(coord_type),
505                                )
506                                    .try_into()?,
507                            );
508                        }
509                        4 => {
510                            mpoints[index] = Some(
511                                (
512                                    value.child(type_id).as_ref(),
513                                    MultiPointType::new(dim, Default::default())
514                                        .with_coord_type(coord_type),
515                                )
516                                    .try_into()?,
517                            );
518                        }
519                        5 => {
520                            mline_strings[index] = Some(
521                                (
522                                    value.child(type_id).as_ref(),
523                                    MultiLineStringType::new(dim, Default::default())
524                                        .with_coord_type(coord_type),
525                                )
526                                    .try_into()?,
527                            );
528                        }
529                        6 => {
530                            mpolygons[index] = Some(
531                                (
532                                    value.child(type_id).as_ref(),
533                                    MultiPolygonType::new(dim, Default::default())
534                                        .with_coord_type(coord_type),
535                                )
536                                    .try_into()?,
537                            );
538                        }
539                        7 => {
540                            gcs[index] = Some(
541                                (
542                                    value.child(type_id).as_ref(),
543                                    GeometryCollectionType::new(dim, Default::default())
544                                        .with_coord_type(coord_type),
545                                )
546                                    .try_into()?,
547                            );
548                        }
549                        _ => {
550                            return Err(GeoArrowError::InvalidGeoArrow(format!(
551                                "Unexpected type_id when converting to GeometryArray {type_id}",
552                            )));
553                        }
554                    }
555                }
556            }
557            _ => {
558                return Err(GeoArrowError::InvalidGeoArrow(
559                    "expected union type when converting to GeometryArray".to_string(),
560                ));
561            }
562        };
563
564        let type_ids = value.type_ids().clone();
565        // This is after checking for dense union
566        let offsets = value.offsets().unwrap().clone();
567
568        // We need to convert the array [Option<PointArray>; 4] into `[PointArray; 4]`.
569        // But we also need to ensure the underlying PointArray has the correct `Dimension` for the
570        // given array index.
571        // In order to do this, we need the index of the array, which `map` doesn't give us. And
572        // using `core::array::from_fn` doesn't let us move out of the existing array.
573        // So we mutate the existing array of `[Option<PointArray>; 4]` to ensure all values are
574        // `Some`, and then later we call `unwrap` on all array values in a `map`.
575        points.iter_mut().enumerate().for_each(|(i, arr)| {
576            let new_val = if let Some(arr) = arr.take() {
577                arr
578            } else {
579                PointBuilder::new(
580                    PointType::new(Dimension::from_order(i).unwrap(), Default::default())
581                        .with_coord_type(coord_type),
582                )
583                .finish()
584            };
585            arr.replace(new_val);
586        });
587        line_strings.iter_mut().enumerate().for_each(|(i, arr)| {
588            let new_val = if let Some(arr) = arr.take() {
589                arr
590            } else {
591                LineStringBuilder::new(
592                    LineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
593                        .with_coord_type(coord_type),
594                )
595                .finish()
596            };
597            arr.replace(new_val);
598        });
599        polygons.iter_mut().enumerate().for_each(|(i, arr)| {
600            let new_val = if let Some(arr) = arr.take() {
601                arr
602            } else {
603                PolygonBuilder::new(
604                    PolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
605                        .with_coord_type(coord_type),
606                )
607                .finish()
608            };
609            arr.replace(new_val);
610        });
611        mpoints.iter_mut().enumerate().for_each(|(i, arr)| {
612            let new_val = if let Some(arr) = arr.take() {
613                arr
614            } else {
615                MultiPointBuilder::new(
616                    MultiPointType::new(Dimension::from_order(i).unwrap(), Default::default())
617                        .with_coord_type(coord_type),
618                )
619                .finish()
620            };
621            arr.replace(new_val);
622        });
623        mline_strings.iter_mut().enumerate().for_each(|(i, arr)| {
624            let new_val = if let Some(arr) = arr.take() {
625                arr
626            } else {
627                MultiLineStringBuilder::new(
628                    MultiLineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
629                        .with_coord_type(coord_type),
630                )
631                .finish()
632            };
633            arr.replace(new_val);
634        });
635        mpolygons.iter_mut().enumerate().for_each(|(i, arr)| {
636            let new_val = if let Some(arr) = arr.take() {
637                arr
638            } else {
639                MultiPolygonBuilder::new(
640                    MultiPolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
641                        .with_coord_type(coord_type),
642                )
643                .finish()
644            };
645            arr.replace(new_val);
646        });
647        gcs.iter_mut().enumerate().for_each(|(i, arr)| {
648            let new_val = if let Some(arr) = arr.take() {
649                arr
650            } else {
651                GeometryCollectionBuilder::new(
652                    GeometryCollectionType::new(
653                        Dimension::from_order(i).unwrap(),
654                        Default::default(),
655                    )
656                    .with_coord_type(coord_type),
657                )
658                .finish()
659            };
660            arr.replace(new_val);
661        });
662
663        Ok(Self::new(
664            type_ids,
665            offsets,
666            points.map(|x| x.unwrap()),
667            line_strings.map(|x| x.unwrap()),
668            polygons.map(|x| x.unwrap()),
669            mpoints.map(|x| x.unwrap()),
670            mline_strings.map(|x| x.unwrap()),
671            mpolygons.map(|x| x.unwrap()),
672            gcs.map(|x| x.unwrap()),
673            metadata,
674        ))
675    }
676}
677
678impl TryFrom<(&dyn Array, GeometryType)> for GeometryArray {
679    type Error = GeoArrowError;
680
681    fn try_from((value, typ): (&dyn Array, GeometryType)) -> GeoArrowResult<Self> {
682        match value.data_type() {
683            DataType::Union(_, _) => (value.as_union(), typ).try_into(),
684            dt => Err(GeoArrowError::InvalidGeoArrow(format!(
685                "Unexpected GeometryArray DataType: {dt:?}",
686            ))),
687        }
688    }
689}
690
691impl TryFrom<(&dyn Array, &Field)> for GeometryArray {
692    type Error = GeoArrowError;
693
694    fn try_from((arr, field): (&dyn Array, &Field)) -> GeoArrowResult<Self> {
695        let typ = field.try_extension_type::<GeometryType>()?;
696        (arr, typ).try_into()
697    }
698}
699
700impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, GeometryType)> for GeometryArray {
701    type Error = GeoArrowError;
702
703    fn try_from(value: (GenericWkbArray<O>, GeometryType)) -> GeoArrowResult<Self> {
704        let mut_arr: GeometryBuilder = value.try_into()?;
705        Ok(mut_arr.finish())
706    }
707}
708
709pub(crate) trait DimensionIndex: Sized {
710    /// Get the positional index of the internal array for the given dimension.
711    fn order(&self) -> usize;
712
713    fn from_order(index: usize) -> GeoArrowResult<Self>;
714}
715
716impl DimensionIndex for Dimension {
717    fn order(&self) -> usize {
718        match self {
719            Self::XY => 0,
720            Self::XYZ => 1,
721            Self::XYM => 2,
722            Self::XYZM => 3,
723        }
724    }
725
726    fn from_order(index: usize) -> GeoArrowResult<Self> {
727        match index {
728            0 => Ok(Self::XY),
729            1 => Ok(Self::XYZ),
730            2 => Ok(Self::XYM),
731            3 => Ok(Self::XYZM),
732            i => {
733                Err(ArrowError::SchemaError(format!("unsupported index in from_order: {i}")).into())
734            }
735        }
736    }
737}
738
739impl PartialEq for GeometryArray {
740    fn eq(&self, other: &Self) -> bool {
741        self.type_ids == other.type_ids
742            && self.offsets == other.offsets
743            && self.points == other.points
744            && self.line_strings == other.line_strings
745            && self.polygons == other.polygons
746            && self.mpoints == other.mpoints
747            && self.mline_strings == other.mline_strings
748            && self.mpolygons == other.mpolygons
749            && self.gcs == other.gcs
750    }
751}
752
753impl TypeId for PointArray {
754    const ARRAY_TYPE_OFFSET: i8 = 1;
755}
756impl TypeId for LineStringArray {
757    const ARRAY_TYPE_OFFSET: i8 = 2;
758}
759impl TypeId for PolygonArray {
760    const ARRAY_TYPE_OFFSET: i8 = 3;
761}
762impl TypeId for MultiPointArray {
763    const ARRAY_TYPE_OFFSET: i8 = 4;
764}
765impl TypeId for MultiLineStringArray {
766    const ARRAY_TYPE_OFFSET: i8 = 5;
767}
768impl TypeId for MultiPolygonArray {
769    const ARRAY_TYPE_OFFSET: i8 = 6;
770}
771impl TypeId for GeometryCollectionArray {
772    const ARRAY_TYPE_OFFSET: i8 = 7;
773}
774
775type ChildrenArrays = (
776    [PointArray; 4],
777    [LineStringArray; 4],
778    [PolygonArray; 4],
779    [MultiPointArray; 4],
780    [MultiLineStringArray; 4],
781    [MultiPolygonArray; 4],
782    [GeometryCollectionArray; 4],
783);
784
785/// Initialize empty children with the given coord type.
786///
787/// This is used in the impls like `From<PointArray> for GeometryArray`. This lets us initialize
788/// all empty children and then just swap in the one array that's valid.
789fn empty_children(coord_type: CoordType) -> ChildrenArrays {
790    (
791        core::array::from_fn(|i| {
792            PointBuilder::new(
793                PointType::new(Dimension::from_order(i).unwrap(), Default::default())
794                    .with_coord_type(coord_type),
795            )
796            .finish()
797        }),
798        core::array::from_fn(|i| {
799            LineStringBuilder::new(
800                LineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
801                    .with_coord_type(coord_type),
802            )
803            .finish()
804        }),
805        core::array::from_fn(|i| {
806            PolygonBuilder::new(
807                PolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
808                    .with_coord_type(coord_type),
809            )
810            .finish()
811        }),
812        core::array::from_fn(|i| {
813            MultiPointBuilder::new(
814                MultiPointType::new(Dimension::from_order(i).unwrap(), Default::default())
815                    .with_coord_type(coord_type),
816            )
817            .finish()
818        }),
819        core::array::from_fn(|i| {
820            MultiLineStringBuilder::new(
821                MultiLineStringType::new(Dimension::from_order(i).unwrap(), Default::default())
822                    .with_coord_type(coord_type),
823            )
824            .finish()
825        }),
826        core::array::from_fn(|i| {
827            MultiPolygonBuilder::new(
828                MultiPolygonType::new(Dimension::from_order(i).unwrap(), Default::default())
829                    .with_coord_type(coord_type),
830            )
831            .finish()
832        }),
833        core::array::from_fn(|i| {
834            GeometryCollectionBuilder::new(
835                GeometryCollectionType::new(Dimension::from_order(i).unwrap(), Default::default())
836                    .with_coord_type(coord_type),
837            )
838            .finish()
839        }),
840    )
841}
842
843macro_rules! impl_primitive_cast {
844    ($source_array:ty, $value_edit:tt) => {
845        impl From<$source_array> for GeometryArray {
846            fn from(value: $source_array) -> Self {
847                let coord_type = value.data_type.coord_type();
848                let dim = value.data_type.dimension();
849                let metadata = value.data_type.metadata().clone();
850
851                let type_ids = vec![value.type_id(dim); value.len()].into();
852                let offsets = ScalarBuffer::from_iter(0..value.len() as i32);
853                let data_type = GeometryType::new(metadata).with_coord_type(coord_type);
854                let mut children = empty_children(coord_type);
855
856                children.$value_edit[dim.order()] = value;
857                Self {
858                    data_type,
859                    type_ids,
860                    offsets,
861                    points: children.0,
862                    line_strings: children.1,
863                    polygons: children.2,
864                    mpoints: children.3,
865                    mline_strings: children.4,
866                    mpolygons: children.5,
867                    gcs: children.6,
868                }
869            }
870        }
871    };
872}
873
874impl_primitive_cast!(PointArray, 0);
875impl_primitive_cast!(LineStringArray, 1);
876impl_primitive_cast!(PolygonArray, 2);
877impl_primitive_cast!(MultiPointArray, 3);
878impl_primitive_cast!(MultiLineStringArray, 4);
879impl_primitive_cast!(MultiPolygonArray, 5);
880impl_primitive_cast!(GeometryCollectionArray, 6);
881
882#[cfg(test)]
883mod test {
884    use geo_traits::to_geo::ToGeoGeometry;
885    use geoarrow_schema::Crs;
886    use geoarrow_test::raw;
887
888    use super::*;
889    use crate::test::{linestring, multilinestring, multipoint, multipolygon, point, polygon};
890
891    fn geoms() -> Vec<geo_types::Geometry> {
892        vec![
893            point::p0().into(),
894            point::p1().into(),
895            point::p2().into(),
896            linestring::ls0().into(),
897            linestring::ls1().into(),
898            polygon::p0().into(),
899            polygon::p1().into(),
900            multipoint::mp0().into(),
901            multipoint::mp1().into(),
902            multilinestring::ml0().into(),
903            multilinestring::ml1().into(),
904            multipolygon::mp0().into(),
905            multipolygon::mp1().into(),
906        ]
907    }
908
909    fn geom_array(coord_type: CoordType) -> GeometryArray {
910        let geoms = geoms().into_iter().map(Some).collect::<Vec<_>>();
911        let typ = GeometryType::new(Default::default()).with_coord_type(coord_type);
912        GeometryBuilder::from_nullable_geometries(&geoms, typ)
913            .unwrap()
914            .finish()
915    }
916
917    #[test]
918    fn test_2d() {
919        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
920            let geoms = geoms();
921            let geometry_array = geom_array(coord_type);
922            let geoms_again = geometry_array
923                .iter_values()
924                .map(|g| g.unwrap().to_geometry())
925                .collect::<Vec<_>>();
926            assert_eq!(geoms, geoms_again);
927        }
928    }
929
930    #[test]
931    fn test_2d_roundtrip_arrow() {
932        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
933            let geoms = geoms();
934            let geometry_array = geom_array(coord_type);
935            let field = geometry_array.data_type.to_field("geometry", true);
936            let union_array = geometry_array.into_arrow();
937
938            let geometry_array_again =
939                GeometryArray::try_from((&union_array as _, &field)).unwrap();
940            let geoms_again = geometry_array_again
941                .iter_values()
942                .map(|g| g.unwrap().to_geometry())
943                .collect::<Vec<_>>();
944            assert_eq!(geoms, geoms_again);
945        }
946    }
947
948    #[test]
949    fn try_from_arrow() {
950        for coord_type in [CoordType::Interleaved, CoordType::Separated] {
951            for prefer_multi in [true, false] {
952                let geo_arr = crate::test::geometry::array(coord_type, prefer_multi);
953
954                let point_type = geo_arr.extension_type().clone();
955                let field = point_type.to_field("geometry", true);
956
957                let arrow_arr = geo_arr.to_array_ref();
958
959                let geo_arr2: GeometryArray = (arrow_arr.as_ref(), point_type).try_into().unwrap();
960                let geo_arr3: GeometryArray = (arrow_arr.as_ref(), &field).try_into().unwrap();
961
962                assert_eq!(geo_arr, geo_arr2);
963                assert_eq!(geo_arr, geo_arr3);
964            }
965        }
966    }
967
968    #[test]
969    fn test_nullability() {
970        let geoms = raw::geometry::geoms();
971        let null_idxs = geoms
972            .iter()
973            .enumerate()
974            .filter_map(|(i, geom)| if geom.is_none() { Some(i) } else { None })
975            .collect::<Vec<_>>();
976
977        let typ = GeometryType::new(Default::default());
978        let geo_arr = GeometryBuilder::from_nullable_geometries(&geoms, typ)
979            .unwrap()
980            .finish();
981
982        for null_idx in &null_idxs {
983            assert!(geo_arr.is_null(*null_idx));
984        }
985    }
986
987    #[test]
988    fn test_logical_nulls() {
989        let geoms = raw::geometry::geoms();
990        let expected_nulls = NullBuffer::from_iter(geoms.iter().map(|g| g.is_some()));
991
992        let typ = GeometryType::new(Default::default());
993        let geo_arr = GeometryBuilder::from_nullable_geometries(&geoms, typ)
994            .unwrap()
995            .finish();
996
997        assert_eq!(geo_arr.logical_nulls().unwrap(), expected_nulls);
998    }
999
1000    #[test]
1001    fn into_coord_type() {
1002        for prefer_multi in [true, false] {
1003            let geo_arr = crate::test::geometry::array(CoordType::Interleaved, prefer_multi);
1004            let geo_arr2 = geo_arr
1005                .clone()
1006                .into_coord_type(CoordType::Separated)
1007                .into_coord_type(CoordType::Interleaved);
1008
1009            assert_eq!(geo_arr, geo_arr2);
1010        }
1011    }
1012
1013    #[test]
1014    fn partial_eq() {
1015        for prefer_multi in [true, false] {
1016            let arr1 = crate::test::geometry::array(CoordType::Interleaved, prefer_multi);
1017            let arr2 = crate::test::geometry::array(CoordType::Separated, prefer_multi);
1018
1019            assert_eq!(arr1, arr1);
1020            assert_eq!(arr2, arr2);
1021            assert_eq!(arr1, arr2);
1022
1023            assert_ne!(arr1, arr2.slice(0, 2));
1024        }
1025    }
1026
1027    #[test]
1028    fn should_persist_crs() {
1029        let geo_arr = crate::test::geometry::array(CoordType::Interleaved, false);
1030        let crs = Crs::from_authority_code("EPSG:4326".to_string());
1031        let geo_arr = geo_arr.with_metadata(Arc::new(Metadata::new(crs.clone(), None)));
1032
1033        let arrow_arr = geo_arr.to_array_ref();
1034        let field = geo_arr.data_type().to_field("geometry", true);
1035
1036        let geo_arr2: GeometryArray = (arrow_arr.as_ref(), &field).try_into().unwrap();
1037
1038        assert_eq!(geo_arr, geo_arr2);
1039        assert_eq!(geo_arr2.data_type.metadata().crs().clone(), crs);
1040    }
1041}
1042
1043// #[cfg(test)]
1044// mod test {
1045//     use super::*;
1046//     use crate::test::{linestring, multilinestring, multipoint, multipolygon, point, polygon};
1047
1048//     #[test]
1049//     fn geo_roundtrip_accurate_points() {
1050//         let geoms: Vec<geo::Geometry> = vec![
1051//             geo::Geometry::Point(point::p0()),
1052//             geo::Geometry::Point(point::p1()),
1053//             geo::Geometry::Point(point::p2()),
1054//         ];
1055
1056//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1057//             geoms.as_slice(),
1058//             CoordType::Interleaved,
1059//             Default::default(),
1060//             false,
1061//         )
1062//         .unwrap()
1063//         .finish();
1064
1065//         assert_eq!(arr.value_as_geo(0), geo::Geometry::Point(point::p0()));
1066//         assert_eq!(arr.value_as_geo(1), geo::Geometry::Point(point::p1()));
1067//         assert_eq!(arr.value_as_geo(2), geo::Geometry::Point(point::p2()));
1068//     }
1069
1070//     #[test]
1071//     fn geo_roundtrip_accurate_multi_points() {
1072//         let geoms: Vec<geo::Geometry> = vec![
1073//             geo::Geometry::Point(point::p0()),
1074//             geo::Geometry::Point(point::p1()),
1075//             geo::Geometry::Point(point::p2()),
1076//         ];
1077//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1078//             geoms.as_slice(),
1079//             CoordType::Interleaved,
1080//             Default::default(),
1081//             true,
1082//         )
1083//         .unwrap()
1084//         .finish();
1085
1086//         assert_eq!(
1087//             arr.value_as_geo(0),
1088//             geo::Geometry::MultiPoint(geo::MultiPoint(vec![point::p0()]))
1089//         );
1090//         assert_eq!(
1091//             arr.value_as_geo(1),
1092//             geo::Geometry::MultiPoint(geo::MultiPoint(vec![point::p1()]))
1093//         );
1094//         assert_eq!(
1095//             arr.value_as_geo(2),
1096//             geo::Geometry::MultiPoint(geo::MultiPoint(vec![point::p2()]))
1097//         );
1098//     }
1099
1100//     #[test]
1101//     fn geo_roundtrip_accurate_all() {
1102//         let geoms: Vec<geo::Geometry> = vec![
1103//             geo::Geometry::Point(point::p0()),
1104//             geo::Geometry::LineString(linestring::ls0()),
1105//             geo::Geometry::Polygon(polygon::p0()),
1106//             geo::Geometry::MultiPoint(multipoint::mp0()),
1107//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1108//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1109//         ];
1110
1111//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1112//             geoms.as_slice(),
1113//             CoordType::Interleaved,
1114//             Default::default(),
1115//             false,
1116//         )
1117//         .unwrap()
1118//         .finish();
1119
1120//         assert_eq!(arr.value_as_geo(0), geoms[0]);
1121//         assert_eq!(arr.value_as_geo(1), geoms[1]);
1122//         assert_eq!(arr.value_as_geo(2), geoms[2]);
1123//         assert_eq!(arr.value_as_geo(3), geoms[3]);
1124//         assert_eq!(arr.value_as_geo(4), geoms[4]);
1125//         assert_eq!(arr.value_as_geo(5), geoms[5]);
1126//     }
1127
1128//     #[test]
1129//     fn arrow_roundtrip() {
1130//         let geoms: Vec<geo::Geometry> = vec![
1131//             geo::Geometry::Point(point::p0()),
1132//             geo::Geometry::LineString(linestring::ls0()),
1133//             geo::Geometry::Polygon(polygon::p0()),
1134//             geo::Geometry::MultiPoint(multipoint::mp0()),
1135//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1136//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1137//         ];
1138
1139//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1140//             geoms.as_slice(),
1141//             CoordType::Interleaved,
1142//             Default::default(),
1143//             false,
1144//         )
1145//         .unwrap()
1146//         .finish();
1147
1148//         // Round trip to/from arrow-rs
1149//         let arrow_array = arr.into_arrow();
1150//         let round_trip_arr: GeometryArray = (&arrow_array).try_into().unwrap();
1151
1152//         assert_eq!(round_trip_arr.value_as_geo(0), geoms[0]);
1153//         assert_eq!(round_trip_arr.value_as_geo(1), geoms[1]);
1154//         assert_eq!(round_trip_arr.value_as_geo(2), geoms[2]);
1155//         assert_eq!(round_trip_arr.value_as_geo(3), geoms[3]);
1156//         assert_eq!(round_trip_arr.value_as_geo(4), geoms[4]);
1157//         assert_eq!(round_trip_arr.value_as_geo(5), geoms[5]);
1158//     }
1159
1160//     #[test]
1161//     fn arrow_roundtrip_not_all_types() {
1162//         let geoms: Vec<geo::Geometry> = vec![
1163//             geo::Geometry::MultiPoint(multipoint::mp0()),
1164//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1165//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1166//         ];
1167
1168//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1169//             geoms.as_slice(),
1170//             CoordType::Interleaved,
1171//             Default::default(),
1172//             false,
1173//         )
1174//         .unwrap()
1175//         .finish();
1176
1177//         // Round trip to/from arrow-rs
1178//         let arrow_array = arr.into_arrow();
1179//         let round_trip_arr: GeometryArray = (&arrow_array).try_into().unwrap();
1180
1181//         assert_eq!(round_trip_arr.value_as_geo(0), geoms[0]);
1182//         assert_eq!(round_trip_arr.value_as_geo(1), geoms[1]);
1183//         assert_eq!(round_trip_arr.value_as_geo(2), geoms[2]);
1184//     }
1185
1186//     #[test]
1187//     fn arrow_roundtrip_not_all_types2() {
1188//         let geoms: Vec<geo::Geometry> = vec![
1189//             geo::Geometry::MultiPoint(multipoint::mp0()),
1190//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1191//         ];
1192
1193//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1194//             geoms.as_slice(),
1195//             CoordType::Interleaved,
1196//             Default::default(),
1197//             false,
1198//         )
1199//         .unwrap()
1200//         .finish();
1201
1202//         // Round trip to/from arrow-rs
1203//         let arrow_array = arr.into_arrow();
1204//         let round_trip_arr: GeometryArray = (&arrow_array).try_into().unwrap();
1205
1206//         assert_eq!(round_trip_arr.value_as_geo(0), geoms[0]);
1207//         assert_eq!(round_trip_arr.value_as_geo(1), geoms[1]);
1208//     }
1209
1210//     #[test]
1211//     fn test_slicing() {
1212//         let geoms: Vec<geo::Geometry> = vec![
1213//             geo::Geometry::Point(point::p0()),
1214//             geo::Geometry::LineString(linestring::ls0()),
1215//             geo::Geometry::Polygon(polygon::p0()),
1216//             geo::Geometry::MultiPoint(multipoint::mp0()),
1217//             geo::Geometry::MultiLineString(multilinestring::ml0()),
1218//             geo::Geometry::MultiPolygon(multipolygon::mp0()),
1219//         ];
1220
1221//         let arr: GeometryArray = GeometryBuilder::from_geometries(
1222//             geoms.as_slice(),
1223//             CoordType::Interleaved,
1224//             Default::default(),
1225//             false,
1226//         )
1227//         .unwrap()
1228//         .finish();
1229
1230//         assert_eq!(arr.slice(1, 2).value_as_geo(0), geoms[1]);
1231//         assert_eq!(arr.slice(1, 2).value_as_geo(1), geoms[2]);
1232//         assert_eq!(arr.slice(3, 3).value_as_geo(2), geoms[5]);
1233//     }
1234// }