diff --git a/Cargo.lock b/Cargo.lock index 1d3f82406c268..ad5ae40b5b399 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1573,6 +1573,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "c_vec" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd7a427adc0135366d99db65b36dae9237130997e560ed61118041fb72be6e8" + [[package]] name = "cache-padded" version = "1.3.0" @@ -3067,7 +3073,9 @@ dependencies = [ "databend-common-vector", "ethnum 1.5.0 (git+https://github.com/ariesdevil/ethnum-rs?rev=4cb05f1)", "geo", + "geo-types", "geohash", + "geos", "geozero", "goldenfile", "h3o", @@ -6099,6 +6107,42 @@ dependencies = [ "thiserror", ] +[[package]] +name = "geos" +version = "8.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b57a162bbfe8866b1ab898aad47e7fe634a9177c4b1bacc80af04d2b45772a46" +dependencies = [ + "c_vec", + "geo-types", + "geos-sys", + "libc", + "num", + "wkt", +] + +[[package]] +name = "geos-src" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f5247861370ecfcd91a239376c572021cf355cddf52a6602ebbf403d9bb99e" +dependencies = [ + "cmake", +] + +[[package]] +name = "geos-sys" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc873d24aefc72aa94c3c1c251afb82beb7be5926002746c0e1f585fef9854c" +dependencies = [ + "geos-src", + "libc", + "link-cplusplus", + "pkg-config", + "semver", +] + [[package]] name = "geozero" version = "0.11.0" @@ -8571,6 +8615,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" diff --git a/src/query/functions/Cargo.toml b/src/query/functions/Cargo.toml index 6e3416576782b..b521e79b0c769 100644 --- a/src/query/functions/Cargo.toml +++ b/src/query/functions/Cargo.toml @@ -34,7 +34,9 @@ criterion = "0.4" ctor = "0.1.26" ethnum = { workspace = true } geo = { workspace = true } +geo-types = "0.7.13" geohash = "0.13.0" +geos = { version = "8.3", features = ["static", "geo", "geo-types"] } geozero = { workspace = true } h3o = "0.4.0" hex = "0.4.3" diff --git a/src/query/functions/src/scalars/geometry.rs b/src/query/functions/src/scalars/geometry.rs index 8050e315f9621..5ddb5c74b3714 100644 --- a/src/query/functions/src/scalars/geometry.rs +++ b/src/query/functions/src/scalars/geometry.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use databend_common_exception::ErrorCode; use databend_common_expression::types::geometry::GeometryType; use databend_common_expression::types::Int32Type; use databend_common_expression::types::NumberType; @@ -22,8 +23,13 @@ use databend_common_expression::vectorize_with_builder_2_arg; use databend_common_expression::FunctionDomain; use databend_common_expression::FunctionRegistry; use databend_common_io::parse_to_ewkb; -use geo::Geometry; +use geo::MultiPoint; use geo::Point; +use geos::geo_types; +use geos::geo_types::Coord; +use geos::geo_types::LineString; +use geos::Geom; +use geos::Geometry; use geozero::wkb::Ewkb; use geozero::CoordDimensions; use geozero::ToWkb; @@ -34,6 +40,7 @@ use geozero::ToWkt; pub fn register(registry: &mut FunctionRegistry) { // aliases registry.register_aliases("st_makegeompoint", &["st_geom_point"]); + registry.register_aliases("st_makeline", &["st_make_line"]); registry.register_aliases("st_geometryfromwkt", &[ "st_geomfromwkt", "st_geometryfromewkt", @@ -54,7 +61,7 @@ pub fn register(registry: &mut FunctionRegistry) { return; } } - let geom = Geometry::from(Point::new(longitude.0, latitude.0)); + let geom = geo::Geometry::from(Point::new(longitude.0, latitude.0)); match geom.to_ewkb(CoordDimensions::xy(), None) { Ok(data) => { builder.put_slice(data.as_slice()) @@ -67,6 +74,96 @@ pub fn register(registry: &mut FunctionRegistry) { }) ); + registry.register_passthrough_nullable_2_arg::( + "st_makeline", + |_, _, _| FunctionDomain::Full, + vectorize_with_builder_2_arg::( + |left_exp, right_exp, builder, ctx| { + if let Some(validity) = &ctx.validity { + if !validity.get_bit(builder.len()) { + builder.commit_row(); + return; + } + } + let srid: Option; + let params = &vec![left_exp, right_exp]; + let geos: Vec = + match binary_to_geos(params) + { + Ok(geos) => { + match get_shared_srid(&geos){ + Ok(s) => { + srid = s; + geos + }, + Err(e) => { + ctx.set_error(builder.len(), ErrorCode::GeometryError(e).to_string()); + builder.put_str(""); + return; + } + } + }, + Err(e) => { + ctx.set_error(builder.len(), ErrorCode::GeometryError(e.to_string()).to_string()); + return builder.put_str(""); + } + }; + + let mut coords: Vec = vec![]; + for geometry in geos.into_iter() { + let g : geo_types::Geometry = (&geometry).try_into().unwrap(); + match g { + geo_types::Geometry::Point(_) => { + let point: Point = match g.try_into() { + Ok(point) => point, + Err(e) => { + ctx.set_error(builder.len(), ErrorCode::GeometryError(e.to_string()).to_string()); + return builder.put_str(""); + } + }; + coords.push(point.into()); + }, + geo_types::Geometry::LineString(_)=> { + let line: LineString = match g.try_into() { + Ok(line) => line, + Err(e) => { + ctx.set_error(builder.len(), e.to_string()); + return builder.put_str(""); + } + }; + coords.append(&mut line.into_inner()); + }, + geo_types::Geometry::MultiPoint(_)=> { + let multi_point: MultiPoint = match g.try_into() { + Ok(multi_point) => multi_point, + Err(e) => { + ctx.set_error(builder.len(), e.to_string()); + return builder.put_str(""); + } + }; + for point in multi_point.into_iter() { + coords.push(point.into()); + } + }, + _ => { + ctx.set_error( + builder.len(), + ErrorCode::GeometryError("Geometry expression must be a Point, MultiPoint, or LineString.").to_string(), + ); + return builder.put_str(""); + } + } + } + let geom = geo::Geometry::from(LineString::new(coords)); + match geom.to_ewkb(CoordDimensions::xy(), srid) { + Ok(data) => builder.put_slice(data.as_slice()), + Err(e) => ctx.set_error(builder.len(), e.to_string()), + } + builder.commit_row(); + }, + ), + ); + registry.register_passthrough_nullable_1_arg::( "st_geometryfromwkt", |_, _| FunctionDomain::MayThrow, @@ -244,3 +341,50 @@ pub fn register(registry: &mut FunctionRegistry) { // // Ok(srid) // } + +#[inline] +fn binary_to_geos<'a>(binaries: &'a Vec<&'a [u8]>) -> Result>, String> { + let mut geos: Vec = Vec::with_capacity(binaries.len()); + let mut srid: Option = None; + for (index, binary) in binaries.iter().enumerate() { + match Geometry::new_from_wkb(binary) { + Ok(data) => { + if index == 0 { + srid = data.get_srid().map_or_else(|_| None, |v| Some(v as i32)); + } else { + let t_srid = data.get_srid().map_or_else(|_| None, |v| Some(v as i32)); + if !srid.eq(&t_srid) { + return Err("Srid does not match!".to_string()); + } + } + geos.push(data) + } + Err(e) => return Err(e.to_string()), + }; + } + Ok(geos) +} + +#[inline] +fn get_shared_srid(geometries: &Vec) -> Result, String> { + let mut srid: Option = None; + let mut error_srid: String = String::new(); + let check_srid = geometries.windows(2).all(|w| { + let v1 = w[0].get_srid().map_or_else(|_| None, |v| Some(v as i32)); + let v2 = w[1].get_srid().map_or_else(|_| None, |v| Some(v as i32)); + match v1.eq(&v2) { + true => { + srid = v1; + true + } + false => { + error_srid = "Srid does not match!".to_string(); + false + } + } + }); + match check_srid { + true => Ok(srid), + false => Err(error_srid.clone()), + } +} diff --git a/src/query/functions/tests/it/scalars/geometry.rs b/src/query/functions/tests/it/scalars/geometry.rs index 76c87439546ff..3384ca488667b 100644 --- a/src/query/functions/tests/it/scalars/geometry.rs +++ b/src/query/functions/tests/it/scalars/geometry.rs @@ -26,13 +26,37 @@ use crate::scalars::run_ast; fn test_geometry() { let mut mint = Mint::new("tests/it/scalars/testdata"); let file = &mut mint.new_goldenfile("geometry.txt").unwrap(); - + test_st_makeline(file); test_st_makepoint(file); test_to_string(file); test_st_geometryfromwkt(file); // test_st_transform(file); } +fn test_st_makeline(file: &mut impl Write) { + run_ast( + file, + "st_makeline( + to_geometry('SRID=4326;POINT(1.0 2.0)'), + to_geometry('SRID=4326;POINT(3.5 4.5)'))", + &[], + ); + run_ast( + file, + "st_makeline( + to_geometry('SRID=3857;POINT(1.0 2.0)'), + to_geometry('SRID=3857;LINESTRING(1.0 2.0, 10.1 5.5)'))", + &[], + ); + run_ast( + file, + "st_makeline( + to_geometry('LINESTRING(1.0 2.0, 10.1 5.5)'), + to_geometry('MULTIPOINT(3.5 4.5, 6.1 7.9)'))", + &[], + ); +} + fn test_st_makepoint(file: &mut impl Write) { run_ast(file, "st_makegeompoint(7.0, 8.0)", &[]); run_ast(file, "st_makegeompoint(7.0, -8.0)", &[]); diff --git a/src/query/functions/tests/it/scalars/testdata/function_list.txt b/src/query/functions/tests/it/scalars/testdata/function_list.txt index 8382816ba9add..2ae37454f9571 100644 --- a/src/query/functions/tests/it/scalars/testdata/function_list.txt +++ b/src/query/functions/tests/it/scalars/testdata/function_list.txt @@ -40,6 +40,7 @@ st_geometryfromtext -> st_geometryfromwkt st_geomfromewkt -> st_geometryfromwkt st_geomfromtext -> st_geometryfromwkt st_geomfromwkt -> st_geometryfromwkt +st_make_line -> st_makeline str_to_date -> to_date str_to_timestamp -> to_timestamp str_to_year -> to_year @@ -3500,6 +3501,8 @@ Functions overloads: 3 st_geometryfromwkt(String NULL, Int32 NULL) :: Geometry NULL 0 st_makegeompoint(Float64, Float64) :: Geometry 1 st_makegeompoint(Float64 NULL, Float64 NULL) :: Geometry NULL +0 st_makeline(Geometry, Geometry) :: Geometry +1 st_makeline(Geometry NULL, Geometry NULL) :: Geometry NULL 0 strcmp(String, String) :: Int8 1 strcmp(String NULL, String NULL) :: Int8 NULL 0 string_to_h3(String) :: UInt64 diff --git a/src/query/functions/tests/it/scalars/testdata/geometry.txt b/src/query/functions/tests/it/scalars/testdata/geometry.txt index 641e478679413..a0176294d3d85 100644 --- a/src/query/functions/tests/it/scalars/testdata/geometry.txt +++ b/src/query/functions/tests/it/scalars/testdata/geometry.txt @@ -1,3 +1,36 @@ +ast : st_makeline( + to_geometry('SRID=4326;POINT(1.0 2.0)'), + to_geometry('SRID=4326;POINT(3.5 4.5)')) +raw expr : st_makeline(to_geometry('SRID=4326;POINT(1.0 2.0)'), to_geometry('SRID=4326;POINT(3.5 4.5)')) +checked expr : st_makeline(st_geometryfromwkt("SRID=4326;POINT(1.0 2.0)"), st_geometryfromwkt("SRID=4326;POINT(3.5 4.5)")) +optimized expr : "SRID=4326;LINESTRING(1 2,3.5 4.5)" +output type : Geometry +output domain : Undefined +output : 'SRID=4326;LINESTRING(1 2,3.5 4.5)' + + +ast : st_makeline( + to_geometry('SRID=3857;POINT(1.0 2.0)'), + to_geometry('SRID=3857;LINESTRING(1.0 2.0, 10.1 5.5)')) +raw expr : st_makeline(to_geometry('SRID=3857;POINT(1.0 2.0)'), to_geometry('SRID=3857;LINESTRING(1.0 2.0, 10.1 5.5)')) +checked expr : st_makeline(st_geometryfromwkt("SRID=3857;POINT(1.0 2.0)"), st_geometryfromwkt("SRID=3857;LINESTRING(1.0 2.0, 10.1 5.5)")) +optimized expr : "SRID=3857;LINESTRING(1 2,1 2,10.1 5.5)" +output type : Geometry +output domain : Undefined +output : 'SRID=3857;LINESTRING(1 2,1 2,10.1 5.5)' + + +ast : st_makeline( + to_geometry('LINESTRING(1.0 2.0, 10.1 5.5)'), + to_geometry('MULTIPOINT(3.5 4.5, 6.1 7.9)')) +raw expr : st_makeline(to_geometry('LINESTRING(1.0 2.0, 10.1 5.5)'), to_geometry('MULTIPOINT(3.5 4.5, 6.1 7.9)')) +checked expr : st_makeline(st_geometryfromwkt("LINESTRING(1.0 2.0, 10.1 5.5)"), st_geometryfromwkt("MULTIPOINT(3.5 4.5, 6.1 7.9)")) +optimized expr : "LINESTRING(1 2,10.1 5.5,3.5 4.5,6.1 7.9)" +output type : Geometry +output domain : Undefined +output : 'LINESTRING(1 2,10.1 5.5,3.5 4.5,6.1 7.9)' + + ast : st_makegeompoint(7.0, 8.0) raw expr : st_makegeompoint(7.0, 8.0) checked expr : st_makegeompoint(to_float64(7.0_d128(2,1)), to_float64(8.0_d128(2,1))) diff --git a/tests/sqllogictests/suites/query/02_function/02_0060_function_geometry.test b/tests/sqllogictests/suites/query/02_function/02_0060_function_geometry.test index 553c53b38dbda..7f47a3cbc7273 100644 --- a/tests/sqllogictests/suites/query/02_function/02_0060_function_geometry.test +++ b/tests/sqllogictests/suites/query/02_function/02_0060_function_geometry.test @@ -46,8 +46,29 @@ SELECT a, g FROM t1 #SRID=3857;POINT(1489140.0937656453 6892872.198680114) #SRID=3857;POINT(500961.30830177927 6829319.683153116) +statement ok +DROP TABLE IF EXISTS t1 + +statement ok +CREATE TABLE t1 (g1 geometry, g2 geometry) + +statement ok +SELECT to_string(st_makeline(TO_GEOMETRY('POINT(33.0 44.2)', 32633), TO_GEOMETRY('POINT(224.5 41.5)', 32633))) FROM t1 + +statement ok +INSERT INTO t1 VALUES(TO_GEOMETRY('POINT(1.0 2.0)', 32633), TO_GEOMETRY('POINT(3.5 4.5)', 32633)), +(TO_GEOMETRY('POINT(1.0 2.0)', 4326), TO_GEOMETRY('LINESTRING(1.0 2.0, 10.1 5.5)', 4326)), +(TO_GEOMETRY('LINESTRING(1.0 2.0, 10.1 5.5)'), TO_GEOMETRY('MULTIPOINT(3.5 4.5, 6.1 7.9)')) + +query T +SELECT to_string(st_makeline(g1, g2)) FROM t1 +---- +SRID=32633;LINESTRING(1 2,3.5 4.5) +SRID=4326;LINESTRING(1 2,1 2,10.1 5.5) +LINESTRING(1 2,10.1 5.5,3.5 4.5,6.1 7.9) + statement ok SET enable_geo_create_table=0 statement ok -DROP TABLE IF EXISTS t1 +DROP TABLE IF EXISTS t1 \ No newline at end of file