Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions vortex-bench/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,7 @@ fn geoparquet_columns(metadata: &ParquetMetaData) -> HashSet<String> {

/// The erased `vortex.st.wkb` extension dtype over a binary `storage` dtype.
fn wkb_ext_dtype(storage: &DType) -> VortexResult<ExtDTypeRef> {
Ok(
ExtDType::<WellKnownBinary>::try_new(SpatialMetadata { crs: None }, storage.clone())?
.erased(),
)
Ok(ExtDType::<WellKnownBinary>::try_new(SpatialMetadata::default(), storage.clone())?.erased())
}

/// Re-type the named binary columns of a struct `dtype` as `vortex.st.wkb`, so the column
Expand Down
6 changes: 5 additions & 1 deletion vortex-duckdb/src/convert/dtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ impl FromLogicalType for DType {
let crs = logical_type.geometry_crs().map(|crs| crs.to_string());
DType::Extension(
ExtDType::<WellKnownBinary>::try_new(
SpatialMetadata { crs },
SpatialMetadata {
crs,
..Default::default()
},
Comment on lines +176 to +179

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might as well just add a constructor that takes crs?

DType::Binary(nullability),
)?
.erased(),
Expand Down Expand Up @@ -616,6 +619,7 @@ mod tests {
ExtDType::<WellKnownBinary>::try_new(
SpatialMetadata {
crs: Some("EPSG:4326".to_string()),
..Default::default()
},
DType::Binary(Nullability::NonNullable),
)?
Expand Down
2 changes: 2 additions & 0 deletions vortex-duckdb/src/convert/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ mod tests {
Scalar::extension::<WellKnownBinary>(
SpatialMetadata {
crs: crs.map(str::to_string),
..Default::default()
},
Scalar::binary(bytes.to_vec(), Nullability::Nullable),
)
Expand Down Expand Up @@ -533,6 +534,7 @@ mod tests {
let dtype = ExtDType::<WellKnownBinary>::try_new(
SpatialMetadata {
crs: Some("EPSG:4326".to_string()),
..Default::default()
},
DType::Binary(Nullability::Nullable),
)
Expand Down
5 changes: 4 additions & 1 deletion vortex-duckdb/src/convert/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,10 @@ pub fn flat_vector_to_vortex(vector: &VectorRef, len: usize) -> VortexResult<Arr
vector_as_string_blob(vector, len, DType::Binary(Nullability::Nullable));
let crs = logical_type.geometry_crs().map(|crs| crs.to_string());
let wkb_type = ExtDType::<WellKnownBinary>::try_new(
SpatialMetadata { crs },
SpatialMetadata {
crs,
..Default::default()
},
DType::Binary(Nullability::Nullable),
)?
.erased();
Expand Down
5 changes: 2 additions & 3 deletions vortex-duckdb/src/e2e_test/spatial_pushdown_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ fn native_point_file() -> NamedTempFile {
let storage = StructArray::from_fields(&[("x", xs), ("y", ys)])
.unwrap()
.into_array();
let dtype =
ExtDType::<Point>::try_new(SpatialMetadata { crs: None }, storage.dtype().clone())
.unwrap();
let dtype = ExtDType::<Point>::try_new(SpatialMetadata::default(), storage.dtype().clone())
.unwrap();
let points = ExtensionArray::new(dtype.erased(), storage).into_array();

let file = NamedTempFile::with_suffix(".vortex").unwrap();
Expand Down
1 change: 1 addition & 0 deletions vortex-duckdb/src/e2e_test/vortex_scan_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,7 @@ fn test_geometry() {
ExtDType::<WellKnownBinary>::try_new(
SpatialMetadata {
crs: Some("EPSG:32600".to_string()),
..Default::default()
},
geometry.dtype().clone(),
)
Expand Down
1 change: 1 addition & 0 deletions vortex-spatial/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ geo-types = { workspace = true }
geoarrow = { workspace = true }
geoarrow-cast = { workspace = true }
prost = { workspace = true }
serde_json = { workspace = true }
vortex-array = { workspace = true }
vortex-arrow = { workspace = true }
vortex-buffer = { workspace = true }
Expand Down
145 changes: 145 additions & 0 deletions vortex-spatial/src/extension/geometry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

//! Native geometry helpers and conversion to [`geo_types::Geometry`] for `geo` algorithms.

use geo_types::Geometry;
use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::IntoArray;
use vortex_array::arrays::ConstantArray;
use vortex_array::arrays::ExtensionArray;
use vortex_array::arrays::ListViewArray;
use vortex_array::arrays::StructArray;
use vortex_array::arrays::extension::ExtensionArrayExt;
use vortex_array::arrays::list::ListArraySlotsExt;
use vortex_array::arrays::listview::ListViewArraySlotsExt;
use vortex_array::arrays::listview::list_from_list_view;
use vortex_array::builtins::ArrayBuiltins;
use vortex_array::dtype::DType;
use vortex_array::dtype::Nullability;
use vortex_array::dtype::PType;
use vortex_array::scalar::Scalar;
use vortex_buffer::Buffer;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_err;

use super::LineString;
use super::MultiLineString;
use super::MultiPoint;
use super::MultiPolygon;
use super::Point;
use super::Polygon;
use super::Rect;
use super::linestring_geometries;
use super::multilinestring_geometries;
use super::multipoint_geometries;
use super::multipolygon_geometries;
use super::point_geometries;
use super::polygon_geometries;
use super::rect_geometries;

/// Whether `dtype` is a native geometry extension type.
pub(crate) fn is_native_geometry(dtype: &DType) -> bool {
dtype.as_extension_opt().is_some_and(|ext| {
ext.is::<Point>()
|| ext.is::<LineString>()
|| ext.is::<MultiPoint>()
|| ext.is::<Polygon>()
|| ext.is::<MultiLineString>()
|| ext.is::<MultiPolygon>()
|| ext.is::<Rect>()
})
}

/// Flatten a native geometry column to its coordinates.
pub(crate) fn flatten_coordinates(
array: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<StructArray> {
if !is_native_geometry(array.dtype()) {
vortex_bail!(
"spatial: operand is not a native geometry extension type, was {}",
array.dtype()
);
}
let mut node = array
.clone()
.execute::<ExtensionArray>(ctx)?
.storage_array()
.clone();
while node.dtype().is_list() {
node = node.execute::<ListViewArray>(ctx)?.elements().clone();
}
node.execute::<StructArray>(ctx)
}

/// Flatten native geometry storage and return each row's coordinate offsets.
pub(crate) fn flatten_row_offsets(
storage: ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<(Vec<usize>, StructArray)> {
let mut row_offsets: Vec<usize> = (0..=storage.len()).collect();
let mut level = storage;
while level.dtype().is_list() {
let list = list_from_list_view(level.execute::<ListViewArray>(ctx)?, ctx)?;
let offsets = list
.offsets()
.clone()
.cast(DType::Primitive(PType::U64, Nullability::NonNullable))?
.execute::<Buffer<u64>>(ctx)?;
for row_offset in &mut row_offsets {
*row_offset = usize::try_from(offsets[*row_offset])
.map_err(|_| vortex_err!("spatial: list offset exceeds usize"))?;
}
level = list.elements().clone();
}
Ok((row_offsets, level.execute::<StructArray>(ctx)?))
}

/// Decode a native geometry column to `geo_types`.
pub(crate) fn geometries(
array: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<Vec<Geometry<f64>>> {
let Some(ext) = array.dtype().as_extension_opt() else {
vortex_bail!(
"spatial: operand is not a geometry extension type, was {}",
array.dtype()
);
};
let storage = array
.clone()
.execute::<ExtensionArray>(ctx)?
.storage_array()
.clone();
if ext.is::<Point>() {
point_geometries(&storage, ext.metadata::<Point>(), ctx)
} else if ext.is::<LineString>() {
linestring_geometries(&storage, ext.metadata::<LineString>(), ctx)
} else if ext.is::<MultiPoint>() {
multipoint_geometries(&storage, ext.metadata::<MultiPoint>(), ctx)
} else if ext.is::<Polygon>() {
polygon_geometries(&storage, ext.metadata::<Polygon>(), ctx)
} else if ext.is::<MultiLineString>() {
multilinestring_geometries(&storage, ext.metadata::<MultiLineString>(), ctx)
} else if ext.is::<MultiPolygon>() {
multipolygon_geometries(&storage, ext.metadata::<MultiPolygon>(), ctx)
} else if ext.is::<Rect>() {
rect_geometries(&storage, ext.metadata::<Rect>(), ctx)
} else {
vortex_bail!("spatial: unsupported geometry extension {}", array.dtype())
}
}

/// Decode a constant operand to one geometry.
pub(crate) fn single_geometry(
scalar: &Scalar,
ctx: &mut ExecutionCtx,
) -> VortexResult<Geometry<f64>> {
let array = ConstantArray::new(scalar.clone(), 1).into_array();
geometries(&array, ctx)?
.pop()
.ok_or_else(|| vortex_err!("spatial: constant operand decoded to no geometry"))
}
51 changes: 31 additions & 20 deletions vortex-spatial/src/extension/linestring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

//! The [`LineString`] geometry extension type (`vortex.st.linestring`): an ordered path of the
//! [`Point`](super::Point) coordinate struct, stored as `List<Struct<x, y[, z][, m]>>` and tagged
//! with [`SpatialMetadata`] (CRS).
//! with [`SpatialMetadata`].

use std::sync::Arc;

Expand All @@ -18,7 +18,6 @@ use geoarrow::array::IntoArrow;
use geoarrow::array::LineStringArray;
use geoarrow::datatypes::CoordType;
use geoarrow::datatypes::LineStringType;
use prost::Message;
use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::IntoArray;
Expand Down Expand Up @@ -59,9 +58,9 @@ use super::SpatialMetadata;
use super::coordinate::Dimension;
use super::coordinate::coordinate_dimension;
use super::coordinate::coordinate_storage_dtype;
use super::geoarrow_metadata;
use super::geoarrow_to_wkb;
use super::spatial_metadata_from_arrow;
use super::metadata::from_geoarrow;
use super::metadata::to_geoarrow;

/// A line string: `geoarrow.linestring`, stored as `List<Struct<x, y[, z][, m]>>` (an ordered path
/// of vertices).
Expand All @@ -79,11 +78,11 @@ impl ExtVTable for LineString {
}

fn serialize_metadata(&self, metadata: &Self::Metadata) -> VortexResult<Vec<u8>> {
Ok(metadata.encode_to_vec())
Ok(metadata.serialize())
}

fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult<Self::Metadata> {
Ok(SpatialMetadata::decode(metadata)?)
SpatialMetadata::deserialize(metadata)
}

fn validate_dtype(ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
Expand Down Expand Up @@ -186,17 +185,23 @@ static ARROW_LINESTRING: CachedId = CachedId::new(LineStringType::NAME);

/// The `geoarrow.linestring` extension type for `dimension`, with separated (struct) coordinates
/// matching `LineString` storage.
fn linestring_type(spatial_metadata: &SpatialMetadata, dimension: Dimension) -> LineStringType {
LineStringType::new(dimension.into(), geoarrow_metadata(spatial_metadata))
fn linestring_type(
metadata: &SpatialMetadata,
dimension: Dimension,
) -> VortexResult<LineStringType> {
Ok(LineStringType::new(
dimension.into(),
to_geoarrow(metadata)?,
))
}

/// Decode `LineString` storage (`List<coordinate>`) to `geo_types` line strings, for the spatial scalar
/// functions. CRS does not affect planar geometry ops, so default metadata is used.
/// Decode line string storage to `geo_types`.
pub(crate) fn linestring_geometries(
storage: &ArrayRef,
metadata: &SpatialMetadata,
ctx: &mut ExecutionCtx,
) -> VortexResult<Vec<Geometry<f64>>> {
linestring_array(storage, ctx)?
linestring_array(storage, metadata, ctx)?
.iter()
.map(|geometry| -> VortexResult<Geometry<f64>> {
Ok(geometry
Expand All @@ -208,11 +213,12 @@ pub(crate) fn linestring_geometries(
}

/// Build a geoarrow `LineStringArray` from a `LineString`'s `List<coordinate>` storage.
fn linestring_array(storage: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<LineStringArray> {
let linestring_type = linestring_type(
&SpatialMetadata::default(),
linestring_dimension(storage.dtype())?,
);
fn linestring_array(
storage: &ArrayRef,
metadata: &SpatialMetadata,
ctx: &mut ExecutionCtx,
) -> VortexResult<LineStringArray> {
let linestring_type = linestring_type(metadata, linestring_dimension(storage.dtype())?)?;
let session = ctx.session().clone();
let arrow = session.arrow().execute_arrow(storage.clone(), None, ctx)?;
LineStringArray::try_from((arrow.as_ref(), linestring_type))
Expand All @@ -237,7 +243,11 @@ impl TryFrom<ExtensionArray> for LineStringData {
impl LineStringData {
/// Serialize line strings to WKB (a view array) — the form DuckDB `GEOMETRY` takes.
pub fn to_wkb(&self, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {
geoarrow_to_wkb(&linestring_array(self.0.storage_array(), ctx)?)
geoarrow_to_wkb(&linestring_array(
self.0.storage_array(),
self.0.ext_dtype().metadata::<LineString>(),
ctx,
)?)
}
}

Expand All @@ -257,11 +267,11 @@ impl ArrowExportVTable for LineString {
session: &ArrowSession,
) -> VortexResult<Option<Field>> {
let ext_type = dtype.as_extension();
let spatial_metadata = ext_type.metadata::<LineString>();
let metadata = ext_type.metadata::<LineString>();
let dimension = linestring_dimension(ext_type.storage_dtype())?;

let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?;
field.try_with_extension_type(linestring_type(spatial_metadata, dimension))?;
field.try_with_extension_type(linestring_type(metadata, dimension)?)?;

Ok(Some(field))
}
Expand Down Expand Up @@ -332,7 +342,7 @@ impl ArrowImportVTable for LineString {
);
(
linestring_meta.dimension().into(),
spatial_metadata_from_arrow(linestring_meta.metadata()),
from_geoarrow(linestring_meta.metadata()),
)
} else {
// Literal: peel the `List` layer to the coordinate struct and read its dimension from
Expand Down Expand Up @@ -400,6 +410,7 @@ mod tests {
fn spatial_meta() -> SpatialMetadata {
SpatialMetadata {
crs: Some("EPSG:4326".to_string()),
..Default::default()
}
}

Expand Down
Loading
Loading