1use std::sync::Arc;
2
3use arrow_array::cast::AsArray;
4use arrow_array::types::Float64Type;
5use arrow_array::{Array, ArrayRef, StructArray};
6use arrow_buffer::NullBuffer;
7use arrow_schema::{DataType, Field};
8use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
9use geoarrow_schema::{BoxType, GeoArrowType, Metadata};
10
11use crate::array::SeparatedCoordBuffer;
12use crate::scalar::Rect;
13use crate::trait_::{GeoArrowArray, GeoArrowArrayAccessor, IntoArrow};
14
15#[derive(Debug, Clone)]
28pub struct RectArray {
29 pub(crate) data_type: BoxType,
30
31 lower: SeparatedCoordBuffer,
33
34 upper: SeparatedCoordBuffer,
36
37 nulls: Option<NullBuffer>,
38}
39
40impl RectArray {
41 pub fn new(
43 lower: SeparatedCoordBuffer,
44 upper: SeparatedCoordBuffer,
45 nulls: Option<NullBuffer>,
46 metadata: Arc<Metadata>,
47 ) -> Self {
48 assert_eq!(lower.dim(), upper.dim());
49 Self {
50 data_type: BoxType::new(lower.dim(), metadata),
51 lower,
52 upper,
53 nulls,
54 }
55 }
56
57 pub fn lower(&self) -> &SeparatedCoordBuffer {
62 &self.lower
63 }
64
65 pub fn upper(&self) -> &SeparatedCoordBuffer {
70 &self.upper
71 }
72
73 #[inline]
78 pub fn slice(&self, offset: usize, length: usize) -> Self {
79 assert!(
80 offset + length <= self.len(),
81 "offset + length may not exceed length of array"
82 );
83
84 Self {
85 data_type: self.data_type.clone(),
86 lower: self.lower().slice(offset, length),
87 upper: self.upper().slice(offset, length),
88 nulls: self.nulls.as_ref().map(|v| v.slice(offset, length)),
89 }
90 }
91
92 pub fn with_metadata(self, metadata: Arc<Metadata>) -> Self {
94 Self {
95 data_type: self.data_type.with_metadata(metadata),
96 ..self
97 }
98 }
99}
100
101impl GeoArrowArray for RectArray {
102 fn as_any(&self) -> &dyn std::any::Any {
103 self
104 }
105
106 fn into_array_ref(self) -> ArrayRef {
107 Arc::new(self.into_arrow())
108 }
109
110 fn to_array_ref(&self) -> ArrayRef {
111 self.clone().into_array_ref()
112 }
113
114 #[inline]
115 fn len(&self) -> usize {
116 self.lower.len()
117 }
118
119 #[inline]
120 fn logical_nulls(&self) -> Option<NullBuffer> {
121 self.nulls.clone()
122 }
123
124 #[inline]
125 fn logical_null_count(&self) -> usize {
126 self.nulls.as_ref().map(|v| v.null_count()).unwrap_or(0)
127 }
128
129 #[inline]
130 fn is_null(&self, i: usize) -> bool {
131 self.nulls
132 .as_ref()
133 .map(|n| n.is_null(i))
134 .unwrap_or_default()
135 }
136
137 fn data_type(&self) -> GeoArrowType {
138 GeoArrowType::Rect(self.data_type.clone())
139 }
140
141 fn slice(&self, offset: usize, length: usize) -> Arc<dyn GeoArrowArray> {
142 Arc::new(self.slice(offset, length))
143 }
144
145 fn with_metadata(self, metadata: Arc<Metadata>) -> Arc<dyn GeoArrowArray> {
146 Arc::new(self.with_metadata(metadata))
147 }
148}
149
150impl<'a> GeoArrowArrayAccessor<'a> for RectArray {
151 type Item = Rect<'a>;
152
153 unsafe fn value_unchecked(&'a self, index: usize) -> GeoArrowResult<Self::Item> {
154 Ok(Rect::new(&self.lower, &self.upper, index))
155 }
156}
157
158impl IntoArrow for RectArray {
159 type ArrowArray = StructArray;
160 type ExtensionType = BoxType;
161
162 fn into_arrow(self) -> Self::ArrowArray {
163 let fields = match self.data_type.data_type() {
164 DataType::Struct(fields) => fields,
165 _ => unreachable!(),
166 };
167
168 let mut arrays: Vec<ArrayRef> = vec![];
169
170 arrays.extend_from_slice(self.lower.values_array().as_slice());
172 arrays.extend_from_slice(self.upper.values_array().as_slice());
173
174 let nulls = self.nulls;
175 StructArray::new(fields, arrays, nulls)
176 }
177
178 fn extension_type(&self) -> &Self::ExtensionType {
179 &self.data_type
180 }
181}
182
183impl TryFrom<(&StructArray, BoxType)> for RectArray {
184 type Error = GeoArrowError;
185
186 fn try_from((value, typ): (&StructArray, BoxType)) -> GeoArrowResult<Self> {
187 let dim = typ.dimension();
188 let nulls = value.nulls();
189 let columns = value.columns();
190 if columns.len() != dim.size() * 2 {
191 return Err(GeoArrowError::InvalidGeoArrow(format!(
192 "Invalid number of columns for RectArray: expected {} but got {}",
193 dim.size() * 2,
194 columns.len()
195 )));
196 }
197
198 let lower = columns[0..dim.size()]
199 .iter()
200 .map(|c| c.as_primitive::<Float64Type>().values().clone())
201 .collect::<Vec<_>>();
202 let lower = SeparatedCoordBuffer::from_vec(lower, dim)?;
203
204 let upper = columns[dim.size()..]
205 .iter()
206 .map(|c| c.as_primitive::<Float64Type>().values().clone())
207 .collect::<Vec<_>>();
208 let upper = SeparatedCoordBuffer::from_vec(upper, dim)?;
209
210 Ok(Self::new(
211 lower,
212 upper,
213 nulls.cloned(),
214 typ.metadata().clone(),
215 ))
216 }
217}
218
219impl TryFrom<(&dyn Array, BoxType)> for RectArray {
220 type Error = GeoArrowError;
221
222 fn try_from((value, dim): (&dyn Array, BoxType)) -> GeoArrowResult<Self> {
223 match value.data_type() {
224 DataType::Struct(_) => (value.as_struct(), dim).try_into(),
225 dt => Err(GeoArrowError::InvalidGeoArrow(format!(
226 "Unexpected Rect DataType: {:?}",
227 dt
228 ))),
229 }
230 }
231}
232
233impl TryFrom<(&dyn Array, &Field)> for RectArray {
234 type Error = GeoArrowError;
235
236 fn try_from((arr, field): (&dyn Array, &Field)) -> GeoArrowResult<Self> {
237 let typ = field.try_extension_type::<BoxType>()?;
238 (arr, typ).try_into()
239 }
240}
241
242impl PartialEq for RectArray {
243 fn eq(&self, other: &Self) -> bool {
244 self.clone().into_arrow() == other.clone().into_arrow()
249 }
250}
251
252#[cfg(test)]
253mod test {
254 use geo_traits::to_geo::ToGeoRect;
255 use geoarrow_schema::Dimension;
256
257 use super::*;
258 use crate::builder::RectBuilder;
259 use crate::test::rect;
260
261 #[test]
262 fn geo_round_trip() {
263 let geoms = [Some(rect::r0()), None, Some(rect::r1()), None];
264 let typ = BoxType::new(Dimension::XY, Default::default());
265 let geo_arr =
266 RectBuilder::from_nullable_rects(geoms.iter().map(|x| x.as_ref()), typ).finish();
267
268 for (i, g) in geo_arr.iter().enumerate() {
269 assert_eq!(geoms[i], g.transpose().unwrap().map(|g| g.to_rect()));
270 }
271
272 for (i, g) in geo_arr.slice(2, 2).iter().enumerate() {
274 assert_eq!(geoms[i + 2], g.transpose().unwrap().map(|g| g.to_rect()));
275 }
276 }
277
278 #[test]
279 fn try_from_arrow() {
280 let geo_arr = rect::r_array();
281
282 let extension_type = geo_arr.extension_type().clone();
283 let field = extension_type.to_field("geometry", true);
284
285 let arrow_arr = geo_arr.to_array_ref();
286
287 let geo_arr2: RectArray = (arrow_arr.as_ref(), extension_type).try_into().unwrap();
288 let geo_arr3: RectArray = (arrow_arr.as_ref(), &field).try_into().unwrap();
289
290 assert_eq!(geo_arr, geo_arr2);
291 assert_eq!(geo_arr, geo_arr3);
292 }
293
294 #[test]
295 fn partial_eq() {
296 let arr1 = rect::r_array();
297 assert_eq!(arr1, arr1);
298 }
299}