use geo::Distance;
use geo::Euclidean;
use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::arrays::ScalarFnArray;
use vortex_array::dtype::DType;
use vortex_array::dtype::Nullability;
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_session::VortexSession;
use vortex_session::registry::CachedId;
use crate::extension::validate_geometry_operands;
use crate::scalar_fn::execute::execute_null_propagating;
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct GeoDistance;
impl GeoDistance {
pub fn try_new_array(a: ArrayRef, b: ArrayRef) -> VortexResult<ScalarFnArray> {
ScalarFnArray::try_new(
TypedScalarFnInstance::new(GeoDistance, EmptyOptions).erased(),
vec![a, b],
)
}
}
impl ScalarFnVTable for GeoDistance {
type Options = EmptyOptions;
fn id(&self) -> ScalarFnId {
static ID: CachedId = CachedId::new("vortex.geo.distance");
*ID
}
fn serialize(&self, _: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
Ok(Some(vec![]))
}
fn deserialize(&self, _: &[u8], _: &VortexSession) -> VortexResult<Self::Options> {
Ok(EmptyOptions)
}
fn arity(&self, _: &Self::Options) -> Arity {
Arity::Exact(2)
}
fn child_name(&self, _: &Self::Options, child_idx: usize) -> ChildName {
match child_idx {
0 => ChildName::from("a"),
1 => ChildName::from("b"),
_ => unreachable!("distance has exactly two children"),
}
}
fn return_dtype(&self, _: &Self::Options, dtypes: &[DType]) -> VortexResult<DType> {
validate_geometry_operands(dtypes)?;
let nullability = Nullability::from(dtypes.iter().any(DType::is_nullable));
Ok(DType::Primitive(PType::F64, nullability))
}
fn execute(
&self,
_: &Self::Options,
args: &dyn ExecutionArgs,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let a = args.get(0)?;
let b = args.get(1)?;
execute_null_propagating(&a, &b, |x, y| Euclidean.distance(x, y), ctx)
}
fn validity(
&self,
_: &Self::Options,
expression: &Expression,
) -> VortexResult<Option<Expression>> {
union_child_validities(expression)
}
fn is_null_sensitive(&self, _: &Self::Options) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use vortex_array::ArrayRef;
use vortex_array::Canonical;
use vortex_array::ExecutionCtx;
use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
use vortex_array::arrays::ConstantArray;
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::Scalar;
use vortex_array::scalar_fn::EmptyOptions;
use vortex_array::scalar_fn::ScalarFnVTable;
use vortex_array::validity::Validity;
use vortex_error::VortexResult;
use super::GeoDistance;
use crate::test_harness::nullable_point_column;
use crate::test_harness::point_column;
fn point_constant(
x: f64,
y: f64,
len: usize,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let single = point_column(vec![x], vec![y])?.execute_scalar(0, ctx)?;
Ok(ConstantArray::new(single, len).into_array())
}
fn distances(distance: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Vec<f64>> {
Ok(distance
.execute::<Canonical>(ctx)?
.into_primitive()
.as_slice::<f64>()
.to_vec())
}
#[test]
fn distance_over_points() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = point_column(vec![0.0, 3.0, 0.0, 3.0], vec![0.0, 0.0, 4.0, 4.0])?;
let b = point_constant(0.0, 0.0, 4, &mut ctx)?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
assert_eq!(distances(distance, &mut ctx)?, vec![0.0, 3.0, 4.0, 5.0]);
Ok(())
}
#[test]
fn distance_between_columns() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = point_column(vec![0.0, 1.0], vec![0.0, 1.0])?;
let b = point_column(vec![3.0, 1.0], vec![4.0, 1.0])?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
assert_eq!(distances(distance, &mut ctx)?, vec![5.0, 0.0]);
Ok(())
}
#[test]
fn distance_with_constant_first_operand() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = point_constant(0.0, 0.0, 4, &mut ctx)?;
let b = point_column(vec![0.0, 3.0, 0.0, 3.0], vec![0.0, 0.0, 4.0, 4.0])?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
assert_eq!(distances(distance, &mut ctx)?, vec![0.0, 3.0, 4.0, 5.0]);
Ok(())
}
#[test]
fn distance_between_two_constants() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = point_constant(0.0, 0.0, 3, &mut ctx)?;
let b = point_constant(3.0, 4.0, 3, &mut ctx)?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
assert_eq!(distances(distance, &mut ctx)?, vec![5.0, 5.0, 5.0]);
Ok(())
}
#[test]
fn output_nullability_mirrors_operands() -> VortexResult<()> {
let dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone();
let non_nullable =
GeoDistance.return_dtype(&EmptyOptions, &[dtype.clone(), dtype.clone()])?;
assert!(!non_nullable.is_nullable());
let nullable = GeoDistance.return_dtype(&EmptyOptions, &[dtype.as_nullable(), dtype])?;
assert!(nullable.is_nullable());
Ok(())
}
#[test]
fn distance_propagates_null_rows() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = nullable_point_column(vec![Some((0.0, 0.0)), None, Some((3.0, 4.0))])?;
let b = point_constant(0.0, 0.0, 3, &mut ctx)?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
let expected = PrimitiveArray::new(
vec![0.0f64, 0.0, 5.0],
Validity::from_iter([true, false, true]),
)
.into_array();
assert_arrays_eq!(distance, expected, &mut ctx);
Ok(())
}
#[test]
fn distance_propagates_column_pair_nulls() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = nullable_point_column(vec![Some((0.0, 0.0)), None, Some((0.0, 0.0))])?;
let b = nullable_point_column(vec![Some((3.0, 4.0)), Some((1.0, 1.0)), None])?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
let expected = PrimitiveArray::new(
vec![5.0f64, 0.0, 0.0],
Validity::from_iter([true, false, false]),
)
.into_array();
assert_arrays_eq!(distance, expected, &mut ctx);
Ok(())
}
#[test]
fn distance_constant_null_is_all_null() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().as_nullable();
let null_const = ConstantArray::new(Scalar::null(point_dtype), 3).into_array();
let b = point_column(vec![0.0, 3.0, 0.0], vec![0.0, 0.0, 4.0])?;
let distance = GeoDistance::try_new_array(null_const, b)?.into_array();
let expected = PrimitiveArray::new(vec![0.0f64; 3], Validity::AllInvalid).into_array();
assert_arrays_eq!(distance, expected, &mut ctx);
Ok(())
}
#[test]
fn distance_all_null_column_is_all_null() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = nullable_point_column(vec![None, None])?;
let b = point_constant(0.0, 0.0, 2, &mut ctx)?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
let expected = PrimitiveArray::new(vec![0.0f64; 2], Validity::AllInvalid).into_array();
assert_arrays_eq!(distance, expected, &mut ctx);
Ok(())
}
#[test]
fn distance_column_pair_all_null() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = nullable_point_column(vec![Some((0.0, 0.0)), None])?;
let b = nullable_point_column(vec![None, Some((1.0, 1.0))])?;
let distance = GeoDistance::try_new_array(a, b)?.into_array();
let expected = PrimitiveArray::new(vec![0.0f64; 2], Validity::AllInvalid).into_array();
assert_arrays_eq!(distance, expected, &mut ctx);
Ok(())
}
#[test]
fn distance_empty_non_nullable_keeps_dtype() -> VortexResult<()> {
let session = vortex_array::array_session();
let mut ctx = session.create_execution_ctx();
let a = point_column(vec![], vec![])?;
let b = point_column(vec![], vec![])?;
let result = GeoDistance::try_new_array(a, b)?
.into_array()
.execute::<Canonical>(&mut ctx)?
.into_array();
assert!(!result.dtype().is_nullable());
assert_eq!(result.len(), 0);
Ok(())
}
#[test]
fn non_geometry_operand_is_rejected() -> VortexResult<()> {
let geo = point_column(vec![0.0], vec![0.0])?.dtype().clone();
let numeric = DType::Primitive(PType::I32, Nullability::NonNullable);
let result = GeoDistance.return_dtype(&EmptyOptions, &[geo, numeric]);
assert!(result.is_err());
Ok(())
}
}