1use std::sync::Arc;
2
3use arrow_array::cast::AsArray;
4use arrow_array::{Array, ArrayRef, GenericListArray, OffsetSizeTrait};
5use arrow_buffer::{NullBuffer, OffsetBuffer};
6use arrow_schema::{DataType, Field};
7use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
8use geoarrow_schema::{CoordType, GeoArrowType, GeometryCollectionType, Metadata};
9
10use crate::array::{GenericWkbArray, MixedGeometryArray};
11use crate::builder::GeometryCollectionBuilder;
12use crate::capacity::GeometryCollectionCapacity;
13use crate::eq::offset_buffer_eq;
14use crate::scalar::GeometryCollection;
15use crate::trait_::{GeoArrowArray, GeoArrowArrayAccessor, IntoArrow};
16use crate::util::{OffsetBufferUtils, offsets_buffer_i64_to_i32};
17
18#[derive(Debug, Clone)]
23pub struct GeometryCollectionArray {
24 pub(crate) data_type: GeometryCollectionType,
25
26 pub(crate) array: MixedGeometryArray,
27
28 pub(crate) geom_offsets: OffsetBuffer<i32>,
30
31 pub(crate) nulls: Option<NullBuffer>,
33}
34
35impl GeometryCollectionArray {
36 pub fn new(
42 array: MixedGeometryArray,
43 geom_offsets: OffsetBuffer<i32>,
44 nulls: Option<NullBuffer>,
45 metadata: Arc<Metadata>,
46 ) -> Self {
47 Self {
48 data_type: GeometryCollectionType::new(array.dim, metadata)
49 .with_coord_type(array.coord_type),
50 array,
51 geom_offsets,
52 nulls,
53 }
54 }
55
56 fn geometries_field(&self) -> Arc<Field> {
57 Field::new("geometries", self.array.storage_type(), false).into()
58 }
59
60 pub fn buffer_lengths(&self) -> GeometryCollectionCapacity {
62 GeometryCollectionCapacity::new(
63 self.array.buffer_lengths(),
64 *self.geom_offsets.last() as usize,
65 )
66 }
67
68 pub fn num_bytes(&self) -> usize {
70 let validity_len = self.nulls.as_ref().map(|v| v.buffer().len()).unwrap_or(0);
71 validity_len + self.buffer_lengths().num_bytes(self.data_type.dimension())
72 }
73
74 #[inline]
83 pub fn slice(&self, offset: usize, length: usize) -> Self {
84 assert!(
85 offset + length <= self.len(),
86 "offset + length may not exceed length of array"
87 );
88 Self {
90 data_type: self.data_type.clone(),
91 array: self.array.clone(),
92 geom_offsets: self.geom_offsets.slice(offset, length),
93 nulls: self.nulls.as_ref().map(|v| v.slice(offset, length)),
94 }
95 }
96
97 pub fn into_coord_type(self, coord_type: CoordType) -> Self {
99 Self {
100 data_type: self.data_type.with_coord_type(coord_type),
101 array: self.array.into_coord_type(coord_type),
102 ..self
103 }
104 }
105
106 pub fn with_metadata(self, metadata: Arc<Metadata>) -> Self {
108 Self {
109 data_type: self.data_type.with_metadata(metadata),
110 ..self
111 }
112 }
113}
114
115impl GeoArrowArray for GeometryCollectionArray {
116 fn as_any(&self) -> &dyn std::any::Any {
117 self
118 }
119
120 fn into_array_ref(self) -> ArrayRef {
121 Arc::new(self.into_arrow())
122 }
123
124 fn to_array_ref(&self) -> ArrayRef {
125 self.clone().into_array_ref()
126 }
127
128 #[inline]
129 fn len(&self) -> usize {
130 self.geom_offsets.len_proxy()
131 }
132
133 #[inline]
134 fn logical_nulls(&self) -> Option<NullBuffer> {
135 self.nulls.clone()
136 }
137
138 #[inline]
139 fn logical_null_count(&self) -> usize {
140 self.nulls.as_ref().map(|v| v.null_count()).unwrap_or(0)
141 }
142
143 #[inline]
144 fn is_null(&self, i: usize) -> bool {
145 self.nulls
146 .as_ref()
147 .map(|n| n.is_null(i))
148 .unwrap_or_default()
149 }
150
151 fn data_type(&self) -> GeoArrowType {
152 GeoArrowType::GeometryCollection(self.data_type.clone())
153 }
154
155 fn slice(&self, offset: usize, length: usize) -> Arc<dyn GeoArrowArray> {
156 Arc::new(self.slice(offset, length))
157 }
158
159 fn with_metadata(self, metadata: Arc<Metadata>) -> Arc<dyn GeoArrowArray> {
160 Arc::new(self.with_metadata(metadata))
161 }
162}
163
164impl<'a> GeoArrowArrayAccessor<'a> for GeometryCollectionArray {
165 type Item = GeometryCollection<'a>;
166
167 unsafe fn value_unchecked(&'a self, index: usize) -> GeoArrowResult<Self::Item> {
168 Ok(GeometryCollection::new(
169 &self.array,
170 &self.geom_offsets,
171 index,
172 ))
173 }
174}
175
176impl IntoArrow for GeometryCollectionArray {
177 type ArrowArray = GenericListArray<i32>;
178 type ExtensionType = GeometryCollectionType;
179
180 fn into_arrow(self) -> Self::ArrowArray {
181 let geometries_field = self.geometries_field();
182 let nulls = self.nulls;
183 let values = self.array.into_array_ref();
184 GenericListArray::new(geometries_field, self.geom_offsets, values, nulls)
185 }
186
187 fn extension_type(&self) -> &Self::ExtensionType {
188 &self.data_type
189 }
190}
191
192impl TryFrom<(&GenericListArray<i32>, GeometryCollectionType)> for GeometryCollectionArray {
193 type Error = GeoArrowError;
194
195 fn try_from(
196 (value, typ): (&GenericListArray<i32>, GeometryCollectionType),
197 ) -> GeoArrowResult<Self> {
198 let geoms: MixedGeometryArray =
199 (value.values().as_ref(), typ.dimension(), typ.coord_type()).try_into()?;
200 let geom_offsets = value.offsets();
201 let nulls = value.nulls();
202
203 Ok(Self::new(
204 geoms,
205 geom_offsets.clone(),
206 nulls.cloned(),
207 typ.metadata().clone(),
208 ))
209 }
210}
211
212impl TryFrom<(&GenericListArray<i64>, GeometryCollectionType)> for GeometryCollectionArray {
213 type Error = GeoArrowError;
214
215 fn try_from(
216 (value, typ): (&GenericListArray<i64>, GeometryCollectionType),
217 ) -> GeoArrowResult<Self> {
218 let geoms: MixedGeometryArray =
219 (value.values().as_ref(), typ.dimension(), typ.coord_type()).try_into()?;
220 let geom_offsets = offsets_buffer_i64_to_i32(value.offsets())?;
221 let nulls = value.nulls();
222
223 Ok(Self::new(
224 geoms,
225 geom_offsets,
226 nulls.cloned(),
227 typ.metadata().clone(),
228 ))
229 }
230}
231
232impl TryFrom<(&dyn Array, GeometryCollectionType)> for GeometryCollectionArray {
233 type Error = GeoArrowError;
234
235 fn try_from((value, typ): (&dyn Array, GeometryCollectionType)) -> GeoArrowResult<Self> {
236 match value.data_type() {
237 DataType::List(_) => (value.as_list::<i32>(), typ).try_into(),
238 DataType::LargeList(_) => (value.as_list::<i64>(), typ).try_into(),
239 dt => Err(GeoArrowError::InvalidGeoArrow(format!(
240 "Unexpected GeometryCollection Arrow DataType: {dt:?}"
241 ))),
242 }
243 }
244}
245
246impl TryFrom<(&dyn Array, &Field)> for GeometryCollectionArray {
247 type Error = GeoArrowError;
248
249 fn try_from((arr, field): (&dyn Array, &Field)) -> GeoArrowResult<Self> {
250 let typ = field.try_extension_type::<GeometryCollectionType>()?;
251 (arr, typ).try_into()
252 }
253}
254
255impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, GeometryCollectionType)>
256 for GeometryCollectionArray
257{
258 type Error = GeoArrowError;
259
260 fn try_from(value: (GenericWkbArray<O>, GeometryCollectionType)) -> GeoArrowResult<Self> {
261 let mut_arr: GeometryCollectionBuilder = value.try_into()?;
262 Ok(mut_arr.finish())
263 }
264}
265
266impl PartialEq for GeometryCollectionArray {
267 fn eq(&self, other: &Self) -> bool {
268 self.nulls == other.nulls
269 && offset_buffer_eq(&self.geom_offsets, &other.geom_offsets)
270 && self.array == other.array
271 }
272}
273
274#[cfg(test)]
275mod test {
276 use geoarrow_schema::{CoordType, Dimension};
277 use geoarrow_test::raw;
278
279 use super::*;
280 use crate::test::geometrycollection;
281
282 #[test]
283 fn try_from_arrow() {
284 for coord_type in [CoordType::Interleaved, CoordType::Separated] {
285 for dim in [
286 Dimension::XY,
287 Dimension::XYZ,
288 Dimension::XYM,
289 Dimension::XYZM,
290 ] {
291 for prefer_multi in [true, false] {
292 let geo_arr = geometrycollection::array(coord_type, dim, prefer_multi);
293
294 let point_type = geo_arr.extension_type().clone();
295 let field = point_type.to_field("geometry", true);
296
297 let arrow_arr = geo_arr.to_array_ref();
298
299 let geo_arr2: GeometryCollectionArray =
300 (arrow_arr.as_ref(), point_type).try_into().unwrap();
301 let geo_arr3: GeometryCollectionArray =
302 (arrow_arr.as_ref(), &field).try_into().unwrap();
303
304 assert_eq!(geo_arr, geo_arr2);
305 assert_eq!(geo_arr, geo_arr3);
306 }
307 }
308 }
309 }
310
311 #[test]
312 fn test_nullability() {
313 let geoms = raw::geometrycollection::xy::geoms();
314 let null_idxs = geoms
315 .iter()
316 .enumerate()
317 .filter_map(|(i, geom)| if geom.is_none() { Some(i) } else { None })
318 .collect::<Vec<_>>();
319
320 let typ = GeometryCollectionType::new(Dimension::XY, Default::default());
321 let geo_arr = GeometryCollectionBuilder::from_nullable_geometry_collections(&geoms, typ)
322 .unwrap()
323 .finish();
324
325 for null_idx in &null_idxs {
326 assert!(geo_arr.is_null(*null_idx));
327 }
328 }
329
330 #[test]
331 fn test_logical_nulls() {
332 let geoms = raw::geometrycollection::xy::geoms();
333 let expected_nulls = NullBuffer::from_iter(geoms.iter().map(|g| g.is_some()));
334
335 let typ = GeometryCollectionType::new(Dimension::XY, Default::default());
336 let geo_arr = GeometryCollectionBuilder::from_nullable_geometry_collections(&geoms, typ)
337 .unwrap()
338 .finish();
339
340 assert_eq!(geo_arr.logical_nulls().unwrap(), expected_nulls);
341 }
342}