use geo_types::Geometry;
use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::IntoArray;
use vortex_array::arrays::BoolArray;
use vortex_array::arrays::Constant;
use vortex_array::arrays::ConstantArray;
use vortex_array::arrays::PrimitiveArray;
use vortex_array::dtype::Nullability;
use vortex_array::scalar::Scalar;
use vortex_array::validity::Validity;
use vortex_buffer::BitBuffer;
use vortex_error::VortexResult;
use vortex_error::vortex_ensure_eq;
use vortex_mask::AllOr;
use vortex_mask::Mask;
use crate::extension::geometries;
use crate::extension::single_geometry;
pub(crate) trait GeoOutput: Copy {
fn into_scalar(self, nullability: Nullability) -> Scalar;
fn build_array(
len: usize,
valid: &Mask,
values: Vec<Self>,
nullability: Nullability,
) -> ArrayRef;
}
impl GeoOutput for f64 {
fn into_scalar(self, nullability: Nullability) -> Scalar {
Scalar::primitive(self, nullability)
}
fn build_array(
len: usize,
valid: &Mask,
values: Vec<Self>,
nullability: Nullability,
) -> ArrayRef {
let validity = Validity::from_mask(valid.clone(), nullability);
match valid.indices() {
AllOr::All => PrimitiveArray::new(values, validity).into_array(),
AllOr::None => PrimitiveArray::new(vec![0.0f64; len], validity).into_array(),
AllOr::Some(rows) => {
let mut data = vec![0.0f64; len];
for (&row, value) in rows.iter().zip(values) {
data[row] = value;
}
PrimitiveArray::new(data, validity).into_array()
}
}
}
}
impl GeoOutput for bool {
fn into_scalar(self, nullability: Nullability) -> Scalar {
Scalar::bool(self, nullability)
}
fn build_array(
len: usize,
valid: &Mask,
values: Vec<Self>,
nullability: Nullability,
) -> ArrayRef {
let validity = Validity::from_mask(valid.clone(), nullability);
match valid.indices() {
AllOr::All => BoolArray::new(BitBuffer::from_iter(values), validity).into_array(),
AllOr::None => BoolArray::new(BitBuffer::new_unset(len), validity).into_array(),
AllOr::Some(rows) => {
let mut data = vec![false; len];
for (&row, value) in rows.iter().zip(values) {
data[row] = value;
}
BoolArray::new(BitBuffer::from_iter(data), validity).into_array()
}
}
}
}
pub(crate) fn execute_null_propagating<T, F>(
a: &ArrayRef,
b: &ArrayRef,
compute: F,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef>
where
T: GeoOutput,
F: Fn(&Geometry<f64>, &Geometry<f64>) -> T + Copy,
{
let len = a.len();
let nullability = Nullability::from(a.dtype().is_nullable() || b.dtype().is_nullable());
for operand in [a, b] {
if operand
.as_opt::<Constant>()
.is_some_and(|c| c.scalar().is_null())
{
return Ok(T::build_array(
len,
&Mask::new_false(len),
vec![],
nullability,
));
}
}
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)?;
Ok(ConstantArray::new(compute(&ga, &gb).into_scalar(nullability), len).into_array())
}
(Some(qa), None) => {
let ga = single_geometry(qa.scalar(), ctx)?;
eval_column(b, |g| compute(&ga, g), nullability, ctx)
}
(None, Some(qb)) => {
let gb = single_geometry(qb.scalar(), ctx)?;
eval_column(a, |g| compute(g, &gb), nullability, ctx)
}
(None, None) => {
vortex_ensure_eq!(
a.len(),
b.len(),
"geo binary: operand length mismatch {} vs {}",
a.len(),
b.len()
);
eval_column_pair(a, b, compute, nullability, ctx)
}
}
}
fn eval_column<T, F>(
column: &ArrayRef,
f: F,
nullability: Nullability,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef>
where
T: GeoOutput,
F: Fn(&Geometry<f64>) -> T,
{
let len = column.len();
let valid = column.validity()?.execute_mask(len, ctx)?;
let decoded = geometries(&column.filter(valid.clone())?, ctx)?;
let values = decoded.iter().map(f).collect();
Ok(T::build_array(len, &valid, values, nullability))
}
fn eval_column_pair<T, F>(
a: &ArrayRef,
b: &ArrayRef,
compute: F,
nullability: Nullability,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef>
where
T: GeoOutput,
F: Fn(&Geometry<f64>, &Geometry<f64>) -> T,
{
let len = a.len();
let a_present = a.validity()?.execute_mask(len, ctx)?;
let b_present = b.validity()?.execute_mask(len, ctx)?;
let valid = &a_present & &b_present;
let ag = geometries(&a.filter(valid.clone())?, ctx)?;
let bg = geometries(&b.filter(valid.clone())?, ctx)?;
let values = ag.iter().zip(&bg).map(|(x, y)| compute(x, y)).collect();
Ok(T::build_array(len, &valid, values, nullability))
}