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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ site/

# Python cache files
__pycache__
.venv/

# R-related files
.Rproj.user

# .env file for release management
dev/release/.env
.mcp.env

/.luarc.json
.positai
4 changes: 2 additions & 2 deletions c/sedona-geos/src/geos_to_wkb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::io::Write;
use byteorder::{LittleEndian, WriteBytesExt};
use datafusion_common::{error::Result, DataFusionError};
use geo_traits::Dimensions;
use geos::{CoordType, Geom, Geometry, GeometryTypes};
use geos::{CoordType, Geom, GeometryTypes};
use sedona_common::sedona_internal_err;
use sedona_geometry::wkb_factory::{
write_wkb_geometrycollection_header, write_wkb_linestring_header,
Expand All @@ -31,7 +31,7 @@ use sedona_geometry::wkb_factory::{
///
/// This is a fast, custom implementation that directly extracts coordinates
/// from GEOS geometries and writes them in WKB format into a buffer.
pub fn write_geos_geometry(geom: &Geometry, writer: &mut impl Write) -> Result<()> {
pub fn write_geos_geometry(geom: &impl Geom, writer: &mut impl Write) -> Result<()> {
write_geometry(geom, writer)
}

Expand Down
4 changes: 4 additions & 0 deletions c/sedona-geos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ pub mod register;
mod st_area;
mod st_boundary;
mod st_buffer;
mod st_buildarea;
mod st_centroid;
mod st_concavehull;
mod st_convexhull;
mod st_delaunaytriangles;
mod st_dwithin;
mod st_exteriorring;
mod st_isring;
mod st_issimple;
mod st_isvalid;
Expand All @@ -43,6 +46,7 @@ mod st_nrings;
mod st_numinteriorrings;
mod st_numpoints;
mod st_perimeter;
mod st_pointonsurface;
mod st_polygonize;
mod st_polygonize_agg;
mod st_relate;
Expand Down
7 changes: 7 additions & 0 deletions c/sedona-geos/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub fn scalar_kernels() -> Vec<(&'static str, Vec<ScalarKernelRef>)> {
define_scalar_kernels!(
"st_area" => crate::st_area::st_area_impl,
"st_boundary" => crate::st_boundary::st_boundary_impl,
"st_buildarea" => crate::st_buildarea::st_build_area_impl,
"st_buffer" => crate::st_buffer::st_buffer_impl,
"st_buffer" => crate::st_buffer::st_buffer_style_impl,
"st_centroid" => crate::st_centroid::st_centroid_impl,
Expand All @@ -51,11 +52,15 @@ pub fn scalar_kernels() -> Vec<(&'static str, Vec<ScalarKernelRef>)> {
"st_coveredby" => crate::binary_predicates::st_covered_by_impl,
"st_covers" => crate::binary_predicates::st_covers_impl,
"st_crosses" => crate::binary_predicates::st_crosses_impl,
"st_delaunaytriangles" => crate::st_delaunaytriangles::st_delaunay_triangles_impl,
"st_delaunaytriangles" => crate::st_delaunaytriangles::st_delaunay_triangles_tolerance_impl,
"st_delaunaytriangles" => crate::st_delaunaytriangles::st_delaunay_triangles_flags_impl,
"st_difference" => crate::overlay::st_difference_impl,
"st_disjoint" => crate::binary_predicates::st_disjoint_impl,
"st_distance" => crate::distance::st_distance_impl,
"st_dwithin" => crate::st_dwithin::st_dwithin_impl,
"st_equals" => crate::binary_predicates::st_equals_impl,
"st_exteriorring" => crate::st_exteriorring::st_exterior_ring_impl,
"st_intersection" => crate::overlay::st_intersection_impl,
"st_intersects" => crate::binary_predicates::st_intersects_impl,
"st_isring" => crate::st_isring::st_is_ring_impl,
Expand All @@ -69,10 +74,12 @@ pub fn scalar_kernels() -> Vec<(&'static str, Vec<ScalarKernelRef>)> {
"st_minimumclearanceline" => crate::st_minimumclearance_line::st_minimum_clearance_line_impl,
"st_normalize" => crate::st_normalize::st_normalize_impl,
"st_nrings" => crate::st_nrings::st_nrings_impl,
"st_numinteriorring" => crate::st_numinteriorrings::st_num_interior_rings_impl,
"st_numinteriorrings" => crate::st_numinteriorrings::st_num_interior_rings_impl,
"st_numpoints" => crate::st_numpoints::st_num_points_impl,
"st_overlaps" => crate::binary_predicates::st_overlaps_impl,
"st_perimeter" => crate::st_perimeter::st_perimeter_impl,
"st_pointonsurface" => crate::st_pointonsurface::st_point_on_surface_impl,
"st_polygonize" => crate::st_polygonize::st_polygonize_impl,
"st_relate" => crate::st_relate::st_relate_impl,
"st_relate" => crate::st_relate::st_relate_pattern_impl,
Expand Down
156 changes: 156 additions & 0 deletions c/sedona-geos/src/st_buildarea.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

use std::sync::Arc;

use arrow_array::builder::BinaryBuilder;
use datafusion_common::{error::Result, DataFusionError};
use datafusion_expr::ColumnarValue;
use geos::{Geom, Geometry, GeometryTypes};
use sedona_expr::{
item_crs::ItemCrsKernel,
scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
};
use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
use sedona_schema::{
datatypes::{SedonaType, WKB_GEOGRAPHY, WKB_GEOMETRY},
matchers::ArgMatcher,
};

use crate::executor::GeosExecutor;
use crate::geos_to_wkb::write_geos_geometry;

/// ST_BuildArea() implementation using the geos crate
pub fn st_build_area_impl() -> Vec<ScalarKernelRef> {
ItemCrsKernel::wrap_impl(vec![
Arc::new(STBuildArea {
matcher: ArgMatcher::new(vec![ArgMatcher::is_geometry()], WKB_GEOMETRY),
}),
Arc::new(STBuildArea {
matcher: ArgMatcher::new(vec![ArgMatcher::is_geography()], WKB_GEOGRAPHY),
}),
Comment on lines +43 to +45

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Arc::new(STBuildArea {
matcher: ArgMatcher::new(vec![ArgMatcher::is_geography()], WKB_GEOGRAPHY),
}),

Sorry for missing this, but we can't support Geography with this one because the definition of "enclosing" isn't the same on the sphere.

])
}

#[derive(Debug)]
struct STBuildArea {
matcher: ArgMatcher,
}

impl SedonaScalarKernel for STBuildArea {
fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
self.matcher.match_args(args)
}

fn invoke_batch(
&self,
arg_types: &[SedonaType],
args: &[ColumnarValue],
) -> Result<ColumnarValue> {
let executor = GeosExecutor::new(arg_types, args);
let mut builder = BinaryBuilder::with_capacity(
executor.num_iterations(),
WKB_MIN_PROBABLE_BYTES * executor.num_iterations(),
);
executor.execute_wkb_void(|maybe_geom| {
match maybe_geom {
Some(geom) => {
if invoke_scalar(&geom, &mut builder)? {
builder.append_value([]);
} else {
builder.append_null();
}
}
_ => builder.append_null(),
}
Ok(())
})?;

executor.finish(Arc::new(builder.finish()))
}
}

fn invoke_scalar(geom: &Geometry, writer: &mut impl std::io::Write) -> Result<bool> {
let geom_type = geom
.geometry_type()
.map_err(|e| DataFusionError::Execution(format!("Failed to get geometry type: {e}")))?;

match geom_type {
GeometryTypes::LineString
| GeometryTypes::MultiLineString
| GeometryTypes::GeometryCollection => {}
_ => return Ok(false),
}

let result = geom
.build_area()
.map_err(|e| DataFusionError::Execution(format!("ST_BuildArea failed: {e}")))?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
.map_err(|e| DataFusionError::Execution(format!("ST_BuildArea failed: {e}")))?;
.map_err(|e| exec_datafusion_err!("ST_BuildArea failed: {e}"))?;

write_geos_geometry(&result, writer)?;
Ok(true)
}

#[cfg(test)]
mod tests {
use datafusion_common::ScalarValue;
use rstest::rstest;
use sedona_expr::scalar_udf::SedonaScalarUDF;
use sedona_schema::datatypes::{
WKB_GEOGRAPHY, WKB_GEOGRAPHY_ITEM_CRS, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS,
};
use sedona_testing::testers::ScalarUdfTester;

use super::*;

#[rstest]
fn udf(#[values(WKB_GEOMETRY, WKB_GEOGRAPHY)] sedona_type: SedonaType) {
let udf = SedonaScalarUDF::from_impl("st_buildarea", st_build_area_impl());
let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type.clone()]);

tester.assert_return_type(sedona_type.clone());

let result = tester
.invoke_scalar("LINESTRING (0 0, 1 0, 1 1, 0 1, 0 0)")
.unwrap();
tester.assert_scalar_result_equals(result, "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))");

let result = tester.invoke_scalar("POINT (0 0)").unwrap();
assert!(result.is_null());

let result = tester
.invoke_scalar("POLYGON ((0 0, 1 0, 1 1, 0 0))")
.unwrap();
assert!(result.is_null());

let result = tester.invoke_scalar(ScalarValue::Null).unwrap();
assert!(result.is_null());
}

#[rstest]
fn udf_invoke_item_crs(
#[values(WKB_GEOMETRY_ITEM_CRS.clone(), WKB_GEOGRAPHY_ITEM_CRS.clone())]
sedona_type: SedonaType,
) {
let udf = SedonaScalarUDF::from_impl("st_buildarea", st_build_area_impl());
let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type.clone()]);
tester.assert_return_type(sedona_type);

let result = tester
.invoke_scalar("LINESTRING (0 0, 1 0, 1 1, 0 1, 0 0)")
.unwrap();
tester.assert_scalar_result_equals(result, "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))");
}
}
Loading
Loading