From 3db4ec46d4f08f79b5d89fff63ed765c6c2d8e2e Mon Sep 17 00:00:00 2001 From: Nemo Yu Date: Wed, 5 Aug 2026 12:18:26 -0400 Subject: [PATCH 1/5] feat(vortex-geo): add area scalar function Signed-off-by: Nemo Yu --- vortex-spatial/src/lib.rs | 2 + vortex-spatial/src/scalar_fn/area.rs | 252 +++++++++++++++++++++++++++ vortex-spatial/src/scalar_fn/mod.rs | 1 + 3 files changed, 255 insertions(+) create mode 100644 vortex-spatial/src/scalar_fn/area.rs diff --git a/vortex-spatial/src/lib.rs b/vortex-spatial/src/lib.rs index 318e6736e8f..6bf96831c48 100644 --- a/vortex-spatial/src/lib.rs +++ b/vortex-spatial/src/lib.rs @@ -21,6 +21,7 @@ use crate::extension::Rect; use crate::extension::WellKnownBinary; use crate::prune::SpatialDistancePrune; use crate::prune::SpatialIntersectsPrune; +use crate::scalar_fn::area::SpatialArea; use crate::scalar_fn::contains::SpatialContains; use crate::scalar_fn::distance::SpatialDistance; use crate::scalar_fn::envelope::SpatialEnvelope; @@ -65,6 +66,7 @@ pub fn initialize(session: &VortexSession) { session.arrow().register_importer(Arc::new(Rect)); // Register the geometry scalar functions. + session.scalar_fns().register(SpatialArea); session.scalar_fns().register(SpatialEnvelope); session.scalar_fns().register(SpatialContains); session.scalar_fns().register(SpatialDistance); diff --git a/vortex-spatial/src/scalar_fn/area.rs b/vortex-spatial/src/scalar_fn/area.rs new file mode 100644 index 00000000000..fba85ea224b --- /dev/null +++ b/vortex-spatial/src/scalar_fn/area.rs @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! `ST_Area`: unsigned planar area of native geometries. + +use geo::Area; +use vortex_array::ArrayRef; +use vortex_array::ExecutionCtx; +use vortex_array::arrays::ScalarFnArray; +use vortex_array::dtype::DType; +use vortex_array::dtype::PType; +use vortex_array::expr::Expression; +use vortex_array::expr::union_child_validities; +use vortex_array::scalar_fn::Arity; +use vortex_array::scalar_fn::ChildName; +use vortex_array::scalar_fn::EmptyOptions; +use vortex_array::scalar_fn::ExecutionArgs; +use vortex_array::scalar_fn::ScalarFnId; +use vortex_array::scalar_fn::ScalarFnVTable; +use vortex_array::scalar_fn::TypedScalarFnInstance; +use vortex_error::VortexResult; +use vortex_error::vortex_ensure; +use vortex_session::VortexSession; +use vortex_session::registry::CachedId; + +use crate::extension::is_native_geometry; +use crate::scalar_fn::execute::execute_unary_geo_types; + +/// Validate the native geometry operand accepted by `ST_Area`. +fn validate_area_operand(dtypes: &[DType]) -> VortexResult<()> { + vortex_ensure!( + dtypes.len() == 1, + "spatial: area requires exactly one geometry operand, got {}", + dtypes.len() + ); + vortex_ensure!( + is_native_geometry(&dtypes[0]), + "spatial: area operand {} is not a native geometry", + dtypes[0] + ); + Ok(()) +} + +/// Unsigned planar `ST_Area` of native geometries. +/// +/// Points and line strings have zero area, polygons and multipolygons use their two-dimensional +/// coordinates, and rectangles use width times height. Higher coordinate dimensions are ignored. +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] +pub struct SpatialArea; + +impl SpatialArea { + /// A lazy `ScalarFnArray` computing the per-row area of a native geometry operand. + pub fn try_new_array(array: ArrayRef) -> VortexResult { + ScalarFnArray::try_new( + TypedScalarFnInstance::new(SpatialArea, EmptyOptions).erased(), + vec![array], + ) + } +} + +impl ScalarFnVTable for SpatialArea { + type Options = EmptyOptions; + + fn id(&self) -> ScalarFnId { + static ID: CachedId = CachedId::new("vortex.st.area"); + *ID + } + + fn serialize(&self, _: &Self::Options) -> VortexResult>> { + Ok(Some(vec![])) + } + + fn deserialize(&self, _: &[u8], _: &VortexSession) -> VortexResult { + Ok(EmptyOptions) + } + + fn arity(&self, _: &Self::Options) -> Arity { + Arity::Exact(1) + } + + fn child_name(&self, _: &Self::Options, child_idx: usize) -> ChildName { + match child_idx { + 0 => ChildName::from("geometry"), + _ => unreachable!("area has exactly one child"), + } + } + + fn return_dtype(&self, _: &Self::Options, dtypes: &[DType]) -> VortexResult { + validate_area_operand(dtypes)?; + Ok(DType::Primitive(PType::F64, dtypes[0].nullability())) + } + + fn execute( + &self, + _: &Self::Options, + args: &dyn ExecutionArgs, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + let array = args.get(0)?; + execute_unary_geo_types(&array, Area::unsigned_area, ctx) + } + + fn validity( + &self, + _: &Self::Options, + expression: &Expression, + ) -> VortexResult> { + union_child_validities(expression) + } + + fn is_strict(&self, _: &Self::Options) -> bool { + true + } + + fn is_fallible(&self, _: &Self::Options) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + use vortex_array::Canonical; + use vortex_array::IntoArray; + use vortex_array::VortexSessionExecute; + use vortex_array::arrays::PrimitiveArray; + use vortex_array::assert_arrays_eq; + use vortex_array::dtype::DType; + use vortex_array::dtype::Nullability; + use vortex_array::dtype::PType; + use vortex_array::scalar_fn::EmptyOptions; + use vortex_array::scalar_fn::ScalarFnVTable; + use vortex_array::validity::Validity; + use vortex_error::VortexResult; + + use super::SpatialArea; + use crate::test_harness::linestring_column; + use crate::test_harness::multilinestring_column; + use crate::test_harness::multipoint_column; + use crate::test_harness::multipolygon_column; + use crate::test_harness::nullable_multipolygon_column; + use crate::test_harness::point_column; + use crate::test_harness::polygon_column; + use crate::test_harness::rect_column; + + #[test] + fn measures_non_polygon_native_geometries() -> VortexResult<()> { + let session = vortex_array::array_session(); + let mut ctx = session.create_execution_ctx(); + let cases = vec![ + (point_column(vec![1.0], vec![2.0])?, 0.0), + (linestring_column(vec![vec![(0.0, 0.0), (3.0, 4.0)]])?, 0.0), + (multipoint_column(vec![vec![(0.0, 0.0), (1.0, 1.0)]])?, 0.0), + ( + multilinestring_column(vec![vec![ + vec![(0.0, 0.0), (1.0, 1.0)], + vec![(2.0, 2.0), (3.0, 3.0)], + ]])?, + 0.0, + ), + (rect_column(vec![(0.0, 0.0, 5.0, 3.0)])?, 15.0), + ]; + + for (geometry, expected) in cases { + let areas = SpatialArea::try_new_array(geometry)? + .into_array() + .execute::(&mut ctx)? + .into_primitive(); + assert_eq!(areas.as_slice::(), &[expected]); + } + Ok(()) + } + + #[test] + fn measures_polygons_with_holes_and_empty_polygons() -> VortexResult<()> { + let session = vortex_array::array_session(); + let mut ctx = session.create_execution_ctx(); + let polygons = polygon_column(vec![ + vec![ + vec![(0.0, 0.0), (4.0, 0.0), (4.0, 3.0), (0.0, 3.0), (0.0, 0.0)], + vec![(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)], + ], + vec![], + ])?; + + let areas = SpatialArea::try_new_array(polygons)? + .into_array() + .execute::(&mut ctx)? + .into_primitive(); + + assert_eq!(areas.as_slice::(), &[11.0, 0.0]); + Ok(()) + } + + #[test] + fn measures_multipolygons() -> VortexResult<()> { + let session = vortex_array::array_session(); + let mut ctx = session.create_execution_ctx(); + let multipolygons = multipolygon_column(vec![vec![ + vec![vec![ + (0.0, 0.0), + (2.0, 0.0), + (2.0, 2.0), + (0.0, 2.0), + (0.0, 0.0), + ]], + vec![vec![ + (3.0, 0.0), + (6.0, 0.0), + (6.0, 3.0), + (3.0, 3.0), + (3.0, 0.0), + ]], + ]])?; + + let areas = SpatialArea::try_new_array(multipolygons)? + .into_array() + .execute::(&mut ctx)? + .into_primitive(); + + assert_eq!(areas.as_slice::(), &[13.0]); + Ok(()) + } + + #[test] + fn propagates_nulls() -> VortexResult<()> { + let session = vortex_array::array_session(); + let mut ctx = session.create_execution_ctx(); + let multipolygons = nullable_multipolygon_column(vec![ + Some(vec![vec![vec![ + (0.0, 0.0), + (2.0, 0.0), + (2.0, 2.0), + (0.0, 2.0), + (0.0, 0.0), + ]]]), + None, + ])?; + let areas = SpatialArea::try_new_array(multipolygons)?.into_array(); + let expected = + PrimitiveArray::new(vec![4.0f64, 0.0], Validity::from_iter([true, false])).into_array(); + + assert_arrays_eq!(areas, expected, &mut ctx); + Ok(()) + } + + #[test] + fn rejects_non_geometry_dtype() -> VortexResult<()> { + let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); + assert!(SpatialArea.return_dtype(&EmptyOptions, &[primitive]).is_err()); + Ok(()) + } +} diff --git a/vortex-spatial/src/scalar_fn/mod.rs b/vortex-spatial/src/scalar_fn/mod.rs index fda570a2c1e..1dcff7d0b95 100644 --- a/vortex-spatial/src/scalar_fn/mod.rs +++ b/vortex-spatial/src/scalar_fn/mod.rs @@ -3,6 +3,7 @@ //! Geometry scalar functions over the native geometry extension types. +pub mod area; pub mod contains; pub mod distance; pub mod envelope; From a16be72ba59074c689ac949f7ce40f9e70040770 Mon Sep 17 00:00:00 2001 From: Nemo Yu Date: Wed, 5 Aug 2026 12:27:51 -0400 Subject: [PATCH 2/5] fix(vortex-geo): validate scalar function operands Signed-off-by: Nemo Yu --- vortex-spatial/src/scalar_fn/area.rs | 110 +++++++++++++-------------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/vortex-spatial/src/scalar_fn/area.rs b/vortex-spatial/src/scalar_fn/area.rs index fba85ea224b..08aee43519b 100644 --- a/vortex-spatial/src/scalar_fn/area.rs +++ b/vortex-spatial/src/scalar_fn/area.rs @@ -119,7 +119,8 @@ impl ScalarFnVTable for SpatialArea { #[cfg(test)] mod tests { - use vortex_array::Canonical; + use rstest::rstest; + use vortex_array::ArrayRef; use vortex_array::IntoArray; use vortex_array::VortexSessionExecute; use vortex_array::arrays::PrimitiveArray; @@ -142,60 +143,35 @@ mod tests { use crate::test_harness::polygon_column; use crate::test_harness::rect_column; - #[test] - fn measures_non_polygon_native_geometries() -> VortexResult<()> { - let session = vortex_array::array_session(); - let mut ctx = session.create_execution_ctx(); - let cases = vec![ - (point_column(vec![1.0], vec![2.0])?, 0.0), - (linestring_column(vec![vec![(0.0, 0.0), (3.0, 4.0)]])?, 0.0), - (multipoint_column(vec![vec![(0.0, 0.0), (1.0, 1.0)]])?, 0.0), - ( - multilinestring_column(vec![vec![ - vec![(0.0, 0.0), (1.0, 1.0)], - vec![(2.0, 2.0), (3.0, 3.0)], - ]])?, - 0.0, - ), - (rect_column(vec![(0.0, 0.0, 5.0, 3.0)])?, 15.0), - ]; - - for (geometry, expected) in cases { - let areas = SpatialArea::try_new_array(geometry)? - .into_array() - .execute::(&mut ctx)? - .into_primitive(); - assert_eq!(areas.as_slice::(), &[expected]); - } - Ok(()) - } - - #[test] - fn measures_polygons_with_holes_and_empty_polygons() -> VortexResult<()> { - let session = vortex_array::array_session(); - let mut ctx = session.create_execution_ctx(); - let polygons = polygon_column(vec![ + #[rstest] + #[case::point(point_column(vec![1.0], vec![2.0]), &[0.0])] + #[case::line_string( + linestring_column(vec![vec![(0.0, 0.0), (3.0, 4.0)]]), + &[0.0] + )] + #[case::multi_point( + multipoint_column(vec![vec![(0.0, 0.0), (1.0, 1.0)]]), + &[0.0] + )] + #[case::multi_line_string( + multilinestring_column(vec![vec![ + vec![(0.0, 0.0), (1.0, 1.0)], + vec![(2.0, 2.0), (3.0, 3.0)], + ]]), + &[0.0] + )] + #[case::polygon( + polygon_column(vec![ vec![ vec![(0.0, 0.0), (4.0, 0.0), (4.0, 3.0), (0.0, 3.0), (0.0, 0.0)], vec![(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)], ], vec![], - ])?; - - let areas = SpatialArea::try_new_array(polygons)? - .into_array() - .execute::(&mut ctx)? - .into_primitive(); - - assert_eq!(areas.as_slice::(), &[11.0, 0.0]); - Ok(()) - } - - #[test] - fn measures_multipolygons() -> VortexResult<()> { - let session = vortex_array::array_session(); - let mut ctx = session.create_execution_ctx(); - let multipolygons = multipolygon_column(vec![vec![ + ]), + &[11.0, 0.0] + )] + #[case::multi_polygon( + multipolygon_column(vec![vec![ vec![vec![ (0.0, 0.0), (2.0, 0.0), @@ -210,14 +186,19 @@ mod tests { (3.0, 3.0), (3.0, 0.0), ]], - ]])?; - - let areas = SpatialArea::try_new_array(multipolygons)? - .into_array() - .execute::(&mut ctx)? - .into_primitive(); - - assert_eq!(areas.as_slice::(), &[13.0]); + ]]), + &[13.0] + )] + #[case::rect(rect_column(vec![(0.0, 0.0, 5.0, 3.0)]), &[15.0])] + fn measures_native_geometries( + #[case] geometry: VortexResult, + #[case] expected: &[f64], + ) -> VortexResult<()> { + let session = vortex_array::array_session(); + let mut ctx = session.create_execution_ctx(); + let areas = SpatialArea::try_new_array(geometry?)?.into_array(); + let expected = PrimitiveArray::from_iter(expected.iter().copied()).into_array(); + assert_arrays_eq!(areas, expected, &mut ctx); Ok(()) } @@ -243,6 +224,19 @@ mod tests { Ok(()) } + #[rstest] + #[case::none(0)] + #[case::two(2)] + fn rejects_wrong_arity(#[case] arity: usize) -> VortexResult<()> { + let dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); + assert!( + SpatialArea + .return_dtype(&EmptyOptions, &vec![dtype; arity]) + .is_err() + ); + Ok(()) + } + #[test] fn rejects_non_geometry_dtype() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); From 1583e14e3b9dc79d93191d78ccbf217f8b9307b2 Mon Sep 17 00:00:00 2001 From: Nemo Yu Date: Wed, 5 Aug 2026 16:11:28 -0400 Subject: [PATCH 3/5] bench(vortex-geo): cover area Signed-off-by: Nemo Yu --- vortex-spatial/Cargo.toml | 3 + vortex-spatial/benches/area.rs | 136 +++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 vortex-spatial/benches/area.rs diff --git a/vortex-spatial/Cargo.toml b/vortex-spatial/Cargo.toml index a6c9cbfbfc3..8d2b8cfe509 100644 --- a/vortex-spatial/Cargo.toml +++ b/vortex-spatial/Cargo.toml @@ -62,5 +62,8 @@ harness = false name = "make_line" harness = false +[[bench]] +name = "area" +harness = false [lints] workspace = true diff --git a/vortex-spatial/benches/area.rs b/vortex-spatial/benches/area.rs new file mode 100644 index 00000000000..7b09c61ff81 --- /dev/null +++ b/vortex-spatial/benches/area.rs @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Microbenchmarks for native `ST_Area` over polygons and multipolygons. +//! +//! The cases separate the costs of vertex traversal, interior rings, nested polygons, and strict +//! null propagation. They execute through the scalar function and materialize the `f64` result. +//! +//! Run with `cargo bench -p vortex-spatial --bench area`. + +#![expect(clippy::unwrap_used)] + +use std::sync::LazyLock; + +use divan::Bencher; +use divan::counter::ItemsCount; +use mimalloc::MiMalloc; +use vortex_array::ArrayRef; +use vortex_array::Canonical; +use vortex_array::ExecutionCtx; +use vortex_array::IntoArray; +use vortex_array::VortexSessionExecute; +use vortex_array::arrays::MaskedArray; +use vortex_array::validity::Validity; +use vortex_session::VortexSession; +use vortex_spatial::scalar_fn::area::SpatialArea; +use vortex_spatial::test_harness::multipolygon_column; +use vortex_spatial::test_harness::polygon_column; +use vortex_spatial::test_harness::spatial_session; + +// Scalar function execution allocates its output inside the timed region, so use the vendored +// allocator instead of measuring glibc differences between CodSpeed runner images. +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +static SESSION: LazyLock = LazyLock::new(spatial_session); + +const ROWS: usize = 512; + +fn main() { + divan::main(); +} + +/// A closed square ring centered at `(cx, cy)`. +fn square(cx: f64, cy: f64, radius: f64) -> Vec<(f64, f64)> { + vec![ + (cx - radius, cy - radius), + (cx + radius, cy - radius), + (cx + radius, cy + radius), + (cx - radius, cy + radius), + (cx - radius, cy - radius), + ] +} + +fn simple_polygons() -> ArrayRef { + polygon_column( + (0..ROWS) + .map(|row| vec![square(row as f64, row as f64, 10.0)]) + .collect(), + ) + .unwrap() +} + +fn polygons_with_holes() -> ArrayRef { + polygon_column( + (0..ROWS) + .map(|row| { + let center = row as f64; + vec![ + square(center, center, 10.0), + square(center - 4.0, center, 1.0), + square(center + 4.0, center, 1.0), + ] + }) + .collect(), + ) + .unwrap() +} + +fn multipolygons() -> ArrayRef { + multipolygon_column( + (0..ROWS) + .map(|row| { + let center = row as f64; + vec![ + vec![square(center - 12.0, center, 5.0)], + vec![square(center, center, 5.0)], + vec![square(center + 12.0, center, 5.0)], + ] + }) + .collect(), + ) + .unwrap() +} + +fn areas(geometry: &ArrayRef, ctx: &mut ExecutionCtx) -> ArrayRef { + SpatialArea::try_new_array(geometry.clone()) + .unwrap() + .into_array() + .execute::(ctx) + .unwrap() + .into_array() +} + +fn bench_area(bencher: Bencher, geometry: ArrayRef) { + let mut ctx = SESSION.create_execution_ctx(); + bencher + .counter(ItemsCount::new(ROWS)) + .bench_local(|| areas(&geometry, &mut ctx)); +} + +#[divan::bench] +fn simple_polygon(bencher: Bencher) { + bench_area(bencher, simple_polygons()); +} + +#[divan::bench] +fn polygon_with_holes(bencher: Bencher) { + bench_area(bencher, polygons_with_holes()); +} + +#[divan::bench] +fn multipolygon(bencher: Bencher) { + bench_area(bencher, multipolygons()); +} + +#[divan::bench] +fn nullable_polygon(bencher: Bencher) { + let geometry = MaskedArray::try_new( + simple_polygons(), + Validity::from_iter((0..ROWS).map(|row| !row.is_multiple_of(8))), + ) + .unwrap() + .into_array(); + bench_area(bencher, geometry); +} From 7980cb409b26ab1570e3fb24dc5b968cd96c97c5 Mon Sep 17 00:00:00 2001 From: Nemo Yu Date: Fri, 7 Aug 2026 14:02:19 -0400 Subject: [PATCH 4/5] style(vortex-spatial): format area test Signed-off-by: Nemo Yu --- vortex-spatial/src/scalar_fn/area.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vortex-spatial/src/scalar_fn/area.rs b/vortex-spatial/src/scalar_fn/area.rs index 08aee43519b..e1ea91db06a 100644 --- a/vortex-spatial/src/scalar_fn/area.rs +++ b/vortex-spatial/src/scalar_fn/area.rs @@ -240,7 +240,11 @@ mod tests { #[test] fn rejects_non_geometry_dtype() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(SpatialArea.return_dtype(&EmptyOptions, &[primitive]).is_err()); + assert!( + SpatialArea + .return_dtype(&EmptyOptions, &[primitive]) + .is_err() + ); Ok(()) } } From 2980eb40bc45f9c83218f8aac4628078fafe29b1 Mon Sep 17 00:00:00 2001 From: Nemo Yu Date: Fri, 7 Aug 2026 15:30:51 -0400 Subject: [PATCH 5/5] fix(vortex-spatial): restore unary geo adapter Signed-off-by: Nemo Yu --- vortex-spatial/src/scalar_fn/execute.rs | 8 ++-- vortex-spatial/src/scalar_fn/execute/unary.rs | 42 ++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/vortex-spatial/src/scalar_fn/execute.rs b/vortex-spatial/src/scalar_fn/execute.rs index c83e03474b1..3a7494bcb39 100644 --- a/vortex-spatial/src/scalar_fn/execute.rs +++ b/vortex-spatial/src/scalar_fn/execute.rs @@ -7,9 +7,10 @@ //! propagation without prescribing how a kernel represents geometries or builds its output. //! Native columnar kernels such as `ST_MakeLine` use these dispatchers directly. //! -//! [`execute_binary_geo_types`] is a convenience adapter for row-oriented algorithms from the -//! `geo` ecosystem. It decodes valid inputs into `geo_types::Geometry`; the final output is still -//! a Vortex [`ArrayRef`], such as an `f64` or boolean array. +//! [`execute_unary_geo_types`] and [`execute_binary_geo_types`] are convenience adapters for +//! row-oriented algorithms from the `geo` ecosystem. They decode valid inputs into +//! `geo_types::Geometry`; the final output is still a Vortex [`ArrayRef`], such as an `f64` or +//! boolean array. mod binary; mod geo_types; @@ -18,6 +19,7 @@ mod unary; pub(crate) use binary::dispatch_binary; pub(crate) use binary::execute_binary_geo_types; pub(crate) use unary::dispatch_unary; +pub(crate) use unary::execute_unary_geo_types; use vortex_array::ArrayRef; use vortex_array::dtype::Nullability; use vortex_array::scalar::Scalar; diff --git a/vortex-spatial/src/scalar_fn/execute/unary.rs b/vortex-spatial/src/scalar_fn/execute/unary.rs index bdbbd0b33ac..a8bb74f850c 100644 --- a/vortex-spatial/src/scalar_fn/execute/unary.rs +++ b/vortex-spatial/src/scalar_fn/execute/unary.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Unary operand dispatch for native geometry kernels. +//! Unary operand dispatch, plus an adapter for row-oriented `geo_types` kernels. +use geo_types::Geometry; use vortex_array::ArrayRef; use vortex_array::ExecutionCtx; use vortex_array::IntoArray; @@ -15,6 +16,9 @@ use vortex_error::VortexResult; use super::Execution; use super::Operand; +use super::geo_types::GeoTypesOutput; +use super::geo_types::eval_column; +use crate::extension::single_geometry; /// Dispatch a unary strict geometry kernel over a constant or column. /// @@ -60,3 +64,39 @@ where ctx, ) } + +/// Run a unary row-oriented kernel whose input is decoded to `geo_types::Geometry`. +/// +/// The `geo_types` name describes the value passed to `compute`, not the output. `T` is converted +/// into a Vortex array before this function returns. A constant is decoded and computed once +/// before broadcast; a column is decoded only for its valid rows. +pub(crate) fn execute_unary_geo_types( + array: &ArrayRef, + compute: F, + ctx: &mut ExecutionCtx, +) -> VortexResult +where + T: GeoTypesOutput, + F: Fn(&Geometry) -> T, +{ + let nullability = array.dtype().nullability(); + dispatch_unary( + array, + T::dtype(nullability), + |execution, ctx| match execution.operands { + [Operand::Constant(scalar)] => { + let geometry = single_geometry(&scalar, ctx)?; + Ok(ConstantArray::new( + compute(&geometry).into_scalar(execution.nullability), + execution.len, + ) + .into_array()) + } + [Operand::Column(array)] => { + let valid = execution.valid.execute_mask(execution.len, ctx)?; + eval_column(&array, &valid, compute, execution.nullability, ctx) + } + }, + ctx, + ) +}