Skip to content

Commit 7c58436

Browse files
authored
feat(geoarrow-array): Implement child array access from GeometryArray (#1373)
Closes #1324
1 parent 0c1d120 commit 7c58436

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed

rust/geoarrow-array/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- New `GeozeroRecordBatchWriter` to allow for an iterative push-based API for writing to geozero-based data sinks.
88
- perf(geoarrow-array): Improve perf of from_wkb/from_wkt when parsing to WKB/WKT output types https://github.com/geoarrow/geoarrow-rs/pull/1313
9+
- feat(geoarrow-array): Implement child array access from GeometryArray #1373
910

1011
## 0.5.0 - 2025-08-07
1112

rust/geoarrow-array/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ wkt = { workspace = true }
3333
geo = { workspace = true }
3434
geo-types = { workspace = true }
3535
geoarrow-test = { workspace = true }
36+
wkt = { workspace = true }
3637

3738
[package.metadata.docs.rs]
3839
all-features = true

rust/geoarrow-array/src/array/geometry.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,50 @@ use crate::capacity::GeometryCapacity;
1818
use crate::scalar::Geometry;
1919
use crate::trait_::{GeoArrowArray, GeoArrowArrayAccessor, IntoArrow};
2020

21+
/// Macro to implement child array accessor with proper slicing support
22+
///
23+
/// This macro generates code to access a child array from a GeometryArray,
24+
/// handling both sliced and non-sliced cases.
25+
///
26+
/// # Arguments
27+
/// * `$geom_arr` - The child geometry array
28+
///
29+
/// # Returns
30+
/// A cloned or sliced version of the child array
31+
macro_rules! impl_child_accessor {
32+
($self:expr, $geom_arr:expr) => {{
33+
let geom_arr = $geom_arr;
34+
if !$self.is_sliced() {
35+
// Fast path: if not sliced, just clone the array
36+
geom_arr.clone()
37+
} else {
38+
// Slow path: find the range of this geometry type in the sliced view
39+
let target_type_id = geom_arr.geometry_type_id();
40+
let first_index = $self.type_ids.iter().position(|id| *id == target_type_id);
41+
let last_index = $self.type_ids.iter().rposition(|id| *id == target_type_id);
42+
43+
match (first_index, last_index) {
44+
(Some(first), Some(last)) => {
45+
// Found both first and last occurrence
46+
let first_offset = $self.offsets[first] as usize;
47+
let last_offset = $self.offsets[last] as usize;
48+
geom_arr.slice(first_offset, last_offset - first_offset + 1)
49+
}
50+
(Some(first), None) => {
51+
unreachable!("Shouldn't happen: found first offset but not last: {first}");
52+
}
53+
(None, Some(last)) => {
54+
unreachable!("Shouldn't happen: found last offset but not first: {last}");
55+
}
56+
(None, None) => {
57+
// This geometry type is not present in the sliced view
58+
geom_arr.slice(0, 0)
59+
}
60+
}
61+
}
62+
}};
63+
}
64+
2165
/// An immutable array of geometries of unknown geometry type and dimension.
2266
///
2367
// # Invariants
@@ -160,6 +204,84 @@ impl GeometryArray {
160204
&self.offsets
161205
}
162206

207+
/// Determine whether this array has been sliced.
208+
///
209+
/// This array has been sliced iff the total number of geometries in the child arrays does not
210+
/// equal the number of values in the type_ids array.
211+
///
212+
/// Since the length of each child array is pre-computed, this operation is O(1).
213+
fn is_sliced(&self) -> bool {
214+
let mut physical_geom_len = 0;
215+
physical_geom_len += self.points.iter().fold(0, |acc, arr| acc + arr.len());
216+
physical_geom_len += self.line_strings.iter().fold(0, |acc, arr| acc + arr.len());
217+
physical_geom_len += self.polygons.iter().fold(0, |acc, arr| acc + arr.len());
218+
physical_geom_len += self.mpoints.iter().fold(0, |acc, arr| acc + arr.len());
219+
physical_geom_len += self
220+
.mline_strings
221+
.iter()
222+
.fold(0, |acc, arr| acc + arr.len());
223+
physical_geom_len += self.mpolygons.iter().fold(0, |acc, arr| acc + arr.len());
224+
physical_geom_len += self.gcs.iter().fold(0, |acc, arr| acc + arr.len());
225+
226+
physical_geom_len != self.type_ids.len()
227+
}
228+
229+
/// Access the PointArray child for the given dimension.
230+
///
231+
/// Note that ordering will be maintained within the child array, but there may have been other
232+
/// geometries in between in the parent array.
233+
pub fn point_child(&self, dim: Dimension) -> PointArray {
234+
impl_child_accessor!(self, &self.points[dim.order()])
235+
}
236+
237+
/// Access the LineStringArray child for the given dimension.
238+
///
239+
/// Note that ordering will be maintained within the child array, but there may have been other
240+
/// geometries in between in the parent array.
241+
pub fn line_string_child(&self, dim: Dimension) -> LineStringArray {
242+
impl_child_accessor!(self, &self.line_strings[dim.order()])
243+
}
244+
245+
/// Access the PolygonArray child for the given dimension.
246+
///
247+
/// Note that ordering will be maintained within the child array, but there may have been other
248+
/// geometries in between in the parent array.
249+
pub fn polygon_child(&self, dim: Dimension) -> PolygonArray {
250+
impl_child_accessor!(self, &self.polygons[dim.order()])
251+
}
252+
253+
/// Access the MultiPointArray child for the given dimension.
254+
///
255+
/// Note that ordering will be maintained within the child array, but there may have been other
256+
/// geometries in between in the parent array.
257+
pub fn multi_point_child(&self, dim: Dimension) -> MultiPointArray {
258+
impl_child_accessor!(self, &self.mpoints[dim.order()])
259+
}
260+
261+
/// Access the MultiLineStringArray child for the given dimension.
262+
///
263+
/// Note that ordering will be maintained within the child array, but there may have been other
264+
/// geometries in between in the parent array.
265+
pub fn multi_line_string_child(&self, dim: Dimension) -> MultiLineStringArray {
266+
impl_child_accessor!(self, &self.mline_strings[dim.order()])
267+
}
268+
269+
/// Access the MultiPolygonArray child for the given dimension.
270+
///
271+
/// Note that ordering will be maintained within the child array, but there may have been other
272+
/// geometries in between in the parent array.
273+
pub fn multi_polygon_child(&self, dim: Dimension) -> MultiPolygonArray {
274+
impl_child_accessor!(self, &self.mpolygons[dim.order()])
275+
}
276+
277+
/// Access the GeometryCollectionArray child for the given dimension.
278+
///
279+
/// Note that ordering will be maintained within the child array, but there may have been other
280+
/// geometries in between in the parent array.
281+
pub fn geometry_collection_child(&self, dim: Dimension) -> GeometryCollectionArray {
282+
impl_child_accessor!(self, &self.gcs[dim.order()])
283+
}
284+
163285
// TODO: handle slicing
164286
pub(crate) fn has_points(&self, dim: Dimension) -> bool {
165287
!self.points[dim.order()].is_empty()
@@ -872,6 +994,7 @@ impl_primitive_cast!(GeometryCollectionArray, 6);
872994

873995
#[cfg(test)]
874996
mod test {
997+
use ::wkt::{Wkt, wkt};
875998
use geo_traits::to_geo::ToGeoGeometry;
876999
use geoarrow_schema::Crs;
8771000
use geoarrow_test::raw;
@@ -1042,4 +1165,92 @@ mod test {
10421165
assert_eq!(sliced, geo_arr2);
10431166
assert_eq!(sliced.value(0).unwrap(), geo_arr2.value(0).unwrap());
10441167
}
1168+
1169+
#[test]
1170+
fn determine_if_sliced() {
1171+
let geo_arr = crate::test::geometry::array(CoordType::Separated, false);
1172+
assert!(!geo_arr.is_sliced());
1173+
1174+
let sliced = geo_arr.slice(2, 4);
1175+
assert!(sliced.is_sliced());
1176+
}
1177+
1178+
#[test]
1179+
fn test_point_child_via_slicing() {
1180+
let point_array = crate::test::point::array(Default::default(), Dimension::XY);
1181+
let geometry_array = GeometryArray::from(point_array.clone());
1182+
1183+
let returned = geometry_array.point_child(Dimension::XY);
1184+
assert_eq!(returned, point_array);
1185+
1186+
// Sliced at beginning
1187+
let sliced_geometry_array = geometry_array.slice(0, 2);
1188+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1189+
assert_eq!(point_child, point_array.slice(0, 2));
1190+
1191+
// Sliced in middle
1192+
let sliced_geometry_array = geometry_array.slice(1, 2);
1193+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1194+
assert_eq!(point_child, point_array.slice(1, 2));
1195+
1196+
// Sliced at end
1197+
let sliced_geometry_array = geometry_array.slice(2, 2);
1198+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1199+
assert_eq!(point_child, point_array.slice(2, 2));
1200+
}
1201+
1202+
#[test]
1203+
fn test_point_child_mixed_geometries() {
1204+
let geoms: Vec<Option<Wkt>> = vec![
1205+
// 2D points
1206+
Some(wkt! { POINT (30. 10.) }.into()),
1207+
Some(wkt! { POINT (40. 20.) }.into()),
1208+
// 3D points
1209+
Some(wkt! { POINT Z (30. 10. 40.) }.into()),
1210+
Some(wkt! { POINT Z (40. 20. 60.) }.into()),
1211+
// More 2D points
1212+
Some(wkt! { POINT (30. 10.) }.into()),
1213+
Some(wkt! { POINT (40. 20.) }.into()),
1214+
];
1215+
1216+
let mut full_xy_point_arr =
1217+
PointBuilder::new(PointType::new(Dimension::XY, Default::default()));
1218+
for idx in [0, 1, 4, 5] {
1219+
full_xy_point_arr
1220+
.push_geometry(geoms[idx].as_ref())
1221+
.unwrap();
1222+
}
1223+
let full_xy_point_arr = full_xy_point_arr.finish();
1224+
1225+
let geometry_array = GeometryBuilder::from_nullable_geometries(&geoms, Default::default())
1226+
.unwrap()
1227+
.finish();
1228+
1229+
let returned = geometry_array.point_child(Dimension::XY);
1230+
assert_eq!(returned, full_xy_point_arr);
1231+
1232+
// Sliced at beginning
1233+
let sliced_geometry_array = geometry_array.slice(0, 2);
1234+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1235+
assert_eq!(point_child, full_xy_point_arr.slice(0, 2));
1236+
1237+
// Sliced in middle
1238+
let sliced_geometry_array = geometry_array.slice(1, 2);
1239+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1240+
assert_eq!(point_child, full_xy_point_arr.slice(1, 1));
1241+
1242+
// Sliced in middle, removing all 2D points
1243+
let sliced_geometry_array = geometry_array.slice(2, 2);
1244+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1245+
assert_eq!(point_child, full_xy_point_arr.slice(1, 0));
1246+
1247+
let sliced_geometry_array = geometry_array.slice(3, 2);
1248+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1249+
assert_eq!(point_child, full_xy_point_arr.slice(2, 1));
1250+
1251+
// Sliced at end
1252+
let sliced_geometry_array = geometry_array.slice(4, 2);
1253+
let point_child = sliced_geometry_array.point_child(Dimension::XY);
1254+
assert_eq!(point_child, full_xy_point_arr.slice(2, 2));
1255+
}
10451256
}

0 commit comments

Comments
 (0)