diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 29a5bbd3fdb..8faebf79f86 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -57,7 +57,7 @@ jobs: - { shard: 6, name: "Encodings 3", packages: "vortex-pco vortex-runend vortex-sequence" } - { shard: 7, name: "Encodings 4", packages: "vortex-sparse vortex-zigzag vortex-zstd" } - { shard: 8, name: "Storage formats & row encoding", packages: "vortex-flatbuffers vortex-proto vortex-btrblocks vortex-row" } - - { shard: 9, name: "Tensor & geo", packages: "vortex-tensor vortex-geo" } + - { shard: 9, name: "Tensor & spatial", packages: "vortex-tensor vortex-spatial" } name: "Benchmark with Codspeed (Shard #${{ matrix.shard }})" timeout-minutes: 30 runs-on: >- diff --git a/Cargo.lock b/Cargo.lock index cba7a70ede5..63359686289 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9724,7 +9724,7 @@ dependencies = [ "uuid", "vortex", "vortex-arrow", - "vortex-geo", + "vortex-spatial", "vortex-tensor", "wkb", ] @@ -10032,9 +10032,9 @@ dependencies = [ "url", "vortex", "vortex-array", - "vortex-geo", "vortex-runend", "vortex-sequence", + "vortex-spatial", "vortex-utils", "wkb", "zip", @@ -10207,32 +10207,6 @@ dependencies = [ "vortex-utils", ] -[[package]] -name = "vortex-geo" -version = "0.1.0" -dependencies = [ - "arrow-array 58.4.0", - "arrow-schema 58.4.0", - "codspeed-divan-compat", - "geo", - "geo-traits", - "geo-types", - "geoarrow", - "geoarrow-cast", - "mimalloc", - "prost 0.14.4", - "rstest", - "vortex-array", - "vortex-arrow", - "vortex-buffer", - "vortex-error", - "vortex-geo", - "vortex-layout", - "vortex-mask", - "vortex-session", - "wkb", -] - [[package]] name = "vortex-io" version = "0.1.0" @@ -10303,8 +10277,8 @@ dependencies = [ "vortex", "vortex-arrow", "vortex-cloud", - "vortex-geo", "vortex-parquet-variant", + "vortex-spatial", ] [[package]] @@ -10605,6 +10579,32 @@ dependencies = [ "vortex-session", ] +[[package]] +name = "vortex-spatial" +version = "0.1.0" +dependencies = [ + "arrow-array 58.4.0", + "arrow-schema 58.4.0", + "codspeed-divan-compat", + "geo", + "geo-traits", + "geo-types", + "geoarrow", + "geoarrow-cast", + "mimalloc", + "prost 0.14.4", + "rstest", + "vortex-array", + "vortex-arrow", + "vortex-buffer", + "vortex-error", + "vortex-layout", + "vortex-mask", + "vortex-session", + "vortex-spatial", + "wkb", +] + [[package]] name = "vortex-sqllogictest" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 00a45b03618..36aa5b2ac9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ members = [ "benchmarks/duckdb-bench", "benchmarks/random-access-bench", "benchmarks/string-bench", - "vortex-geo", + "vortex-spatial", ] exclude = ["java/testfiles", "wasm-test"] resolver = "2" @@ -310,7 +310,6 @@ vortex-fastlanes = { version = "0.1.0", path = "./encodings/fastlanes", default- vortex-file = { version = "0.1.0", path = "./vortex-file", default-features = false } vortex-flatbuffers = { version = "0.1.0", path = "./vortex-flatbuffers", default-features = false } vortex-fsst = { version = "0.1.0", path = "./encodings/fsst", default-features = false } -vortex-geo = { version = "0.1.0", path = "./vortex-geo", default-features = false } vortex-io = { version = "0.1.0", path = "./vortex-io", default-features = false } vortex-ipc = { version = "0.1.0", path = "./vortex-ipc", default-features = false } vortex-json = { version = "0.1.0", path = "./vortex-json", default-features = false } @@ -327,6 +326,7 @@ vortex-scan = { version = "0.1.0", path = "./vortex-scan", default-features = fa vortex-sequence = { version = "0.1.0", path = "encodings/sequence", default-features = false } vortex-session = { version = "0.1.0", path = "./vortex-session", default-features = false } vortex-sparse = { version = "0.1.0", path = "./encodings/sparse", default-features = false } +vortex-spatial = { version = "0.1.0", path = "./vortex-spatial", default-features = false } vortex-tensor = { version = "0.1.0", path = "./vortex-tensor", default-features = false } vortex-utils = { version = "0.1.0", path = "./vortex-utils", default-features = false } vortex-zigzag = { version = "0.1.0", path = "./encodings/zigzag", default-features = false } diff --git a/bench-orchestrator/bench_orchestrator/ci_matrix/render.py b/bench-orchestrator/bench_orchestrator/ci_matrix/render.py index 4a2051cf30a..8b814349a40 100644 --- a/bench-orchestrator/bench_orchestrator/ci_matrix/render.py +++ b/bench-orchestrator/bench_orchestrator/ci_matrix/render.py @@ -12,7 +12,7 @@ Format.PARQUET, Format.VORTEX, Format.VORTEX_COMPACT, - Format.VORTEX_NATIVE, + Format.VORTEX_SPATIAL_NATIVE, Format.DUCKDB, Format.LANCE, ) diff --git a/bench-orchestrator/bench_orchestrator/config.py b/bench-orchestrator/bench_orchestrator/config.py index dfb1aaa920e..4aceaa59ac5 100644 --- a/bench-orchestrator/bench_orchestrator/config.py +++ b/bench-orchestrator/bench_orchestrator/config.py @@ -43,7 +43,7 @@ class Format(Enum): PARQUET = "parquet" VORTEX = "vortex" VORTEX_COMPACT = "vortex-compact" - VORTEX_NATIVE = "vortex-geo-native" + VORTEX_SPATIAL_NATIVE = "vortex-spatial-native" DUCKDB = "duckdb" LANCE = "lance" @@ -77,7 +77,7 @@ class Benchmark(Enum): Format.PARQUET, Format.VORTEX, Format.VORTEX_COMPACT, - Format.VORTEX_NATIVE, + Format.VORTEX_SPATIAL_NATIVE, Format.DUCKDB, ], Engine.LANCE: [Format.LANCE], diff --git a/bench-orchestrator/tests/test_config.py b/bench-orchestrator/tests/test_config.py index 1652cd82934..e2b09b06141 100644 --- a/bench-orchestrator/tests/test_config.py +++ b/bench-orchestrator/tests/test_config.py @@ -26,21 +26,21 @@ def test_parse_formats_json_accepts_ci_format_arrays() -> None: assert formats == [Format.PARQUET, Format.VORTEX, Format.DUCKDB] -def test_parse_formats_json_accepts_vortex_native() -> None: - formats = parse_formats_json('["parquet","vortex","vortex-geo-native"]') +def test_parse_formats_json_accepts_vortex_spatial_native() -> None: + formats = parse_formats_json('["parquet","vortex","vortex-spatial-native"]') - assert formats == [Format.PARQUET, Format.VORTEX, Format.VORTEX_NATIVE] + assert formats == [Format.PARQUET, Format.VORTEX, Format.VORTEX_SPATIAL_NATIVE] -def test_resolve_axis_targets_offers_vortex_native_on_duckdb_only() -> None: - # vortex-geo-native is a DuckDB-only lane; the DataFusion axis is dropped as unsupported. +def test_resolve_axis_targets_offers_vortex_spatial_native_on_duckdb_only() -> None: + # vortex-spatial-native is a DuckDB-only lane; the DataFusion axis is dropped as unsupported. targets, warnings = resolve_axis_targets( [Engine.DATAFUSION, Engine.DUCKDB], - [Format.VORTEX_NATIVE], + [Format.VORTEX_SPATIAL_NATIVE], ) - assert targets == [BenchmarkTarget(engine=Engine.DUCKDB, format=Format.VORTEX_NATIVE)] - assert warnings == ["Format vortex-geo-native is not supported by engine datafusion"] + assert targets == [BenchmarkTarget(engine=Engine.DUCKDB, format=Format.VORTEX_SPATIAL_NATIVE)] + assert warnings == ["Format vortex-spatial-native is not supported by engine datafusion"] def test_resolve_axis_targets_filters_unsupported_combinations() -> None: @@ -72,19 +72,19 @@ def test_resolve_axis_targets_skips_engines_a_benchmark_cannot_run() -> None: assert warnings == ["Benchmark spatialbench does not support engine datafusion"] -def test_resolve_axis_targets_expands_spatialbench_three_lanes() -> None: +def test_resolve_axis_targets_supports_spatialbench_three_lanes() -> None: # The single-command three-lane comparison: parquet, WKB vortex, and native-geometry vortex, all # on DuckDB. targets, warnings = resolve_axis_targets( [Engine.DUCKDB], - [Format.PARQUET, Format.VORTEX, Format.VORTEX_NATIVE], + [Format.PARQUET, Format.VORTEX, Format.VORTEX_SPATIAL_NATIVE], Benchmark.SPATIALBENCH, ) assert targets == [ BenchmarkTarget(engine=Engine.DUCKDB, format=Format.PARQUET), BenchmarkTarget(engine=Engine.DUCKDB, format=Format.VORTEX), - BenchmarkTarget(engine=Engine.DUCKDB, format=Format.VORTEX_NATIVE), + BenchmarkTarget(engine=Engine.DUCKDB, format=Format.VORTEX_SPATIAL_NATIVE), ] assert warnings == [] diff --git a/bench-orchestrator/tests/test_executor.py b/bench-orchestrator/tests/test_executor.py index 984f0600b6c..33f0f20b132 100644 --- a/bench-orchestrator/tests/test_executor.py +++ b/bench-orchestrator/tests/test_executor.py @@ -33,17 +33,17 @@ def test_build_command_adds_duckdb_cleanup_flag() -> None: assert "scale-factor=1.0" in cmd -def test_build_command_serializes_vortex_native_format() -> None: +def test_build_command_serializes_vortex_spatial_native_format() -> None: executor = BenchmarkExecutor(Path("/tmp/duckdb-bench"), Engine.DUCKDB) cmd = executor.build_command( benchmark=Benchmark.SPATIALBENCH, - formats=[Format.PARQUET, Format.VORTEX, Format.VORTEX_NATIVE], + formats=[Format.PARQUET, Format.VORTEX, Format.VORTEX_SPATIAL_NATIVE], iterations=1, options={"scale-factor": "1.0"}, ) - assert "parquet,vortex,vortex-geo-native" in cmd + assert "parquet,vortex,vortex-spatial-native" in cmd def test_build_command_omits_formats_for_lance_backend() -> None: diff --git a/benchmarks/datafusion-bench/src/lib.rs b/benchmarks/datafusion-bench/src/lib.rs index b93aa26391e..aeea953ba3d 100644 --- a/benchmarks/datafusion-bench/src/lib.rs +++ b/benchmarks/datafusion-bench/src/lib.rs @@ -109,7 +109,7 @@ pub fn format_to_df_format(format: Format) -> Arc { match format { Format::Csv => Arc::new(CsvFormat::default()) as _, Format::Parquet => Arc::new(ParquetFormat::new()), - Format::OnDiskVortex | Format::VortexCompact | Format::VortexNative => Arc::new( + Format::OnDiskVortex | Format::VortexCompact | Format::VortexSpatialNative => Arc::new( VortexFormat::new_with_options(SESSION.clone(), vortex_table_options()), ), Format::OnDiskDuckDB | Format::Lance => { diff --git a/benchmarks/duckdb-bench/src/lib.rs b/benchmarks/duckdb-bench/src/lib.rs index 4f3bdd388fc..0810e26e9cc 100644 --- a/benchmarks/duckdb-bench/src/lib.rs +++ b/benchmarks/duckdb-bench/src/lib.rs @@ -183,7 +183,7 @@ impl DuckClient { Format::Parquet | Format::OnDiskVortex | Format::VortexCompact - | Format::VortexNative => "VIEW", + | Format::VortexSpatialNative => "VIEW", Format::OnDiskDuckDB => "TABLE", Format::Lance => { anyhow::bail!( diff --git a/docs/concepts/expressions.md b/docs/concepts/expressions.md index 2a8c5a4b78b..ca5362763e7 100644 --- a/docs/concepts/expressions.md +++ b/docs/concepts/expressions.md @@ -12,7 +12,7 @@ such as whether the function is strict, and the actual logic for executing the f The built-in scalar functions can be found in the `vortex-array::expr` module, with additional use-case specific functions provided by integration and plugin crates. -In the future, we plan to add full support for geospatial functions, date-time functions, and more. +In the future, we plan to add full support for spatial functions, date-time functions, and more. ## Scalar Function Arrays diff --git a/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java b/java/vortex-jni/src/test/java/dev/vortex/api/SpatialTypesTest.java similarity index 94% rename from java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java rename to java/vortex-jni/src/test/java/dev/vortex/api/SpatialTypesTest.java index 381a07e2945..6f6977ec70a 100644 --- a/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java +++ b/java/vortex-jni/src/test/java/dev/vortex/api/SpatialTypesTest.java @@ -33,11 +33,11 @@ import org.junit.jupiter.api.io.TempDir; /** - * Round-trips a Vortex geo extension column ({@code vortex.geo.wkb}) through the JNI boundary. Geo columns cross the - * boundary as Arrow fields tagged with the GeoArrow extension name ({@code geoarrow.wkb}) and JSON metadata carrying - * the CRS. + * Round-trips a Vortex spatial extension column ({@code vortex.st.wkb}) through the JNI boundary. Spatial columns cross + * the boundary as Arrow fields tagged with the GeoArrow extension name ({@code geoarrow.wkb}) and JSON metadata + * carrying the CRS. */ -public final class GeoTypesTest { +public final class SpatialTypesTest { private static final String EXTENSION_NAME_KEY = "ARROW:extension:name"; private static final String EXTENSION_METADATA_KEY = "ARROW:extension:metadata"; private static final String GEOARROW_WKB = "geoarrow.wkb"; @@ -57,7 +57,7 @@ public static void loadLibrary() { @BeforeAll static void setup() throws IOException { - writePath = tempDir.resolve("geo.vortex").toAbsolutePath().toUri().toString(); + writePath = tempDir.resolve("spatial.vortex").toAbsolutePath().toUri().toString(); WKB_POINTS.add(wkbPoint(1.0, 2.0)); WKB_POINTS.add(wkbPoint(-111.7610, 34.8697)); diff --git a/scripts/compare-benchmark-jsons.py b/scripts/compare-benchmark-jsons.py index 16310e305ee..14b66c2e2fd 100644 --- a/scripts/compare-benchmark-jsons.py +++ b/scripts/compare-benchmark-jsons.py @@ -551,7 +551,7 @@ def build_statistical_analysis(df: pd.DataFrame, threshold_pct: int) -> dict[str } -def calculate_geo_mean(df: pd.DataFrame) -> float: +def calculate_geometric_mean(df: pd.DataFrame) -> float: """Geometric mean of positive ratios from a DataFrame ratio column.""" valid_ratios = [r for r in df["ratio"] if r > 0 and not pd.isna(r)] @@ -1019,8 +1019,8 @@ def main() -> None: vortex_df = headline_df[headline_df["file_format"].str.startswith("vortex")] parquet_df = headline_df[headline_df["file_format"].eq(CONTROL_FORMAT)] - vortex_geo_mean_ratio = calculate_geo_mean(vortex_df) - parquet_geo_mean_ratio = calculate_geo_mean(parquet_df) + vortex_geometric_mean_ratio = calculate_geometric_mean(vortex_df) + parquet_geometric_mean_ratio = calculate_geometric_mean(parquet_df) statistical_analysis = build_statistical_analysis(query_df, threshold_pct) verdict = build_verdict(statistical_analysis) if statistical_analysis is not None else None @@ -1037,7 +1037,7 @@ def main() -> None: if len(vortex_df) > 0: vortex_performance = format_performance( - vortex_geo_mean_ratio, + vortex_geometric_mean_ratio, improvement_threshold, regression_threshold, "vortex", @@ -1045,7 +1045,7 @@ def main() -> None: summary_fields.append(f"**Vortex (geomean)**: {vortex_performance}") if len(parquet_df) > 0: parquet_performance = format_performance( - parquet_geo_mean_ratio, + parquet_geometric_mean_ratio, improvement_threshold, regression_threshold, "parquet", @@ -1079,7 +1079,7 @@ def main() -> None: for engine, file_format, unit in sorted(grouped_tables.groups.keys(), key=group_sort_key): group_df = grouped_tables.get_group((engine, file_format, unit)).sort_values("name") group_performance = format_performance( - calculate_geo_mean(group_df), + calculate_geometric_mean(group_df), improvement_threshold, regression_threshold, "group", diff --git a/vortex-array/src/expr/exprs.rs b/vortex-array/src/expr/exprs.rs index 71225c4bf43..18d81509f46 100644 --- a/vortex-array/src/expr/exprs.rs +++ b/vortex-array/src/expr/exprs.rs @@ -610,7 +610,7 @@ where /// whose result is null exactly when any operand is null. /// /// This is the `ScalarFnVTable::validity` for kernels that propagate nulls and never produce a -/// null from non-null inputs (comparisons, arithmetic, most geo and tensor ops). Returning it lets +/// null from non-null inputs (comparisons, arithmetic, most spatial and tensor operations). Returning it lets /// the planner derive the output's null mask without executing the kernel. Yields `None` when the /// expression has no children. pub fn union_child_validities(expression: &Expression) -> VortexResult> { diff --git a/vortex-bench/Cargo.toml b/vortex-bench/Cargo.toml index 6dbe8ffea98..965a80c6259 100644 --- a/vortex-bench/Cargo.toml +++ b/vortex-bench/Cargo.toml @@ -28,7 +28,7 @@ vortex = { workspace = true, features = [ "zstd", ] } vortex-arrow = { workspace = true } -vortex-geo = { workspace = true } +vortex-spatial = { workspace = true } vortex-tensor = { workspace = true } # TODO(connor): In the future, this might be inside vortex. anyhow = { workspace = true } diff --git a/vortex-bench/sql/spatialbench.md b/vortex-bench/sql/spatialbench.md index f959e006887..4b67405aaeb 100644 --- a/vortex-bench/sql/spatialbench.md +++ b/vortex-bench/sql/spatialbench.md @@ -1,6 +1,6 @@ # SpatialBench benchmark -The [Apache Sedona SpatialBench](https://sedona.apache.org/spatialbench/) geospatial +The [Apache Sedona SpatialBench](https://sedona.apache.org/spatialbench/) spatial analytics benchmark: twelve queries (Q1 ... Q12 in [`spatialbench.sql`](./spatialbench.sql), DuckDB dialect) over a trips/zones schema, exercising spatial predicates and functions such as `ST_DWithin`, `ST_Intersects`, and `ST_Distance`. The query logic matches upstream @@ -13,3 +13,16 @@ The harness lives in [`src/spatialbench`](../src/spatialbench). ```bash vx-bench run spatialbench ``` + +The default command compares the Parquet and Vortex WKB representations with DuckDB. To run the +native Vortex spatial representation explicitly: + +```bash +vx-bench run spatialbench --engine duckdb --format vortex-spatial-native +``` + +To compare all three representations in one run: + +```bash +vx-bench run spatialbench --engine duckdb --format parquet,vortex,vortex-spatial-native +``` diff --git a/vortex-bench/src/conversions.rs b/vortex-bench/src/conversions.rs index 3732f646232..f2c3d398861 100644 --- a/vortex-bench/src/conversions.rs +++ b/vortex-bench/src/conversions.rs @@ -55,8 +55,8 @@ use vortex::utils::aliases::hash_set::HashSet; use vortex::utils::parallelism::get_available_parallelism; use vortex_arrow::FromArrowArray; use vortex_arrow::FromArrowType; -use vortex_geo::extension::GeoMetadata; -use vortex_geo::extension::WellKnownBinary; +use vortex_spatial::extension::SpatialMetadata; +use vortex_spatial::extension::WellKnownBinary; use wkb::Endianness; use wkb::reader::read_wkb; use wkb::writer::WriteOptions; @@ -144,10 +144,13 @@ pub async fn convert_parquet_file_to_vortex( let builder = ParquetRecordBatchStreamBuilder::new(file).await?; // GeoParquet geometry tagging. - let geo_columns = geoparquet_columns(builder.metadata()); - let dtype = tag_geo_dtype(DType::from_arrow(builder.schema().as_ref()), &geo_columns)?; + let spatial_columns = geoparquet_columns(builder.metadata()); + let dtype = tag_spatial_dtype( + DType::from_arrow(builder.schema().as_ref()), + &spatial_columns, + )?; let stream = parquet_to_vortex_stream(builder.build()?) - .map(move |chunk| chunk.and_then(|chunk| tag_geo_array(chunk, &geo_columns))); + .map(move |chunk| chunk.and_then(|chunk| tag_spatial_array(chunk, &spatial_columns))); let mut output_file = OpenOptions::new() .write(true) @@ -301,7 +304,10 @@ pub async fn write_parquet_as_vortex( } /// Add GeoParquet `geo` file metadata to externally-sourced parquet we don't generate (e.g. SpatialBench `zone`). -pub async fn add_geoparquet_metadata(parquet_path: &Path, geo_json: &str) -> anyhow::Result<()> { +pub async fn add_geoparquet_metadata( + parquet_path: &Path, + geoparquet_json: &str, +) -> anyhow::Result<()> { let builder = ParquetRecordBatchStreamBuilder::new(File::open(parquet_path).await?).await?; let already_tagged = builder .metadata() @@ -320,7 +326,10 @@ pub async fn add_geoparquet_metadata(parquet_path: &Path, geo_json: &str) -> any while let Some(batch) = reader.try_next().await? { writer.write(&batch).await?; } - writer.append_key_value_metadata(KeyValue::new("geo".to_string(), Some(geo_json.to_string()))); + writer.append_key_value_metadata(KeyValue::new( + "geo".to_string(), + Some(geoparquet_json.to_string()), + )); writer.close().await?; tokio::fs::rename(&tmp_path, parquet_path).await?; Ok(()) @@ -333,7 +342,7 @@ fn geoparquet_columns(metadata: &ParquetMetaData) -> HashSet { .key_value_metadata() .and_then(|kvs| kvs.iter().find(|kv| kv.key == "geo")) .and_then(|kv| kv.value.as_deref()) - .and_then(|geo| serde_json::from_str::(geo).ok()) + .and_then(|metadata| serde_json::from_str::(metadata).ok()) .and_then(|value| { value .get("columns") @@ -343,15 +352,18 @@ fn geoparquet_columns(metadata: &ParquetMetaData) -> HashSet { .unwrap_or_default() } -/// The erased `vortex.geo.wkb` extension dtype over a binary `storage` dtype. +/// The erased `vortex.st.wkb` extension dtype over a binary `storage` dtype. fn wkb_ext_dtype(storage: &DType) -> VortexResult { - Ok(ExtDType::::try_new(GeoMetadata { crs: None }, storage.clone())?.erased()) + Ok( + ExtDType::::try_new(SpatialMetadata { crs: None }, storage.clone())? + .erased(), + ) } -/// Re-type the named binary columns of a struct `dtype` as `vortex.geo.wkb`, so the column +/// Re-type the named binary columns of a struct `dtype` as `vortex.st.wkb`, so the column /// self-describes as geometry. -fn tag_geo_dtype(dtype: DType, geo_columns: &HashSet) -> VortexResult { - if geo_columns.is_empty() { +fn tag_spatial_dtype(dtype: DType, spatial_columns: &HashSet) -> VortexResult { + if spatial_columns.is_empty() { return Ok(dtype); } let DType::Struct(fields, nullability) = &dtype else { @@ -362,7 +374,7 @@ fn tag_geo_dtype(dtype: DType, geo_columns: &HashSet) -> VortexResult
) -> VortexResult
) -> VortexResult { - if geo_columns.is_empty() { +fn tag_spatial_array(chunk: ArrayRef, spatial_columns: &HashSet) -> VortexResult { + if spatial_columns.is_empty() { return Ok(chunk); } let Some(struct_array) = chunk.as_opt::() else { @@ -390,7 +402,7 @@ fn tag_geo_array(chunk: ArrayRef, geo_columns: &HashSet) -> VortexResult let mut ctx = SESSION.create_execution_ctx(); let mut tagged = Vec::with_capacity(names.len()); for (name, field) in names.iter().zip(struct_array.iter_unmasked_fields()) { - if geo_columns.contains(name.as_ref()) && field.dtype().is_binary() { + if spatial_columns.contains(name.as_ref()) && field.dtype().is_binary() { let ext = wkb_ext_dtype(field.dtype())?; let little_endian = wkb_field_to_little_endian(field, &mut ctx)?; tagged.push(ExtensionArray::try_new(ext, little_endian)?.into_array()); diff --git a/vortex-bench/src/lib.rs b/vortex-bench/src/lib.rs index bc077a64719..af11d2b20ce 100644 --- a/vortex-bench/src/lib.rs +++ b/vortex-bench/src/lib.rs @@ -80,7 +80,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; pub static SESSION: LazyLock = LazyLock::new(|| { let session = VortexSession::default().with_tokio(); - vortex_geo::initialize(&session); + vortex_spatial::initialize(&session); session }); @@ -149,9 +149,9 @@ pub enum Format { #[clap(name = "vortex-compact")] #[serde(rename = "vortex-compact")] VortexCompact, - #[clap(name = "vortex-geo-native")] - #[serde(rename = "vortex-geo-native")] - VortexNative, + #[clap(name = "vortex-spatial-native")] + #[serde(rename = "vortex-spatial-native")] + VortexSpatialNative, #[clap(name = "duckdb")] #[serde(rename = "duckdb")] OnDiskDuckDB, @@ -190,7 +190,7 @@ impl Format { Format::Parquet => "parquet", Format::OnDiskVortex => "vortex-file-compressed", Format::VortexCompact => "vortex-compact", - Format::VortexNative => "vortex-geo-native", + Format::VortexSpatialNative => "vortex-spatial-native", Format::OnDiskDuckDB => "duckdb", Format::Lance => "lance", } @@ -202,7 +202,7 @@ impl Format { Format::Parquet => "parquet", Format::OnDiskVortex => "vortex", Format::VortexCompact => "vortex", - Format::VortexNative => "vortex", + Format::VortexSpatialNative => "vortex", Format::OnDiskDuckDB => "duckdb", Format::Lance => "lance", } diff --git a/vortex-bench/src/spatialbench/benchmark.rs b/vortex-bench/src/spatialbench/benchmark.rs index 28e88ed1f08..693a174eae1 100644 --- a/vortex-bench/src/spatialbench/benchmark.rs +++ b/vortex-bench/src/spatialbench/benchmark.rs @@ -18,10 +18,10 @@ use crate::spatialbench::datagen::Table; use crate::utils::file::resolve_data_url; use crate::workspace_root; -/// Data-dir subfolder for the native-geometry Vortex files (the `vortex-geo-native` lane). -pub const NATIVE_DIR: &str = "vortex-geo-native"; +/// Data-dir subfolder for the native-geometry Vortex files (the `vortex-spatial-native` lane). +pub const SPATIAL_NATIVE_DIR: &str = "vortex-spatial-native"; -/// SpatialBench geospatial benchmark (Apache Sedona): a `trip` point table, `building` polygons, and +/// SpatialBench spatial benchmark (Apache Sedona): a `trip` point table, `building` polygons, and /// a `customer` attribute table, queried with spatial filters and joins. `zone` polygons are sourced /// externally and registered when present. See . pub struct SpatialBenchBenchmark { @@ -90,14 +90,15 @@ impl Benchmark for SpatialBenchBenchmark { .map_err(|_| anyhow::anyhow!("Invalid file URL: {}", self.data_url.as_str()))?; datagen::generate_tables(&self.scale_factor, base_data_dir.clone()).await?; - // `zone` is externally sourced (SpatialBench directly generate it), so - // re-tag its parquet with the geo metadata. - if let Some(geo) = datagen::wkb::geo_parquet_metadata(Table::Zone) { + // `zone` is externally sourced (SpatialBench directly generates it), so restore its + // GeoParquet metadata. + if let Some(geoparquet_metadata) = datagen::wkb::geoparquet_metadata(Table::Zone) { let zone_glob = base_data_dir .join(Format::Parquet.name()) .join("zone_*.parquet"); for zone_file in glob::glob(&zone_glob.to_string_lossy())?.flatten() { - crate::conversions::add_geoparquet_metadata(&zone_file, &geo).await?; + crate::conversions::add_geoparquet_metadata(&zone_file, &geoparquet_metadata) + .await?; } } @@ -106,19 +107,19 @@ impl Benchmark for SpatialBenchBenchmark { let derived_dirs = [ base_data_dir.join(Format::OnDiskVortex.name()), base_data_dir.join(Format::VortexCompact.name()), - base_data_dir.join(NATIVE_DIR), + base_data_dir.join(SPATIAL_NATIVE_DIR), ]; datagen::spatially_sort_tables(&base_data_dir.join(Format::Parquet.name()), &derived_dirs) .await?; Ok(()) } - /// The `vortex-geo-native` lane decodes each table's WKB geometry to native GeoArrow once, into the - /// `vortex-geo-native` dir, so its queries read DuckDB `GEOMETRY` directly. Idempotent. + /// The `vortex-spatial-native` lane decodes each table's WKB geometry to native GeoArrow once, into the + /// `vortex-spatial-native` dir, so its queries read DuckDB `GEOMETRY` directly. Idempotent. async fn prepare_format(&self, format: Format, base_path: &Path) -> anyhow::Result<()> { - if format == Format::VortexNative { + if format == Format::VortexSpatialNative { let parquet_dir = base_path.join(Format::Parquet.name()); - let native_dir = base_path.join(NATIVE_DIR); + let native_dir = base_path.join(SPATIAL_NATIVE_DIR); for table in self.base_tables() { datagen::write_native_vortex(table, &parquet_dir, &native_dir).await?; } @@ -130,11 +131,11 @@ impl Benchmark for SpatialBenchBenchmark { &self.data_url } - /// The `vortex-geo-native` lane reads the native-geometry Vortex dir; every other format reads its + /// The `vortex-spatial-native` lane reads the native-geometry Vortex dir; every other format reads its /// own `{format}` subfolder. fn format_path(&self, format: Format, base_url: &Url) -> anyhow::Result { let dir = match format { - Format::VortexNative => NATIVE_DIR, + Format::VortexSpatialNative => SPATIAL_NATIVE_DIR, other => other.name(), }; Ok(base_url.join(&format!("{dir}/"))?) @@ -168,7 +169,7 @@ impl Benchmark for SpatialBenchBenchmark { } /// Both lanes register the same tables (WKB reads `parquet`/`vortex`, native reads - /// `vortex-geo-native`); `zone` is externally sourced and optional, registered only when present. + /// `vortex-spatial-native`); `zone` is externally sourced and optional, registered only when present. fn table_specs(&self) -> Vec { self.base_tables() .iter() diff --git a/vortex-bench/src/spatialbench/datagen/mod.rs b/vortex-bench/src/spatialbench/datagen/mod.rs index 4c0d4bd2f81..d144746712e 100644 --- a/vortex-bench/src/spatialbench/datagen/mod.rs +++ b/vortex-bench/src/spatialbench/datagen/mod.rs @@ -2,8 +2,8 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors //! SpatialBench data preparation. [`wkb`] generates the canonical WKB base tables; [`native`] derives -//! native-geometry Vortex files from them for `points=native`. The [`table`] catalog is the single -//! source of truth for the base tables both stages share. +//! native-geometry Vortex files for the `vortex-spatial-native` format. The [`table`] catalog is +//! the single source of truth for the base tables both stages share. pub mod native; pub mod spatial_sort; diff --git a/vortex-bench/src/spatialbench/datagen/native.rs b/vortex-bench/src/spatialbench/datagen/native.rs index 2e03b61b39f..8a7acc7d2fd 100644 --- a/vortex-bench/src/spatialbench/datagen/native.rs +++ b/vortex-bench/src/spatialbench/datagen/native.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Native-geometry prep for `points=native`: decode a table's WKB geometry to native -//! `vortex.geo.{point,polygon,multipolygon}` via `geoarrow_cast` (so Vortex never decodes WKB), then +//! Native-geometry prep for `vortex-spatial-native`: decode a table's WKB geometry to native +//! `vortex.st.{point,polygon,multipolygon}` via `geoarrow_cast` (so Vortex never decodes WKB), then //! write a Vortex file. A one-time cost; queries then see DuckDB `GEOMETRY` directly. use std::path::Path; @@ -40,7 +40,7 @@ use super::table::Table; use crate::SESSION; use crate::utils::file::idempotent_async; -fn geo_metadata() -> Arc { +fn geoarrow_metadata() -> Arc { Arc::new(Metadata::new(Crs::default(), None)) } @@ -103,7 +103,7 @@ fn native_record_batch(batch: RecordBatch, table: Table) -> anyhow::Result anyhow::Result cast( wkb.as_ref(), &GeoArrowType::Point( - PointType::new(Dimension::XY, geo_metadata()) + PointType::new(Dimension::XY, geoarrow_metadata()) .with_coord_type(CoordType::Separated), ), )?, GeometryKind::Polygon => cast( wkb.as_ref(), &GeoArrowType::Polygon( - PolygonType::new(Dimension::XY, geo_metadata()) + PolygonType::new(Dimension::XY, geoarrow_metadata()) .with_coord_type(CoordType::Separated), ), )?, @@ -138,7 +138,7 @@ fn native_record_batch(batch: RecordBatch, table: Table) -> anyhow::Result cast( wkb.as_ref(), &GeoArrowType::MultiPolygon( - MultiPolygonType::new(Dimension::XY, geo_metadata()) + MultiPolygonType::new(Dimension::XY, geoarrow_metadata()) .with_coord_type(CoordType::Separated), ), )?, diff --git a/vortex-bench/src/spatialbench/datagen/spatial_sort.rs b/vortex-bench/src/spatialbench/datagen/spatial_sort.rs index d77aea15846..e4382ffb4f3 100644 --- a/vortex-bench/src/spatialbench/datagen/spatial_sort.rs +++ b/vortex-bench/src/spatialbench/datagen/spatial_sort.rs @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors //! Spatial clustering of the source parquet, in place, so every downstream lane (parquet, -//! vortex-WKB, `vortex-geo-native`) reads the same layout. +//! vortex-WKB, `vortex-spatial-native`) reads the same layout. //! //! The geometry zone-map prune skips a chunk only when its bounding box is disjoint from the query //! region. In generation order every chunk's box spans the whole map, so nothing prunes; ordering @@ -44,12 +44,12 @@ use tokio::fs::File as TokioFile; use tracing::info; use super::table::Table; -use super::wkb::geo_parquet_metadata; +use super::wkb::geoparquet_metadata; /// Parquet metadata marker: this file is already spatially sorted (makes the step idempotent). const SORTED_KEY: &str = "vortex_spatial_sorted"; -fn geo_metadata() -> Arc { +fn geoarrow_metadata() -> Arc { Arc::new(Metadata::new(Crs::default(), None)) } @@ -148,9 +148,11 @@ async fn sort_part(path: &Path, table: Table, geom_col: &str) -> anyhow::Result< let mut writer = AsyncArrowWriter::try_new(TokioFile::create(&tmp_path).await?, schema, Some(props))?; writer.write(&sorted).await?; - // A fresh write drops metadata: re-tag geo so DuckDB reads `GEOMETRY`, and add the sorted marker. - if let Some(geo) = geo_parquet_metadata(table) { - writer.append_key_value_metadata(KeyValue::new("geo".to_string(), Some(geo))); + // A fresh write drops metadata: restore the GeoParquet tag so DuckDB reads `GEOMETRY`, and add + // the sorted marker. + if let Some(geoparquet_metadata) = geoparquet_metadata(table) { + writer + .append_key_value_metadata(KeyValue::new("geo".to_string(), Some(geoparquet_metadata))); } writer.append_key_value_metadata(KeyValue::new( SORTED_KEY.to_string(), @@ -170,7 +172,7 @@ async fn sort_part(path: &Path, table: Table, geom_col: &str) -> anyhow::Result< /// The bounding-box center `(x, y)` of every geometry in a WKB column, whatever its geometry type. fn wkb_centers(column: &dyn Array) -> anyhow::Result<(Vec, Vec)> { - let wkb_type = WkbType::new(geo_metadata()); + let wkb_type = WkbType::new(geoarrow_metadata()); // Expanded per concrete WKB array type. macro_rules! centers { ($array:expr) => {{ diff --git a/vortex-bench/src/spatialbench/datagen/wkb.rs b/vortex-bench/src/spatialbench/datagen/wkb.rs index 51b34bf3016..417c95cdf62 100644 --- a/vortex-bench/src/spatialbench/datagen/wkb.rs +++ b/vortex-bench/src/spatialbench/datagen/wkb.rs @@ -101,8 +101,11 @@ pub async fn generate_tables(scale_factor: &str, output_dir: PathBuf) -> Result< } // Tag geometry columns with GeoParquet `geo` metadata so DuckDB's `read_parquet` // surfaces them as `GEOMETRY` directly. - if let Some(geo) = geo_parquet_metadata(table) { - writer.append_key_value_metadata(KeyValue::new("geo".to_string(), Some(geo))); + if let Some(geoparquet_metadata) = geoparquet_metadata(table) { + writer.append_key_value_metadata(KeyValue::new( + "geo".to_string(), + Some(geoparquet_metadata), + )); } writer.close().await?; @@ -162,7 +165,7 @@ async fn generate_zone(scale_factor: f64, parquet_dir: &Path) -> Result<()> { } /// GeoParquet metadata for WKB geometry columns, or `None` when it has none. -pub(crate) fn geo_parquet_metadata(table: Table) -> Option { +pub(crate) fn geoparquet_metadata(table: Table) -> Option { let geometry_columns = table.geometry_columns(); let primary = geometry_columns.first()?; let columns: serde_json::Map = geometry_columns diff --git a/vortex-bench/src/spatialbench/mod.rs b/vortex-bench/src/spatialbench/mod.rs index bba06bd7ef9..24de5fc3d64 100644 --- a/vortex-bench/src/spatialbench/mod.rs +++ b/vortex-bench/src/spatialbench/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! SpatialBench geospatial analytics benchmark. +//! SpatialBench spatial analytics benchmark. //! //! See . diff --git a/vortex-duckdb/Cargo.toml b/vortex-duckdb/Cargo.toml index f7512861cff..fd611af030d 100644 --- a/vortex-duckdb/Cargo.toml +++ b/vortex-duckdb/Cargo.toml @@ -45,7 +45,7 @@ vortex = { workspace = true, features = [ "object_store", "object_store_registry", ] } -vortex-geo = { workspace = true } +vortex-spatial = { workspace = true } vortex-utils = { workspace = true, features = ["dashmap"] } [features] diff --git a/vortex-duckdb/src/convert/dtype.rs b/vortex-duckdb/src/convert/dtype.rs index 6b0568d4e5c..b00dee81a02 100644 --- a/vortex-duckdb/src/convert/dtype.rs +++ b/vortex-duckdb/src/convert/dtype.rs @@ -57,14 +57,14 @@ use vortex::extension::datetime::TemporalMetadata; use vortex::extension::datetime::Time; use vortex::extension::datetime::TimeUnit; use vortex::extension::datetime::Timestamp; -use vortex_geo::extension::GeoMetadata; -use vortex_geo::extension::LineString; -use vortex_geo::extension::MultiLineString; -use vortex_geo::extension::MultiPoint; -use vortex_geo::extension::MultiPolygon; -use vortex_geo::extension::Point; -use vortex_geo::extension::Polygon; -use vortex_geo::extension::WellKnownBinary; +use vortex_spatial::extension::LineString; +use vortex_spatial::extension::MultiLineString; +use vortex_spatial::extension::MultiPoint; +use vortex_spatial::extension::MultiPolygon; +use vortex_spatial::extension::Point; +use vortex_spatial::extension::Polygon; +use vortex_spatial::extension::SpatialMetadata; +use vortex_spatial::extension::WellKnownBinary; use vortex_utils::aliases::hash_set::HashSet; use crate::cpp::DUCKDB_TYPE; @@ -173,7 +173,7 @@ impl FromLogicalType for DType { let crs = logical_type.geometry_crs().map(|crs| crs.to_string()); DType::Extension( ExtDType::::try_new( - GeoMetadata { crs }, + SpatialMetadata { crs }, DType::Binary(nullability), )? .erased(), @@ -255,7 +255,7 @@ impl TryFrom<&DType> for LogicalType { } // Native geometry types and WKB all surface to DuckDB as GEOMETRY so `ST_*` bind. - if let Some(geo) = ext_dtype + if let Some(spatial_metadata) = ext_dtype .metadata_opt::() .or_else(|| ext_dtype.metadata_opt::()) .or_else(|| ext_dtype.metadata_opt::()) @@ -264,7 +264,7 @@ impl TryFrom<&DType> for LogicalType { .or_else(|| ext_dtype.metadata_opt::()) .or_else(|| ext_dtype.metadata_opt::()) { - return LogicalType::geometry_type(geo.crs.as_deref()); + return LogicalType::geometry_type(spatial_metadata.crs.as_deref()); } vortex_bail!("Unsupported extension type \"{}\"", ext_dtype.id()); @@ -384,8 +384,8 @@ mod tests { use vortex::extension::datetime::Time; use vortex::extension::datetime::Timestamp; use vortex::scalar::ScalarValue; - use vortex_geo::extension::GeoMetadata; - use vortex_geo::extension::WellKnownBinary; + use vortex_spatial::extension::SpatialMetadata; + use vortex_spatial::extension::WellKnownBinary; use crate::convert::dtype::FromLogicalType; use crate::cpp; @@ -614,7 +614,7 @@ mod tests { fn test_geometry_roundtrip() -> VortexResult<()> { let vortex_geometry = DType::Extension( ExtDType::::try_new( - GeoMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), }, DType::Binary(Nullability::NonNullable), diff --git a/vortex-duckdb/src/convert/expr.rs b/vortex-duckdb/src/convert/expr.rs index 9d57159c36f..5ee407994de 100644 --- a/vortex-duckdb/src/convert/expr.rs +++ b/vortex-duckdb/src/convert/expr.rs @@ -52,17 +52,17 @@ use vortex::scalar_fn::fns::like::Like; use vortex::scalar_fn::fns::like::LikeOptions; use vortex::scalar_fn::fns::literal::Literal; use vortex::scalar_fn::fns::operators::Operator; -use vortex_geo::extension::LineString; -use vortex_geo::extension::MultiLineString; -use vortex_geo::extension::MultiPoint; -use vortex_geo::extension::MultiPolygon; -use vortex_geo::extension::Point; -use vortex_geo::extension::Polygon; -use vortex_geo::extension::WellKnownBinary; -use vortex_geo::extension::native_geometry_scalar_from_wkb; -use vortex_geo::scalar_fn::contains::GeoContains; -use vortex_geo::scalar_fn::distance::GeoDistance; -use vortex_geo::scalar_fn::intersects::GeoIntersects; +use vortex_spatial::extension::LineString; +use vortex_spatial::extension::MultiLineString; +use vortex_spatial::extension::MultiPoint; +use vortex_spatial::extension::MultiPolygon; +use vortex_spatial::extension::Point; +use vortex_spatial::extension::Polygon; +use vortex_spatial::extension::WellKnownBinary; +use vortex_spatial::extension::native_geometry_scalar_from_wkb; +use vortex_spatial::scalar_fn::contains::SpatialContains; +use vortex_spatial::scalar_fn::distance::SpatialDistance; +use vortex_spatial::scalar_fn::intersects::SpatialIntersects; use crate::convert::dtype::FromLogicalType; use crate::cpp::DUCKDB_TYPE; @@ -118,10 +118,10 @@ struct ConvertCtx<'a> { fields: Option<&'a [DuckdbField]>, } -/// Whether `name` is a non-nullable native geometry column of the scan. The pushed geo kernels -/// reject nullable operands and cannot evaluate `vortex.geo.wkb` columns, which also surface to +/// Whether `name` is a non-nullable native geometry column of the scan. The pushed spatial kernels +/// reject nullable operands and cannot evaluate `vortex.st.wkb` columns, which also surface to /// DuckDB as `GEOMETRY`. -fn is_native_geo_column(fields: Option<&[DuckdbField]>, name: &str) -> bool { +fn is_native_spatial_column(fields: Option<&[DuckdbField]>, name: &str) -> bool { fields .into_iter() .flatten() @@ -139,9 +139,9 @@ fn is_native_geo_column(fields: Option<&[DuckdbField]>, name: &str) -> bool { }) } -/// Lower a geo operand: a `GEOMETRY` literal arrives as WKB, decoded once to its native type so the -/// pushed `GeoDistance` stays native; a column must be native geometry. `None` skips the push. -fn geo_operand( +/// Lower a spatial operand: a `GEOMETRY` literal arrives as WKB, decoded once to its native type so the +/// pushed `SpatialDistance` stays native; a column must be native geometry. `None` skips the push. +fn spatial_operand( value: &duckdb::ExpressionRef, ctx: ConvertCtx<'_>, ) -> VortexResult> { @@ -161,7 +161,7 @@ fn geo_operand( Ok(native_geometry_scalar_from_wkb(buf.as_slice())?.map(lit)) } Some(BoundColumnRef(col_ref)) - if is_native_geo_column(ctx.fields, col_ref.name.as_ref()) => + if is_native_spatial_column(ctx.fields, col_ref.name.as_ref()) => { try_from_expression_inner(value, ctx) } @@ -169,72 +169,72 @@ fn geo_operand( } } -/// Lower all geometry operands of a geo function. Returns `None`, skipping the push, when any +/// Lower all geometry operands of a spatial function. Returns `None`, skipping the push, when any /// operand is neither a constant geometry nor a native geometry column. -fn geo_operands( +fn spatial_operands( children: &[&duckdb::ExpressionRef], ctx: ConvertCtx<'_>, ) -> VortexResult>> { children .iter() - .map(|child| geo_operand(child, ctx)) + .map(|child| spatial_operand(child, ctx)) .collect() } -/// Lower geo UDFs to native Vortex geo ops so the work runs in the scan. `None` otherwise. -fn try_from_geo_function( +/// Lower spatial UDFs to native Vortex spatial operations so the work runs in the scan. `None` otherwise. +fn try_from_spatial_function( name: &str, func: &BoundFunction, ctx: ConvertCtx<'_>, ) -> VortexResult> { let children: Vec<_> = func.children().collect(); let expr = match name.to_ascii_lowercase().as_str() { - // Spatial's own st_dwithin folds the radius into bind data; the override + // DuckDB's spatial extension folds the radius of `ST_DWithin` into bind data; the override // (cpp/spatial_overrides.cpp) keeps it visible here as `children[2]`. "st_dwithin" => { if children.len() != 3 { return Ok(None); } - let Some(operands) = geo_operands(&children[..2], ctx)? else { + let Some(operands) = spatial_operands(&children[..2], ctx)? else { return Ok(None); }; // A non-constant radius is left for DuckDB to evaluate. let Some(distance) = from_bound_f64(children[2])? else { return Ok(None); }; - let geo_distance = GeoDistance.new_expr(ScalarEmptyOptions, operands); - Binary.new_expr(Operator::Lte, [geo_distance, lit(distance)]) + let spatial_distance = SpatialDistance.new_expr(ScalarEmptyOptions, operands); + Binary.new_expr(Operator::Lte, [spatial_distance, lit(distance)]) } "st_distance" => { if children.len() != 2 { return Ok(None); } - let Some(operands) = geo_operands(&children, ctx)? else { + let Some(operands) = spatial_operands(&children, ctx)? else { return Ok(None); }; - GeoDistance.new_expr(ScalarEmptyOptions, operands) + SpatialDistance.new_expr(ScalarEmptyOptions, operands) } "st_intersects" => { if children.len() != 2 { return Ok(None); } - let Some(operands) = geo_operands(&children, ctx)? else { + let Some(operands) = spatial_operands(&children, ctx)? else { return Ok(None); }; - GeoIntersects.new_expr(ScalarEmptyOptions, operands) + SpatialIntersects.new_expr(ScalarEmptyOptions, operands) } containment @ ("st_contains" | "st_within") => { if children.len() != 2 { return Ok(None); } - let Some(mut operands) = geo_operands(&children, ctx)? else { + let Some(mut operands) = spatial_operands(&children, ctx)? else { return Ok(None); }; // `st_within(a, b)` is `st_contains(b, a)`; both lower to the contains kernel. if containment == "st_within" { operands.swap(0, 1); } - GeoContains.new_expr(ScalarEmptyOptions, operands) + SpatialContains.new_expr(ScalarEmptyOptions, operands) } _ => return Ok(None), }; @@ -330,8 +330,8 @@ fn try_from_bound_function( return Ok(None); } } - // Geo UDFs are handled here; non-geo names return `None` inside. - name => return try_from_geo_function(name, func, ctx), + // Spatial UDFs are handled here; non-spatial names return `None` inside. + name => return try_from_spatial_function(name, func, ctx), }; Ok(Some(expr)) @@ -354,7 +354,7 @@ pub(super) fn try_from_bound_expression_with_col_sub( value: &duckdb::ExpressionRef, col_sub: &Expression, ) -> VortexResult> { - // No fields: scan-time table filters never carry geo functions, because + // No fields: scan-time table filters never carry spatial functions, because // `can_push_expression` refuses them. try_from_expression_inner( value, @@ -425,7 +425,7 @@ pub fn can_push_expression(value: &duckdb::ExpressionRef) -> bool { || name == "strlen" || name == "array_length" || (matches!(name, "len" | "length") && is_supported_length_alias(&func)) - // Geo functions are absent on purpose: they push only via + // Spatial functions are absent on purpose: they push only via // `pushdown_complex_filter`, which has the scan's fields to verify the geometry // columns are native. } diff --git a/vortex-duckdb/src/convert/scalar.rs b/vortex-duckdb/src/convert/scalar.rs index 51d0efaa5dd..804db2b05b5 100644 --- a/vortex-duckdb/src/convert/scalar.rs +++ b/vortex-duckdb/src/convert/scalar.rs @@ -47,7 +47,7 @@ use vortex::scalar::PrimitiveScalar; use vortex::scalar::Scalar; use vortex::scalar::ScalarValue; use vortex::scalar::Utf8Scalar; -use vortex_geo::extension::WellKnownBinary; +use vortex_spatial::extension::WellKnownBinary; use crate::convert::dtype::FromLogicalType; use crate::duckdb::LogicalType; @@ -409,8 +409,8 @@ mod tests { use vortex::extension::datetime::TimestampOptions; use vortex::scalar::Scalar; use vortex::scalar::ScalarValue; - use vortex_geo::extension::GeoMetadata; - use vortex_geo::extension::WellKnownBinary; + use vortex_spatial::extension::SpatialMetadata; + use vortex_spatial::extension::WellKnownBinary; use crate::convert::ToDuckDBScalar; use crate::cpp::DUCKDB_TYPE; @@ -480,7 +480,7 @@ mod tests { fn wkb_scalar(crs: Option<&str>, bytes: &[u8]) -> Scalar { Scalar::extension::( - GeoMetadata { + SpatialMetadata { crs: crs.map(str::to_string), }, Scalar::binary(bytes.to_vec(), Nullability::Nullable), @@ -531,7 +531,7 @@ mod tests { #[test] fn test_null_geometry_to_duckdb_scalar() { let dtype = ExtDType::::try_new( - GeoMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), }, DType::Binary(Nullability::Nullable), diff --git a/vortex-duckdb/src/convert/vector.rs b/vortex-duckdb/src/convert/vector.rs index 76d8f92f48e..4a5bd42cda5 100644 --- a/vortex-duckdb/src/convert/vector.rs +++ b/vortex-duckdb/src/convert/vector.rs @@ -30,8 +30,8 @@ use vortex::error::VortexResult; use vortex::error::vortex_bail; use vortex::extension::datetime::TimeUnit; use vortex::mask::Mask; -use vortex_geo::extension::GeoMetadata; -use vortex_geo::extension::WellKnownBinary; +use vortex_spatial::extension::SpatialMetadata; +use vortex_spatial::extension::WellKnownBinary; use crate::cpp::DUCKDB_TYPE; use crate::cpp::duckdb_date; @@ -262,7 +262,7 @@ pub fn flat_vector_to_vortex(vector: &VectorRef, len: usize) -> VortexResult::try_new( - GeoMetadata { crs }, + SpatialMetadata { crs }, DType::Binary(Nullability::Nullable), )? .erased(); @@ -402,7 +402,7 @@ mod tests { use vortex::error::VortexExpect; use vortex::mask::Mask; use vortex_array::array_session; - use vortex_geo::extension::WellKnownBinaryData; + use vortex_spatial::extension::WellKnownBinaryData; use wkb::writer::WriteOptions; use wkb::writer::write_point; @@ -1046,7 +1046,10 @@ mod tests { .into_owned(); let wkb_data = WellKnownBinaryData::try_from(extension)?; - assert_eq!(wkb_data.geo_metadata().crs.as_deref(), Some("EPSG:4326")); + assert_eq!( + wkb_data.spatial_metadata().crs.as_deref(), + Some("EPSG:4326") + ); let storage = wkb_data .wkb_values() diff --git a/vortex-duckdb/src/e2e_test/mod.rs b/vortex-duckdb/src/e2e_test/mod.rs index 5d41df51220..3bba671e79b 100644 --- a/vortex-duckdb/src/e2e_test/mod.rs +++ b/vortex-duckdb/src/e2e_test/mod.rs @@ -2,6 +2,6 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors #[cfg(test)] -mod geo_pushdown_test; +mod spatial_pushdown_test; #[cfg(test)] mod vortex_scan_test; diff --git a/vortex-duckdb/src/e2e_test/geo_pushdown_test.rs b/vortex-duckdb/src/e2e_test/spatial_pushdown_test.rs similarity index 90% rename from vortex-duckdb/src/e2e_test/geo_pushdown_test.rs rename to vortex-duckdb/src/e2e_test/spatial_pushdown_test.rs index f058de20d47..39301da81b4 100644 --- a/vortex-duckdb/src/e2e_test/geo_pushdown_test.rs +++ b/vortex-duckdb/src/e2e_test/spatial_pushdown_test.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Pushdown tests for the geo scalar functions: every lowered filter must reach the Vortex +//! Pushdown tests for the spatial scalar functions: every lowered filter must reach the Vortex //! scan on a direct file scan and through a view. use num_traits::AsPrimitive; @@ -14,8 +14,8 @@ use vortex::file::WriteOptionsSessionExt; use vortex::io::runtime::BlockingRuntime; use vortex_array::arrays::ExtensionArray; use vortex_array::dtype::extension::ExtDType; -use vortex_geo::extension::GeoMetadata; -use vortex_geo::extension::Point; +use vortex_spatial::extension::Point; +use vortex_spatial::extension::SpatialMetadata; use crate::RUNTIME; use crate::SESSION; @@ -60,7 +60,8 @@ fn native_point_file() -> NamedTempFile { .unwrap() .into_array(); let dtype = - ExtDType::::try_new(GeoMetadata { crs: None }, storage.dtype().clone()).unwrap(); + ExtDType::::try_new(SpatialMetadata { crs: None }, storage.dtype().clone()) + .unwrap(); let points = ExtensionArray::new(dtype.erased(), storage).into_array(); let file = NamedTempFile::with_suffix(".vortex").unwrap(); @@ -126,21 +127,21 @@ fn assert_pushed(conn: &Connection, table: &str, filter: &str, expected: i64) { assert!(!plan.contains("FILTER"), "filter was not pushed:\n{plan}"); } -/// Every lowered geo filter pushes on a direct file scan. +/// Every lowered spatial filter pushes on a direct file scan. #[rstest] #[case::st_intersects(ST_INTERSECTS_FILTER, 3)] #[case::st_distance(ST_DISTANCE_FILTER, 2)] #[case::st_dwithin(ST_DWITHIN_FILTER, 2)] #[case::st_contains(ST_CONTAINS_FILTER, 2)] #[case::st_within(ST_WITHIN_FILTER, 2)] -fn geo_filter_pushes_on_file_scan(#[case] filter: &str, #[case] expected: i64) { +fn spatial_filter_pushes_on_file_scan(#[case] filter: &str, #[case] expected: i64) { let file = native_point_file(); let (_db, conn) = spatial_database(); let table = format!("'{}'", file.path().to_string_lossy()); assert_pushed(&conn, &table, filter, expected); } -/// Every lowered geo filter pushes through a view; without the overrides, DuckDB would keep +/// Every lowered spatial filter pushes through a view; without the overrides, DuckDB would keep /// `ST_Intersects` (can-throw) above the view's projection and hide `ST_DWithin`'s radius. #[rstest] #[case::st_intersects(ST_INTERSECTS_FILTER, 3)] @@ -148,7 +149,7 @@ fn geo_filter_pushes_on_file_scan(#[case] filter: &str, #[case] expected: i64) { #[case::st_dwithin(ST_DWITHIN_FILTER, 2)] #[case::st_contains(ST_CONTAINS_FILTER, 2)] #[case::st_within(ST_WITHIN_FILTER, 2)] -fn geo_filter_pushes_through_view(#[case] filter: &str, #[case] expected: i64) { +fn spatial_filter_pushes_through_view(#[case] filter: &str, #[case] expected: i64) { let file = native_point_file(); let (_db, conn) = spatial_database(); conn.query(&format!( diff --git a/vortex-duckdb/src/e2e_test/vortex_scan_test.rs b/vortex-duckdb/src/e2e_test/vortex_scan_test.rs index 246317b28bd..0876be1ca4c 100644 --- a/vortex-duckdb/src/e2e_test/vortex_scan_test.rs +++ b/vortex-duckdb/src/e2e_test/vortex_scan_test.rs @@ -45,10 +45,10 @@ use vortex_array::arrays::ExtensionArray; use vortex_array::arrays::varbin::builder::VarBinBuilder; use vortex_array::dtype::DType; use vortex_array::dtype::extension::ExtDType; -use vortex_geo::extension::GeoMetadata; -use vortex_geo::extension::WellKnownBinary; use vortex_runend::RunEnd; use vortex_sequence::Sequence; +use vortex_spatial::extension::SpatialMetadata; +use vortex_spatial::extension::WellKnownBinary; use wkb::writer::WriteOptions; use crate::RUNTIME; @@ -992,7 +992,7 @@ fn test_geometry() { let geometry = ExtensionArray::new( ExtDType::::try_new( - GeoMetadata { + SpatialMetadata { crs: Some("EPSG:32600".to_string()), }, geometry.dtype().clone(), diff --git a/vortex-duckdb/src/exporter/extension.rs b/vortex-duckdb/src/exporter/extension.rs index c07dc25a886..294c398e6b1 100644 --- a/vortex-duckdb/src/exporter/extension.rs +++ b/vortex-duckdb/src/exporter/extension.rs @@ -8,23 +8,23 @@ use vortex::array::arrays::extension::ExtensionArrayExt; use vortex::array::extension::datetime::AnyTemporal; use vortex::error::VortexResult; use vortex::error::vortex_bail; -use vortex_geo::extension::LineString; -use vortex_geo::extension::LineStringData; -use vortex_geo::extension::MultiLineString; -use vortex_geo::extension::MultiLineStringData; -use vortex_geo::extension::MultiPoint; -use vortex_geo::extension::MultiPointData; -use vortex_geo::extension::MultiPolygon; -use vortex_geo::extension::MultiPolygonData; -use vortex_geo::extension::Point; -use vortex_geo::extension::PointData; -use vortex_geo::extension::Polygon; -use vortex_geo::extension::PolygonData; -use vortex_geo::extension::WellKnownBinary; -use vortex_geo::extension::WellKnownBinaryData; +use vortex_spatial::extension::LineString; +use vortex_spatial::extension::LineStringData; +use vortex_spatial::extension::MultiLineString; +use vortex_spatial::extension::MultiLineStringData; +use vortex_spatial::extension::MultiPoint; +use vortex_spatial::extension::MultiPointData; +use vortex_spatial::extension::MultiPolygon; +use vortex_spatial::extension::MultiPolygonData; +use vortex_spatial::extension::Point; +use vortex_spatial::extension::PointData; +use vortex_spatial::extension::Polygon; +use vortex_spatial::extension::PolygonData; +use vortex_spatial::extension::WellKnownBinary; +use vortex_spatial::extension::WellKnownBinaryData; use crate::exporter::ColumnExporter; -use crate::exporter::geo; +use crate::exporter::spatial; use crate::exporter::temporal; pub(crate) fn new_exporter( @@ -36,31 +36,31 @@ pub(crate) fn new_exporter( } if ext.ext_dtype().is::() { - return geo::new_wkb_exporter(WellKnownBinaryData::try_from(ext)?, ctx); + return spatial::new_wkb_exporter(WellKnownBinaryData::try_from(ext)?, ctx); } if ext.ext_dtype().is::() { - return geo::new_point_exporter(PointData::try_from(ext)?, ctx); + return spatial::new_point_exporter(PointData::try_from(ext)?, ctx); } if ext.ext_dtype().is::() { - return geo::new_linestring_exporter(LineStringData::try_from(ext)?, ctx); + return spatial::new_linestring_exporter(LineStringData::try_from(ext)?, ctx); } if ext.ext_dtype().is::() { - return geo::new_multipoint_exporter(MultiPointData::try_from(ext)?, ctx); + return spatial::new_multipoint_exporter(MultiPointData::try_from(ext)?, ctx); } if ext.ext_dtype().is::() { - return geo::new_polygon_exporter(PolygonData::try_from(ext)?, ctx); + return spatial::new_polygon_exporter(PolygonData::try_from(ext)?, ctx); } if ext.ext_dtype().is::() { - return geo::new_multilinestring_exporter(MultiLineStringData::try_from(ext)?, ctx); + return spatial::new_multilinestring_exporter(MultiLineStringData::try_from(ext)?, ctx); } if ext.ext_dtype().is::() { - return geo::new_multipolygon_exporter(MultiPolygonData::try_from(ext)?, ctx); + return spatial::new_multipolygon_exporter(MultiPolygonData::try_from(ext)?, ctx); } vortex_bail!("no non-temporal extension exporter") diff --git a/vortex-duckdb/src/exporter/mod.rs b/vortex-duckdb/src/exporter/mod.rs index 035cce5b8fb..c75eaf474da 100644 --- a/vortex-duckdb/src/exporter/mod.rs +++ b/vortex-duckdb/src/exporter/mod.rs @@ -11,13 +11,13 @@ mod decimal; mod dict; mod extension; mod fixed_size_list; -mod geo; mod list; mod list_view; mod primitive; mod rle; mod run_end; mod sequence; +mod spatial; mod struct_; mod temporal; mod validity; diff --git a/vortex-duckdb/src/exporter/geo.rs b/vortex-duckdb/src/exporter/spatial.rs similarity index 88% rename from vortex-duckdb/src/exporter/geo.rs rename to vortex-duckdb/src/exporter/spatial.rs index ccc1893ea18..51941c1156c 100644 --- a/vortex-duckdb/src/exporter/geo.rs +++ b/vortex-duckdb/src/exporter/spatial.rs @@ -4,18 +4,18 @@ use vortex::array::ExecutionCtx; use vortex::array::arrays::VarBinViewArray; use vortex::error::VortexResult; -use vortex_geo::extension::LineStringData; -use vortex_geo::extension::MultiLineStringData; -use vortex_geo::extension::MultiPointData; -use vortex_geo::extension::MultiPolygonData; -use vortex_geo::extension::PointData; -use vortex_geo::extension::PolygonData; -use vortex_geo::extension::WellKnownBinaryData; +use vortex_spatial::extension::LineStringData; +use vortex_spatial::extension::MultiLineStringData; +use vortex_spatial::extension::MultiPointData; +use vortex_spatial::extension::MultiPolygonData; +use vortex_spatial::extension::PointData; +use vortex_spatial::extension::PolygonData; +use vortex_spatial::extension::WellKnownBinaryData; use crate::exporter::ColumnExporter; use crate::exporter::varbinview::new_exporter; -/// Create a new exporter for geospatial data stored as Well-Known Binary (WKB) format. +/// Create a new exporter for spatial data stored as Well-Known Binary (WKB) format. pub(crate) fn new_wkb_exporter( array: WellKnownBinaryData, ctx: &mut ExecutionCtx, diff --git a/vortex-duckdb/src/lib.rs b/vortex-duckdb/src/lib.rs index c68924868b3..fe14c86f8f6 100644 --- a/vortex-duckdb/src/lib.rs +++ b/vortex-duckdb/src/lib.rs @@ -45,7 +45,7 @@ mod e2e_test; static RUNTIME: LazyLock = LazyLock::new(CurrentThreadRuntime::new); static SESSION: LazyLock = LazyLock::new(|| { let session = VortexSession::default().with_handle(RUNTIME.handle()); - vortex_geo::initialize(&session); + vortex_spatial::initialize(&session); session }); diff --git a/vortex-geo/README.md b/vortex-geo/README.md deleted file mode 100644 index 139a49323a3..00000000000 --- a/vortex-geo/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# vortex-geo - -Geospatial data types and layouts for Vortex. This crate is the Vortex-equivalent of the -[geoarrow](https://github.com/geoarrow/geoarrow-rs) series of crates. - -You can import this crate into your application to add support for reading/write geospatial Vector -data as part of Vortex files. \ No newline at end of file diff --git a/vortex-jni/Cargo.toml b/vortex-jni/Cargo.toml index f511f9614d3..648c1a79e4f 100644 --- a/vortex-jni/Cargo.toml +++ b/vortex-jni/Cargo.toml @@ -34,8 +34,8 @@ url = { workspace = true } vortex = { workspace = true, features = ["object_store", "files"] } vortex-arrow = { workspace = true } vortex-cloud = { workspace = true, optional = true } -vortex-geo = { workspace = true } vortex-parquet-variant = { workspace = true } +vortex-spatial = { workspace = true } [dev-dependencies] jni = { workspace = true, features = ["invocation"] } diff --git a/vortex-jni/src/session.rs b/vortex-jni/src/session.rs index 881babc5dea..081f55e9b56 100644 --- a/vortex-jni/src/session.rs +++ b/vortex-jni/src/session.rs @@ -18,7 +18,7 @@ use crate::RUNTIME; pub(crate) fn new_session() -> Box { let session = VortexSession::default().with_handle(RUNTIME.handle()); vortex_parquet_variant::initialize(&session); - vortex_geo::initialize(&session); + vortex_spatial::initialize(&session); Box::new(session) } diff --git a/vortex-layout/src/layouts/zoned/writer.rs b/vortex-layout/src/layouts/zoned/writer.rs index 341eded62fb..cca134c0759 100644 --- a/vortex-layout/src/layouts/zoned/writer.rs +++ b/vortex-layout/src/layouts/zoned/writer.rs @@ -217,7 +217,7 @@ fn default_zoned_aggregate_fns(dtype: &DType, session: &VortexSession) -> Arc<[A aggregate_fns.push(NanCount.bind(EmptyOptions)); aggregate_fns.push(NullCount.bind(EmptyOptions)); - // Stats from geo extension types are discovered from the registry at runtime instead. + // Stats from spatial extension types are discovered from the registry at runtime instead. aggregate_fns.extend(session.aggregate_fns().zone_stat_defaults(dtype)); aggregate_fns.into() diff --git a/vortex-geo/Cargo.toml b/vortex-spatial/Cargo.toml similarity index 90% rename from vortex-geo/Cargo.toml rename to vortex-spatial/Cargo.toml index 6eddab411d9..8b790da3c05 100644 --- a/vortex-geo/Cargo.toml +++ b/vortex-spatial/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "vortex-geo" -description = "Geospatial encodings and layouts for Vortex files" +name = "vortex-spatial" +description = "Spatial encodings and layouts for Vortex files" authors.workspace = true categories.workspace = true edition.workspace = true @@ -39,8 +39,8 @@ divan = { workspace = true } mimalloc = { workspace = true } rstest = { workspace = true } vortex-array = { workspace = true, features = ["_test-harness"] } -vortex-geo = { path = ".", features = ["_test-harness"] } vortex-layout = { workspace = true } +vortex-spatial = { path = ".", features = ["_test-harness"] } [[bench]] name = "envelope" diff --git a/vortex-spatial/README.md b/vortex-spatial/README.md new file mode 100644 index 00000000000..e522913cf60 --- /dev/null +++ b/vortex-spatial/README.md @@ -0,0 +1,7 @@ +# vortex-spatial + +Spatial data types and layouts for Vortex. This crate is the Vortex equivalent of the +[GeoArrow](https://github.com/geoarrow/geoarrow-rs) series of crates. + +You can import this crate into your application to add support for reading and writing spatial +vector data as part of Vortex files. diff --git a/vortex-geo/benches/binary_predicates.rs b/vortex-spatial/benches/binary_predicates.rs similarity index 85% rename from vortex-geo/benches/binary_predicates.rs rename to vortex-spatial/benches/binary_predicates.rs index e8aeff53b10..b84ab67b17c 100644 --- a/vortex-geo/benches/binary_predicates.rs +++ b/vortex-spatial/benches/binary_predicates.rs @@ -5,8 +5,8 @@ //! on the cost of a batch-constant operand. //! //! The constant is a 128-vertex query polygon, the shape a spatial filter broadcasts against a -//! column. Arms pair it with a point column (geo answers those pairings with direct -//! point-in-polygon algorithms) and with a small-polygon column (geo routes those pairings through +//! column. Arms pair it with a point column (the external `geo` crate answers those pairings with +//! direct point-in-polygon algorithms) and with a small-polygon column (`geo` routes those through //! bounding-box prechecks and `relate`), covering both a mostly-disjoint dataset where the bbox //! early-out rejects nearly every row and an all-overlapping one where it never does. The //! column-x-column arms are the control: no operand is constant, so a prepared path has nothing to @@ -17,7 +17,7 @@ //! around 120 µs, so no row count both fits the per-iteration budget and exercises the row loop. //! [`intersects::polygons_overlapping_x_constant`] covers the never-rejects case instead. //! -//! Run with `cargo bench -p vortex-geo --bench binary_predicates`. +//! Run with `cargo bench -p vortex-spatial --bench binary_predicates`. #![expect(clippy::unwrap_used)] @@ -36,12 +36,12 @@ use vortex_array::arrays::ConstantArray; use vortex_array::arrays::MaskedArray; use vortex_array::validity::Validity; use vortex_error::VortexResult; -use vortex_geo::scalar_fn::contains::GeoContains; -use vortex_geo::scalar_fn::intersects::GeoIntersects; -use vortex_geo::test_harness::geo_session; -use vortex_geo::test_harness::point_column; -use vortex_geo::test_harness::polygon_column; use vortex_session::VortexSession; +use vortex_spatial::scalar_fn::contains::SpatialContains; +use vortex_spatial::scalar_fn::intersects::SpatialIntersects; +use vortex_spatial::test_harness::point_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. @@ -52,7 +52,7 @@ fn main() { divan::main(); } -static SESSION: LazyLock = LazyLock::new(geo_session); +static SESSION: LazyLock = LazyLock::new(spatial_session); /// The ordinary arms use the same row count so results are comparable across shapes. A geometry /// predicate costs roughly a microsecond per row under CodSpeed's CPU simulation, which caps the @@ -176,7 +176,7 @@ mod contains { let points = points(ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(polygons.clone(), points.clone()), + SpatialContains::try_new_array(polygons.clone(), points.clone()), &mut ctx, ) }); @@ -188,12 +188,15 @@ mod contains { let mut ctx = SESSION.create_execution_ctx(); let a = squares_mostly_overlapping(ROWS); let b = squares_mostly_disjoint(ROWS); - bencher - .counter(ItemsCount::new(ROWS)) - .bench_local(|| execute(GeoContains::try_new_array(a.clone(), b.clone()), &mut ctx)); + bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { + execute( + SpatialContains::try_new_array(a.clone(), b.clone()), + &mut ctx, + ) + }); } - /// Constant container against a point column: geo's direct point-in-polygon pairing. + /// Constant container against a point column: the `geo` crate's direct point-in-polygon pairing. #[divan::bench] fn constant_x_points(bencher: Bencher) { let mut ctx = SESSION.create_execution_ctx(); @@ -201,7 +204,7 @@ mod contains { let points = points(ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(query.clone(), points.clone()), + SpatialContains::try_new_array(query.clone(), points.clone()), &mut ctx, ) }); @@ -216,7 +219,7 @@ mod contains { let polygons = squares_mostly_disjoint(ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(query.clone(), polygons.clone()), + SpatialContains::try_new_array(query.clone(), polygons.clone()), &mut ctx, ) }); @@ -230,7 +233,7 @@ mod contains { let points = nullable_every(points(ROWS), 8); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(query.clone(), points.clone()), + SpatialContains::try_new_array(query.clone(), points.clone()), &mut ctx, ) }); @@ -244,7 +247,7 @@ mod contains { let polygons = nullable_every(squares_mostly_disjoint(ROWS), 8); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(query.clone(), polygons.clone()), + SpatialContains::try_new_array(query.clone(), polygons.clone()), &mut ctx, ) }); @@ -259,7 +262,7 @@ mod contains { let point = point_constant(&mut ctx, ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(polygons.clone(), point.clone()), + SpatialContains::try_new_array(polygons.clone(), point.clone()), &mut ctx, ) }); @@ -273,7 +276,7 @@ mod contains { let point = point_constant(&mut ctx, ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(polygons.clone(), point.clone()), + SpatialContains::try_new_array(polygons.clone(), point.clone()), &mut ctx, ) }); @@ -288,7 +291,7 @@ mod contains { let points = half_valid(points(ROWS), 1); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoContains::try_new_array(polygons.clone(), points.clone()), + SpatialContains::try_new_array(polygons.clone(), points.clone()), &mut ctx, ) }); @@ -304,12 +307,15 @@ mod intersects { let mut ctx = SESSION.create_execution_ctx(); let a = squares_mostly_overlapping(ROWS); let b = squares_mostly_disjoint(ROWS); - bencher - .counter(ItemsCount::new(ROWS)) - .bench_local(|| execute(GeoIntersects::try_new_array(a.clone(), b.clone()), &mut ctx)); + bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { + execute( + SpatialIntersects::try_new_array(a.clone(), b.clone()), + &mut ctx, + ) + }); } - /// Point column against the constant query: geo answers point-x-polygon directly, with no + /// Point column against the constant query: the `geo` crate answers point-x-polygon directly, with no /// bbox precheck to hoist. #[divan::bench] fn points_x_constant(bencher: Bencher) { @@ -318,7 +324,7 @@ mod intersects { let query = query_constant(&mut ctx, ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoIntersects::try_new_array(points.clone(), query.clone()), + SpatialIntersects::try_new_array(points.clone(), query.clone()), &mut ctx, ) }); @@ -333,7 +339,7 @@ mod intersects { let query = query_constant(&mut ctx, ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoIntersects::try_new_array(polygons.clone(), query.clone()), + SpatialIntersects::try_new_array(polygons.clone(), query.clone()), &mut ctx, ) }); @@ -351,7 +357,7 @@ mod intersects { .counter(ItemsCount::new(OVERLAPPING_POLYGON_ROWS)) .bench_local(|| { execute( - GeoIntersects::try_new_array(polygons.clone(), query.clone()), + SpatialIntersects::try_new_array(polygons.clone(), query.clone()), &mut ctx, ) }); @@ -365,7 +371,7 @@ mod intersects { let query = query_constant(&mut ctx, ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoIntersects::try_new_array(points.clone(), query.clone()), + SpatialIntersects::try_new_array(points.clone(), query.clone()), &mut ctx, ) }); @@ -379,7 +385,7 @@ mod intersects { let query = query_constant(&mut ctx, ROWS); bencher.counter(ItemsCount::new(ROWS)).bench_local(|| { execute( - GeoIntersects::try_new_array(polygons.clone(), query.clone()), + SpatialIntersects::try_new_array(polygons.clone(), query.clone()), &mut ctx, ) }); diff --git a/vortex-geo/benches/distance.rs b/vortex-spatial/benches/distance.rs similarity index 92% rename from vortex-geo/benches/distance.rs rename to vortex-spatial/benches/distance.rs index 3abd8d91d05..6713c8d4dac 100644 --- a/vortex-geo/benches/distance.rs +++ b/vortex-spatial/benches/distance.rs @@ -24,18 +24,18 @@ use vortex_array::arrays::ConstantArray; use vortex_array::arrays::MaskedArray; use vortex_array::validity::Validity; use vortex_error::VortexResult; -use vortex_geo::scalar_fn::distance::GeoDistance; -use vortex_geo::test_harness::geo_session; -use vortex_geo::test_harness::point_column; -use vortex_geo::test_harness::polygon_column; use vortex_session::VortexSession; +use vortex_spatial::scalar_fn::distance::SpatialDistance; +use vortex_spatial::test_harness::point_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(geo_session); +static SESSION: LazyLock = LazyLock::new(spatial_session); const POINT_ROWS: usize = 512; const POLYGON_ROWS: usize = 32; @@ -107,7 +107,7 @@ fn bench_distance(bencher: Bencher, lhs: ArrayRef, rhs: ArrayRef) { let mut ctx = SESSION.create_execution_ctx(); bencher.counter(ItemsCount::new(rows)).bench_local(|| { execute( - GeoDistance::try_new_array(lhs.clone(), rhs.clone()), + SpatialDistance::try_new_array(lhs.clone(), rhs.clone()), &mut ctx, ) }); diff --git a/vortex-geo/benches/envelope.rs b/vortex-spatial/benches/envelope.rs similarity index 88% rename from vortex-geo/benches/envelope.rs rename to vortex-spatial/benches/envelope.rs index 1aa26d86207..30a02d4d8b6 100644 --- a/vortex-geo/benches/envelope.rs +++ b/vortex-spatial/benches/envelope.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Microbenchmark for the `vortex.geo.envelope` scalar function: per-row bounding boxes over +//! Microbenchmark for the `vortex.st.envelope` scalar function: per-row bounding boxes over //! native geometry storage. //! //! Cases vary the two axes that dominate the kernel's cost profile: @@ -12,7 +12,7 @@ //! //! All cases share the same row count, so numbers are comparable across shapes. //! -//! Run with `cargo bench -p vortex-geo --bench envelope`. +//! Run with `cargo bench -p vortex-spatial --bench envelope`. #![expect(clippy::unwrap_used)] @@ -26,15 +26,15 @@ use vortex_array::Canonical; use vortex_array::ExecutionCtx; use vortex_array::IntoArray; use vortex_array::VortexSessionExecute; -use vortex_geo::scalar_fn::envelope::GeoEnvelope; -use vortex_geo::test_harness::MultiPolygonRings; -use vortex_geo::test_harness::geo_session; -use vortex_geo::test_harness::multipoint_column; -use vortex_geo::test_harness::multipolygon_column; -use vortex_geo::test_harness::nullable_multipolygon_column; -use vortex_geo::test_harness::nullable_point_column; -use vortex_geo::test_harness::point_column; use vortex_session::VortexSession; +use vortex_spatial::scalar_fn::envelope::SpatialEnvelope; +use vortex_spatial::test_harness::MultiPolygonRings; +use vortex_spatial::test_harness::multipoint_column; +use vortex_spatial::test_harness::multipolygon_column; +use vortex_spatial::test_harness::nullable_multipolygon_column; +use vortex_spatial::test_harness::nullable_point_column; +use vortex_spatial::test_harness::point_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. @@ -45,7 +45,7 @@ fn main() { divan::main(); } -static SESSION: LazyLock = LazyLock::new(geo_session); +static SESSION: LazyLock = LazyLock::new(spatial_session); /// Every case has the same row count so results are comparable across shapes: differences then /// reflect per-row cost (nesting depth, validity handling) rather than input size. @@ -64,7 +64,7 @@ fn coin(i: usize) -> bool { /// Execute the envelope of `column` to completion. fn envelope(column: &ArrayRef, ctx: &mut ExecutionCtx) -> ArrayRef { - GeoEnvelope::try_new_array(column.clone()) + SpatialEnvelope::try_new_array(column.clone()) .unwrap() .into_array() .execute::(ctx) diff --git a/vortex-geo/benches/predicate_bbox.rs b/vortex-spatial/benches/predicate_bbox.rs similarity index 98% rename from vortex-geo/benches/predicate_bbox.rs rename to vortex-spatial/benches/predicate_bbox.rs index 74dcca16b48..f7172c578cc 100644 --- a/vortex-geo/benches/predicate_bbox.rs +++ b/vortex-spatial/benches/predicate_bbox.rs @@ -7,7 +7,7 @@ //! constant's box and therefore pay for both the pre-check and the exact predicate. Comparing the //! candidate cases tracks the worst-case overhead of the optimization. //! -//! Run with `cargo bench -p vortex-geo --bench predicate_bbox`. +//! Run with `cargo bench -p vortex-spatial --bench predicate_bbox`. use std::f64::consts::TAU; use std::sync::LazyLock; diff --git a/vortex-geo/src/aggregate_fn/aabb.rs b/vortex-spatial/src/aggregate_fn/aabb.rs similarity index 94% rename from vortex-geo/src/aggregate_fn/aabb.rs rename to vortex-spatial/src/aggregate_fn/aabb.rs index 8ac5c9e2452..7c3bcaf7659 100644 --- a/vortex-geo/src/aggregate_fn/aabb.rs +++ b/vortex-spatial/src/aggregate_fn/aabb.rs @@ -3,7 +3,7 @@ //! The 2D axis-aligned bounding-box (AABB) aggregate for native geometry columns. -use geo::Rect as GeoRect; +use geo::Rect as SpatialRect; use vortex_array::ArrayRef; use vortex_array::Columnar; use vortex_array::ExecutionCtx; @@ -23,8 +23,8 @@ use vortex_error::vortex_err; use vortex_session::VortexSession; use vortex_session::registry::CachedId; -use crate::extension::GeoMetadata; use crate::extension::Rect; +use crate::extension::SpatialMetadata; use crate::extension::box_storage_dtype; use crate::extension::coordinate::Dimension; use crate::extension::coordinate::box_corners; @@ -41,14 +41,14 @@ pub struct GeometryAabb; /// Running union of geometry AABBs, or `None` until the first row. A transient /// `geo::Rect` value - the persisted stat is the native box (see `to_scalar`). pub struct AabbPartial { - rect: Option>, + rect: Option>, } impl AabbPartial { /// Grow the accumulated box to also cover `other`. - fn merge(&mut self, other: GeoRect) { + fn merge(&mut self, other: SpatialRect) { self.rect = Some(self.rect.map_or(other, |cur| { - GeoRect::new( + SpatialRect::new( ( cur.min().x.min(other.min().x), cur.min().y.min(other.min().y), @@ -65,7 +65,7 @@ impl AabbPartial { /// The stat's type: the native `geoarrow.box` (2D), nullable so an empty group is a null box. fn aabb_dtype() -> DType { DType::Extension( - ExtDType::::try_new(GeoMetadata::default(), aabb_storage_dtype()) + ExtDType::::try_new(SpatialMetadata::default(), aabb_storage_dtype()) .vortex_expect("2D box storage is a valid Rect") .erased(), ) @@ -77,16 +77,16 @@ fn aabb_storage_dtype() -> DType { } /// The AABB of the raw `x`/`y` slices, or `None` when empty. -fn aabb_of(xs: &[f64], ys: &[f64]) -> Option> { +fn aabb_of(xs: &[f64], ys: &[f64]) -> Option> { (!xs.is_empty()).then(|| { let [xmin, ymin, xmax, ymax] = box_corners(xs, ys); - GeoRect::new((xmin, ymin), (xmax, ymax)) + SpatialRect::new((xmin, ymin), (xmax, ymax)) }) } -/// Read an AABB stat scalar (a nullable native `geoarrow.box`) into a [`GeoRect`], or `None` when +/// Read an AABB stat scalar (a nullable native `geoarrow.box`) into a [`SpatialRect`], or `None` when /// the scalar is null (an empty group). -fn rect_from_storage(scalar: &Scalar) -> VortexResult>> { +fn rect_from_storage(scalar: &Scalar) -> VortexResult>> { if scalar.is_null() { return Ok(None); } @@ -99,14 +99,14 @@ fn rect_from_storage(scalar: &Scalar) -> VortexResult>> { .ok_or_else(|| vortex_err!("AABB missing {name}"))?, ) }; - Ok(Some(GeoRect::new( + Ok(Some(SpatialRect::new( (read("xmin")?, read("ymin")?), (read("xmax")?, read("ymax")?), ))) } -/// Serialize a [`GeoRect`] as a native `geoarrow.box` stat scalar (inverse of [`rect_from_storage`]). -fn rect_to_storage(rect: GeoRect) -> Scalar { +/// Serialize a [`SpatialRect`] as a native `geoarrow.box` stat scalar (inverse of [`rect_from_storage`]). +fn rect_to_storage(rect: SpatialRect) -> Scalar { let storage = Scalar::struct_( aabb_storage_dtype(), vec![ @@ -116,7 +116,7 @@ fn rect_to_storage(rect: GeoRect) -> Scalar { Scalar::primitive(rect.max().y, Nullability::NonNullable), ], ); - Scalar::extension::(GeoMetadata::default(), storage) + Scalar::extension::(SpatialMetadata::default(), storage) } impl AggregateFnVTable for GeometryAabb { @@ -124,7 +124,7 @@ impl AggregateFnVTable for GeometryAabb { type Partial = AabbPartial; fn id(&self) -> AggregateFnId { - static ID: CachedId = CachedId::new("vortex.geo.aabb"); + static ID: CachedId = CachedId::new("vortex.st.aabb"); *ID } @@ -229,7 +229,7 @@ impl AggregateFnVTable for GeometryAabb { #[cfg(test)] mod tests { - use geo::Rect as GeoRect; + use geo::Rect as SpatialRect; use vortex_array::ArrayRef; use vortex_array::VortexSessionExecute; use vortex_array::aggregate_fn::Accumulator; @@ -247,7 +247,6 @@ mod tests { use super::GeometryAabb; use super::aabb_dtype; use super::rect_from_storage; - use crate::test_harness::geo_session; use crate::test_harness::linestring_column; use crate::test_harness::multilinestring_column; use crate::test_harness::multipoint_column; @@ -255,6 +254,7 @@ mod tests { use crate::test_harness::nullable_point_column; use crate::test_harness::point_column; use crate::test_harness::polygon_column; + use crate::test_harness::spatial_session; /// One column of every native geometry type over the same `(x, y)` vertex set. fn every_native_column(vertices: &[(f64, f64)]) -> VortexResult> { @@ -393,7 +393,7 @@ mod tests { #[test] fn combine_partials_unions_boxes() -> VortexResult<()> { let bbox = |xmin, ymin, xmax, ymax| AabbPartial { - rect: Some(GeoRect::new((xmin, ymin), (xmax, ymax))), + rect: Some(SpatialRect::new((xmin, ymin), (xmax, ymax))), }; let mut partial = AabbPartial { rect: None }; GeometryAabb.combine_partials( @@ -415,7 +415,7 @@ mod tests { #[test] fn combine_partials_ignores_null() -> VortexResult<()> { let mut partial = AabbPartial { - rect: Some(GeoRect::new((0.0, 0.0), (1.0, 1.0))), + rect: Some(SpatialRect::new((0.0, 0.0), (1.0, 1.0))), }; GeometryAabb.combine_partials(&mut partial, Scalar::null(aabb_dtype()))?; assert_eq!( @@ -455,7 +455,7 @@ mod tests { /// type (so the zoned writer stores it) but none for ordinary numeric columns. #[test] fn registered_as_geometry_zone_default() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); for column in every_native_column(&[(0.0, 0.0), (1.0, 1.0)])? { assert!( diff --git a/vortex-geo/src/aggregate_fn/mod.rs b/vortex-spatial/src/aggregate_fn/mod.rs similarity index 100% rename from vortex-geo/src/aggregate_fn/mod.rs rename to vortex-spatial/src/aggregate_fn/mod.rs diff --git a/vortex-geo/src/extension/coordinate.rs b/vortex-spatial/src/extension/coordinate.rs similarity index 100% rename from vortex-geo/src/extension/coordinate.rs rename to vortex-spatial/src/extension/coordinate.rs diff --git a/vortex-geo/src/extension/linestring.rs b/vortex-spatial/src/extension/linestring.rs similarity index 89% rename from vortex-geo/src/extension/linestring.rs rename to vortex-spatial/src/extension/linestring.rs index 274d20f28ba..8de97ed386e 100644 --- a/vortex-geo/src/extension/linestring.rs +++ b/vortex-spatial/src/extension/linestring.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! The [`LineString`] geometry extension type (`vortex.geo.linestring`): an ordered path of the +//! The [`LineString`] geometry extension type (`vortex.st.linestring`): an ordered path of the //! [`Point`](super::Point) coordinate struct, stored as `List>` and tagged -//! with [`GeoMetadata`] (CRS). +//! with [`SpatialMetadata`] (CRS). use std::sync::Arc; @@ -46,13 +46,13 @@ use vortex_error::vortex_err; use vortex_session::registry::CachedId; use vortex_session::registry::Id; -use super::GeoMetadata; +use super::SpatialMetadata; use super::coordinate::Dimension; use super::coordinate::coordinate_dimension; use super::coordinate::coordinate_storage_dtype; -use super::geo_metadata_from_arrow; use super::geoarrow_metadata; use super::geoarrow_to_wkb; +use super::spatial_metadata_from_arrow; /// A line string: `geoarrow.linestring`, stored as `List>` (an ordered path /// of vertices). @@ -60,12 +60,12 @@ use super::geoarrow_to_wkb; pub struct LineString; impl ExtVTable for LineString { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; // No cheap owned value like Point's `Coordinate`; expose the raw storage scalar. type NativeValue<'a> = &'a ScalarValue; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.linestring"); + static ID: CachedId = CachedId::new("vortex.st.linestring"); *ID } @@ -74,7 +74,7 @@ impl ExtVTable for LineString { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { @@ -107,11 +107,11 @@ 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(geo_metadata: &GeoMetadata, dimension: Dimension) -> LineStringType { - LineStringType::new(dimension.into(), geoarrow_metadata(geo_metadata)) +fn linestring_type(spatial_metadata: &SpatialMetadata, dimension: Dimension) -> LineStringType { + LineStringType::new(dimension.into(), geoarrow_metadata(spatial_metadata)) } -/// Decode `LineString` storage (`List`) to `geo_types` line strings, for the geo scalar +/// Decode `LineString` storage (`List`) to `geo_types` line strings, for the spatial scalar /// functions. CRS does not affect planar geometry ops, so default metadata is used. pub(crate) fn linestring_geometries( storage: &ArrayRef, @@ -121,8 +121,8 @@ pub(crate) fn linestring_geometries( .iter() .map(|geometry| -> VortexResult> { Ok(geometry - .ok_or_else(|| vortex_err!("geo: null geometry is not supported"))? - .map_err(|e| vortex_err!("geo: geometry access failed: {e}"))? + .ok_or_else(|| vortex_err!("spatial: null geometry is not supported"))? + .map_err(|e| vortex_err!("spatial: geometry access failed: {e}"))? .to_geometry()) }) .collect() @@ -131,7 +131,7 @@ pub(crate) fn linestring_geometries( /// Build a geoarrow `LineStringArray` from a `LineString`'s `List` storage. fn linestring_array(storage: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { let linestring_type = linestring_type( - &GeoMetadata::default(), + &SpatialMetadata::default(), linestring_dimension(storage.dtype())?, ); let session = ctx.session().clone(); @@ -178,11 +178,11 @@ impl ArrowExportVTable for LineString { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); 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(geo_metadata, dimension))?; + field.try_with_extension_type(linestring_type(spatial_metadata, dimension))?; Ok(Some(field)) } @@ -249,7 +249,7 @@ impl ArrowImportVTable for LineString { ); ( linestring_meta.dimension().into(), - geo_metadata_from_arrow(linestring_meta.metadata()), + spatial_metadata_from_arrow(linestring_meta.metadata()), ) } else { // Literal: peel the `List` layer to the coordinate struct and read its dimension from @@ -266,7 +266,7 @@ impl ArrowImportVTable for LineString { let Ok(dimension) = Dimension::from_field_names(fields.names()) else { return Ok(None); }; - (dimension, GeoMetadata::default()) + (dimension, SpatialMetadata::default()) }; let storage_dtype = linestring_storage_dtype(dimension, field.is_nullable().into()); @@ -309,11 +309,11 @@ mod tests { use super::LineString; use super::linestring_storage_dtype; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; use crate::extension::coordinate::Dimension; - fn geo_meta() -> GeoMetadata { - GeoMetadata { + fn spatial_meta() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -326,7 +326,7 @@ mod tests { #[case::xyzm(Dimension::Xyzm)] fn linestring_validates_every_dimension(#[case] dim: Dimension) -> VortexResult<()> { let storage = linestring_storage_dtype(dim, Nullability::NonNullable); - ExtDType::::try_new(geo_meta(), storage)?; + ExtDType::::try_new(spatial_meta(), storage)?; Ok(()) } @@ -335,7 +335,7 @@ mod tests { #[test] fn linestring_rejects_invalid_storage() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), primitive).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), primitive).is_err()); Ok(()) } } diff --git a/vortex-geo/src/extension/mod.rs b/vortex-spatial/src/extension/mod.rs similarity index 88% rename from vortex-geo/src/extension/mod.rs rename to vortex-spatial/src/extension/mod.rs index 374ddfc466f..d1e2c37ebf4 100644 --- a/vortex-geo/src/extension/mod.rs +++ b/vortex-spatial/src/extension/mod.rs @@ -64,7 +64,7 @@ use vortex_error::vortex_bail; use vortex_error::vortex_err; pub use wkb::*; -/// Whether `dtype` is one of the native geometry extension types the geo kernels operate on. +/// Whether `dtype` is one of the native geometry extension types the spatial kernels operate on. pub(crate) fn is_native_geometry(dtype: &DType) -> bool { dtype.as_extension_opt().is_some_and(|ext| { ext.is::() @@ -85,7 +85,7 @@ pub(crate) fn flatten_coordinates( ) -> VortexResult { if !is_native_geometry(array.dtype()) { vortex_bail!( - "geo: operand is not a native geometry extension type, was {}", + "spatial: operand is not a native geometry extension type, was {}", array.dtype() ); } @@ -136,7 +136,7 @@ pub(crate) fn flatten_row_offsets( .execute::>(ctx)?; for row_offset in &mut row_offsets { *row_offset = usize::try_from(offsets[*row_offset]) - .map_err(|_| vortex_err!("geo: list offset exceeds usize"))?; + .map_err(|_| vortex_err!("spatial: list offset exceeds usize"))?; } level = list.elements().clone(); } @@ -150,7 +150,7 @@ pub(crate) fn geometries( ) -> VortexResult>> { let Some(ext) = array.dtype().as_extension_opt() else { vortex_bail!( - "geo: operand is not a geometry extension type, was {}", + "spatial: operand is not a geometry extension type, was {}", array.dtype() ); }; @@ -174,11 +174,11 @@ pub(crate) fn geometries( } else if ext.is::() { rect_geometries(&storage, ctx) } else { - vortex_bail!("geo: unsupported geometry extension {}", array.dtype()) + vortex_bail!("spatial: unsupported geometry extension {}", array.dtype()) } } -/// Decode a constant operand scalar to one geo geometry, a constant of any +/// Decode a constant operand scalar to one geometry, a constant of any /// supported geometry type is decoded exactly like a column. pub(crate) fn single_geometry( scalar: &Scalar, @@ -187,13 +187,13 @@ pub(crate) fn single_geometry( let array = ConstantArray::new(scalar.clone(), 1).into_array(); geometries(&array, ctx)? .pop() - .ok_or_else(|| vortex_err!("geo: constant operand decoded to no geometry")) + .ok_or_else(|| vortex_err!("spatial: constant operand decoded to no geometry")) } /// Decode a WKB geometry literal (DuckDB's wire form for `GEOMETRY` constants) to its native /// `Point`/`Polygon`/`MultiPolygon` scalar. `None` for unsupported types. Plan-time, one value only. pub fn native_geometry_scalar_from_wkb(bytes: &[u8]) -> VortexResult> { - let metadata = geoarrow_metadata(&GeoMetadata::default()); + let metadata = geoarrow_metadata(&SpatialMetadata::default()); let binary = BinaryArray::from(vec![Some(bytes)]); let wkb = GenericWkbArray::::try_from(( &binary as &dyn arrow_array::Array, @@ -213,39 +213,39 @@ pub fn native_geometry_scalar_from_wkb(bytes: &[u8]) -> VortexResult { let target = GeoArrowType::LineString( LineStringType::new(Dimension::XY, metadata).with_coord_type(CoordType::Separated), ); - geo_ext_scalar(LineString, to_storage(&target)?)? + spatial_ext_scalar(LineString, to_storage(&target)?)? } GeometryType::Polygon => { let target = GeoArrowType::Polygon( PolygonType::new(Dimension::XY, metadata).with_coord_type(CoordType::Separated), ); - geo_ext_scalar(Polygon, to_storage(&target)?)? + spatial_ext_scalar(Polygon, to_storage(&target)?)? } GeometryType::MultiPoint => { let target = GeoArrowType::MultiPoint( MultiPointType::new(Dimension::XY, metadata).with_coord_type(CoordType::Separated), ); - geo_ext_scalar(MultiPoint, to_storage(&target)?)? + spatial_ext_scalar(MultiPoint, to_storage(&target)?)? } GeometryType::MultiLineString => { let target = GeoArrowType::MultiLineString( MultiLineStringType::new(Dimension::XY, metadata) .with_coord_type(CoordType::Separated), ); - geo_ext_scalar(MultiLineString, to_storage(&target)?)? + spatial_ext_scalar(MultiLineString, to_storage(&target)?)? } GeometryType::MultiPolygon => { let target = GeoArrowType::MultiPolygon( MultiPolygonType::new(Dimension::XY, metadata) .with_coord_type(CoordType::Separated), ); - geo_ext_scalar(MultiPolygon, to_storage(&target)?)? + spatial_ext_scalar(MultiPolygon, to_storage(&target)?)? } _ => return Ok(None), }; @@ -255,29 +255,30 @@ pub fn native_geometry_scalar_from_wkb(bytes: &[u8]) -> VortexResult>( +fn spatial_ext_scalar>( vtable: V, storage: ArrayRef, ) -> VortexResult { - let ext = ExtDType::try_with_vtable(vtable, GeoMetadata::default(), storage.dtype().clone())? - .erased(); + let ext = + ExtDType::try_with_vtable(vtable, SpatialMetadata::default(), storage.dtype().clone())? + .erased(); ExtensionArray::try_new(ext, storage)? .into_array() .scalar_at(0) } -/// Extension metadata that is common to all the geospatial extension types. +/// Extension metadata that is common to all the spatial extension types. /// /// Currently, this is just the coordinate reference system (CRS). /// We may wish to add a second field for edges interpretation in the future similar to /// the GeoArrow standard. #[derive(Clone, PartialEq, Eq, Hash, prost::Message)] -pub struct GeoMetadata { +pub struct SpatialMetadata { #[prost(optional, string, tag = "1")] pub crs: Option, } -impl Display for GeoMetadata { +impl Display for SpatialMetadata { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.crs.as_ref() { Some(crs) => write!(f, "Geometry(crs={crs})"), @@ -286,10 +287,10 @@ impl Display for GeoMetadata { } } -/// The GeoArrow [`Metadata`] equivalent of `geo_metadata`. -pub(crate) fn geoarrow_metadata(geo_metadata: &GeoMetadata) -> Arc { +/// The GeoArrow [`Metadata`] equivalent of `spatial_metadata`. +pub(crate) fn geoarrow_metadata(spatial_metadata: &SpatialMetadata) -> Arc { Arc::new(Metadata::new( - geo_metadata + spatial_metadata .crs .as_ref() .map(|crs| Crs::from_unknown_crs_type(crs.to_string())) @@ -300,15 +301,16 @@ pub(crate) fn geoarrow_metadata(geo_metadata: &GeoMetadata) -> Arc { /// Serialize a native geometry array to WKB (a `WkbView` array) via geoarrow's cast. /// Shared by the `to_wkb` methods on the geometry extension types. -pub(crate) fn geoarrow_to_wkb(geo_array: &dyn GeoArrowArray) -> VortexResult { - let wkb_type = GeoArrowType::WkbView(WkbType::new(geoarrow_metadata(&GeoMetadata::default()))); - let wkb = cast(geo_array, &wkb_type) +pub(crate) fn geoarrow_to_wkb(geoarrow_array: &dyn GeoArrowArray) -> VortexResult { + let wkb_type = + GeoArrowType::WkbView(WkbType::new(geoarrow_metadata(&SpatialMetadata::default()))); + let wkb = cast(geoarrow_array, &wkb_type) .map_err(|e| vortex_err!("failed to cast geometry to WKB: {e}"))?; ArrayRef::from_arrow(wkb.to_array_ref().as_ref(), false) } -/// Recover [`GeoMetadata`] from GeoArrow metadata. -pub(crate) fn geo_metadata_from_arrow(metadata: &Metadata) -> GeoMetadata { +/// Recover [`SpatialMetadata`] from GeoArrow metadata. +pub(crate) fn spatial_metadata_from_arrow(metadata: &Metadata) -> SpatialMetadata { let crs = metadata.crs().crs_value().map(|value| { // `Crs::from_unknown_crs_type` stores the user's string verbatim as a JSON string // value, so prefer the raw string when available to round-trip cleanly. For other @@ -318,7 +320,7 @@ pub(crate) fn geo_metadata_from_arrow(metadata: &Metadata) -> GeoMetadata { .map(str::to_string) .unwrap_or_else(|| value.to_string()) }); - GeoMetadata { crs } + SpatialMetadata { crs } } #[cfg(test)] @@ -334,18 +336,18 @@ mod tests { use super::Point; use super::Polygon; use super::native_geometry_scalar_from_wkb; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; #[test] fn test_metadata() { - let meta = GeoMetadata { + let meta = SpatialMetadata { crs: Some("EPSG:4326".to_string()), }; assert_eq!(meta.to_string(), "Geometry(crs=EPSG:4326)"); // round trip let bytes = meta.encode_to_vec(); - let decoded = GeoMetadata::decode(bytes.as_slice()).unwrap(); + let decoded = SpatialMetadata::decode(bytes.as_slice()).unwrap(); assert_eq!(decoded, meta); } diff --git a/vortex-geo/src/extension/multilinestring.rs b/vortex-spatial/src/extension/multilinestring.rs similarity index 88% rename from vortex-geo/src/extension/multilinestring.rs rename to vortex-spatial/src/extension/multilinestring.rs index 6ba4c4c5324..488871fa94d 100644 --- a/vortex-geo/src/extension/multilinestring.rs +++ b/vortex-spatial/src/extension/multilinestring.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! The [`MultiLineString`] extension type (`vortex.geo.multilinestring`), stored as +//! The [`MultiLineString`] extension type (`vortex.st.multilinestring`), stored as //! `List>>` (line strings → coordinates) and tagged with -//! [`GeoMetadata`]. The storage layout matches [`Polygon`](super::Polygon); the two are +//! [`SpatialMetadata`]. The storage layout matches [`Polygon`](super::Polygon); the two are //! distinguished by their GeoArrow extension name, not their shape. use std::sync::Arc; @@ -47,13 +47,13 @@ use vortex_error::vortex_err; use vortex_session::registry::CachedId; use vortex_session::registry::Id; -use super::GeoMetadata; +use super::SpatialMetadata; use super::coordinate::Dimension; use super::coordinate::coordinate_dimension; use super::coordinate::coordinate_storage_dtype; -use super::geo_metadata_from_arrow; use super::geoarrow_metadata; use super::geoarrow_to_wkb; +use super::spatial_metadata_from_arrow; /// A multilinestring: `geoarrow.multilinestring`, stored as `List>>` /// (line strings of vertices). @@ -61,12 +61,12 @@ use super::geoarrow_to_wkb; pub struct MultiLineString; impl ExtVTable for MultiLineString { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; // No cheap owned value like Point's `Coordinate`; expose the raw storage scalar. type NativeValue<'a> = &'a ScalarValue; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.multilinestring"); + static ID: CachedId = CachedId::new("vortex.st.multilinestring"); *ID } @@ -75,7 +75,7 @@ impl ExtVTable for MultiLineString { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { @@ -112,11 +112,14 @@ pub(crate) fn multilinestring_dimension(dtype: &DType) -> VortexResult MultiLineStringType { - MultiLineStringType::new(dimension.into(), geoarrow_metadata(geo_metadata)) +fn multilinestring_type( + spatial_metadata: &SpatialMetadata, + dimension: Dimension, +) -> MultiLineStringType { + MultiLineStringType::new(dimension.into(), geoarrow_metadata(spatial_metadata)) } -/// Decode storage to `geo_types` for the geo scalar functions (CRS is irrelevant to planar ops). +/// Decode storage to `geo_types` for the spatial scalar functions (CRS is irrelevant to planar ops). pub(crate) fn multilinestring_geometries( storage: &ArrayRef, ctx: &mut ExecutionCtx, @@ -125,8 +128,8 @@ pub(crate) fn multilinestring_geometries( .iter() .map(|geometry| -> VortexResult> { Ok(geometry - .ok_or_else(|| vortex_err!("geo: null geometry is not supported"))? - .map_err(|e| vortex_err!("geo: geometry access failed: {e}"))? + .ok_or_else(|| vortex_err!("spatial: null geometry is not supported"))? + .map_err(|e| vortex_err!("spatial: geometry access failed: {e}"))? .to_geometry()) }) .collect() @@ -138,7 +141,7 @@ fn multilinestring_array( ctx: &mut ExecutionCtx, ) -> VortexResult { let multilinestring_type = multilinestring_type( - &GeoMetadata::default(), + &SpatialMetadata::default(), multilinestring_dimension(storage.dtype())?, ); let session = ctx.session().clone(); @@ -185,11 +188,11 @@ impl ArrowExportVTable for MultiLineString { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); let dimension = multilinestring_dimension(ext_type.storage_dtype())?; let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?; - field.try_with_extension_type(multilinestring_type(geo_metadata, dimension))?; + field.try_with_extension_type(multilinestring_type(spatial_metadata, dimension))?; Ok(Some(field)) } @@ -256,7 +259,7 @@ impl ArrowImportVTable for MultiLineString { ); ( multilinestring_meta.dimension().into(), - geo_metadata_from_arrow(multilinestring_meta.metadata()), + spatial_metadata_from_arrow(multilinestring_meta.metadata()), ) } else { // Literal: peel the two `List` layers to the coordinate struct and read its dimension @@ -276,7 +279,7 @@ impl ArrowImportVTable for MultiLineString { let Ok(dimension) = Dimension::from_field_names(fields.names()) else { return Ok(None); }; - (dimension, GeoMetadata::default()) + (dimension, SpatialMetadata::default()) }; let storage_dtype = multilinestring_storage_dtype(dimension, field.is_nullable().into()); @@ -321,12 +324,12 @@ mod tests { use super::MultiLineString; use super::multilinestring_storage_dtype; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; use crate::extension::coordinate::Dimension; use crate::extension::coordinate::coordinate_storage_dtype; - fn geo_meta() -> GeoMetadata { - GeoMetadata { + fn spatial_meta() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -340,7 +343,7 @@ mod tests { #[case::xyzm(Dimension::Xyzm)] fn multilinestring_validates_every_dimension(#[case] dim: Dimension) -> VortexResult<()> { let storage = multilinestring_storage_dtype(dim, Nullability::NonNullable); - ExtDType::::try_new(geo_meta(), storage)?; + ExtDType::::try_new(spatial_meta(), storage)?; Ok(()) } @@ -348,12 +351,12 @@ mod tests { #[test] fn multilinestring_rejects_invalid_storage() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), primitive).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), primitive).is_err()); // A bare list of coordinates is a single line string, not a multilinestring. let coords = coordinate_storage_dtype(Dimension::Xy, Nullability::NonNullable); let line = DType::List(Arc::new(coords), Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), line).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), line).is_err()); Ok(()) } } diff --git a/vortex-geo/src/extension/multipoint.rs b/vortex-spatial/src/extension/multipoint.rs similarity index 88% rename from vortex-geo/src/extension/multipoint.rs rename to vortex-spatial/src/extension/multipoint.rs index 7d8e429dcc0..4fe22298c5c 100644 --- a/vortex-geo/src/extension/multipoint.rs +++ b/vortex-spatial/src/extension/multipoint.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! The [`MultiPoint`] geometry extension type (`vortex.geo.multipoint`): an unordered set of the +//! The [`MultiPoint`] geometry extension type (`vortex.st.multipoint`): an unordered set of the //! [`Point`](super::Point) coordinate struct, stored as `List>` and tagged -//! with [`GeoMetadata`] (CRS). The storage layout matches [`LineString`](super::LineString); the +//! with [`SpatialMetadata`] (CRS). The storage layout matches [`LineString`](super::LineString); the //! two are distinguished by their GeoArrow extension name, not their shape. use std::sync::Arc; @@ -47,25 +47,25 @@ use vortex_error::vortex_err; use vortex_session::registry::CachedId; use vortex_session::registry::Id; -use super::GeoMetadata; +use super::SpatialMetadata; use super::coordinate::Dimension; use super::coordinate::coordinate_dimension; use super::coordinate::coordinate_storage_dtype; -use super::geo_metadata_from_arrow; use super::geoarrow_metadata; use super::geoarrow_to_wkb; +use super::spatial_metadata_from_arrow; /// A multipoint: `geoarrow.multipoint`, stored as `List>` (a set of points). #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct MultiPoint; impl ExtVTable for MultiPoint { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; // No cheap owned value like Point's `Coordinate`; expose the raw storage scalar. type NativeValue<'a> = &'a ScalarValue; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.multipoint"); + static ID: CachedId = CachedId::new("vortex.st.multipoint"); *ID } @@ -74,7 +74,7 @@ impl ExtVTable for MultiPoint { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { @@ -106,11 +106,11 @@ pub(crate) fn multipoint_dimension(dtype: &DType) -> VortexResult { static ARROW_MULTIPOINT: CachedId = CachedId::new(MultiPointType::NAME); /// The `geoarrow.multipoint` extension type for `dimension`, with separated (struct) coordinates. -fn multipoint_type(geo_metadata: &GeoMetadata, dimension: Dimension) -> MultiPointType { - MultiPointType::new(dimension.into(), geoarrow_metadata(geo_metadata)) +fn multipoint_type(spatial_metadata: &SpatialMetadata, dimension: Dimension) -> MultiPointType { + MultiPointType::new(dimension.into(), geoarrow_metadata(spatial_metadata)) } -/// Decode `MultiPoint` storage (`List`) to `geo_types`, for the geo scalar functions. +/// Decode `MultiPoint` storage (`List`) to `geo_types`, for the spatial scalar functions. pub(crate) fn multipoint_geometries( storage: &ArrayRef, ctx: &mut ExecutionCtx, @@ -119,8 +119,8 @@ pub(crate) fn multipoint_geometries( .iter() .map(|geometry| -> VortexResult> { Ok(geometry - .ok_or_else(|| vortex_err!("geo: null geometry is not supported"))? - .map_err(|e| vortex_err!("geo: geometry access failed: {e}"))? + .ok_or_else(|| vortex_err!("spatial: null geometry is not supported"))? + .map_err(|e| vortex_err!("spatial: geometry access failed: {e}"))? .to_geometry()) }) .collect() @@ -129,7 +129,7 @@ pub(crate) fn multipoint_geometries( /// Build a geoarrow `MultiPointArray` from a `MultiPoint`'s `List` storage. fn multipoint_array(storage: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { let multipoint_type = multipoint_type( - &GeoMetadata::default(), + &SpatialMetadata::default(), multipoint_dimension(storage.dtype())?, ); let session = ctx.session().clone(); @@ -176,11 +176,11 @@ impl ArrowExportVTable for MultiPoint { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); let dimension = multipoint_dimension(ext_type.storage_dtype())?; let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?; - field.try_with_extension_type(multipoint_type(geo_metadata, dimension))?; + field.try_with_extension_type(multipoint_type(spatial_metadata, dimension))?; Ok(Some(field)) } @@ -246,7 +246,7 @@ impl ArrowImportVTable for MultiPoint { ); ( multipoint_meta.dimension().into(), - geo_metadata_from_arrow(multipoint_meta.metadata()), + spatial_metadata_from_arrow(multipoint_meta.metadata()), ) } else { if field.extension_type_name() != Some(MultiPointType::NAME) { @@ -261,7 +261,7 @@ impl ArrowImportVTable for MultiPoint { let Ok(dimension) = Dimension::from_field_names(fields.names()) else { return Ok(None); }; - (dimension, GeoMetadata::default()) + (dimension, SpatialMetadata::default()) }; let storage_dtype = multipoint_storage_dtype(dimension, field.is_nullable().into()); @@ -304,11 +304,11 @@ mod tests { use super::MultiPoint; use super::multipoint_storage_dtype; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; use crate::extension::coordinate::Dimension; - fn geo_meta() -> GeoMetadata { - GeoMetadata { + fn spatial_meta() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -321,7 +321,7 @@ mod tests { #[case::xyzm(Dimension::Xyzm)] fn multipoint_validates_every_dimension(#[case] dim: Dimension) -> VortexResult<()> { let storage = multipoint_storage_dtype(dim, Nullability::NonNullable); - ExtDType::::try_new(geo_meta(), storage)?; + ExtDType::::try_new(spatial_meta(), storage)?; Ok(()) } @@ -330,7 +330,7 @@ mod tests { #[test] fn multipoint_rejects_invalid_storage() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), primitive).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), primitive).is_err()); Ok(()) } } diff --git a/vortex-geo/src/extension/multipolygon.rs b/vortex-spatial/src/extension/multipolygon.rs similarity index 88% rename from vortex-geo/src/extension/multipolygon.rs rename to vortex-spatial/src/extension/multipolygon.rs index 524e470749c..c05a430538a 100644 --- a/vortex-geo/src/extension/multipolygon.rs +++ b/vortex-spatial/src/extension/multipolygon.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! The [`MultiPolygon`] extension type (`vortex.geo.multipolygon`), stored as +//! The [`MultiPolygon`] extension type (`vortex.st.multipolygon`), stored as //! `List>>>` (polygons → rings → coordinates) and tagged with -//! [`GeoMetadata`]. A single `Polygon` is a one-element multipolygon. +//! [`SpatialMetadata`]. A single `Polygon` is a one-element multipolygon. use std::sync::Arc; @@ -46,25 +46,25 @@ use vortex_error::vortex_err; use vortex_session::registry::CachedId; use vortex_session::registry::Id; -use super::GeoMetadata; +use super::SpatialMetadata; use super::coordinate::Dimension; use super::coordinate::coordinate_dimension; use super::coordinate::coordinate_storage_dtype; -use super::geo_metadata_from_arrow; use super::geoarrow_metadata; use super::geoarrow_to_wkb; +use super::spatial_metadata_from_arrow; /// A multipolygon (`geoarrow.multipolygon`); a single `Polygon` is a one-element multipolygon. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct MultiPolygon; impl ExtVTable for MultiPolygon { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; // No cheap owned value like Point's `Coordinate`; expose the raw storage scalar. type NativeValue<'a> = &'a ScalarValue; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.multipolygon"); + static ID: CachedId = CachedId::new("vortex.st.multipolygon"); *ID } @@ -73,7 +73,7 @@ impl ExtVTable for MultiPolygon { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { @@ -113,11 +113,11 @@ pub(crate) fn multipolygon_dimension(dtype: &DType) -> VortexResult { static ARROW_MULTIPOLYGON: CachedId = CachedId::new(MultiPolygonType::NAME); /// The `geoarrow.multipolygon` type for `dimension`, with separated (struct) coordinates. -fn multipolygon_type(geo_metadata: &GeoMetadata, dimension: Dimension) -> MultiPolygonType { - MultiPolygonType::new(dimension.into(), geoarrow_metadata(geo_metadata)) +fn multipolygon_type(spatial_metadata: &SpatialMetadata, dimension: Dimension) -> MultiPolygonType { + MultiPolygonType::new(dimension.into(), geoarrow_metadata(spatial_metadata)) } -/// Decode storage to `geo_types` for the geo scalar functions (CRS is irrelevant to planar ops). +/// Decode storage to `geo_types` for the spatial scalar functions (CRS is irrelevant to planar ops). pub(crate) fn multipolygon_geometries( storage: &ArrayRef, ctx: &mut ExecutionCtx, @@ -126,8 +126,8 @@ pub(crate) fn multipolygon_geometries( .iter() .map(|geometry| -> VortexResult> { Ok(geometry - .ok_or_else(|| vortex_err!("geo: null geometry is not supported"))? - .map_err(|e| vortex_err!("geo: geometry access failed: {e}"))? + .ok_or_else(|| vortex_err!("spatial: null geometry is not supported"))? + .map_err(|e| vortex_err!("spatial: geometry access failed: {e}"))? .to_geometry()) }) .collect() @@ -139,7 +139,7 @@ fn multipolygon_array( ctx: &mut ExecutionCtx, ) -> VortexResult { let multipolygon_type = multipolygon_type( - &GeoMetadata::default(), + &SpatialMetadata::default(), multipolygon_dimension(storage.dtype())?, ); let session = ctx.session().clone(); @@ -186,11 +186,11 @@ impl ArrowExportVTable for MultiPolygon { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); let dimension = multipolygon_dimension(ext_type.storage_dtype())?; let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?; - field.try_with_extension_type(multipolygon_type(geo_metadata, dimension))?; + field.try_with_extension_type(multipolygon_type(spatial_metadata, dimension))?; Ok(Some(field)) } @@ -255,7 +255,7 @@ impl ArrowImportVTable for MultiPolygon { ); ( multipolygon_meta.dimension().into(), - geo_metadata_from_arrow(multipolygon_meta.metadata()), + spatial_metadata_from_arrow(multipolygon_meta.metadata()), ) } else { // Literal: peel the three `List` layers to the coordinate struct and read its @@ -278,7 +278,7 @@ impl ArrowImportVTable for MultiPolygon { let Ok(dimension) = Dimension::from_field_names(fields.names()) else { return Ok(None); }; - (dimension, GeoMetadata::default()) + (dimension, SpatialMetadata::default()) }; let storage_dtype = multipolygon_storage_dtype(dimension, field.is_nullable().into()); @@ -323,12 +323,12 @@ mod tests { use super::MultiPolygon; use super::multipolygon_storage_dtype; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; use crate::extension::coordinate::Dimension; use crate::extension::coordinate::coordinate_storage_dtype; - fn geo_meta() -> GeoMetadata { - GeoMetadata { + fn spatial_meta() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -342,7 +342,7 @@ mod tests { #[case::xyzm(Dimension::Xyzm)] fn multipolygon_validates_every_dimension(#[case] dim: Dimension) -> VortexResult<()> { let storage = multipolygon_storage_dtype(dim, Nullability::NonNullable); - ExtDType::::try_new(geo_meta(), storage)?; + ExtDType::::try_new(spatial_meta(), storage)?; Ok(()) } @@ -351,13 +351,13 @@ mod tests { #[test] fn multipolygon_rejects_invalid_storage() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), primitive).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), primitive).is_err()); // A double list (polygon) is not a multipolygon. let coords = coordinate_storage_dtype(Dimension::Xy, Nullability::NonNullable); let ring = DType::List(Arc::new(coords), Nullability::NonNullable); let polygon = DType::List(Arc::new(ring), Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), polygon).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), polygon).is_err()); Ok(()) } } diff --git a/vortex-geo/src/extension/point.rs b/vortex-spatial/src/extension/point.rs similarity index 88% rename from vortex-geo/src/extension/point.rs rename to vortex-spatial/src/extension/point.rs index cfebecee461..e8f3ad3c169 100644 --- a/vortex-geo/src/extension/point.rs +++ b/vortex-spatial/src/extension/point.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! The [`Point`] geometry extension type (`vortex.geo.point`): a location stored columnarly as +//! The [`Point`] geometry extension type (`vortex.st.point`): a location stored columnarly as //! `Struct` of non-nullable `f64` — the four GeoArrow dimensions XY, XYZ, XYM, -//! XYZM — tagged with [`GeoMetadata`] (CRS). `z` is an optional elevation and `m` an optional +//! XYZM — tagged with [`SpatialMetadata`] (CRS). `z` is an optional elevation and `m` an optional //! measure: an arbitrary per-point value such as distance along a route or a timestamp. use arrow_array::ArrayRef as ArrowArrayRef; @@ -44,26 +44,26 @@ use vortex_error::vortex_err; use vortex_session::registry::CachedId; use vortex_session::registry::Id; -use super::GeoMetadata; +use super::SpatialMetadata; use super::coordinate::Coordinate; use super::coordinate::Dimension; use super::coordinate::coordinate_dimension; use super::coordinate::coordinate_from_struct; use super::coordinate::coordinate_storage_dtype; -use super::geo_metadata_from_arrow; use super::geoarrow_metadata; use super::geoarrow_to_wkb; +use super::spatial_metadata_from_arrow; /// A single location: `geoarrow.point`, stored as `Struct` of non-nullable `f64`. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct Point; impl ExtVTable for Point { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; type NativeValue<'a> = Coordinate; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.point"); + static ID: CachedId = CachedId::new("vortex.st.point"); *ID } @@ -72,7 +72,7 @@ impl ExtVTable for Point { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { @@ -95,8 +95,8 @@ static ARROW_POINT: CachedId = CachedId::new(PointType::NAME); /// The `geoarrow.point` extension type for `dimension`, with separated (struct) coordinates /// matching `Point` storage. -fn point_type(geo_metadata: &GeoMetadata, dimension: Dimension) -> PointType { - PointType::new(dimension.into(), geoarrow_metadata(geo_metadata)) +fn point_type(spatial_metadata: &SpatialMetadata, dimension: Dimension) -> PointType { + PointType::new(dimension.into(), geoarrow_metadata(spatial_metadata)) } pub struct PointData(ExtensionArray); @@ -124,7 +124,7 @@ impl PointData { /// and `geo_types` decoding. fn point_array(storage: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { let point_type = point_type( - &GeoMetadata::default(), + &SpatialMetadata::default(), coordinate_dimension(storage.dtype())?, ); let session = ctx.session().clone(); @@ -133,7 +133,7 @@ fn point_array(storage: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult VortexResult> { Ok(geometry - .ok_or_else(|| vortex_err!("geo: null geometry is not supported"))? - .map_err(|e| vortex_err!("geo: geometry access failed: {e}"))? + .ok_or_else(|| vortex_err!("spatial: null geometry is not supported"))? + .map_err(|e| vortex_err!("spatial: geometry access failed: {e}"))? .to_geometry()) }) .collect() @@ -165,11 +165,11 @@ impl ArrowExportVTable for Point { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); let dimension = coordinate_dimension(ext_type.storage_dtype())?; let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?; - field.try_with_extension_type(point_type(geo_metadata, dimension))?; + field.try_with_extension_type(point_type(spatial_metadata, dimension))?; Ok(Some(field)) } @@ -237,7 +237,7 @@ impl ArrowImportVTable for Point { ); ( point_meta.dimension().into(), - geo_metadata_from_arrow(point_meta.metadata()), + spatial_metadata_from_arrow(point_meta.metadata()), ) } else { // Infer the dimension from the field names, not the canonical storage check: a literal's @@ -251,7 +251,7 @@ impl ArrowImportVTable for Point { let Ok(dimension) = Dimension::from_field_names(fields.names()) else { return Ok(None); }; - (dimension, GeoMetadata::default()) + (dimension, SpatialMetadata::default()) }; let storage_dtype = coordinate_storage_dtype(dimension, field.is_nullable().into()); @@ -297,15 +297,15 @@ mod tests { use vortex_error::VortexResult; use super::Point; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; use crate::extension::coordinate::Coordinate; use crate::extension::coordinate::Dimension; use crate::extension::coordinate::coordinate_storage_dtype; use crate::test_harness::coordinate_from_scalar; use crate::test_harness::point_column; - fn geo_meta() -> GeoMetadata { - GeoMetadata { + fn spatial_meta() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -318,7 +318,7 @@ mod tests { #[case::xyzm(Dimension::Xyzm)] fn point_validates_every_dimension(#[case] dim: Dimension) -> VortexResult<()> { let storage = coordinate_storage_dtype(dim, Nullability::NonNullable); - ExtDType::::try_new(geo_meta(), storage)?; + ExtDType::::try_new(spatial_meta(), storage)?; Ok(()) } @@ -327,14 +327,14 @@ mod tests { #[test] fn point_rejects_invalid_storage() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), primitive).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), primitive).is_err()); let wrong_fields = StructArray::from_fields(&[ ("a", PrimitiveArray::from_iter(vec![0.0f64]).into_array()), ("b", PrimitiveArray::from_iter(vec![0.0f64]).into_array()), ])? .into_array(); - assert!(ExtDType::::try_new(geo_meta(), wrong_fields.dtype().clone()).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), wrong_fields.dtype().clone()).is_err()); Ok(()) } diff --git a/vortex-geo/src/extension/polygon.rs b/vortex-spatial/src/extension/polygon.rs similarity index 88% rename from vortex-geo/src/extension/polygon.rs rename to vortex-spatial/src/extension/polygon.rs index 9a74c3ce7b3..dcfa8514ff3 100644 --- a/vortex-geo/src/extension/polygon.rs +++ b/vortex-spatial/src/extension/polygon.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! The [`Polygon`] geometry extension type (`vortex.geo.polygon`): rings of the +//! The [`Polygon`] geometry extension type (`vortex.st.polygon`): rings of the //! [`Point`](super::Point) coordinate struct, stored as `List>>` and tagged with -//! [`GeoMetadata`] (CRS). The first ring is the exterior boundary; the rest are holes. +//! [`SpatialMetadata`] (CRS). The first ring is the exterior boundary; the rest are holes. use std::sync::Arc; @@ -46,25 +46,25 @@ use vortex_error::vortex_err; use vortex_session::registry::CachedId; use vortex_session::registry::Id; -use super::GeoMetadata; +use super::SpatialMetadata; use super::coordinate::Dimension; use super::coordinate::coordinate_dimension; use super::coordinate::coordinate_storage_dtype; -use super::geo_metadata_from_arrow; use super::geoarrow_metadata; use super::geoarrow_to_wkb; +use super::spatial_metadata_from_arrow; /// A polygon: `geoarrow.polygon`, stored as `List>>` (rings of vertices). #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct Polygon; impl ExtVTable for Polygon { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; // No cheap owned value like Point's `Coordinate`; expose the raw storage scalar. type NativeValue<'a> = &'a ScalarValue; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.polygon"); + static ID: CachedId = CachedId::new("vortex.st.polygon"); *ID } @@ -73,7 +73,7 @@ impl ExtVTable for Polygon { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { @@ -110,11 +110,11 @@ static ARROW_POLYGON: CachedId = CachedId::new(PolygonType::NAME); /// The `geoarrow.polygon` extension type for `dimension`, with separated (struct) coordinates /// matching `Polygon` storage. -fn polygon_type(geo_metadata: &GeoMetadata, dimension: Dimension) -> PolygonType { - PolygonType::new(dimension.into(), geoarrow_metadata(geo_metadata)) +fn polygon_type(spatial_metadata: &SpatialMetadata, dimension: Dimension) -> PolygonType { + PolygonType::new(dimension.into(), geoarrow_metadata(spatial_metadata)) } -/// Decode `Polygon` storage (`List>`) to `geo_types` polygons, for the geo scalar +/// Decode `Polygon` storage (`List>`) to `geo_types` polygons, for the spatial scalar /// functions. CRS does not affect planar geometry ops, so default metadata is used. pub(crate) fn polygon_geometries( storage: &ArrayRef, @@ -124,8 +124,8 @@ pub(crate) fn polygon_geometries( .iter() .map(|geometry| -> VortexResult> { Ok(geometry - .ok_or_else(|| vortex_err!("geo: null geometry is not supported"))? - .map_err(|e| vortex_err!("geo: geometry access failed: {e}"))? + .ok_or_else(|| vortex_err!("spatial: null geometry is not supported"))? + .map_err(|e| vortex_err!("spatial: geometry access failed: {e}"))? .to_geometry()) }) .collect() @@ -133,7 +133,10 @@ pub(crate) fn polygon_geometries( /// Build a geoarrow `PolygonArray` from a `Polygon`'s `List>` storage. fn polygon_array(storage: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { - let polygon_type = polygon_type(&GeoMetadata::default(), polygon_dimension(storage.dtype())?); + let polygon_type = polygon_type( + &SpatialMetadata::default(), + polygon_dimension(storage.dtype())?, + ); let session = ctx.session().clone(); let arrow = session.arrow().execute_arrow(storage.clone(), None, ctx)?; PolygonArray::try_from((arrow.as_ref(), polygon_type)) @@ -178,11 +181,11 @@ impl ArrowExportVTable for Polygon { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); let dimension = polygon_dimension(ext_type.storage_dtype())?; let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?; - field.try_with_extension_type(polygon_type(geo_metadata, dimension))?; + field.try_with_extension_type(polygon_type(spatial_metadata, dimension))?; Ok(Some(field)) } @@ -249,7 +252,7 @@ impl ArrowImportVTable for Polygon { ); ( polygon_meta.dimension().into(), - geo_metadata_from_arrow(polygon_meta.metadata()), + spatial_metadata_from_arrow(polygon_meta.metadata()), ) } else { // Infer the dimension from the field names, not the canonical storage check: a literal's @@ -270,7 +273,7 @@ impl ArrowImportVTable for Polygon { let Ok(dimension) = Dimension::from_field_names(fields.names()) else { return Ok(None); }; - (dimension, GeoMetadata::default()) + (dimension, SpatialMetadata::default()) }; let storage_dtype = polygon_storage_dtype(dimension, field.is_nullable().into()); @@ -315,12 +318,12 @@ mod tests { use super::Polygon; use super::polygon_storage_dtype; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; use crate::extension::coordinate::Dimension; use crate::extension::coordinate::coordinate_storage_dtype; - fn geo_meta() -> GeoMetadata { - GeoMetadata { + fn spatial_meta() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -333,7 +336,7 @@ mod tests { #[case::xyzm(Dimension::Xyzm)] fn polygon_validates_every_dimension(#[case] dim: Dimension) -> VortexResult<()> { let storage = polygon_storage_dtype(dim, Nullability::NonNullable); - ExtDType::::try_new(geo_meta(), storage)?; + ExtDType::::try_new(spatial_meta(), storage)?; Ok(()) } @@ -342,12 +345,12 @@ mod tests { #[test] fn polygon_rejects_invalid_storage() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), primitive).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), primitive).is_err()); // A single list of coordinates is a LineString, not a Polygon. let coords = coordinate_storage_dtype(Dimension::Xy, Nullability::NonNullable); let line = DType::List(Arc::new(coords), Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), line).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), line).is_err()); Ok(()) } } diff --git a/vortex-geo/src/extension/rect.rs b/vortex-spatial/src/extension/rect.rs similarity index 89% rename from vortex-geo/src/extension/rect.rs rename to vortex-spatial/src/extension/rect.rs index 19f7f9db61e..8f4332fcc9e 100644 --- a/vortex-geo/src/extension/rect.rs +++ b/vortex-spatial/src/extension/rect.rs @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! The [`Rect`] bounding-box extension type (`vortex.geo.box`): an axis-aligned envelope stored +//! The [`Rect`] bounding-box extension type (`vortex.st.box`): an axis-aligned envelope stored //! columnarly as `Struct` of non-nullable //! `f64` — the lower corner's ordinates followed by the upper corner's — tagged with -//! [`GeoMetadata`] (CRS). Its GeoArrow wire type is `geoarrow.box`. +//! [`SpatialMetadata`] (CRS). Its GeoArrow wire type is `geoarrow.box`. //! //! Decoding to `geo_types` yields a 2D [`Geometry::Rect`]; any `z`/`m` //! bounds are dropped, as for the other geometry types. @@ -53,10 +53,10 @@ use vortex_error::vortex_err; use vortex_session::registry::CachedId; use vortex_session::registry::Id; -use super::GeoMetadata; +use super::SpatialMetadata; use super::coordinate::Dimension; -use super::geo_metadata_from_arrow; use super::geoarrow_metadata; +use super::spatial_metadata_from_arrow; /// An axis-aligned bounding box (`geoarrow.box`), stored as `Struct`. // Named `Rect`, not `Box`: matches `geo::Rect` / geoarrow-rs `RectArray`, and `Box` is a std name. @@ -64,12 +64,12 @@ use super::geoarrow_metadata; pub struct Rect; impl ExtVTable for Rect { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; // No cheap owned value; expose the raw storage scalar like `Polygon`. type NativeValue<'a> = &'a ScalarValue; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.box"); + static ID: CachedId = CachedId::new("vortex.st.box"); *ID } @@ -78,7 +78,7 @@ impl ExtVTable for Rect { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { @@ -178,20 +178,20 @@ pub(crate) fn build_rect_array( static ARROW_BOX: CachedId = CachedId::new(BoxType::NAME); /// The `geoarrow.box` extension type for `dimension`. -fn box_type(geo_metadata: &GeoMetadata, dimension: Dimension) -> BoxType { - BoxType::new(dimension.into(), geoarrow_metadata(geo_metadata)) +fn box_type(spatial_metadata: &SpatialMetadata, dimension: Dimension) -> BoxType { + BoxType::new(dimension.into(), geoarrow_metadata(spatial_metadata)) } /// Build a geoarrow `RectArray` from a `Rect`'s box `Struct` storage. fn rect_array(storage: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { - let box_type = box_type(&GeoMetadata::default(), box_dimension(storage.dtype())?); + let box_type = box_type(&SpatialMetadata::default(), box_dimension(storage.dtype())?); let session = ctx.session().clone(); let arrow = session.arrow().execute_arrow(storage.clone(), None, ctx)?; RectArray::try_from((arrow.as_ref(), box_type)) .map_err(|e| vortex_err!("failed to construct RectArray: {e}")) } -/// Decode `Rect` storage to `geo_types` (2D [`Geometry::Rect`]), for the geo scalar functions. +/// Decode `Rect` storage to `geo_types` (2D [`Geometry::Rect`]), for the spatial scalar functions. pub(crate) fn rect_geometries( storage: &ArrayRef, ctx: &mut ExecutionCtx, @@ -200,8 +200,8 @@ pub(crate) fn rect_geometries( .iter() .map(|geometry| -> VortexResult> { Ok(geometry - .ok_or_else(|| vortex_err!("geo: null geometry is not supported"))? - .map_err(|e| vortex_err!("geo: geometry access failed: {e}"))? + .ok_or_else(|| vortex_err!("spatial: null geometry is not supported"))? + .map_err(|e| vortex_err!("spatial: geometry access failed: {e}"))? .to_geometry()) }) .collect() @@ -223,11 +223,11 @@ impl ArrowExportVTable for Rect { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); let dimension = box_dimension(ext_type.storage_dtype())?; let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?; - field.try_with_extension_type(box_type(geo_metadata, dimension))?; + field.try_with_extension_type(box_type(spatial_metadata, dimension))?; Ok(Some(field)) } @@ -284,7 +284,7 @@ impl ArrowImportVTable for Rect { let (dimension, metadata) = if let Ok(box_meta) = field.try_extension_type::() { ( box_meta.dimension().into(), - geo_metadata_from_arrow(box_meta.metadata()), + spatial_metadata_from_arrow(box_meta.metadata()), ) } else { if field.extension_type_name() != Some(BoxType::NAME) { @@ -298,7 +298,7 @@ impl ArrowImportVTable for Rect { let Some(dimension) = box_dimension_from_names(fields.names()) else { return Ok(None); }; - (dimension, GeoMetadata::default()) + (dimension, SpatialMetadata::default()) }; let storage_dtype = box_storage_dtype(dimension, field.is_nullable().into()); @@ -347,11 +347,11 @@ mod tests { use super::box_dimension; use super::box_storage_dtype; use super::rect_geometries; - use crate::extension::GeoMetadata; + use crate::extension::SpatialMetadata; use crate::extension::coordinate::Dimension; - fn geo_meta() -> GeoMetadata { - GeoMetadata { + fn spatial_meta() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -365,7 +365,7 @@ mod tests { #[case::xyzm(Dimension::Xyzm)] fn box_validates_every_dimension(#[case] dim: Dimension) -> VortexResult<()> { let storage = box_storage_dtype(dim, Nullability::NonNullable); - ExtDType::::try_new(geo_meta(), storage.clone())?; + ExtDType::::try_new(spatial_meta(), storage.clone())?; assert_eq!(box_dimension(&storage)?, dim); Ok(()) } @@ -375,7 +375,7 @@ mod tests { #[test] fn box_rejects_invalid_storage() -> VortexResult<()> { let primitive = DType::Primitive(PType::F64, Nullability::NonNullable); - assert!(ExtDType::::try_new(geo_meta(), primitive).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), primitive).is_err()); let point_like = box_storage_dtype(Dimension::Xy, Nullability::NonNullable); let DType::Struct(fields, _) = &point_like else { @@ -389,13 +389,13 @@ mod tests { ), Nullability::NonNullable, ); - assert!(ExtDType::::try_new(geo_meta(), renamed).is_err()); + assert!(ExtDType::::try_new(spatial_meta(), renamed).is_err()); Ok(()) } /// A `Rect` column decodes to 2D `geo_types` rectangles. #[test] - fn box_decodes_to_geo_rect() -> VortexResult<()> { + fn box_decodes_to_geo_types_rect() -> VortexResult<()> { let session = vortex_array::array_session(); let mut ctx = session.create_execution_ctx(); diff --git a/vortex-geo/src/extension/wkb.rs b/vortex-spatial/src/extension/wkb.rs similarity index 89% rename from vortex-geo/src/extension/wkb.rs rename to vortex-spatial/src/extension/wkb.rs index fb8c443a7b3..66b8ec49a97 100644 --- a/vortex-geo/src/extension/wkb.rs +++ b/vortex-spatial/src/extension/wkb.rs @@ -39,9 +39,9 @@ use vortex_session::registry::CachedId; use vortex_session::registry::Id; use wkb::reader::GeometryType; -use crate::extension::GeoMetadata; -use crate::extension::geo_metadata_from_arrow; +use crate::extension::SpatialMetadata; use crate::extension::geoarrow_metadata; +use crate::extension::spatial_metadata_from_arrow; /// A typed handle to an [`ExtensionArray`] that contains WKB-encoded data. /// @@ -57,8 +57,8 @@ impl WellKnownBinaryData { self.ext.storage_array() } - /// A reference to the [geospatial metadata][GeoMetadata]. - pub fn geo_metadata(&self) -> &GeoMetadata { + /// A reference to the [spatial metadata][SpatialMetadata]. + pub fn spatial_metadata(&self) -> &SpatialMetadata { self.ext .dtype() .as_extension() @@ -89,7 +89,7 @@ pub struct WellKnownBinary; /// A reference to a value of well-known binary encoded geometry data. /// -/// Interpreting this value is dependent on the [geospatial metadata][GeoMetadata] in the extension +/// Interpreting this value is dependent on the [spatial metadata][SpatialMetadata] in the extension /// schema for the array where this scalar is taken from. pub struct Wkb<'a>(wkb::reader::Wkb<'a>); @@ -131,12 +131,12 @@ impl<'a> Deref for Wkb<'a> { } impl ExtVTable for WellKnownBinary { - type Metadata = GeoMetadata; + type Metadata = SpatialMetadata; type NativeValue<'a> = Wkb<'a>; fn id(&self) -> ExtId { - static ID: CachedId = CachedId::new("vortex.geo.wkb"); + static ID: CachedId = CachedId::new("vortex.st.wkb"); *ID } @@ -145,13 +145,13 @@ impl ExtVTable for WellKnownBinary { } fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult { - Ok(GeoMetadata::decode(metadata)?) + Ok(SpatialMetadata::decode(metadata)?) } fn validate_dtype(ext_dtype: &ExtDType) -> VortexResult<()> { vortex_ensure!( ext_dtype.storage_dtype().is_binary(), - "geo.wkb must have binary storage type, was {}", + "vortex.st.wkb must have binary storage type, was {}", ext_dtype.storage_dtype() ); @@ -184,10 +184,10 @@ impl ArrowExportVTable for WellKnownBinary { session: &ArrowSession, ) -> VortexResult> { let ext_type = dtype.as_extension(); - let geo_metadata = ext_type.metadata::(); + let spatial_metadata = ext_type.metadata::(); let mut field = session.to_arrow_field(name, ext_type.storage_dtype())?; - field.try_with_extension_type(wkb_type(geo_metadata))?; + field.try_with_extension_type(wkb_type(spatial_metadata))?; Ok(Some(field)) } @@ -262,7 +262,7 @@ impl ArrowImportVTable for WellKnownBinary { let storage_dtype = DType::Binary(field.is_nullable().into()); Ok(Some(DType::Extension( - ExtDType::try_with_vtable(WellKnownBinary, geo_metadata(&wkb_meta), storage_dtype)? + ExtDType::try_with_vtable(WellKnownBinary, spatial_metadata(&wkb_meta), storage_dtype)? .erased(), ))) } @@ -293,10 +293,10 @@ impl ArrowImportVTable for WellKnownBinary { } } -fn wkb_type(geo_metadata: &GeoMetadata) -> WkbType { - WkbType::new(geoarrow_metadata(geo_metadata)) +fn wkb_type(spatial_metadata: &SpatialMetadata) -> WkbType { + WkbType::new(geoarrow_metadata(spatial_metadata)) } -fn geo_metadata(wkb_type: &WkbType) -> GeoMetadata { - geo_metadata_from_arrow(wkb_type.metadata()) +fn spatial_metadata(wkb_type: &WkbType) -> SpatialMetadata { + spatial_metadata_from_arrow(wkb_type.metadata()) } diff --git a/vortex-geo/src/lib.rs b/vortex-spatial/src/lib.rs similarity index 77% rename from vortex-geo/src/lib.rs rename to vortex-spatial/src/lib.rs index b72df92a3f5..70067f57b16 100644 --- a/vortex-geo/src/lib.rs +++ b/vortex-spatial/src/lib.rs @@ -19,12 +19,12 @@ use crate::extension::Point; use crate::extension::Polygon; use crate::extension::Rect; use crate::extension::WellKnownBinary; -use crate::prune::GeoDistancePrune; -use crate::prune::GeoIntersectsPrune; -use crate::scalar_fn::contains::GeoContains; -use crate::scalar_fn::distance::GeoDistance; -use crate::scalar_fn::envelope::GeoEnvelope; -use crate::scalar_fn::intersects::GeoIntersects; +use crate::prune::SpatialDistancePrune; +use crate::prune::SpatialIntersectsPrune; +use crate::scalar_fn::contains::SpatialContains; +use crate::scalar_fn::distance::SpatialDistance; +use crate::scalar_fn::envelope::SpatialEnvelope; +use crate::scalar_fn::intersects::SpatialIntersects; pub mod aggregate_fn; pub mod extension; @@ -35,9 +35,9 @@ pub mod test_harness; #[cfg(test)] mod tests; -/// Set up a session with support for geospatial extension types, encodings and layouts. +/// Set up a session with support for spatial extension types, encodings and layouts. pub fn initialize(session: &VortexSession) { - // Register the geospatial extension types. + // Register the spatial extension types. session.dtypes().register(WellKnownBinary); session.arrow().register_exporter(Arc::new(WellKnownBinary)); session.arrow().register_importer(Arc::new(WellKnownBinary)); @@ -64,16 +64,16 @@ pub fn initialize(session: &VortexSession) { session.arrow().register_importer(Arc::new(Rect)); // Register the geometry scalar functions. - session.scalar_fns().register(GeoEnvelope); - session.scalar_fns().register(GeoContains); - session.scalar_fns().register(GeoDistance); - session.scalar_fns().register(GeoIntersects); + session.scalar_fns().register(SpatialEnvelope); + session.scalar_fns().register(SpatialContains); + session.scalar_fns().register(SpatialDistance); + session.scalar_fns().register(SpatialIntersects); // The axis-aligned bounding-box (AABB) aggregate; self-declares as a per-chunk zone stat for // geometry columns. session.aggregate_fns().register(GeometryAabb); // Register the spatial pruning rules that use that AABB. - session.stats().register_rewrite(GeoDistancePrune); - session.stats().register_rewrite(GeoIntersectsPrune); + session.stats().register_rewrite(SpatialDistancePrune); + session.stats().register_rewrite(SpatialIntersectsPrune); } diff --git a/vortex-geo/src/prune/distance.rs b/vortex-spatial/src/prune/distance.rs similarity index 88% rename from vortex-geo/src/prune/distance.rs rename to vortex-spatial/src/prune/distance.rs index de4f8e267fc..9231b33875e 100644 --- a/vortex-geo/src/prune/distance.rs +++ b/vortex-spatial/src/prune/distance.rs @@ -3,7 +3,7 @@ //! `ST_Distance(geom, const) radius` pruning. -use geo::Rect as GeoRect; +use geo::Rect as SpatialRect; use vortex_array::expr::BoundExpression; use vortex_array::scalar_fn::ScalarFnId; use vortex_array::scalar_fn::ScalarFnVTable; @@ -24,7 +24,7 @@ use super::lt_eq; use super::max_dist_sq; use super::min_dist_sq; use super::query_aabb; -use crate::scalar_fn::distance::GeoDistance; +use crate::scalar_fn::distance::SpatialDistance; /// Prunes chunks for `ST_Distance(geom, const) r` filters. /// @@ -32,11 +32,11 @@ use crate::scalar_fn::distance::GeoDistance; /// min-distance); `>= r` / `> r` skip one wholly within `r` (box max-distance). `==` / `!=` don't /// prune. #[derive(Debug)] -pub struct GeoDistancePrune; +pub struct SpatialDistancePrune; -impl StatsRewriteRule for GeoDistancePrune { +impl StatsRewriteRule for SpatialDistancePrune { fn scalar_fn_id(&self) -> ScalarFnId { - // The predicate root is the comparison, not `GeoDistance`, so key on `Binary`. + // The predicate root is the comparison, not `SpatialDistance`, so key on `Binary`. Binary.id() } @@ -57,9 +57,9 @@ impl StatsRewriteRule for GeoDistancePrune { return Ok(None); } - // The left operand must be `GeoDistance(geom, const)`; the right, the radius literal. + // The left operand must be `SpatialDistance(geom, const)`; the right, the radius literal. let distance = expr.child(0); - if distance.as_opt::().is_none() { + if distance.as_opt::().is_none() { return Ok(None); } let Some(radius) = expr.child(1).as_opt::() else { @@ -95,7 +95,7 @@ impl StatsRewriteRule for GeoDistancePrune { /// A distance is always `>= 0`, which decides the degenerate radii up front. fn distance_prune_proof( geom: &BoundExpression, - query: GeoRect, + query: SpatialRect, op: Operator, radius: f64, ) -> Option { @@ -144,14 +144,14 @@ mod tests { use vortex_array::stats::rewrite::StatsRewriteRule; use vortex_error::VortexResult; - use super::GeoDistancePrune; + use super::SpatialDistancePrune; use crate::prune::test_harness::aabb_zone_map; use crate::prune::test_harness::empty_zone_map; - use crate::scalar_fn::distance::GeoDistance; - use crate::test_harness::geo_session; + use crate::scalar_fn::distance::SpatialDistance; use crate::test_harness::point_column; + use crate::test_harness::spatial_session; - /// Run the rule against `GeoDistance(root, origin) radius`, operands swapped when + /// Run the rule against `SpatialDistance(root, origin) radius`, operands swapped when /// `geom_first` is false. The radius is any literal scalar, matching the uncoerced filter /// expressions the rule sees in production. fn falsify_distance( @@ -159,7 +159,7 @@ mod tests { geom_first: bool, radius: impl Into, ) -> VortexResult> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let scope = point_column(vec![0.0], vec![0.0])?.dtype().clone(); @@ -169,29 +169,29 @@ mod tests { } else { [lit(origin), root()] }; - let distance = GeoDistance.new_expr(EmptyOptions, operands); + let distance = SpatialDistance.new_expr(EmptyOptions, operands); let predicate = Binary .new_expr(operator, [distance, lit(radius.into())]) .bind(&scope)?; - GeoDistancePrune.falsify(&predicate, &StatsRewriteCtx::new(&session)) + SpatialDistancePrune.falsify(&predicate, &StatsRewriteCtx::new(&session)) } /// A null geometry literal (`ST_Distance(geom, NULL) <= r`) declines cleanly instead of /// erroring in the stats rewrite: the all-null predicate can never prune. #[test] fn null_literal_is_not_pruned() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let scope = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let null_query = Scalar::null(scope.as_nullable()); - let distance = GeoDistance.new_expr(EmptyOptions, [root(), lit(null_query)]); + let distance = SpatialDistance.new_expr(EmptyOptions, [root(), lit(null_query)]); let predicate = Binary .new_expr(Operator::Lte, [distance, lit(0.5f64)]) .bind(&scope)?; let ctx = StatsRewriteCtx::new(&session); - assert!(GeoDistancePrune.falsify(&predicate, &ctx)?.is_none()); + assert!(SpatialDistancePrune.falsify(&predicate, &ctx)?.is_none()); Ok(()) } @@ -254,7 +254,7 @@ mod tests { /// has no numeric value - the rule declines rather than erroring, and the chunk is scanned. #[test] fn extension_radius_never_prunes() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let geometry = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; @@ -265,33 +265,33 @@ mod tests { /// A non-geometry scope is rejected while binding, before stats rewriting. #[test] fn unsupported_scope_is_not_pruned() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let scope = DType::Primitive(PType::F64, Nullability::NonNullable); let origin = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; - let distance = GeoDistance.new_expr(EmptyOptions, [root(), lit(origin)]); + let distance = SpatialDistance.new_expr(EmptyOptions, [root(), lit(origin)]); let predicate = lt_eq(distance, lit(0.5f64)); assert!(predicate.bind(&scope).is_err()); Ok(()) } - /// A comparison that does not wrap `GeoDistance` is left untouched. + /// A comparison that does not wrap `SpatialDistance` is left untouched. #[test] fn ignores_non_distance_comparison() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let scope = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let predicate = lt_eq(lit(1.0f64), lit(2.0f64)).bind(&scope)?; let ctx = StatsRewriteCtx::new(&session); - assert!(GeoDistancePrune.falsify(&predicate, &ctx)?.is_none()); + assert!(SpatialDistancePrune.falsify(&predicate, &ctx)?.is_none()); Ok(()) } /// End-to-end over a hand-built zone map: the far chunk is skipped, the near one kept. #[test] fn prunes_far_chunk_keeps_near() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); @@ -302,7 +302,7 @@ mod tests { )?; let origin = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; - let distance = GeoDistance.new_expr(EmptyOptions, [root(), lit(origin)]); + let distance = SpatialDistance.new_expr(EmptyOptions, [root(), lit(origin)]); let predicate = lt_eq(distance, lit(0.5f64)); let proof = predicate .bind(&point_dtype)? @@ -319,7 +319,7 @@ mod tests { /// neither axis alone exceeds `r`, the case a per-axis box-overlap test would wrongly keep. #[test] fn prunes_diagonally_distant_chunk() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); @@ -328,7 +328,7 @@ mod tests { let zone_map = aabb_zone_map(&point_dtype, &[[0.8, 0.8, 0.9, 0.9]])?; let origin = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; - let distance = GeoDistance.new_expr(EmptyOptions, [root(), lit(origin)]); + let distance = SpatialDistance.new_expr(EmptyOptions, [root(), lit(origin)]); let predicate = lt_eq(distance, lit(1.0f64)); let proof = predicate .bind(&point_dtype)? @@ -349,7 +349,7 @@ mod tests { /// satisfy `>= r`) via the box max-distance, while a chunk beyond `r` is kept. #[test] fn prunes_within_chunk_for_far_filter() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); @@ -361,7 +361,7 @@ mod tests { )?; let origin = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; - let distance = GeoDistance.new_expr(EmptyOptions, [root(), lit(origin)]); + let distance = SpatialDistance.new_expr(EmptyOptions, [root(), lit(origin)]); let proof = gt_eq(distance, lit(2.0f64)) .bind(&point_dtype)? .falsify(&session)? @@ -377,14 +377,14 @@ mod tests { /// every zone, the missing stat binds to null and `null_as_false` retains the zone. #[test] fn missing_aabb_stat_keeps_all_zones() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let zone_map = empty_zone_map(&point_dtype)?; let origin = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; - let distance = GeoDistance.new_expr(EmptyOptions, [root(), lit(origin)]); + let distance = SpatialDistance.new_expr(EmptyOptions, [root(), lit(origin)]); let proof = lt_eq(distance, lit(0.5f64)) .bind(&point_dtype)? .falsify(&session)? diff --git a/vortex-geo/src/prune/intersects.rs b/vortex-spatial/src/prune/intersects.rs similarity index 85% rename from vortex-geo/src/prune/intersects.rs rename to vortex-spatial/src/prune/intersects.rs index 5d238cb955f..74103003b2f 100644 --- a/vortex-geo/src/prune/intersects.rs +++ b/vortex-spatial/src/prune/intersects.rs @@ -16,7 +16,7 @@ use super::gt; use super::lit; use super::min_dist_sq; use super::query_aabb; -use crate::scalar_fn::intersects::GeoIntersects; +use crate::scalar_fn::intersects::SpatialIntersects; /// Prunes chunks for `ST_Intersects(geom, const)` filters: a chunk whose box is strictly /// separated from the constant's bounding box cannot contain an intersecting row. @@ -24,12 +24,12 @@ use crate::scalar_fn::intersects::GeoIntersects; /// Only the positive form prunes. `NOT ST_Intersects` cannot: it would need every row to provably /// intersect the constant, and box overlap never proves geometry intersection. #[derive(Debug)] -pub struct GeoIntersectsPrune; +pub struct SpatialIntersectsPrune; -impl StatsRewriteRule for GeoIntersectsPrune { +impl StatsRewriteRule for SpatialIntersectsPrune { fn scalar_fn_id(&self) -> ScalarFnId { // Unlike the distance rule, the boolean predicate is itself the expression root. - GeoIntersects.id() + SpatialIntersects.id() } fn falsify( @@ -66,17 +66,17 @@ mod tests { use vortex_array::stats::rewrite::StatsRewriteRule; use vortex_error::VortexResult; - use super::GeoIntersectsPrune; + use super::SpatialIntersectsPrune; use crate::prune::test_harness::aabb_zone_map; use crate::prune::test_harness::empty_zone_map; - use crate::scalar_fn::intersects::GeoIntersects; - use crate::test_harness::geo_session; + use crate::scalar_fn::intersects::SpatialIntersects; use crate::test_harness::point_column; + use crate::test_harness::spatial_session; - /// Run the intersects rule against `GeoIntersects(root, point(1.0, 0.5))`, operands swapped + /// Run the intersects rule against `SpatialIntersects(root, point(1.0, 0.5))`, operands swapped /// when `geom_first` is false. fn falsify_intersects(geom_first: bool) -> VortexResult> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let scope = point_column(vec![0.0], vec![0.0])?.dtype().clone(); @@ -86,10 +86,10 @@ mod tests { } else { [lit(query), root()] }; - let predicate = GeoIntersects + let predicate = SpatialIntersects .new_expr(EmptyOptions, operands) .bind(&scope)?; - GeoIntersectsPrune.falsify(&predicate, &StatsRewriteCtx::new(&session)) + SpatialIntersectsPrune.falsify(&predicate, &StatsRewriteCtx::new(&session)) } /// Intersects is symmetric: both operand orders produce a proof. @@ -104,12 +104,12 @@ mod tests { /// A non-geometry scope is rejected while binding, before stats rewriting. #[test] fn unsupported_scope_is_not_pruned() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let scope = DType::Primitive(PType::F64, Nullability::NonNullable); let query = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; - let predicate = GeoIntersects.new_expr(EmptyOptions, [root(), lit(query)]); + let predicate = SpatialIntersects.new_expr(EmptyOptions, [root(), lit(query)]); assert!(predicate.bind(&scope).is_err()); Ok(()) } @@ -118,16 +118,16 @@ mod tests { /// in the stats rewrite: the all-null predicate can never prune. #[test] fn null_literal_is_not_pruned() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let scope = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let null_query = Scalar::null(scope.as_nullable()); - let predicate = GeoIntersects + let predicate = SpatialIntersects .new_expr(EmptyOptions, [root(), lit(null_query)]) .bind(&scope)?; let ctx = StatsRewriteCtx::new(&session); - assert!(GeoIntersectsPrune.falsify(&predicate, &ctx)?.is_none()); + assert!(SpatialIntersectsPrune.falsify(&predicate, &ctx)?.is_none()); Ok(()) } @@ -135,7 +135,7 @@ mod tests { /// touching the query must scan, touching geometries intersect under OGC semantics. #[test] fn prunes_disjoint_keeps_touching_and_containing() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); @@ -151,7 +151,7 @@ mod tests { )?; let query = point_column(vec![1.0], vec![0.5])?.execute_scalar(0, &mut ctx)?; - let predicate = GeoIntersects.new_expr(EmptyOptions, [root(), lit(query)]); + let predicate = SpatialIntersects.new_expr(EmptyOptions, [root(), lit(query)]); let proof = predicate .bind(&point_dtype)? .falsify(&session)? @@ -165,14 +165,14 @@ mod tests { /// Backward compat: a zone map written without the `GeometryAabb` stat keeps every zone. #[test] fn missing_aabb_stat_keeps_all_zones() -> VortexResult<()> { - let session = geo_session(); + let session = spatial_session(); let mut ctx = session.create_execution_ctx(); let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let zone_map = empty_zone_map(&point_dtype)?; let query = point_column(vec![0.0], vec![0.0])?.execute_scalar(0, &mut ctx)?; - let proof = GeoIntersects + let proof = SpatialIntersects .new_expr(EmptyOptions, [root(), lit(query)]) .bind(&point_dtype)? .falsify(&session)? diff --git a/vortex-geo/src/prune/mod.rs b/vortex-spatial/src/prune/mod.rs similarity index 92% rename from vortex-geo/src/prune/mod.rs rename to vortex-spatial/src/prune/mod.rs index 1e718fa2016..32c2058b384 100644 --- a/vortex-geo/src/prune/mod.rs +++ b/vortex-spatial/src/prune/mod.rs @@ -15,10 +15,10 @@ mod intersects; #[cfg(test)] mod test_harness; -pub use distance::GeoDistancePrune; +pub use distance::SpatialDistancePrune; use geo::BoundingRect; -use geo::Rect as GeoRect; -pub use intersects::GeoIntersectsPrune; +use geo::Rect as SpatialRect; +pub use intersects::SpatialIntersectsPrune; use vortex_array::VortexSessionExecute; use vortex_array::aggregate_fn::AggregateFnVTableExt; use vortex_array::aggregate_fn::EmptyOptions; @@ -44,7 +44,7 @@ use crate::aggregate_fn::GeometryAabb; use crate::extension::is_native_geometry; use crate::extension::single_geometry; -/// Splits a symmetric two-operand geo predicate into the scope-rooted geometry column and the +/// Splits a symmetric two-operand spatial predicate into the scope-rooted geometry column and the /// constant operand's scalar. /// /// `None` means the rule must decline: the expression doesn't have the `f(column, constant)` @@ -80,7 +80,10 @@ fn geometry_and_constant<'a>( /// /// Prove claims against this box rather than the constant itself: the constant lies inside it, /// so whatever holds for the box holds for the geometry. -fn query_aabb(constant: &Scalar, ctx: &StatsRewriteCtx<'_>) -> VortexResult>> { +fn query_aabb( + constant: &Scalar, + ctx: &StatsRewriteCtx<'_>, +) -> VortexResult>> { // A null geometry literal has no extent to prove against, so it can never prune. if constant.is_null() { return Ok(None); @@ -105,7 +108,7 @@ fn aabb_stat(geom: &BoundExpression) -> BoundExpression { /// touch, positive iff they are strictly separated. /// /// Prunes "near" predicates: `min_dist_sq > r^2` proves every row is farther than `r`. -fn min_dist_sq(aabb: &BoundExpression, query: GeoRect) -> BoundExpression { +fn min_dist_sq(aabb: &BoundExpression, query: SpatialRect) -> BoundExpression { let field = |name: &str| get_item(name, aabb.clone()); // Per axis: gap = max(0, q_lo - aabb_hi, aabb_lo - q_hi), positive only when the intervals // are separated. The nearest two points of the boxes are one axis-gap apart per axis, so the @@ -127,7 +130,7 @@ fn min_dist_sq(aabb: &BoundExpression, query: GeoRect) -> BoundExpression { /// Upper bound on every row's squared distance to the query AABB. /// /// Prunes "far" predicates: `max_dist_sq < r^2` proves every row is within `r`. -fn max_dist_sq(aabb: &BoundExpression, query: GeoRect) -> BoundExpression { +fn max_dist_sq(aabb: &BoundExpression, query: SpatialRect) -> BoundExpression { let field = |name: &str| get_item(name, aabb.clone()); // Per axis: span = max(q_hi, aabb_hi) - min(q_lo, aabb_lo), the farthest two points of the // boxes can be apart. The nullable AABB field is the second `maximum`/`minimum` argument so diff --git a/vortex-geo/src/prune/test_harness.rs b/vortex-spatial/src/prune/test_harness.rs similarity index 93% rename from vortex-geo/src/prune/test_harness.rs rename to vortex-spatial/src/prune/test_harness.rs index fb3e12ef9d5..938371dd6ab 100644 --- a/vortex-geo/src/prune/test_harness.rs +++ b/vortex-spatial/src/prune/test_harness.rs @@ -19,8 +19,8 @@ use vortex_error::VortexResult; use vortex_layout::layouts::zoned::zone_map::ZoneMap; use crate::aggregate_fn::GeometryAabb; -use crate::extension::GeoMetadata; use crate::extension::Rect; +use crate::extension::SpatialMetadata; /// A single-column zone map holding one native-box AABB stat row (`[xmin, ymin, xmax, ymax]`) /// per zone, with default (unreferenced) metadata to match the aggregate's return dtype. @@ -35,7 +35,7 @@ pub(super) fn aabb_zone_map(point_dtype: &DType, boxes: &[[f64; 4]]) -> VortexRe )? .into_array(); let box_dtype = - ExtDType::::try_new(GeoMetadata::default(), storage.dtype().clone())?.erased(); + ExtDType::::try_new(SpatialMetadata::default(), storage.dtype().clone())?.erased(); let aabbs = ExtensionArray::try_new(box_dtype, storage)?.into_array(); let zone_array = StructArray::from_fields(&[(aabb_fn.to_string().as_str(), aabbs)])?; ZoneMap::try_new( diff --git a/vortex-geo/src/scalar_fn/contains.rs b/vortex-spatial/src/scalar_fn/contains.rs similarity index 91% rename from vortex-geo/src/scalar_fn/contains.rs rename to vortex-spatial/src/scalar_fn/contains.rs index a10bd7c4524..599c0eee2be 100644 --- a/vortex-geo/src/scalar_fn/contains.rs +++ b/vortex-spatial/src/scalar_fn/contains.rs @@ -30,13 +30,13 @@ use crate::scalar_fn::execute::execute_binary_geo_types; fn validate_contains_operands(dtypes: &[DType]) -> VortexResult<()> { vortex_ensure!( dtypes.len() == 2, - "geo: contains requires exactly two geometry operands, got {}", + "spatial: contains requires exactly two geometry operands, got {}", dtypes.len() ); for dtype in dtypes { vortex_ensure!( is_native_geometry(dtype), - "geo: contains operand {dtype} is not a native geometry type" + "spatial: contains operand {dtype} is not a native geometry type" ); } Ok(()) @@ -46,24 +46,24 @@ fn validate_contains_operands(dtypes: &[DType]) -> VortexResult<()> { /// literal: true where operand `b` lies completely inside operand `a` (boundary contact alone /// does not count). Containment is not symmetric; the operand order is significant. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] -pub struct GeoContains; +pub struct SpatialContains; -impl GeoContains { +impl SpatialContains { /// A lazy `ScalarFnArray` computing per-row whether operand `a` contains operand `b`; /// either may be constant. The output length is taken from `a`. pub fn try_new_array(a: ArrayRef, b: ArrayRef) -> VortexResult { ScalarFnArray::try_new( - TypedScalarFnInstance::new(GeoContains, EmptyOptions).erased(), + TypedScalarFnInstance::new(SpatialContains, EmptyOptions).erased(), vec![a, b], ) } } -impl ScalarFnVTable for GeoContains { +impl ScalarFnVTable for SpatialContains { type Options = EmptyOptions; fn id(&self) -> ScalarFnId { - static ID: CachedId = CachedId::new("vortex.geo.contains"); + static ID: CachedId = CachedId::new("vortex.st.contains"); *ID } @@ -157,7 +157,7 @@ mod tests { use vortex_error::vortex_err; use wkb::writer::WriteOptions; - use super::GeoContains; + use super::SpatialContains; use crate::test_harness::linestring_column; use crate::test_harness::nullable_point_column; use crate::test_harness::point_column; @@ -186,7 +186,7 @@ mod tests { Ok(array.execute::(ctx)?.into_array()) } - /// Execute `GeoContains(a, b)` and assert the per-row verdicts equal `expected`. + /// Execute `SpatialContains(a, b)` and assert the per-row verdicts equal `expected`. fn assert_contains( a: ArrayRef, b: ArrayRef, @@ -194,7 +194,7 @@ mod tests { ) -> VortexResult<()> { let session = vortex_array::array_session(); let mut ctx = session.create_execution_ctx(); - let contains = GeoContains::try_new_array(a, b)?.into_array(); + let contains = SpatialContains::try_new_array(a, b)?.into_array(); assert_arrays_eq!(contains, BoolArray::from_iter(expected), &mut ctx); Ok(()) } @@ -299,9 +299,10 @@ mod tests { fn output_nullability_mirrors_operands() -> VortexResult<()> { let dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let non_nullable = - GeoContains.return_dtype(&EmptyOptions, &[dtype.clone(), dtype.clone()])?; + SpatialContains.return_dtype(&EmptyOptions, &[dtype.clone(), dtype.clone()])?; assert!(!non_nullable.is_nullable()); - let nullable = GeoContains.return_dtype(&EmptyOptions, &[dtype.as_nullable(), dtype])?; + let nullable = + SpatialContains.return_dtype(&EmptyOptions, &[dtype.as_nullable(), dtype])?; assert!(nullable.is_nullable()); Ok(()) } @@ -315,7 +316,7 @@ mod tests { let container = geometry_constant(&Geometry::Polygon(rect_polygon(0.0, 0.0, 4.0, 4.0)), 3)?; let points = nullable_point_column(vec![Some((2.0, 2.0)), None, Some((10.0, 10.0))])?; - let contains = GeoContains::try_new_array(container, points)?.into_array(); + let contains = SpatialContains::try_new_array(container, points)?.into_array(); let expected = BoolArray::new( BitBuffer::from_iter([true, false, false]), @@ -335,7 +336,7 @@ mod tests { let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().as_nullable(); let null_const = ConstantArray::new(Scalar::null(point_dtype), 2).into_array(); let points = point_column(vec![2.0, 10.0], vec![2.0, 10.0])?; - let contains = GeoContains::try_new_array(null_const, points)?.into_array(); + let contains = SpatialContains::try_new_array(null_const, points)?.into_array(); let expected = BoolArray::new(BitBuffer::from_iter([false, false]), Validity::AllInvalid).into_array(); @@ -363,7 +364,7 @@ mod tests { None, Some((4.0, 4.0)), ])?; - let contains = GeoContains::try_new_array(container, contained)?.into_array(); + let contains = SpatialContains::try_new_array(container, contained)?.into_array(); let expected = BoolArray::new( BitBuffer::from_iter([true, false, false, false]), @@ -382,7 +383,7 @@ mod tests { let container = geometry_constant(&Geometry::Polygon(rect_polygon(0.0, 0.0, 4.0, 4.0)), 2)?; let points = nullable_point_column(vec![None, None])?; - let contains = GeoContains::try_new_array(container, points)?.into_array(); + let contains = SpatialContains::try_new_array(container, points)?.into_array(); let expected = BoolArray::new(BitBuffer::from_iter([false, false]), Validity::AllInvalid).into_array(); @@ -399,7 +400,7 @@ mod tests { let container = nullable_point_column(vec![Some((1.0, 1.0)), None])?; let contained = nullable_point_column(vec![None, Some((2.0, 2.0))])?; - let contains = GeoContains::try_new_array(container, contained)?.into_array(); + let contains = SpatialContains::try_new_array(container, contained)?.into_array(); let expected = BoolArray::new(BitBuffer::from_iter([false, false]), Validity::AllInvalid).into_array(); @@ -410,9 +411,9 @@ mod tests { /// A non-geometry operand dtype is rejected up front, before execution. #[test] fn non_geometry_operand_is_rejected() -> VortexResult<()> { - let geo = point_column(vec![0.0], vec![0.0])?.dtype().clone(); + let spatial_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let numeric = DType::Primitive(PType::I32, Nullability::NonNullable); - let result = GeoContains.return_dtype(&EmptyOptions, &[geo, numeric]); + let result = SpatialContains.return_dtype(&EmptyOptions, &[spatial_dtype, numeric]); assert!(result.is_err()); Ok(()) } diff --git a/vortex-geo/src/scalar_fn/distance.rs b/vortex-spatial/src/scalar_fn/distance.rs similarity index 86% rename from vortex-geo/src/scalar_fn/distance.rs rename to vortex-spatial/src/scalar_fn/distance.rs index cd9475f69ba..dfd3d09ed23 100644 --- a/vortex-geo/src/scalar_fn/distance.rs +++ b/vortex-spatial/src/scalar_fn/distance.rs @@ -32,13 +32,13 @@ use crate::scalar_fn::execute::execute_binary_geo_types; fn validate_distance_operands(dtypes: &[DType]) -> VortexResult<()> { vortex_ensure!( dtypes.len() == 2, - "geo: distance requires exactly two geometry operands, got {}", + "spatial: distance requires exactly two geometry operands, got {}", dtypes.len() ); for dtype in dtypes { vortex_ensure!( is_native_geometry(dtype), - "geo: distance operand {dtype} is not a native geometry type" + "spatial: distance operand {dtype} is not a native geometry type" ); } Ok(()) @@ -47,24 +47,24 @@ fn validate_distance_operands(dtypes: &[DType]) -> VortexResult<()> { /// Planar (Euclidean) `ST_Distance` (no geodesic correction) between two native geometry /// operands, each a column or a constant literal. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] -pub struct GeoDistance; +pub struct SpatialDistance; -impl GeoDistance { +impl SpatialDistance { /// A lazy `ScalarFnArray` computing the per-row distance between operands `a` and `b`; either may /// be constant. The output length is taken from `a`. pub fn try_new_array(a: ArrayRef, b: ArrayRef) -> VortexResult { ScalarFnArray::try_new( - TypedScalarFnInstance::new(GeoDistance, EmptyOptions).erased(), + TypedScalarFnInstance::new(SpatialDistance, EmptyOptions).erased(), vec![a, b], ) } } -impl ScalarFnVTable for GeoDistance { +impl ScalarFnVTable for SpatialDistance { type Options = EmptyOptions; fn id(&self) -> ScalarFnId { - static ID: CachedId = CachedId::new("vortex.geo.distance"); + static ID: CachedId = CachedId::new("vortex.st.distance"); *ID } @@ -142,7 +142,7 @@ mod tests { use vortex_array::validity::Validity; use vortex_error::VortexResult; - use super::GeoDistance; + use super::SpatialDistance; use crate::test_harness::nullable_point_column; use crate::test_harness::point_column; use crate::test_harness::polygon_column; @@ -158,7 +158,7 @@ mod tests { Ok(ConstantArray::new(single, len).into_array()) } - /// Execute a `GeoDistance` array and read back its per-row `f64` distances. + /// Execute a `SpatialDistance` array and read back its per-row `f64` distances. fn distances(distance: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult> { Ok(distance .execute::(ctx)? @@ -167,8 +167,8 @@ mod tests { .to_vec()) } - /// `GeoDistance` returns the per-row distance between a point column and a constant query point - /// (3–4–5 triangles), computed via the geo crate. + /// `SpatialDistance` returns the per-row distance between a point column and a constant query point + /// (3–4–5 triangles), computed via the external `geo` crate. #[test] fn distance_over_points() -> VortexResult<()> { let session = vortex_array::array_session(); @@ -176,7 +176,7 @@ mod tests { 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(); + let distance = SpatialDistance::try_new_array(a, b)?.into_array(); assert_eq!(distances(distance, &mut ctx)?, vec![0.0, 3.0, 4.0, 5.0]); Ok(()) @@ -190,7 +190,7 @@ mod tests { 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(); + let distance = SpatialDistance::try_new_array(a, b)?.into_array(); assert_eq!(distances(distance, &mut ctx)?, vec![5.0, 0.0]); Ok(()) @@ -207,7 +207,7 @@ mod tests { let single = polygon_column(vec![vec![ring]])?.execute_scalar(0, &mut ctx)?; let square = ConstantArray::new(single, 2).into_array(); let points = point_column(vec![7.0, 2.0], vec![2.0, 2.0])?; - let distance = GeoDistance::try_new_array(points, square)?.into_array(); + let distance = SpatialDistance::try_new_array(points, square)?.into_array(); assert_eq!(distances(distance, &mut ctx)?, vec![3.0, 0.0]); Ok(()) @@ -221,7 +221,7 @@ mod tests { 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(); + let distance = SpatialDistance::try_new_array(a, b)?.into_array(); assert_eq!(distances(distance, &mut ctx)?, vec![0.0, 3.0, 4.0, 5.0]); Ok(()) @@ -235,7 +235,7 @@ mod tests { 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(); + let distance = SpatialDistance::try_new_array(a, b)?.into_array(); assert_eq!(distances(distance, &mut ctx)?, vec![5.0, 5.0, 5.0]); Ok(()) @@ -247,9 +247,10 @@ mod tests { 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()])?; + SpatialDistance.return_dtype(&EmptyOptions, &[dtype.clone(), dtype.clone()])?; assert!(!non_nullable.is_nullable()); - let nullable = GeoDistance.return_dtype(&EmptyOptions, &[dtype.as_nullable(), dtype])?; + let nullable = + SpatialDistance.return_dtype(&EmptyOptions, &[dtype.as_nullable(), dtype])?; assert!(nullable.is_nullable()); Ok(()) } @@ -262,7 +263,7 @@ mod tests { 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 distance = SpatialDistance::try_new_array(a, b)?.into_array(); let expected = PrimitiveArray::new( vec![0.0f64, 0.0, 5.0], @@ -281,7 +282,7 @@ mod tests { 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 distance = SpatialDistance::try_new_array(a, b)?.into_array(); let expected = PrimitiveArray::new( vec![5.0f64, 0.0, 0.0], @@ -301,7 +302,7 @@ mod tests { 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 distance = SpatialDistance::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); @@ -316,7 +317,7 @@ mod tests { 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 distance = SpatialDistance::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); @@ -332,7 +333,7 @@ mod tests { 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 distance = SpatialDistance::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); @@ -349,7 +350,7 @@ mod tests { let a = point_column(vec![], vec![])?; let b = point_column(vec![], vec![])?; - let result = GeoDistance::try_new_array(a, b)? + let result = SpatialDistance::try_new_array(a, b)? .into_array() .execute::(&mut ctx)? .into_array(); @@ -362,9 +363,9 @@ mod tests { /// A non-geometry operand dtype is rejected up front, before execution. #[test] fn non_geometry_operand_is_rejected() -> VortexResult<()> { - let geo = point_column(vec![0.0], vec![0.0])?.dtype().clone(); + let spatial_dtype = 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]); + let result = SpatialDistance.return_dtype(&EmptyOptions, &[spatial_dtype, numeric]); assert!(result.is_err()); Ok(()) } diff --git a/vortex-geo/src/scalar_fn/envelope.rs b/vortex-spatial/src/scalar_fn/envelope.rs similarity index 91% rename from vortex-geo/src/scalar_fn/envelope.rs rename to vortex-spatial/src/scalar_fn/envelope.rs index e4932ea19f6..f1e8ca33b1a 100644 --- a/vortex-geo/src/scalar_fn/envelope.rs +++ b/vortex-spatial/src/scalar_fn/envelope.rs @@ -37,8 +37,8 @@ use vortex_mask::Mask; use vortex_session::VortexSession; use vortex_session::registry::CachedId; -use crate::extension::GeoMetadata; use crate::extension::Rect; +use crate::extension::SpatialMetadata; use crate::extension::box_field_names; use crate::extension::box_storage_dtype; use crate::extension::build_rect_array; @@ -55,12 +55,12 @@ use crate::scalar_fn::execute::dispatch_unary; fn validate_envelope_operands(dtypes: &[DType]) -> VortexResult<()> { vortex_ensure!( dtypes.len() == 1, - "geo: envelope requires exactly one geometry operand, got {}", + "spatial: envelope requires exactly one geometry operand, got {}", dtypes.len() ); vortex_ensure!( is_native_geometry(&dtypes[0]), - "geo: envelope operand {} is not a native geometry type", + "spatial: envelope operand {} is not a native geometry type", dtypes[0] ); Ok(()) @@ -72,14 +72,14 @@ fn validate_envelope_operands(dtypes: &[DType]) -> VortexResult<()> { /// 2-D only: only the `x`/`y` leaf ordinates are read, so any `z`/`m` are ignored and each box is /// the XY extent — matching the [`GeometryAabb`](crate::aggregate_fn::GeometryAabb) aggregate. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] -pub struct GeoEnvelope; +pub struct SpatialEnvelope; -impl GeoEnvelope { +impl SpatialEnvelope { /// A lazy `ScalarFnArray` computing the per-row bounding box of geometry operand `a`, which may /// be constant. The output length is taken from `a`. pub fn try_new_array(a: ArrayRef) -> VortexResult { ScalarFnArray::try_new( - TypedScalarFnInstance::new(GeoEnvelope, EmptyOptions).erased(), + TypedScalarFnInstance::new(SpatialEnvelope, EmptyOptions).erased(), vec![a], ) } @@ -89,7 +89,7 @@ impl GeoEnvelope { /// because rows without a box — null or empty geometries — are null. Metadata is defaulted. fn output_box_dtype() -> VortexResult> { ExtDType::::try_new( - GeoMetadata::default(), + SpatialMetadata::default(), box_storage_dtype(Dimension::Xy, Nullability::Nullable), ) } @@ -149,7 +149,7 @@ fn envelope_array( let is_rect = array .dtype() .as_extension_opt() - .ok_or_else(|| vortex_err!("geo: envelope operand is not a geometry extension type"))? + .ok_or_else(|| vortex_err!("spatial: envelope operand is not a geometry extension type"))? .is::(); let storage = array .execute::(ctx)? @@ -203,11 +203,11 @@ fn execute_envelope( } } -impl ScalarFnVTable for GeoEnvelope { +impl ScalarFnVTable for SpatialEnvelope { type Options = EmptyOptions; fn id(&self) -> ScalarFnId { - static ID: CachedId = CachedId::new("vortex.geo.envelope"); + static ID: CachedId = CachedId::new("vortex.st.envelope"); *ID } @@ -292,9 +292,9 @@ mod tests { use vortex_error::VortexResult; use vortex_error::vortex_err; - use super::GeoEnvelope; - use crate::extension::GeoMetadata; + use super::SpatialEnvelope; use crate::extension::Rect; + use crate::extension::SpatialMetadata; use crate::extension::box_storage_dtype; use crate::extension::coordinate::Dimension; use crate::test_harness::linestring_column; @@ -308,15 +308,15 @@ mod tests { use crate::test_harness::polygon_column; use crate::test_harness::rect_column; - /// Execute a `GeoEnvelope` over `array`, returning the lazy box column. + /// Execute a `SpatialEnvelope` over `array`, returning the lazy box column. fn boxes(array: ArrayRef) -> VortexResult { - Ok(GeoEnvelope::try_new_array(array)?.into_array()) + Ok(SpatialEnvelope::try_new_array(array)?.into_array()) } /// A point's box is degenerate: both corners are the point itself. #[test] fn point_box_is_degenerate() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let points = point_column(vec![1.0, 3.0], vec![2.0, 4.0])?; @@ -329,7 +329,7 @@ mod tests { /// A polygon's box is its extent: the min/max over every ring vertex, one box per row. #[test] fn polygon_box_is_extent() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let polygons = polygon_column(vec![ @@ -347,7 +347,7 @@ mod tests { /// A `Rect` row is its own bounding box. #[test] fn rect_box_is_itself() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let rects = rect_column(vec![(0.0, 0.0, 2.0, 3.0), (-1.0, -1.0, 1.0, 1.0)])?; @@ -363,7 +363,7 @@ mod tests { /// fields are interleaved between them in storage — yields its XY extent as a 2-D box. #[test] fn xyz_rect_drops_z_bounds() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let ordinate = |value: f64| PrimitiveArray::from_iter([value]).into_array(); @@ -377,7 +377,7 @@ mod tests { ])? .into_array(); let ext = ExtDType::::try_new( - GeoMetadata::default(), + SpatialMetadata::default(), box_storage_dtype(Dimension::Xyz, Nullability::NonNullable), )?; let rects = ExtensionArray::try_new(ext.erased(), storage)?.into_array(); @@ -391,7 +391,7 @@ mod tests { /// type family is covered (`Point` has its own degenerate-box test above). #[test] fn covers_every_native_geometry_type() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let vertices = vec![(1.0, 2.0), (-1.0, 5.0), (3.0, 4.0)]; @@ -414,7 +414,7 @@ mod tests { /// the second polygon, so composed bounds diverging from loop positions shows up here. #[test] fn uneven_nesting_keeps_rows_aligned() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let multipolygons = multipolygon_column(vec![ @@ -436,7 +436,7 @@ mod tests { /// rows keep their boxes. #[test] fn empty_geometry_has_no_box() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let multipolygons = multipolygon_column(vec![ @@ -452,7 +452,7 @@ mod tests { /// vertices, in the first row — has no box, exactly like one empty at the outer level. #[test] fn inner_empty_ring_yields_null_box() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let polygons = polygon_column(vec![ @@ -468,7 +468,7 @@ mod tests { /// present in the sliced list's element buffer) must not leak into any row's box. #[test] fn sliced_operand_ignores_out_of_slice_coordinates() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let multipoints = multipoint_column(vec![ @@ -485,7 +485,7 @@ mod tests { /// The zero-copy point fast path respects slicing: corners come from the slice window only. #[test] fn sliced_point_column_keeps_rows_aligned() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let points = point_column(vec![9.0, 1.0, 3.0], vec![8.0, 2.0, 4.0])?; @@ -498,7 +498,7 @@ mod tests { /// A null geometry row yields a null box, just like an empty geometry; valid rows keep theirs. #[test] fn null_row_yields_null_box() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let points = nullable_point_column(vec![Some((1.0, 2.0)), None, Some((3.0, 4.0))])?; @@ -515,7 +515,7 @@ mod tests { /// empty and null rows are both null. #[test] fn mixed_valid_empty_null_rows_align() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let multipolygons = nullable_multipolygon_column(vec![ @@ -536,7 +536,7 @@ mod tests { /// A constant-null operand yields an all-null box column. #[test] fn constant_null_is_all_null() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().as_nullable(); @@ -549,7 +549,7 @@ mod tests { /// A non-null constant is boxed once and retained as a constant output. #[test] fn constant_point_remains_constant() -> VortexResult<()> { - let session = crate::test_harness::geo_session(); + let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); let scalar = point_column(vec![1.0], vec![2.0])?.execute_scalar(0, &mut ctx)?; @@ -571,7 +571,7 @@ mod tests { fn output_is_always_nullable() -> VortexResult<()> { let dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); assert!(!dtype.is_nullable()); - let out = GeoEnvelope.return_dtype(&EmptyOptions, &[dtype])?; + let out = SpatialEnvelope.return_dtype(&EmptyOptions, &[dtype])?; assert!(out.is_nullable()); Ok(()) } @@ -580,7 +580,11 @@ mod tests { #[test] fn non_geometry_operand_is_rejected() -> VortexResult<()> { let numeric = DType::Primitive(PType::I32, Nullability::NonNullable); - assert!(GeoEnvelope.return_dtype(&EmptyOptions, &[numeric]).is_err()); + assert!( + SpatialEnvelope + .return_dtype(&EmptyOptions, &[numeric]) + .is_err() + ); Ok(()) } } diff --git a/vortex-geo/src/scalar_fn/execute.rs b/vortex-spatial/src/scalar_fn/execute.rs similarity index 100% rename from vortex-geo/src/scalar_fn/execute.rs rename to vortex-spatial/src/scalar_fn/execute.rs diff --git a/vortex-geo/src/scalar_fn/execute/binary.rs b/vortex-spatial/src/scalar_fn/execute/binary.rs similarity index 100% rename from vortex-geo/src/scalar_fn/execute/binary.rs rename to vortex-spatial/src/scalar_fn/execute/binary.rs diff --git a/vortex-geo/src/scalar_fn/execute/geo_types.rs b/vortex-spatial/src/scalar_fn/execute/geo_types.rs similarity index 100% rename from vortex-geo/src/scalar_fn/execute/geo_types.rs rename to vortex-spatial/src/scalar_fn/execute/geo_types.rs diff --git a/vortex-geo/src/scalar_fn/execute/unary.rs b/vortex-spatial/src/scalar_fn/execute/unary.rs similarity index 100% rename from vortex-geo/src/scalar_fn/execute/unary.rs rename to vortex-spatial/src/scalar_fn/execute/unary.rs diff --git a/vortex-geo/src/scalar_fn/intersects.rs b/vortex-spatial/src/scalar_fn/intersects.rs similarity index 90% rename from vortex-geo/src/scalar_fn/intersects.rs rename to vortex-spatial/src/scalar_fn/intersects.rs index 8fa65aba34a..bdabd2b9967 100644 --- a/vortex-geo/src/scalar_fn/intersects.rs +++ b/vortex-spatial/src/scalar_fn/intersects.rs @@ -30,13 +30,13 @@ use crate::scalar_fn::execute::execute_binary_geo_types; fn validate_intersects_operands(dtypes: &[DType]) -> VortexResult<()> { vortex_ensure!( dtypes.len() == 2, - "geo: intersects requires exactly two geometry operands, got {}", + "spatial: intersects requires exactly two geometry operands, got {}", dtypes.len() ); for dtype in dtypes { vortex_ensure!( is_native_geometry(dtype), - "geo: intersects operand {dtype} is not a native geometry type" + "spatial: intersects operand {dtype} is not a native geometry type" ); } Ok(()) @@ -45,24 +45,24 @@ fn validate_intersects_operands(dtypes: &[DType]) -> VortexResult<()> { /// OGC `ST_Intersects` (not disjoint; boundary contact counts) between two native geometry /// operands, each a column or a constant literal. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] -pub struct GeoIntersects; +pub struct SpatialIntersects; -impl GeoIntersects { +impl SpatialIntersects { /// A lazy `ScalarFnArray` computing per-row whether operands `a` and `b` intersect; either may /// be constant. The output length is taken from `a`. pub fn try_new_array(a: ArrayRef, b: ArrayRef) -> VortexResult { ScalarFnArray::try_new( - TypedScalarFnInstance::new(GeoIntersects, EmptyOptions).erased(), + TypedScalarFnInstance::new(SpatialIntersects, EmptyOptions).erased(), vec![a, b], ) } } -impl ScalarFnVTable for GeoIntersects { +impl ScalarFnVTable for SpatialIntersects { type Options = EmptyOptions; fn id(&self) -> ScalarFnId { - static ID: CachedId = CachedId::new("vortex.geo.intersects"); + static ID: CachedId = CachedId::new("vortex.st.intersects"); *ID } @@ -156,7 +156,7 @@ mod tests { use vortex_error::vortex_err; use wkb::writer::WriteOptions; - use super::GeoIntersects; + use super::SpatialIntersects; use crate::test_harness::nullable_point_column; use crate::test_harness::point_column; @@ -206,7 +206,7 @@ mod tests { Ok(array.execute::(ctx)?.into_array()) } - /// Execute `GeoIntersects(a, b)` and assert the per-row verdicts equal `expected`. + /// Execute `SpatialIntersects(a, b)` and assert the per-row verdicts equal `expected`. fn assert_intersects( a: ArrayRef, b: ArrayRef, @@ -214,7 +214,7 @@ mod tests { ) -> VortexResult<()> { let session = vortex_array::array_session(); let mut ctx = session.create_execution_ctx(); - let intersects = GeoIntersects::try_new_array(a, b)?.into_array(); + let intersects = SpatialIntersects::try_new_array(a, b)?.into_array(); assert_arrays_eq!(intersects, BoolArray::from_iter(expected), &mut ctx); Ok(()) } @@ -301,8 +301,8 @@ mod tests { let column = materialize(constant.clone(), &mut ctx)?; let against_constant = - GeoIntersects::try_new_array(donut_probes()?, constant)?.into_array(); - let pairwise = GeoIntersects::try_new_array(donut_probes()?, column)?.into_array(); + SpatialIntersects::try_new_array(donut_probes()?, constant)?.into_array(); + let pairwise = SpatialIntersects::try_new_array(donut_probes()?, column)?.into_array(); assert_arrays_eq!(against_constant, pairwise, &mut ctx); Ok(()) @@ -322,9 +322,10 @@ mod tests { fn output_nullability_mirrors_operands() -> VortexResult<()> { let dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let non_nullable = - GeoIntersects.return_dtype(&EmptyOptions, &[dtype.clone(), dtype.clone()])?; + SpatialIntersects.return_dtype(&EmptyOptions, &[dtype.clone(), dtype.clone()])?; assert!(!non_nullable.is_nullable()); - let nullable = GeoIntersects.return_dtype(&EmptyOptions, &[dtype.as_nullable(), dtype])?; + let nullable = + SpatialIntersects.return_dtype(&EmptyOptions, &[dtype.as_nullable(), dtype])?; assert!(nullable.is_nullable()); Ok(()) } @@ -338,7 +339,7 @@ mod tests { let points = nullable_point_column(vec![Some((2.0, 2.0)), None, Some((20.0, 20.0))])?; let query = geometry_constant(&donut(), 3)?; - let intersects = GeoIntersects::try_new_array(points, query)?.into_array(); + let intersects = SpatialIntersects::try_new_array(points, query)?.into_array(); let expected = BoolArray::new( BitBuffer::from_iter([true, false, false]), @@ -358,7 +359,7 @@ mod tests { let point_dtype = point_column(vec![0.0], vec![0.0])?.dtype().as_nullable(); let null_const = ConstantArray::new(Scalar::null(point_dtype), 2).into_array(); let points = point_column(vec![2.0, 20.0], vec![2.0, 20.0])?; - let intersects = GeoIntersects::try_new_array(null_const, points)?.into_array(); + let intersects = SpatialIntersects::try_new_array(null_const, points)?.into_array(); let expected = BoolArray::new(BitBuffer::from_iter([false, false]), Validity::AllInvalid).into_array(); @@ -385,7 +386,7 @@ mod tests { None, Some((9.0, 9.0)), ])?; - let intersects = GeoIntersects::try_new_array(a, b)?.into_array(); + let intersects = SpatialIntersects::try_new_array(a, b)?.into_array(); let expected = BoolArray::new( BitBuffer::from_iter([true, false, false, false]), @@ -404,7 +405,7 @@ mod tests { let points = nullable_point_column(vec![None, None])?; let query = geometry_constant(&donut(), 2)?; - let intersects = GeoIntersects::try_new_array(points, query)?.into_array(); + let intersects = SpatialIntersects::try_new_array(points, query)?.into_array(); let expected = BoolArray::new(BitBuffer::from_iter([false, false]), Validity::AllInvalid).into_array(); @@ -421,7 +422,7 @@ mod tests { 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 intersects = GeoIntersects::try_new_array(a, b)?.into_array(); + let intersects = SpatialIntersects::try_new_array(a, b)?.into_array(); let expected = BoolArray::new(BitBuffer::from_iter([false, false]), Validity::AllInvalid).into_array(); @@ -432,9 +433,9 @@ mod tests { /// A non-geometry operand dtype is rejected up front, before execution. #[test] fn non_geometry_operand_is_rejected() -> VortexResult<()> { - let geo = point_column(vec![0.0], vec![0.0])?.dtype().clone(); + let spatial_dtype = point_column(vec![0.0], vec![0.0])?.dtype().clone(); let numeric = DType::Primitive(PType::I32, Nullability::NonNullable); - let result = GeoIntersects.return_dtype(&EmptyOptions, &[geo, numeric]); + let result = SpatialIntersects.return_dtype(&EmptyOptions, &[spatial_dtype, numeric]); assert!(result.is_err()); Ok(()) } diff --git a/vortex-geo/src/scalar_fn/mod.rs b/vortex-spatial/src/scalar_fn/mod.rs similarity index 100% rename from vortex-geo/src/scalar_fn/mod.rs rename to vortex-spatial/src/scalar_fn/mod.rs diff --git a/vortex-geo/src/test_harness.rs b/vortex-spatial/src/test_harness.rs similarity index 91% rename from vortex-geo/src/test_harness.rs rename to vortex-spatial/src/test_harness.rs index 7bec0f90ee9..7b471bdf2c4 100644 --- a/vortex-geo/src/test_harness.rs +++ b/vortex-spatial/src/test_harness.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Shared test helpers for the geospatial extension types. +//! Shared test helpers for the spatial extension types. use vortex_array::ArrayRef; use vortex_array::IntoArray; @@ -20,7 +20,6 @@ use vortex_error::VortexResult; use vortex_error::vortex_err; use vortex_session::VortexSession; -use crate::extension::GeoMetadata; use crate::extension::LineString; use crate::extension::MultiLineString; use crate::extension::MultiPoint; @@ -28,6 +27,7 @@ use crate::extension::MultiPolygon; use crate::extension::Point; use crate::extension::Polygon; use crate::extension::Rect; +use crate::extension::SpatialMetadata; use crate::extension::box_storage_dtype; use crate::extension::coordinate::Coordinate; use crate::extension::coordinate::Dimension; @@ -38,16 +38,16 @@ use crate::extension::multipoint_storage_dtype; use crate::extension::multipolygon_storage_dtype; use crate::extension::polygon_storage_dtype; -/// A fresh session with the geospatial types, functions, and pruning rules registered. -pub fn geo_session() -> VortexSession { +/// A fresh session with the spatial types, functions, and pruning rules registered. +pub fn spatial_session() -> VortexSession { let session = vortex_array::array_session(); crate::initialize(&session); session } /// The WGS 84 (`EPSG:4326`) metadata tagged onto test geometry columns. -fn wgs84() -> GeoMetadata { - GeoMetadata { +fn wgs84() -> SpatialMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), } } @@ -97,7 +97,7 @@ fn vertex_list_lists(rows: &[Vec>]) -> VortexResult { /// Wrap `storage` in the geometry extension `vtable` (CRS `EPSG:4326`) with the canonical /// `storage_dtype` of that type. -fn geo_column + Default>( +fn spatial_column + Default>( storage: ArrayRef, storage_dtype: DType, ) -> VortexResult { @@ -109,11 +109,11 @@ fn geo_column + Default>( pub fn point_column(xs: Vec, ys: Vec) -> VortexResult { let storage = xy_struct(xs, ys)?; let storage_dtype = storage.dtype().clone(); - geo_column::(storage, storage_dtype) + spatial_column::(storage, storage_dtype) } /// A nullable `Point` column: `None` rows are null. Null rows carry placeholder coordinates in -/// storage that the geo kernels must never decode (they filter nulls before decoding). +/// storage that the spatial kernels must never decode (they filter nulls before decoding). pub fn nullable_point_column(points: Vec>) -> VortexResult { let len = points.len(); let valid = points.iter().map(Option::is_some); @@ -130,12 +130,12 @@ pub fn nullable_point_column(points: Vec>) -> VortexResult(storage, storage_dtype) + spatial_column::(storage, storage_dtype) } /// A `LineString` column: each line a list of `(x, y)` vertices, stored as `List>`. pub fn linestring_column(lines: Vec>) -> VortexResult { - geo_column::( + spatial_column::( vertex_lists(&lines)?, linestring_storage_dtype(Dimension::Xy, Nullability::NonNullable), ) @@ -143,7 +143,7 @@ pub fn linestring_column(lines: Vec>) -> VortexResult /// A `MultiPoint` column: each row a list of `(x, y)` points, stored as `List>`. pub fn multipoint_column(points: Vec>) -> VortexResult { - geo_column::( + spatial_column::( vertex_lists(&points)?, multipoint_storage_dtype(Dimension::Xy, Nullability::NonNullable), ) @@ -152,7 +152,7 @@ pub fn multipoint_column(points: Vec>) -> VortexResult /// A `Polygon` column: each polygon a list of rings, each ring a list of `(x, y)` vertices, /// stored as `List>>`. pub fn polygon_column(polygons: Vec>>) -> VortexResult { - geo_column::( + spatial_column::( vertex_list_lists(&polygons)?, polygon_storage_dtype(Dimension::Xy, Nullability::NonNullable), ) @@ -160,7 +160,7 @@ pub fn polygon_column(polygons: Vec>>) -> VortexResult>>`. pub fn multilinestring_column(multilines: Vec>>) -> VortexResult { - geo_column::( + spatial_column::( vertex_list_lists(&multilines)?, multilinestring_storage_dtype(Dimension::Xy, Nullability::NonNullable), ) @@ -172,7 +172,7 @@ pub type MultiPolygonRings = Vec>>; /// A `MultiPolygon` column, stored as `List>>>`. pub fn multipolygon_column(multipolygons: Vec) -> VortexResult { let polygons: Vec>> = multipolygons.iter().flatten().cloned().collect(); - geo_column::( + spatial_column::( nest(&multipolygons, vertex_list_lists(&polygons)?)?, multipolygon_storage_dtype(Dimension::Xy, Nullability::NonNullable), ) @@ -199,7 +199,7 @@ pub fn nullable_multipolygon_column( Validity::from_iter(multipolygons.iter().map(Option::is_some)), )? .into_array(); - geo_column::( + spatial_column::( storage, multipolygon_storage_dtype(Dimension::Xy, Nullability::Nullable), ) @@ -218,7 +218,7 @@ pub fn rect_column(boxes: Vec<(f64, f64, f64, f64)>) -> VortexResult { ("ymax", field(|b| b.3)), ])? .into_array(); - geo_column::( + spatial_column::( storage, box_storage_dtype(Dimension::Xy, Nullability::NonNullable), ) @@ -245,7 +245,7 @@ pub fn nullable_rect_column(boxes: Vec>) -> VortexR )? .into_array(); let ext = ExtDType::::try_new( - GeoMetadata::default(), + SpatialMetadata::default(), box_storage_dtype(Dimension::Xy, Nullability::Nullable), )?; Ok(ExtensionArray::try_new(ext.erased(), storage)?.into_array()) diff --git a/vortex-geo/src/tests/linestring.rs b/vortex-spatial/src/tests/linestring.rs similarity index 97% rename from vortex-geo/src/tests/linestring.rs rename to vortex-spatial/src/tests/linestring.rs index 46e2be94647..672c0181f40 100644 --- a/vortex-geo/src/tests/linestring.rs +++ b/vortex-spatial/src/tests/linestring.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop for the `vortex.geo.linestring` extension type (`geoarrow.linestring`). +//! Arrow interop for the `vortex.st.linestring` extension type (`geoarrow.linestring`). use std::sync::Arc; diff --git a/vortex-geo/src/tests/mod.rs b/vortex-spatial/src/tests/mod.rs similarity index 65% rename from vortex-geo/src/tests/mod.rs rename to vortex-spatial/src/tests/mod.rs index 3ee1714ce47..c83a30d476d 100644 --- a/vortex-geo/src/tests/mod.rs +++ b/vortex-spatial/src/tests/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop tests for the geospatial extension types, exercising the session wiring set up +//! Arrow interop tests for the spatial extension types, exercising the session wiring set up //! by [`crate::initialize`]. mod linestring; @@ -16,5 +16,5 @@ use std::sync::LazyLock; use vortex_session::VortexSession; -/// A session with the geospatial types and functions registered. -static SESSION: LazyLock = LazyLock::new(crate::test_harness::geo_session); +/// A session with the spatial types and functions registered. +static SESSION: LazyLock = LazyLock::new(crate::test_harness::spatial_session); diff --git a/vortex-geo/src/tests/multilinestring.rs b/vortex-spatial/src/tests/multilinestring.rs similarity index 97% rename from vortex-geo/src/tests/multilinestring.rs rename to vortex-spatial/src/tests/multilinestring.rs index 694c77cdf3b..c04747b35b6 100644 --- a/vortex-geo/src/tests/multilinestring.rs +++ b/vortex-spatial/src/tests/multilinestring.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop for the `vortex.geo.multilinestring` extension type (`geoarrow.multilinestring`). +//! Arrow interop for the `vortex.st.multilinestring` extension type (`geoarrow.multilinestring`). use std::sync::Arc; diff --git a/vortex-geo/src/tests/multipoint.rs b/vortex-spatial/src/tests/multipoint.rs similarity index 97% rename from vortex-geo/src/tests/multipoint.rs rename to vortex-spatial/src/tests/multipoint.rs index 366dac73fc2..d3098fd40a4 100644 --- a/vortex-geo/src/tests/multipoint.rs +++ b/vortex-spatial/src/tests/multipoint.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop for the `vortex.geo.multipoint` extension type (`geoarrow.multipoint`). +//! Arrow interop for the `vortex.st.multipoint` extension type (`geoarrow.multipoint`). use std::sync::Arc; diff --git a/vortex-geo/src/tests/multipolygon.rs b/vortex-spatial/src/tests/multipolygon.rs similarity index 97% rename from vortex-geo/src/tests/multipolygon.rs rename to vortex-spatial/src/tests/multipolygon.rs index 407661f6a43..04e35202cab 100644 --- a/vortex-geo/src/tests/multipolygon.rs +++ b/vortex-spatial/src/tests/multipolygon.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop for the `vortex.geo.multipolygon` extension type (`geoarrow.multipolygon`). +//! Arrow interop for the `vortex.st.multipolygon` extension type (`geoarrow.multipolygon`). use std::sync::Arc; diff --git a/vortex-geo/src/tests/point.rs b/vortex-spatial/src/tests/point.rs similarity index 98% rename from vortex-geo/src/tests/point.rs rename to vortex-spatial/src/tests/point.rs index 61a2e98dbe7..aec2e91bcbf 100644 --- a/vortex-geo/src/tests/point.rs +++ b/vortex-spatial/src/tests/point.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop for the `vortex.geo.point` extension type (`geoarrow.point`). +//! Arrow interop for the `vortex.st.point` extension type (`geoarrow.point`). use std::sync::Arc; diff --git a/vortex-geo/src/tests/rect.rs b/vortex-spatial/src/tests/rect.rs similarity index 90% rename from vortex-geo/src/tests/rect.rs rename to vortex-spatial/src/tests/rect.rs index 865892493e7..c1f752cfb21 100644 --- a/vortex-geo/src/tests/rect.rs +++ b/vortex-spatial/src/tests/rect.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop for the `vortex.geo.box` extension type (`geoarrow.box`). +//! Arrow interop for the `vortex.st.box` extension type (`geoarrow.box`). use std::sync::Arc; @@ -110,7 +110,7 @@ fn roundtrips_through_arrow() -> VortexResult<()> { Ok(()) } -/// The existing geo scalar functions run on a `Rect` operand via the shared `geometries()` decode, +/// The existing spatial scalar functions run on a `Rect` operand via the shared `geometries()` decode, /// producing the same results as the equivalent polygon: a box `(0,0)-(10,10)` against interior /// point `(5,5)` and exterior point `(20,20)`. #[test] @@ -120,9 +120,9 @@ fn scalar_functions_run_on_rect() -> VortexResult<()> { use vortex_array::arrays::BoolArray; use vortex_array::assert_arrays_eq; - use crate::scalar_fn::contains::GeoContains; - use crate::scalar_fn::distance::GeoDistance; - use crate::scalar_fn::intersects::GeoIntersects; + use crate::scalar_fn::contains::SpatialContains; + use crate::scalar_fn::distance::SpatialDistance; + use crate::scalar_fn::intersects::SpatialIntersects; use crate::test_harness::point_column; let mut ctx = SESSION.create_execution_ctx(); @@ -130,17 +130,17 @@ fn scalar_functions_run_on_rect() -> VortexResult<()> { let points = point_column(vec![5.0, 20.0], vec![5.0, 20.0])?; // Distance: 0 to the interior point, >0 to the exterior point. - let distance = GeoDistance::try_new_array(bbox.clone(), points.clone())?.into_array(); + let distance = SpatialDistance::try_new_array(bbox.clone(), points.clone())?.into_array(); let distance = distance.execute::(&mut ctx)?.into_primitive(); let distances = distance.as_slice::(); assert_eq!(distances[0], 0.0); assert!(distances[1] > 0.0); // Intersects / Contains: true for the interior point, false for the exterior one. - let intersects = GeoIntersects::try_new_array(bbox.clone(), points.clone())?.into_array(); + let intersects = SpatialIntersects::try_new_array(bbox.clone(), points.clone())?.into_array(); assert_arrays_eq!(intersects, BoolArray::from_iter([true, false]), &mut ctx); - let contains = GeoContains::try_new_array(bbox, points)?.into_array(); + let contains = SpatialContains::try_new_array(bbox, points)?.into_array(); assert_arrays_eq!(contains, BoolArray::from_iter([true, false]), &mut ctx); Ok(()) } diff --git a/vortex-geo/src/tests/wkb.rs b/vortex-spatial/src/tests/wkb.rs similarity index 96% rename from vortex-geo/src/tests/wkb.rs rename to vortex-spatial/src/tests/wkb.rs index 9be8871080f..40477a464c9 100644 --- a/vortex-geo/src/tests/wkb.rs +++ b/vortex-spatial/src/tests/wkb.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Arrow interop for the `vortex.geo.wkb` extension type (`geoarrow.wkb`). +//! Arrow interop for the `vortex.st.wkb` extension type (`geoarrow.wkb`). use std::sync::Arc; @@ -37,7 +37,7 @@ use vortex_error::vortex_err; use wkb::writer::WriteOptions; use super::SESSION; -use crate::extension::GeoMetadata; +use crate::extension::SpatialMetadata; use crate::extension::WellKnownBinary; /// The polygon geometry encoded by these tests. @@ -69,7 +69,7 @@ fn wkb_extension_array() -> VortexResult<(Vec, vortex_array::ArrayRef)> { builder.append_value(&buf); let dtype = ExtDType::::try_new( - GeoMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), }, DType::Binary(Nullability::NonNullable), @@ -94,8 +94,8 @@ fn assert_imported_wkb_dtype(dtype: &DType, expected_crs: Option<&str>, nullable }; assert!(ext.is::()); assert_eq!(ext.storage_dtype(), &DType::Binary(nullable.into())); - let geo = ext.metadata::(); - assert_eq!(geo.crs.as_deref(), expected_crs); + let spatial_metadata = ext.metadata::(); + assert_eq!(spatial_metadata.crs.as_deref(), expected_crs); } /// WKB scalars unpack back to the geometry they encode. @@ -105,7 +105,7 @@ fn scalar_unpacks_to_geometry() -> VortexResult<()> { let (_, array) = wkb_extension_array()?; let dtype = ExtDType::::try_new( - GeoMetadata { + SpatialMetadata { crs: Some("EPSG:4326".to_string()), }, DType::Binary(Nullability::NonNullable),