From edb431ab089e8f57943b98cb19280cb9262810c5 Mon Sep 17 00:00:00 2001 From: Max Gabrielsson Date: Thu, 19 Sep 2024 14:20:02 +0200 Subject: [PATCH] add st_multi --- docs/functions.md | 31 +++++++ .../include/spatial/core/functions/scalar.hpp | 4 + .../core/functions/scalar/CMakeLists.txt | 1 + .../core/functions/scalar/st_multi.cpp | 83 +++++++++++++++++++ test/sql/geometry/st_multi.test | 21 +++++ 5 files changed, 140 insertions(+) create mode 100644 spatial/src/spatial/core/functions/scalar/st_multi.cpp create mode 100644 test/sql/geometry/st_multi.test diff --git a/docs/functions.md b/docs/functions.md index 5be832d1..ff62170e 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -69,6 +69,7 @@ | [`ST_MakeLine`](#st_makeline) | Creates a LINESTRING geometry from a pair or list of input points | | [`ST_MakePolygon`](#st_makepolygon) | Creates a polygon from a shell geometry and an optional set of holes | | [`ST_MakeValid`](#st_makevalid) | Attempts to make an invalid geometry valid without removing any vertices | +| [`ST_Multi`](#st_multi) | Turns a single geometry into a multi geometry. | | [`ST_NGeometries`](#st_ngeometries) | Returns the number of component geometries in a collection geometry. | | [`ST_NInteriorRings`](#st_ninteriorrings) | Returns the number if interior rings of a polygon | | [`ST_NPoints`](#st_npoints) | Returns the number of vertices within a geometry | @@ -1431,6 +1432,36 @@ Attempts to make an invalid geometry valid without removing any vertices ---- +### ST_Multi + + +#### Signature + +```sql +GEOMETRY ST_Multi (col0 GEOMETRY) +``` + +#### Description + +Turns a single geometry into a multi geometry. + +If the geometry is already a multi geometry, it is returned as is. + +#### Example + +```sql +SELECT ST_Multi(ST_GeomFromText('POINT(1 2)')); +-- MULTIPOINT (1 2) + +SELECT ST_Multi(ST_GeomFromText('LINESTRING(1 1, 2 2)')); +-- MULTILINESTRING ((1 1, 2 2)) + +SELECT ST_Multi(ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')); +-- MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0))) +``` + +---- + ### ST_NGeometries diff --git a/spatial/include/spatial/core/functions/scalar.hpp b/spatial/include/spatial/core/functions/scalar.hpp index 10913bdd..eda7eb02 100644 --- a/spatial/include/spatial/core/functions/scalar.hpp +++ b/spatial/include/spatial/core/functions/scalar.hpp @@ -41,6 +41,7 @@ struct CoreScalarFunctions { RegisterStMakeEnvelope(db); RegisterStMakeLine(db); RegisterStMakePolygon(db); + RegisterStMulti(db); RegisterStNGeometries(db); RegisterStNInteriorRings(db); RegisterStNPoints(db); @@ -165,6 +166,9 @@ struct CoreScalarFunctions { // ST_MakePolygon static void RegisterStMakePolygon(DatabaseInstance &db); + // ST_Multi + static void RegisterStMulti(DatabaseInstance &db); + // ST_NGeometries static void RegisterStNGeometries(DatabaseInstance &db); diff --git a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt index 68890c88..eb5256a2 100644 --- a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt +++ b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt @@ -32,6 +32,7 @@ set(EXTENSION_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/st_makeenvelope.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_makeline.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_makepolygon.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/st_multi.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_ngeometries.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_ninteriorrings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_npoints.cpp diff --git a/spatial/src/spatial/core/functions/scalar/st_multi.cpp b/spatial/src/spatial/core/functions/scalar/st_multi.cpp new file mode 100644 index 00000000..679b56fb --- /dev/null +++ b/spatial/src/spatial/core/functions/scalar/st_multi.cpp @@ -0,0 +1,83 @@ +#include "duckdb/common/vector_operations/generic_executor.hpp" +#include "spatial/common.hpp" +#include "spatial/core/functions/scalar.hpp" +#include "spatial/core/functions/common.hpp" +#include "spatial/core/geometry/geometry.hpp" +#include "spatial/core/types.hpp" +#include "spatial/core/geometry/geometry_processor.hpp" + +namespace spatial { + +namespace core { + +//------------------------------------------------------------------------------ +// GEOMETRY +//------------------------------------------------------------------------------ + +static void GeometryMultiFunction(DataChunk &args, ExpressionState &state, Vector &result) { + auto &lstate = GeometryFunctionLocalState::ResetAndGet(state); + auto &arena = lstate.arena; + auto &input = args.data[0]; + + UnaryExecutor::Execute(input, result, args.size(), [&](const geometry_t &geom_blob) { + + const bool has_z = geom_blob.GetProperties().HasZ(); + const bool has_m = geom_blob.GetProperties().HasM(); + + switch(geom_blob.GetType()) { + case GeometryType::POINT: { + auto mpoint = MultiPoint::Create(arena, 1, has_z, has_m); + MultiPoint::Part(mpoint, 0) = Geometry::Deserialize(arena, geom_blob); + return Geometry::Serialize(mpoint, result); + } + case GeometryType::LINESTRING: { + auto mline = MultiLineString::Create(arena, 1, has_z, has_m); + MultiLineString::Part(mline, 0) = Geometry::Deserialize(arena, geom_blob); + return Geometry::Serialize(mline, result); + } + case GeometryType::POLYGON: { + auto mpoly = MultiPolygon::Create(arena, 1, has_z, has_m); + MultiPolygon::Part(mpoly, 0) = Geometry::Deserialize(arena, geom_blob); + return Geometry::Serialize(mpoly, result); + } + default: + return geom_blob; + } + }); +} + +//------------------------------------------------------------------------------ +// Documentation +//------------------------------------------------------------------------------ +static constexpr const char *DOC_DESCRIPTION = R"( + Turns a single geometry into a multi geometry. + + If the geometry is already a multi geometry, it is returned as is. +)"; + +static constexpr const char *DOC_EXAMPLE = R"( +SELECT ST_Multi(ST_GeomFromText('POINT(1 2)')); +-- MULTIPOINT (1 2) + +SELECT ST_Multi(ST_GeomFromText('LINESTRING(1 1, 2 2)')); +-- MULTILINESTRING ((1 1, 2 2)) + +SELECT ST_Multi(ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')); +-- MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0))) +)"; + +static constexpr DocTag DOC_TAGS[] = {{"ext", "spatial"}, {"category", "construction"}}; + +//------------------------------------------------------------------------------ +// Register functions +//------------------------------------------------------------------------------ +void CoreScalarFunctions::RegisterStMulti(DatabaseInstance &db) { + ScalarFunction function("ST_Multi",{GeoTypes::GEOMETRY()}, GeoTypes::GEOMETRY(), GeometryMultiFunction); + function.init_local_state = GeometryFunctionLocalState::Init; + ExtensionUtil::RegisterFunction(db, function); + DocUtil::AddDocumentation(db, "ST_Multi", DOC_DESCRIPTION, DOC_EXAMPLE, DOC_TAGS); +} + +} // namespace core + +} // namespace spatial diff --git a/test/sql/geometry/st_multi.test b/test/sql/geometry/st_multi.test new file mode 100644 index 00000000..74ab430a --- /dev/null +++ b/test/sql/geometry/st_multi.test @@ -0,0 +1,21 @@ +require spatial + +query I +SELECT ST_Multi(ST_GeomFromText('POINT(1 2)')); +---- +MULTIPOINT (1 2) + +query I +SELECT ST_Multi(ST_GeomFromText('LINESTRING(1 1, 2 2)')); +---- +MULTILINESTRING ((1 1, 2 2)) + +query I +SELECT ST_Multi(ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')); +---- +MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0))) + +query I +SELECT ST_Multi(ST_GeomFromText('POINT EMPTY')); +---- +MULTIPOINT (EMPTY) \ No newline at end of file