use geo::Distance;
use geo::Euclidean;
use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::IntoArray;
use vortex_array::arrays::Constant;
use vortex_array::arrays::ConstantArray;
use vortex_array::arrays::ExtensionArray;
use vortex_array::arrays::PrimitiveArray;
use vortex_array::arrays::ScalarFnArray;
use vortex_array::arrays::StructArray;
use vortex_array::arrays::extension::ExtensionArrayExt;
use vortex_array::arrays::struct_::StructArrayExt;
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::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::Point;
use crate::extension::coordinate::coordinate_from_struct;
use crate::extension::geometries;
use crate::extension::single_geometry;
#[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, _: &[DType]) -> VortexResult<DType> {
Ok(DType::Primitive(PType::F64, Nullability::NonNullable))
}
fn execute(
&self,
_: &Self::Options,
args: &dyn ExecutionArgs,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let a = args.get(0)?;
let b = args.get(1)?;
match (a.as_opt::<Constant>(), b.as_opt::<Constant>()) {
(Some(qa), Some(qb)) => {
let ga = single_geometry(qa.scalar(), ctx)?;
let gb = single_geometry(qb.scalar(), ctx)?;
let distance = Euclidean.distance(&ga, &gb);
Ok(ConstantArray::new(
Scalar::primitive(distance, Nullability::NonNullable),
a.len(),
)
.into_array())
}
(Some(query), None) => distances_to_constant(&b, query.scalar(), ctx),
(None, Some(query)) => distances_to_constant(&a, query.scalar(), ctx),
(None, None) => {
vortex_ensure!(
a.len() == b.len(),
"geo distance: operand length mismatch {} vs {}",
a.len(),
b.len()
);
if is_nonnull_point(a.dtype()) && is_nonnull_point(b.dtype()) {
let (xa, ya) = point_xy(&a, ctx)?;
let (xb, yb) = point_xy(&b, ctx)?;
return Ok(point_distances(
xa.as_slice::<f64>().iter().copied(),
ya.as_slice::<f64>().iter().copied(),
xb.as_slice::<f64>().iter().copied(),
yb.as_slice::<f64>().iter().copied(),
));
}
let ag = geometries(&a, ctx)?;
let bg = geometries(&b, ctx)?;
let distances = ag.iter().zip(&bg).map(|(x, y)| Euclidean.distance(x, y));
Ok(PrimitiveArray::from_iter(distances).into_array())
}
}
}
}
fn distances_to_constant(
operand: &ArrayRef,
query: &Scalar,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
if is_nonnull_point(operand.dtype()) && is_point(query.dtype()) {
let q = coordinate_from_struct(&query.as_extension().to_storage_scalar())?;
let (xs, ys) = point_xy(operand, ctx)?;
return Ok(point_distances(
xs.as_slice::<f64>().iter().copied(),
ys.as_slice::<f64>().iter().copied(),
std::iter::repeat(q.x),
std::iter::repeat(q.y),
));
}
let query = single_geometry(query, ctx)?;
let geoms = geometries(operand, ctx)?;
let distances = geoms.iter().map(|g| Euclidean.distance(g, &query));
Ok(PrimitiveArray::from_iter(distances).into_array())
}
fn point_xy(
operand: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(PrimitiveArray, PrimitiveArray)> {
let storage = operand
.clone()
.execute::<ExtensionArray>(ctx)?
.storage_array()
.clone()
.execute::<StructArray>(ctx)?;
let xs = storage
.unmasked_field_by_name("x")?
.clone()
.execute::<PrimitiveArray>(ctx)?;
let ys = storage
.unmasked_field_by_name("y")?
.clone()
.execute::<PrimitiveArray>(ctx)?;
Ok((xs, ys))
}
fn point_distances(
xa: impl Iterator<Item = f64>,
ya: impl Iterator<Item = f64>,
xb: impl Iterator<Item = f64>,
yb: impl Iterator<Item = f64>,
) -> ArrayRef {
let distances = xa.zip(ya).zip(xb.zip(yb)).map(|((xa, ya), (xb, yb))| {
let (dx, dy) = (xa - xb, ya - yb);
(dx * dx + dy * dy).sqrt()
});
PrimitiveArray::from_iter(distances).into_array()
}
fn is_point(dtype: &DType) -> bool {
dtype
.as_extension_opt()
.is_some_and(|ext| ext.is::<Point>())
}
fn is_nonnull_point(dtype: &DType) -> bool {
is_point(dtype) && !dtype.is_nullable()
}
#[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_error::VortexResult;
use super::GeoDistance;
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(())
}
}