diff --git a/README.md b/README.md index b389a5aa..34af510c 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,7 @@ Again, please feel free to open an issue if there is a particular function you w | --------------------------- | -------- | ---------- | ------------- | ---------- | --------------- | | ST_Point | 🦆 | 🦆 | | | | | ST_Area | 🦆 | 🦆 | 🦆 | 🦆 | 🦆 | +| ST_AsHEXWKB | 🦆 | 🦆 | 🦆 | 🦆 | 🦆 | | ST_AsText | 🧭 | 🦆 | 🦆 | 🦆 | 🔄 (as POLYGON) | | ST_AsWKB | 🦆 | 🦆 | 🦆 | 🦆 | 🦆 | | ST_Boundary | 🧭 | 🔄 | 🔄 | 🔄 | 🔄 (as POLYGON) | diff --git a/spatial/CMakeLists.txt b/spatial/CMakeLists.txt index e3205fa8..e43df877 100644 --- a/spatial/CMakeLists.txt +++ b/spatial/CMakeLists.txt @@ -8,6 +8,11 @@ set(CMAKE_CXX_STANDARD 11) project(${TARGET_NAME}) +# Options + +# Enable network functionality (OpenSSL and GDAL's CURL based fs/drivers) +option(SPATIAL_USE_NETWORK "Enable network functionality" ON) + include_directories(include) add_subdirectory(src) @@ -26,6 +31,7 @@ execute_process( -G ${CMAKE_GENERATOR} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DOSX_BUILD_UNIVERSAL=${OSX_BUILD_UNIVERSAL} + -DSPATIAL_USE_NETWORK=${SPATIAL_USE_NETWORK} -S ${CMAKE_CURRENT_SOURCE_DIR}/dependencies -B ${CMAKE_BINARY_DIR}/dependencies RESULT_VARIABLE DEPENDENCIES_GENERATE_RESULT @@ -54,9 +60,7 @@ find_package(PROJ REQUIRED) find_package(SQLite3 REQUIRED) find_package(GEOS REQUIRED) find_package(GDAL REQUIRED) -find_package(CURL REQUIRED) find_package(EXPAT REQUIRED) -find_package(OpenSSL REQUIRED) # Important: The link order matters, its the reverse order of dependency target_link_libraries( @@ -65,14 +69,24 @@ target_link_libraries( GDAL::GDAL GEOS::geos_c PROJ::proj - CURL::libcurl EXPAT::EXPAT - OpenSSL::SSL - OpenSSL::Crypto SQLite::SQLite3 ZLIB::ZLIB ${SQLITE3_MEMVFS}) +if(SPATIAL_USE_NETWORK) + find_package(CURL REQUIRED) + find_package(OpenSSL REQUIRED) + + target_link_libraries( + ${EXTENSION_NAME} + PUBLIC + CURL::libcurl + OpenSSL::SSL + OpenSSL::Crypto) +endif() + + if(WIN32) target_link_libraries(${EXTENSION_NAME} PUBLIC wbemuuid.lib) endif() diff --git a/spatial/dependencies/CMakeLists.txt b/spatial/dependencies/CMakeLists.txt index 9056280b..707a7f06 100644 --- a/spatial/dependencies/CMakeLists.txt +++ b/spatial/dependencies/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.20) - - project(dependecies-build) # Install and build dependencies locally @@ -15,7 +13,6 @@ endif() if(APPLE AND ${OSX_BUILD_UNIVERSAL}) set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") - endif() # Escape semicolons in CMAKE_OSX_ARCHITECTURES before passing to ExternalProject_Add @@ -48,6 +45,7 @@ ExternalProject_Add( -DCMAKE_POSITION_INDEPENDENT_CODE=ON ) +if(SPATIAL_USE_NETWORK) # CURL ExternalProject_Add( CURL @@ -69,6 +67,7 @@ ExternalProject_Add( -DCURL_USE_LIBSSH=OFF -DOPENSSL_USE_STATIC_LIBS=ON # Propagate to FindOpenSSL.cmake ) +endif() find_program(EXE_SQLITE3 sqlite3) # PROJ @@ -167,17 +166,68 @@ ExternalProject_Add( -DGDAL_USE_GEOS=ON -DGDAL_USE_SQLITE3=ON -DGDAL_USE_EXPAT=ON - -DGDAL_USE_CURL=ON - -DGDAL_USE_OPENSSL=ON + -DGDAL_USE_CURL=${SPATIAL_USE_NETWORK} + -DGDAL_USE_OPENSSL=${SPATIAL_USE_NETWORK} -DOPENSSL_USE_STATIC_LIBS=ON # Propagate to FindOpenSSL.cmake # This is not true, but a bug in gdal's cmake files -DACCEPT_MISSING_SQLITE3_RTREE:BOOL=ON -DACCEPT_MISSING_SQLITE3_MUTEX_ALLOC:BOOL=ON - # remove optional gdal drivers + # Remove optional gdal drivers -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF - -DOGR_BUILD_OPTIONAL_DRIVERS=ON + -DOGR_BUILD_OPTIONAL_DRIVERS=OFF + + # Build these explicitly + -DOGR_ENABLE_DRIVER_MEM=ON + -DOGR_ENABLE_DRIVER_GEOJSON=ON + -DOGR_ENABLE_DRIVER_GML=ON + -DOGR_ENABLE_DRIVER_TAB=ON + -DOGR_ENABLE_DRIVER_SHAPE=ON + -DOGR_ENABLE_DRIVER_KML=ON + -DOGR_ENABLE_DRIVER_VRT=ON + -DOGR_ENABLE_DRIVER_AVC=ON + -DOGR_ENABLE_DRIVER_NTF=ON + -DOGR_ENABLE_DRIVER_LVBAG=ON + -DOGR_ENABLE_DRIVER_S57=ON + -DOGR_ENABLE_DRIVER_CSV=ON + -DOGR_ENABLE_DRIVER_DGN=ON + -DOGR_ENABLE_DRIVER_GMT=ON + -DOGR_ENABLE_DRIVER_TIGER=ON + -DOGR_ENABLE_DRIVER_GEOCONCEPT=ON + -DOGR_ENABLE_DRIVER_GEORSS=ON + -DOGR_ENABLE_DRIVER_DXF=ON + -DOGR_ENABLE_DRIVER_PGDUMP=ON + -DOGR_ENABLE_DRIVER_GPSBABEL=ON + -DOGR_ENABLE_DRIVER_EDIGEO=ON + -DOGR_ENABLE_DRIVER_SXF=ON + -DOGR_ENABLE_DRIVER_OPENFILEGDB=ON + -DOGR_ENABLE_DRIVER_WASP=ON + -DOGR_ENABLE_DRIVER_SELAFIN=ON + -DOGR_ENABLE_DRIVER_JML=ON + -DOGR_ENABLE_DRIVER_VDV=ON + -DOGR_ENABLE_DRIVER_FLATGEOBUF=ON + -DOGR_ENABLE_DRIVER_MAPML=ON + -DOGR_ENABLE_DRIVER_GPX=ON + -DOGR_ENABLE_DRIVER_SVG=ON + -DOGR_ENABLE_DRIVER_SQLITE=ON + -DOGR_ENABLE_DRIVER_GPKG=ON + -DOGR_ENABLE_DRIVER_OSM=ON + -DOGR_ENABLE_DRIVER_XLSX=ON + -DOGR_ENABLE_DRIVER_CAD=ON + -DOGR_ENABLE_DRIVER_ODS=ON + -DOGR_ENABLE_DRIVER_LVBAG=ON + -DOGR_ENABLE_DRIVER_VFK=ON + -DOGR_ENABLE_DRIVER_MVT=ON + + # Drivers requiring network/curl + -DOGR_ENABLE_DRIVER_AMIGOCLOUD=${SPATIAL_USE_NETWORK} + -DOGR_ENABLE_DRIVER_CARTO=${SPATIAL_USE_NETWORK} + -DOGR_ENABLE_DRIVER_WFS=${SPATIAL_USE_NETWORK} + -DOGR_ENABLE_DRIVER_NGW=${SPATIAL_USE_NETWORK} + -DOGR_ENABLE_DRIVER_ELASTIC=${SPATIAL_USE_NETWORK} + -DOGR_ENABLE_DRIVER_CSW=${SPATIAL_USE_NETWORK} + -DOGR_ENABLE_DRIVER_PLSCENES=${SPATIAL_USE_NETWORK} # Remove bindings -DBUILD_PYTHON_BINDINGS=OFF @@ -185,4 +235,4 @@ ExternalProject_Add( # Ouch! Remember that the order of these libraries is important! (reverse order of dependencies) -#target_link_libraries(dependencies INTERFACE gdal geos_c geos proj expat memvfs sqlite3 zlib) +#target_link_libraries(dependencies INTERFACE gdal geos_c geos proj expat memvfs sqlite3 zlib) \ No newline at end of file diff --git a/spatial/include/spatial/core/functions/cast.hpp b/spatial/include/spatial/core/functions/cast.hpp index 022cd4f5..dfd3823a 100644 --- a/spatial/include/spatial/core/functions/cast.hpp +++ b/spatial/include/spatial/core/functions/cast.hpp @@ -11,11 +11,13 @@ struct CoreCastFunctions { RegisterVarcharCasts(context); RegisterDimensionalCasts(context); RegisterGeometryCasts(context); + RegisterWKBCasts(context); } private: static void RegisterVarcharCasts(ClientContext &context); static void RegisterDimensionalCasts(ClientContext &context); static void RegisterGeometryCasts(ClientContext &context); + static void RegisterWKBCasts(ClientContext &context); }; } // namespace core diff --git a/spatial/include/spatial/core/functions/scalar.hpp b/spatial/include/spatial/core/functions/scalar.hpp index a99e8841..05ce12c8 100644 --- a/spatial/include/spatial/core/functions/scalar.hpp +++ b/spatial/include/spatial/core/functions/scalar.hpp @@ -12,6 +12,7 @@ struct CoreScalarFunctions { RegisterStArea(context); RegisterStAsText(context); RegisterStAsWKB(context); + RegisterStAsHEXWKB(context); RegisterStCentroid(context); RegisterStCollect(context); RegisterStContains(context); @@ -34,6 +35,9 @@ struct CoreScalarFunctions { // ST_AsText static void RegisterStAsText(ClientContext &context); + // ST_AsHextWKB + static void RegisterStAsHEXWKB(ClientContext &context); + // ST_AsWKB static void RegisterStAsWKB(ClientContext &context); diff --git a/spatial/include/spatial/geos/geos_wrappers.hpp b/spatial/include/spatial/geos/geos_wrappers.hpp index bb84e1a1..3c355464 100644 --- a/spatial/include/spatial/geos/geos_wrappers.hpp +++ b/spatial/include/spatial/geos/geos_wrappers.hpp @@ -184,7 +184,7 @@ struct WKTReader { auto str = wkt.GetString(); auto geom = GEOSWKTReader_read_r(ctx, reader, str.c_str()); if (!geom) { - throw InvalidInputException("Could not read WKT"); + return GeometryPtr(ctx, nullptr); } return GeometryPtr(ctx, geom); } diff --git a/spatial/src/spatial/core/functions/cast/CMakeLists.txt b/spatial/src/spatial/core/functions/cast/CMakeLists.txt index b5c6b95c..6848cecf 100644 --- a/spatial/src/spatial/core/functions/cast/CMakeLists.txt +++ b/spatial/src/spatial/core/functions/cast/CMakeLists.txt @@ -3,5 +3,6 @@ set(EXTENSION_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/dimensional_cast.cpp ${CMAKE_CURRENT_SOURCE_DIR}/geometry_cast.cpp ${CMAKE_CURRENT_SOURCE_DIR}/varchar_cast.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/wkb_cast.cpp PARENT_SCOPE ) \ No newline at end of file diff --git a/spatial/src/spatial/core/functions/cast/dimensional_cast.cpp b/spatial/src/spatial/core/functions/cast/dimensional_cast.cpp index 0fa38bb1..b98a5032 100644 --- a/spatial/src/spatial/core/functions/cast/dimensional_cast.cpp +++ b/spatial/src/spatial/core/functions/cast/dimensional_cast.cpp @@ -33,7 +33,6 @@ static bool ToPoint2DCast(Vector &source, Vector &result, idx_t count, CastParam // Register functions //------------------------------------------------------------------------------ void CoreCastFunctions::RegisterDimensionalCasts(ClientContext &context) { - auto &catalog = Catalog::GetSystemCatalog(context); auto &config = DBConfig::GetConfig(context); auto &casts = config.GetCastFunctions(); diff --git a/spatial/src/spatial/core/functions/cast/geometry_cast.cpp b/spatial/src/spatial/core/functions/cast/geometry_cast.cpp index 1d45c0de..f5719df0 100644 --- a/spatial/src/spatial/core/functions/cast/geometry_cast.cpp +++ b/spatial/src/spatial/core/functions/cast/geometry_cast.cpp @@ -4,10 +4,11 @@ #include "spatial/core/geometry/geometry.hpp" #include "spatial/core/geometry/geometry_factory.hpp" #include "spatial/core/functions/common.hpp" +#include "spatial/core/geometry/wkb_writer.hpp" + #include "duckdb/function/cast/cast_function_set.hpp" #include "duckdb/common/vector_operations/generic_executor.hpp" - namespace spatial { namespace core { @@ -155,7 +156,6 @@ static bool Polygon2DToGeometryCast(Vector &source, Vector &result, idx_t count, static bool GeometryToPolygon2DCast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { auto &lstate = GeometryFunctionLocalState::ResetAndGet(parameters); - auto poly_entries = ListVector::GetData(result); auto &ring_vec = ListVector::GetEntry(result); idx_t total_rings = 0; @@ -263,7 +263,6 @@ void CoreCastFunctions::RegisterGeometryCasts(ClientContext &context) { casts.RegisterCastFunction(GeoTypes::POLYGON_2D(), GeoTypes::GEOMETRY(), BoundCastInfo(Polygon2DToGeometryCast, nullptr, GeometryFunctionLocalState::InitCast), 1); - casts.RegisterCastFunction(GeoTypes::BOX_2D(), GeoTypes::GEOMETRY(), BoundCastInfo(Box2DToGeometryCast, nullptr, GeometryFunctionLocalState::InitCast), 1); } diff --git a/spatial/src/spatial/core/functions/cast/wkb_cast.cpp b/spatial/src/spatial/core/functions/cast/wkb_cast.cpp new file mode 100644 index 00000000..31ace8f5 --- /dev/null +++ b/spatial/src/spatial/core/functions/cast/wkb_cast.cpp @@ -0,0 +1,66 @@ +#include "spatial/common.hpp" +#include "spatial/core/types.hpp" +#include "spatial/core/functions/cast.hpp" +#include "spatial/core/geometry/geometry.hpp" +#include "spatial/core/geometry/geometry_factory.hpp" +#include "spatial/core/functions/common.hpp" +#include "spatial/core/geometry/wkb_writer.hpp" + +#include "duckdb/function/cast/cast_function_set.hpp" +#include "duckdb/common/vector_operations/generic_executor.hpp" + +namespace spatial { + +namespace core { + +//------------------------------------------------------------------------------ +// WKB -> GEOMETRY +//------------------------------------------------------------------------------ +static bool WKBToGeometryCast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + auto &lstate = GeometryFunctionLocalState::ResetAndGet(parameters); + + UnaryExecutor::Execute(source, result, count, [&](string_t input) { + auto geometry = lstate.factory.FromWKB(input.GetDataUnsafe(), input.GetSize()); + return lstate.factory.Serialize(result, geometry); + }); + return true; +} + +//------------------------------------------------------------------------------ +// GEOMETRY -> WKB +//------------------------------------------------------------------------------ +static bool GeometryToWKBCast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + + auto &lstate = GeometryFunctionLocalState::ResetAndGet(parameters); + + UnaryExecutor::Execute(source, result, count, [&](string_t input) { + auto geometry = lstate.factory.Deserialize(input); + auto size = WKBWriter::GetRequiredSize(geometry); + auto str = StringVector::EmptyString(result, size); + auto ptr = (data_ptr_t)(str.GetDataUnsafe()); + WKBWriter::Write(geometry, ptr); + return str; + }); + + return true; +} + +//------------------------------------------------------------------------------ +// Register functions +//------------------------------------------------------------------------------ +void CoreCastFunctions::RegisterWKBCasts(ClientContext &context) { + auto &config = DBConfig::GetConfig(context); + auto &casts = config.GetCastFunctions(); + + // Geometry <-> WKB is explicitly castable + casts.RegisterCastFunction(GeoTypes::GEOMETRY(), GeoTypes::WKB_BLOB(), BoundCastInfo(GeometryToWKBCast, nullptr, GeometryFunctionLocalState::InitCast)); + + casts.RegisterCastFunction(GeoTypes::WKB_BLOB(), GeoTypes::GEOMETRY(), BoundCastInfo(WKBToGeometryCast, nullptr, GeometryFunctionLocalState::InitCast)); + + // WKB -> BLOB is implicitly castable + casts.RegisterCastFunction(GeoTypes::WKB_BLOB(), LogicalType::BLOB, DefaultCasts::ReinterpretCast, 1); +} + +} // namespace core + +} // namespace spatial \ No newline at end of file diff --git a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt index 618cdb82..7cf2efd2 100644 --- a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt +++ b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt @@ -1,6 +1,7 @@ set(EXTENSION_SOURCES ${EXTENSION_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/st_area.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/st_ashexwkb.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_astext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_aswkb.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_centroid.cpp diff --git a/spatial/src/spatial/core/functions/scalar/st_ashexwkb.cpp b/spatial/src/spatial/core/functions/scalar/st_ashexwkb.cpp new file mode 100644 index 00000000..2c80ded6 --- /dev/null +++ b/spatial/src/spatial/core/functions/scalar/st_ashexwkb.cpp @@ -0,0 +1,68 @@ +#include "duckdb/common/vector_operations/generic_executor.hpp" +#include "duckdb/common/vector_operations/unary_executor.hpp" +#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp" +#include "duckdb/common/types/blob.hpp" + +#include "spatial/common.hpp" +#include "spatial/core/functions/scalar.hpp" +#include "spatial/core/functions/common.hpp" +#include "spatial/core/geometry/geometry_factory.hpp" +#include "spatial/core/types.hpp" +#include "spatial/core/geometry/wkb_writer.hpp" + +namespace spatial { + +namespace core { + +//------------------------------------------------------------------------------ +// GEOMETRY -> HEX WKB +//------------------------------------------------------------------------------ + + +void GeometryAsHEXWKBFunction(DataChunk &args, ExpressionState &state, Vector &result) { + D_ASSERT(args.data.size() == 1); + auto &input = args.data[0]; + auto count = args.size(); + + auto &lstate = GeometryFunctionLocalState::ResetAndGet(state); + + UnaryExecutor::Execute(input, result, count, [&](string_t input) { + auto geometry = lstate.factory.Deserialize(input); + auto wkb_size = WKBWriter::GetRequiredSize(geometry); + unique_ptr wkb_blob(new data_t[wkb_size]); + + auto wkb_ptr = wkb_blob.get(); + WKBWriter::Write(geometry, wkb_ptr); + + auto blob_size = wkb_size * 2; // every byte is rendered as two characters + auto blob_str = StringVector::EmptyString(result, blob_size); + auto blob_ptr = blob_str.GetDataWriteable(); + + idx_t str_idx = 0; + wkb_ptr = wkb_blob.get(); // reset + for (idx_t i = 0; i < wkb_size; i++) { + auto byte_a = wkb_ptr[i] >> 4; + auto byte_b = wkb_ptr[i] & 0x0F; + + blob_ptr[str_idx++] = Blob::HEX_TABLE[byte_a]; + blob_ptr[str_idx++] = Blob::HEX_TABLE[byte_b]; + } + + return blob_str; + }); +} + +//------------------------------------------------------------------------------ +// Register functions +//------------------------------------------------------------------------------ +void CoreScalarFunctions::RegisterStAsHEXWKB(ClientContext &context) { + auto &catalog = Catalog::GetSystemCatalog(context); + + CreateScalarFunctionInfo info(ScalarFunction("ST_AsHEXWKB", {GeoTypes::GEOMETRY()}, LogicalType::VARCHAR, GeometryAsHEXWKBFunction, nullptr, nullptr, nullptr, GeometryFunctionLocalState::Init)); + info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; + catalog.CreateFunction(context, &info); +} + +} // namespace core + +} // namespace spatial \ No newline at end of file diff --git a/spatial/src/spatial/core/geometry/vertex_vector.cpp b/spatial/src/spatial/core/geometry/vertex_vector.cpp index e6cceb52..67cea485 100644 --- a/spatial/src/spatial/core/geometry/vertex_vector.cpp +++ b/spatial/src/spatial/core/geometry/vertex_vector.cpp @@ -39,13 +39,21 @@ double VertexVector::SignedArea() const { if (count < 3) { return 0; } + + // Subtract the x coordinate of the first vertex from all other vertices + // to normalize the range and avoid floating point errors + // We don't need to do this for the y coordinate because we already + // subtract them between consecutive vertices double area = 0; - for (uint32_t i = 0; i < count - 1; i++) { - auto &p1 = data[i]; - auto &p2 = data[i + 1]; - area += p1.x * p2.y - p2.x * p1.y; + auto x0 = data[0].x; + for(uint32_t i = 1; i < count - 1; ++i) { + auto x1 = data[i].x; + auto y1 = data[i + 1].y; + auto y2 = data[i - 1].y; + area += (x1 - x0) * (y2 - y1); } - return area * 0.5; + + return area * 0.5; } double ColumnarArea(vector xs, vector ys) { diff --git a/spatial/src/spatial/core/types.cpp b/spatial/src/spatial/core/types.cpp index 41839e43..c1816335 100644 --- a/spatial/src/spatial/core/types.cpp +++ b/spatial/src/spatial/core/types.cpp @@ -78,21 +78,8 @@ static void AddType(Catalog &catalog, ClientContext &context, LogicalType type, catalog.CreateType(context, &type_info); } -// Casts -static bool WKBToGeometryCast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { - auto &lstate = GeometryFunctionLocalState::ResetAndGet(parameters); - - UnaryExecutor::Execute(source, result, count, [&](string_t input) { - auto geometry = lstate.factory.FromWKB(input.GetDataUnsafe(), input.GetSize()); - return lstate.factory.Serialize(result, geometry); - }); - return true; -} - void GeoTypes::Register(ClientContext &context) { auto &catalog = Catalog::GetSystemCatalog(context); - auto &config = DBConfig::GetConfig(context); - auto &casts = config.GetCastFunctions(); // POINT_2D AddType(catalog, context, GeoTypes::POINT_2D(), "POINT_2D"); @@ -117,11 +104,6 @@ void GeoTypes::Register(ClientContext &context) { // WKB_BLOB AddType(catalog, context, GeoTypes::WKB_BLOB(), "WKB_BLOB"); - - casts.RegisterCastFunction(GeoTypes::WKB_BLOB(), LogicalType::BLOB, DefaultCasts::ReinterpretCast); - - // TODO: remove this implicit cast once we have more functions for the geometry type itself - casts.RegisterCastFunction(GeoTypes::WKB_BLOB(), GeoTypes::GEOMETRY(), BoundCastInfo(WKBToGeometryCast, nullptr, GeometryFunctionLocalState::InitCast), 1); } } // namespace core diff --git a/spatial/src/spatial/geos/functions/scalar/st_geom_from_text.cpp b/spatial/src/spatial/geos/functions/scalar/st_geom_from_text.cpp index 39502b2f..6a41db50 100644 --- a/spatial/src/spatial/geos/functions/scalar/st_geom_from_text.cpp +++ b/spatial/src/spatial/geos/functions/scalar/st_geom_from_text.cpp @@ -5,8 +5,10 @@ #include "spatial/geos/geos_wrappers.hpp" #include "duckdb/parser/parsed_data/create_scalar_function_info.hpp" +#include "duckdb/planner/expression/bound_function_expression.hpp" #include "duckdb/common/vector_operations/unary_executor.hpp" #include "duckdb/common/vector_operations/binary_executor.hpp" +#include "duckdb/execution/expression_executor.hpp" namespace spatial { @@ -15,37 +17,92 @@ namespace geos { using namespace spatial::core; +struct GeometryFromWKTBindData : public FunctionData { + bool ignore_invalid = false; + + explicit GeometryFromWKTBindData(bool ignore_invalid) + : ignore_invalid(ignore_invalid) { + } + +public: + unique_ptr Copy() const override { + return make_unique(ignore_invalid); + } + bool Equals(const FunctionData &other_p) const override { + return true; + } +}; + // TODO: we should implement our own WKT parser asap. This is a temporary and really inefficient solution. static void GeometryFromWKTFunction(DataChunk &args, ExpressionState &state, Vector &result) { auto count = args.size(); auto input = args.data[0]; + + auto &func_expr = (BoundFunctionExpression&)state.expr; + const auto &info = (GeometryFromWKTBindData&)*func_expr.bind_info; + auto ctx = GeosContextWrapper(); auto reader = ctx.CreateWKTReader(); auto &lstate = GEOSFunctionLocalState::ResetAndGet(state); - UnaryExecutor::Execute(input, result, count, [&](string_t &wkt) { + UnaryExecutor::ExecuteWithNulls(input, result, count, [&](string_t &wkt, ValidityMask &mask, idx_t idx) { auto geos_geom = reader.Read(wkt); if(geos_geom.get() == nullptr) { - throw InvalidInputException("Invalid WKT string"); + if(!info.ignore_invalid) { + throw InvalidInputException("Invalid WKT string"); + } else { + mask.SetInvalid(idx); + return string_t(); + } } auto multidimensional = (GEOSHasZ_r(lstate.ctx.GetCtx(), geos_geom.get()) == 1); if(multidimensional) { throw InvalidInputException("3D/4D geometries are not supported"); } - auto geometry = ctx.ToGeometry(lstate.factory, geos_geom.get()); return lstate.factory.Serialize(result, geometry); }); } +static unique_ptr GeometryFromWKTBind(ClientContext &context, ScalarFunction &bound_function, vector> &arguments) { + if(arguments.empty()) { + throw InvalidInputException("ST_GeomFromText requires at least one argument"); + } + auto &input_type = arguments[0]->return_type; + if(input_type.id() != LogicalTypeId::VARCHAR) { + throw InvalidInputException("ST_GeomFromText requires a string argument"); + } + + bool ignore_invalid = false; + for(idx_t i = 1; i < arguments.size(); i++) { + auto &arg = arguments[i]; + if (arg->HasParameter()) { + throw InvalidInputException("Parameters are not supported in ST_GeomFromText optional arguments"); + } + if (!arg->IsFoldable()) { + throw InvalidInputException("Non-constant arguments are not supported in ST_GeomFromText optional arguments"); + } + if(arg->alias == "ignore_invalid") { + if(arg->return_type.id() != LogicalTypeId::BOOLEAN) { + throw InvalidInputException("ST_GeomFromText optional argument 'ignore_invalid' must be a boolean"); + } + ignore_invalid = BooleanValue::Get(ExpressionExecutor::EvaluateScalar(context, *arg)); + } + } + return make_unique(ignore_invalid); +} + void GEOSScalarFunctions::RegisterStGeomFromText(ClientContext &context) { auto &catalog = Catalog::GetSystemCatalog(context); - CreateScalarFunctionInfo geometry_from_wkt_info( - ScalarFunction("ST_GeomFromText", {LogicalType::VARCHAR}, core::GeoTypes::GEOMETRY(), GeometryFromWKTFunction, nullptr, nullptr, nullptr, GEOSFunctionLocalState::Init)); - geometry_from_wkt_info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; - catalog.AddFunction(context, &geometry_from_wkt_info); + + ScalarFunctionSet set("ST_GeomFromText"); + set.AddFunction(ScalarFunction({LogicalType::VARCHAR}, core::GeoTypes::GEOMETRY(), GeometryFromWKTFunction, GeometryFromWKTBind, nullptr, nullptr, GEOSFunctionLocalState::Init)); + set.AddFunction(ScalarFunction({LogicalType::VARCHAR, LogicalType::BOOLEAN}, core::GeoTypes::GEOMETRY(), GeometryFromWKTFunction, GeometryFromWKTBind, nullptr, nullptr, GEOSFunctionLocalState::Init)); + CreateScalarFunctionInfo info(set); + info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; + catalog.AddFunction(context, &info); } diff --git a/spatial/test/sql/geometry/st_area.test b/spatial/test/sql/geometry/st_area.test index 567f5427..f2851880 100644 --- a/spatial/test/sql/geometry/st_area.test +++ b/spatial/test/sql/geometry/st_area.test @@ -106,3 +106,9 @@ SELECT ST_Area(l) FROM (VALUES 0 0 + +# Test precision for very small area +query I +SELECT ST_AREA(ST_GeomFromText('POLYGON ((-82.03623 29.60482, -82.0357 29.605, -82.03526 29.60513, -82.03623 29.60482))')) +---- +5.150000001635108e-09 \ No newline at end of file diff --git a/spatial/test/sql/geometry/st_ashexwkb.test b/spatial/test/sql/geometry/st_ashexwkb.test new file mode 100644 index 00000000..47f8057f --- /dev/null +++ b/spatial/test/sql/geometry/st_ashexwkb.test @@ -0,0 +1,6 @@ +require spatial + +query I +SELECT ST_AsHEXWKB(ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))')); +---- +01030000000100000005000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000000000000000000000000000000000000000 \ No newline at end of file