diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 77e63709..366abc03 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -329,6 +329,7 @@ ExternalProject_Add( -DOGR_ENABLE_DRIVER_MVT=ON -DOGR_ENABLE_DRIVER_PMTILES=ON -DOGR_ENABLE_DRIVER_JSONFG=ON + -DOGR_ENABLE_DRIVER_GTFS=ON # Drivers requiring network/curl -DOGR_ENABLE_DRIVER_AMIGOCLOUD=${SPATIAL_USE_NETWORK} diff --git a/docs/functions.md b/docs/functions.md index d4e75d4f..951526bb 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -30,7 +30,7 @@ | [ST_Distance](#st_distance) | Returns the distance between two geometries. | | [ST_Distance_Sphere](#st_distance_sphere) | Returns the haversine distance between two geometries. | | [ST_Distance_Spheroid](#st_distance_spheroid) | Returns the distance between two geometries in meters using a ellipsoidal model of the earths surface | -| [ST_Dump](#st_dump) | Dumps a geometry into a set of sub-geometries | +| [ST_Dump](#st_dump) | Dumps a geometry into a set of sub-geometries and their "path" in the original geometry. | | [ST_EndPoint](#st_endpoint) | Returns the end point of a line. | | [ST_Envelope](#st_envelope) | Returns the minimum bounding box for the input geometry as a polygon geometry. | | [ST_Equals](#st_equals) | Compares two geometries for equality | @@ -82,6 +82,7 @@ | [ST_Point4D](#st_point4d) | Creates a POINT_4D | | [ST_PointN](#st_pointn) | Returns the n'th vertex from the input geometry as a point geometry | | [ST_PointOnSurface](#st_pointonsurface) | Returns a point that is guaranteed to be on the surface of the input geometry. Sometimes a useful alternative to ST_Centroid. | +| [ST_Points](#st_points) | Collects all the vertices in the geometry into a multipoint | | [ST_QuadKey](#st_quadkey) | Computes a quadkey from a given lon/lat point. | | [ST_ReducePrecision](#st_reduceprecision) | Returns the geometry with all vertices reduced to the target precision | | [ST_RemoveRepeatedPoints](#st_removerepeatedpoints) | Returns a new geometry with repeated points removed, optionally within a target distance of eachother. | @@ -103,6 +104,7 @@ | [ST_Z](#st_z) | Returns the Z value of a point geometry, or NULL if not a point or empty | | [ST_ZMax](#st_zmax) | Returns the maximum Z value of a geometry | | [ST_ZMin](#st_zmin) | Returns the minimum Z value of a geometry | + **[Aggregate Functions](#aggregate-functions)** | Function | Summary | @@ -110,6 +112,7 @@ | [ST_Envelope_Agg](#st_envelope_agg) | Computes a minimal-bounding-box polygon 'enveloping' the set of input geometries | | [ST_Intersection_Agg](#st_intersection_agg) | Computes the intersection of a set of geometries | | [ST_Union_Agg](#st_union_agg) | Computes the union of a set of input geometries | + **[Table Functions](#table-functions)** | Function | Summary | @@ -118,6 +121,7 @@ | [ST_Read](#st_read) | Read and import a variety of geospatial file formats using the GDAL library. | | [ST_ReadOSM](#st_readosm) | The ST_ReadOsm() table function enables reading compressed OpenStreetMap data directly from a `.osm.pbf file.` | | [ST_Read_Meta](#st_read_meta) | Read and the metadata from a variety of geospatial file formats using the GDAL library. | + ---- ## Scalar Functions @@ -704,7 +708,7 @@ st_point(52.3130, 4.7725) ### ST_Dump -_Dumps a geometry into a set of sub-geometries_ +_Dumps a geometry into a set of sub-geometries and their "path" in the original geometry._ #### Signature @@ -714,8 +718,6 @@ STRUCT(geom GEOMETRY, path INTEGER[])[] ST_Dump (col0 GEOMETRY) #### Description -Dumps a geometry into a set of sub-geometries - Dumps a geometry into a set of sub-geometries and their "path" in the original geometry. #### Example @@ -1615,6 +1617,34 @@ Returns a point that is guaranteed to be on the surface of the input geometry. S +### ST_Points + +_Collects all the vertices in the geometry into a multipoint_ + +#### Signature + +```sql +GEOMETRY ST_Points (col0 GEOMETRY) +``` + +#### Description + +Collects all the vertices in the geometry into a multipoint + +#### Example + +```sql +select st_points('LINESTRING(1 1, 2 2)'::geometry); +---- +MULTIPOINT (1 1, 2 2) + +select st_points('MULTIPOLYGON Z EMPTY'::geometry); +---- +MULTIPOINT Z EMPTY +``` + + + ### ST_QuadKey _Computes a quadkey from a given lon/lat point._ diff --git a/generate_function_reference.py b/generate_function_reference.py index 109b92e0..3f6c87f1 100644 --- a/generate_function_reference.py +++ b/generate_function_reference.py @@ -6,7 +6,7 @@ json({ name: function_name, signatures: signatures, - tags: tags, + tags: func_tags, description: description, example: example }) @@ -18,13 +18,13 @@ return: return_type, params: list_zip(parameters, parameter_types)::STRUCT(name VARCHAR, type VARCHAR)[] }) as signatures, - any_value(try_cast(comment AS STRUCT(key VARCHAR, value VARCHAR)[])) as tags, + any_value(tags) AS func_tags, any_value(description) AS description, any_value(example) AS example - FROM duckdb_functions() + FROM duckdb_functions() as funcs WHERE function_type = '$FUNCTION_TYPE$' GROUP BY function_name, function_type - HAVING list_contains(tags, {key: 'ext', value: 'spatial'}) + HAVING func_tags['ext'] = ['spatial'] ORDER BY function_name ); """ @@ -62,11 +62,13 @@ def main(): f.write("## Function Index \n") f.write("**[Scalar Functions](#scalar-functions)**\n\n") write_table_of_contents(f, scalar_functions) + f.write("\n") f.write("**[Aggregate Functions](#aggregate-functions)**\n\n") write_table_of_contents(f, aggregate_functions) + f.write("\n") f.write("**[Table Functions](#table-functions)**\n\n") write_table_of_contents(f, table_functions) - + f.write("\n") f.write("----\n\n") # Write basic functions diff --git a/spatial/include/spatial/core/functions/scalar.hpp b/spatial/include/spatial/core/functions/scalar.hpp index 2c446e95..e4b0712c 100644 --- a/spatial/include/spatial/core/functions/scalar.hpp +++ b/spatial/include/spatial/core/functions/scalar.hpp @@ -45,6 +45,7 @@ struct CoreScalarFunctions { RegisterStPerimeter(db); RegisterStPoint(db); RegisterStPointN(db); + RegisterStPoints(db); RegisterStQuadKey(db); RegisterStRemoveRepeatedPoints(db); RegisterStStartPoint(db); @@ -174,6 +175,9 @@ struct CoreScalarFunctions { // ST_PointN static void RegisterStPointN(DatabaseInstance &db); + // ST_Points + static void RegisterStPoints(DatabaseInstance &db); + // ST_RemoveRepeatedPoints static void RegisterStRemoveRepeatedPoints(DatabaseInstance &db); diff --git a/spatial/include/spatial/doc_util.hpp b/spatial/include/spatial/doc_util.hpp index 4b8fd815..74f3a034 100644 --- a/spatial/include/spatial/doc_util.hpp +++ b/spatial/include/spatial/doc_util.hpp @@ -11,20 +11,18 @@ struct DocTag { struct DocUtil { static void AddDocumentation(duckdb::DatabaseInstance &db, const char *function_name, const char *description, - const char *example, const duckdb::Value &comment); + const char *example, + const duckdb::unordered_map &tags); // Abuse adding tags as a comment template static void AddDocumentation(duckdb::DatabaseInstance &db, const char *function_name, const char *description, const char *example, const DocTag (&tags)[N]) { - auto kv_type = duckdb::LogicalType::STRUCT( - {{"key", duckdb::LogicalType::VARCHAR}, {"value", duckdb::LogicalType::VARCHAR}}); - duckdb::vector tag_values; + duckdb::unordered_map tag_map; for (size_t i = 0; i < N; i++) { - auto &tag = tags[i]; - tag_values.push_back(duckdb::Value::STRUCT(kv_type, {duckdb::Value(tag.key), duckdb::Value(tag.value)})); + tag_map[tags[i].key] = tags[i].value; } - AddDocumentation(db, function_name, description, example, duckdb::Value::LIST(kv_type, tag_values)); + AddDocumentation(db, function_name, description, example, tag_map); } }; diff --git a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt index 150ca0e9..5df12e25 100644 --- a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt +++ b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt @@ -36,6 +36,7 @@ set(EXTENSION_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/st_perimeter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_point.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_pointn.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/st_points.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_quadkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_removerepeatedpoints.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_startpoint.cpp diff --git a/spatial/src/spatial/core/functions/scalar/st_dump.cpp b/spatial/src/spatial/core/functions/scalar/st_dump.cpp index bbe45614..f59c2ca9 100644 --- a/spatial/src/spatial/core/functions/scalar/st_dump.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_dump.cpp @@ -5,8 +5,6 @@ #include "spatial/core/geometry/geometry.hpp" #include "duckdb/parser/parsed_data/create_scalar_function_info.hpp" -#include "duckdb/common/vector_operations/unary_executor.hpp" -#include "duckdb/common/vector_operations/binary_executor.hpp" namespace spatial { @@ -119,8 +117,6 @@ static void DumpFunction(DataChunk &args, ExpressionState &state, Vector &result // Documentation //------------------------------------------------------------------------------ static constexpr const char *DOC_DESCRIPTION = R"( -Dumps a geometry into a set of sub-geometries - Dumps a geometry into a set of sub-geometries and their "path" in the original geometry. )"; diff --git a/spatial/src/spatial/core/functions/scalar/st_points.cpp b/spatial/src/spatial/core/functions/scalar/st_points.cpp new file mode 100644 index 00000000..7d654ae6 --- /dev/null +++ b/spatial/src/spatial/core/functions/scalar/st_points.cpp @@ -0,0 +1,110 @@ +#include "spatial/common.hpp" +#include "spatial/core/types.hpp" +#include "spatial/core/functions/scalar.hpp" +#include "spatial/core/functions/common.hpp" +#include "spatial/core/geometry/geometry.hpp" + +#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp" +#include "duckdb/common/vector_operations/unary_executor.hpp" +#include "duckdb/common/vector_operations/binary_executor.hpp" + +namespace spatial { + +namespace core { + +//------------------------------------------------------------------------------ +// GEOMETRY +//------------------------------------------------------------------------------ +static void GeometryPointsFunction(DataChunk &args, ExpressionState &state, Vector &result) { + + // Collect all vertex data into a buffer + struct op { + static void Case(Geometry::Tags::SinglePartGeometry, const Geometry &geom, vector &buffer) { + const auto vertex_count = SinglePartGeometry::VertexCount(geom); + const auto vertex_size = SinglePartGeometry::VertexSize(geom); + + // Reserve size for the pointers to the vertices + buffer.reserve(buffer.size() + vertex_count); + + const auto vertex_ptr = geom.GetData(); + + for (uint32_t i = 0; i < vertex_count; i++) { + buffer.push_back(vertex_ptr + i * vertex_size); + } + } + static void Case(Geometry::Tags::MultiPartGeometry, const Geometry &geom, vector &buffer) { + for (auto &part : MultiPartGeometry::Parts(geom)) { + Geometry::Match(part, buffer); + } + } + }; + + auto &lstate = GeometryFunctionLocalState::ResetAndGet(state); + auto &arena = lstate.arena; + auto &geom_vec = args.data[0]; + const auto count = args.size(); + + vector vertex_ptr_buffer; + + UnaryExecutor::Execute(geom_vec, result, count, [&](geometry_t input) { + const auto geom = Geometry::Deserialize(arena, input); + const auto has_z = geom.GetProperties().HasZ(); + const auto has_m = geom.GetProperties().HasM(); + + // Reset the vertex pointer buffer + vertex_ptr_buffer.clear(); + + // Collect the vertex pointers + Geometry::Match(geom, vertex_ptr_buffer); + + if (vertex_ptr_buffer.empty()) { + const auto mpoint = MultiPoint::CreateEmpty(has_z, has_m); + return Geometry::Serialize(mpoint, result); + } + + auto mpoint = MultiPoint::Create(arena, vertex_ptr_buffer.size(), has_z, has_m); + for (size_t i = 0; i < vertex_ptr_buffer.size(); i++) { + // Get the nth point + auto &point = MultiPoint::Part(mpoint, i); + // Set the point to reference the data pointer to the current vertex + Point::ReferenceData(point, vertex_ptr_buffer[i], 1, has_z, has_m); + } + return Geometry::Serialize(mpoint, result); + }); +} + +//------------------------------------------------------------------------------ +// Documentation +//------------------------------------------------------------------------------ +static constexpr const char *DOC_DESCRIPTION = R"( + Collects all the vertices in the geometry into a multipoint +)"; + +static constexpr const char *DOC_EXAMPLE = R"( + select st_points('LINESTRING(1 1, 2 2)'::geometry); + ---- + MULTIPOINT (1 1, 2 2) + + select st_points('MULTIPOLYGON Z EMPTY'::geometry); + ---- + MULTIPOINT Z EMPTY +)"; + +static constexpr DocTag DOC_TAGS[] = {{"ext", "spatial"}, {"category", "construction"}}; +//------------------------------------------------------------------------------ +// Register functions +//------------------------------------------------------------------------------ +void CoreScalarFunctions::RegisterStPoints(DatabaseInstance &db) { + + ScalarFunctionSet set("ST_Points"); + + set.AddFunction(ScalarFunction({GeoTypes::GEOMETRY()}, GeoTypes::GEOMETRY(), GeometryPointsFunction, nullptr, + nullptr, nullptr, GeometryFunctionLocalState::Init)); + + ExtensionUtil::RegisterFunction(db, set); + DocUtil::AddDocumentation(db, "ST_Points", DOC_DESCRIPTION, DOC_EXAMPLE, DOC_TAGS); +} + +} // namespace core + +} // namespace spatial diff --git a/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp b/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp index 9a15e4c1..b799d1ef 100644 --- a/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp +++ b/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp @@ -37,8 +37,8 @@ struct ShapefileBindData : TableFunctionData { vector attribute_types; explicit ShapefileBindData(string file_name_p) - : file_name(std::move(file_name_p)), shape_count(0), - shape_type(0), min_bound {0, 0, 0, 0}, max_bound {0, 0, 0, 0}, attribute_encoding(AttributeEncoding::LATIN1) { + : file_name(std::move(file_name_p)), shape_count(0), shape_type(0), min_bound {0, 0, 0, 0}, + max_bound {0, 0, 0, 0}, attribute_encoding(AttributeEncoding::LATIN1) { } }; diff --git a/spatial/src/spatial/gdal/functions/st_read_meta.cpp b/spatial/src/spatial/gdal/functions/st_read_meta.cpp index f950fe77..c261191b 100644 --- a/spatial/src/spatial/gdal/functions/st_read_meta.cpp +++ b/spatial/src/spatial/gdal/functions/st_read_meta.cpp @@ -59,7 +59,8 @@ static unique_ptr Bind(ClientContext &context, TableFunctionBindIn auto result = make_uniq(); auto multi_file_reader = MultiFileReader::Create(input.table_function); - result->file_names = multi_file_reader->CreateFileList(context, input.inputs[0], FileGlobOptions::ALLOW_EMPTY)->GetAllFiles(); + result->file_names = + multi_file_reader->CreateFileList(context, input.inputs[0], FileGlobOptions::ALLOW_EMPTY)->GetAllFiles(); names.push_back("file_name"); return_types.push_back(LogicalType::VARCHAR); diff --git a/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp b/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp index 446aae19..32bb0979 100644 --- a/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp +++ b/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp @@ -60,10 +60,10 @@ static void GeodesicLineString2DFunction(DataChunk &args, ExpressionState &state //------------------------------------------------------------------------------ static double LineLength(const Geometry &line, GeographicLib::PolygonArea &comp) { comp.Clear(); - for(uint32_t i = 0; i < LineString::VertexCount(line); i++) { - auto vert = LineString::GetVertex(line, i); - comp.AddPoint(vert.x, vert.y); - } + for (uint32_t i = 0; i < LineString::VertexCount(line); i++) { + auto vert = LineString::GetVertex(line, i); + comp.AddPoint(vert.x, vert.y); + } double _area; double linestring_length; comp.Compute(false, true, linestring_length, _area); diff --git a/spatial/src/spatial_extension.cpp b/spatial/src/spatial_extension.cpp index ece0abb8..cf72e6ce 100644 --- a/spatial/src/spatial_extension.cpp +++ b/spatial/src/spatial_extension.cpp @@ -52,7 +52,8 @@ static string RemoveIndentAndTrailingWhitespace(const char *text) { } void spatial::DocUtil::AddDocumentation(duckdb::DatabaseInstance &db, const char *function_name, - const char *description, const char *example, const Value &comment) { + const char *description, const char *example, + const duckdb::unordered_map &tags) { auto &system_catalog = Catalog::GetSystemCatalog(db); auto data = CatalogTransaction::GetSystemTransaction(db); @@ -78,8 +79,8 @@ void spatial::DocUtil::AddDocumentation(duckdb::DatabaseInstance &db, const char if (example != nullptr) { func_entry.example = RemoveIndentAndTrailingWhitespace(example); } - if (!comment.IsNull()) { - func_entry.comment = comment; + if (!tags.empty()) { + func_entry.tags = tags; } }