diff --git a/duckdb b/duckdb index ebf0f8fd..b5614c8f 160000 --- a/duckdb +++ b/duckdb @@ -1 +1 @@ -Subproject commit ebf0f8fde4249b6489dd33bec03b041dc4d2fff2 +Subproject commit b5614c8f445f1f079d9dd316a0763e747cc36dc8 diff --git a/src/spatial/modules/geos/geos_geometry.hpp b/src/spatial/modules/geos/geos_geometry.hpp index a275e915..2a4c9401 100644 --- a/src/spatial/modules/geos/geos_geometry.hpp +++ b/src/spatial/modules/geos/geos_geometry.hpp @@ -3,6 +3,7 @@ #include "geos_c.h" #include "duckdb/common/vector.hpp" +#include "duckdb/common/unique_ptr.hpp" namespace duckdb { @@ -96,6 +97,7 @@ class GeosGeometry { GeosGeometry get_buffer_style(double distance, int quadsegs, int endcap_style, int join_style, double mitre_limit) const; + GeosGeometry get_coverage_clean(double snapping_distance, double gap_maximum_width) const; GeosGeometry get_coverage_invalid_edges(double tolerance) const; GeosGeometry get_coverage_simplified(double tolerance, bool preserve_boundary) const; GeosGeometry get_coverage_union() const; @@ -507,11 +509,53 @@ inline GeosGeometry GeosGeometry::get_buffer_style(double distance, int quadsegs return GeosGeometry(handle, buffer); } +inline GeosGeometry GeosGeometry::get_coverage_clean(double snapping_distance, double gap_maximum_width) const { + + auto *params_raw = GEOSCoverageCleanParams_create_r(handle); + if (!params_raw) { + // Failed to allocate params; return an empty/invalid geometry handle + return GeosGeometry(handle, nullptr); + } + + // ensure params tidy themselves up after use + struct ParamsDeleter { + GEOSContextHandle_t h; + void operator()(GEOSCoverageCleanParams *p) const { + if (p) { + GEOSCoverageCleanParams_destroy_r(h, p); + } + } + }; + + unique_ptr params(params_raw, ParamsDeleter{handle}); + + // Conditionally set optional parameters; check return codes and fail fast + if (snapping_distance >= 0) { + if (!GEOSCoverageCleanParams_setSnappingDistance_r(handle, params.get(), snapping_distance)) { + return GeosGeometry(handle, nullptr); + } + } + if (gap_maximum_width >= 0) { + if (!GEOSCoverageCleanParams_setGapMaximumWidth_r(handle, params.get(), gap_maximum_width)) { + return GeosGeometry(handle, nullptr); + } + } + + // Overlap merge strategy: using a literal to avoid a dependency on enum values when I don't know the wider impact + // 0 is MERGE_LONGEST_BORDER + if (!GEOSCoverageCleanParams_setOverlapMergeStrategy_r(handle, params.get(), 0)) { + return GeosGeometry(handle, nullptr); + } + + return GeosGeometry(handle, GEOSCoverageCleanWithParams_r(handle, geom, params.get())); +} + inline GeosGeometry GeosGeometry::get_coverage_invalid_edges(double tolerance) const { GEOSGeometry *output = nullptr; GEOSCoverageIsValid_r(handle, geom, tolerance, &output); return GeosGeometry(handle, output); } + inline GeosGeometry GeosGeometry::get_coverage_simplified(double tolerance, bool preserve_boundary) const { return GeosGeometry(handle, GEOSCoverageSimplifyVW_r(handle, geom, tolerance, preserve_boundary)); } diff --git a/src/spatial/modules/geos/geos_module.cpp b/src/spatial/modules/geos/geos_module.cpp index bc39f07f..c1a1f9ef 100644 --- a/src/spatial/modules/geos/geos_module.cpp +++ b/src/spatial/modules/geos/geos_module.cpp @@ -843,6 +843,110 @@ struct ST_ConvexHull { } }; +struct ST_CoverageClean { + + static unique_ptr Bind(ClientContext &context, ScalarFunction &bound_function, + vector> &arguments) { + // set default values for coverage_clean parameters + const size_t num_args = arguments.size(); + if (num_args == 2) { // gap max width + arguments.push_back(make_uniq_base(Value::DOUBLE(-1))); + } + + if (num_args == 1) { // snapping distance, gap max width + arguments.push_back(make_uniq_base(Value::DOUBLE(-1))); + arguments.push_back(make_uniq_base(Value::DOUBLE(-1))); + } + + return nullptr; + } + + static void Execute(DataChunk &args, ExpressionState &state, Vector &result) { + auto &lstate = LocalState::ResetAndGet(state); + UnifiedVectorFormat format; + + auto &list_vec = args.data[0]; + auto &item_vec = ListVector::GetEntry(list_vec); + item_vec.ToUnifiedFormat(ListVector::GetListSize(list_vec), format); + + // Collection to hold the working set of geometries + GeosCollection collection(lstate.GetContext()); + + TernaryExecutor::Execute( + list_vec, args.data[1], args.data[2], + result, args.size(), [&](const list_entry_t &list, + double snapping_distance, double gap_maximum_width) { + // Reset the collection + collection.clear(); + collection.reserve(list.length); + + const auto offset = list.offset; + const auto length = list.length; + + // Collect all geometries in the list into the collection + for (idx_t i = offset; i < offset + length; i++) { + const auto mapped_idx = format.sel->get_index(i); + + if (!format.validity.RowIsValid(mapped_idx)) { + continue; + } + + const auto &geom_blob = UnifiedVectorFormat::GetData(format)[mapped_idx]; + + auto geom = lstate.Deserialize(geom_blob); + collection.add(std::move(geom)); + } + + // Now make a geometrycollection and simplify + const auto geometry_col = collection.get_collection(); + const auto cleaned = geometry_col.get_coverage_clean(snapping_distance, gap_maximum_width); + return lstate.Serialize(result, cleaned); + }); + } + + static void Register(ExtensionLoader &loader) { + FunctionBuilder::RegisterScalar(loader, "ST_CoverageClean", [](ScalarFunctionBuilder &func) { + func.AddVariant([](ScalarFunctionVariantBuilder &variant) { + variant.AddParameter("geoms", LogicalType::LIST(LogicalType::GEOMETRY())); + variant.AddParameter("snapping_distance", LogicalType::DOUBLE); + variant.AddParameter("gap_maximum_width", LogicalType::DOUBLE); + variant.SetReturnType(LogicalType::GEOMETRY()); + + variant.SetInit(LocalState::Init); + variant.SetBind(Bind); + variant.SetFunction(Execute); + }); + + func.AddVariant([](ScalarFunctionVariantBuilder &variant) { + variant.AddParameter("geoms", LogicalType::LIST(LogicalType::GEOMETRY())); + variant.AddParameter("snapping_distance", LogicalType::DOUBLE); + variant.SetReturnType(LogicalType::GEOMETRY()); + + variant.SetInit(LocalState::Init); + variant.SetBind(Bind); + variant.SetFunction(Execute); + }); + + func.AddVariant([](ScalarFunctionVariantBuilder &variant) { + variant.AddParameter("geoms", LogicalType::LIST(LogicalType::GEOMETRY())); + variant.SetReturnType(LogicalType::GEOMETRY()); + + variant.SetInit(LocalState::Init); + variant.SetBind(Bind); + variant.SetFunction(Execute); + }); + + func.SetDescription(R"( + Aligns the edges of a list of polygons whose edges are meant to align but are in fact exact matches. + + Returns a collection of fixed polygons with the same size and order as the input polygons. EMPTY will be used in place of collapsed polygons. + )"); + func.SetTag("ext", "spatial"); + func.SetTag("category", "construction"); + }); + } +}; + struct ST_CoverageInvalidEdges { static unique_ptr Bind(ClientContext &context, ScalarFunction &bound_function, @@ -3195,6 +3299,7 @@ void RegisterGEOSModule(ExtensionLoader &loader) { ST_WithinProperly::Register(loader); ST_ConcaveHull::Register(loader); ST_ConvexHull::Register(loader); + ST_CoverageClean::Register(loader); ST_CoverageInvalidEdges::Register(loader); ST_CoverageSimplify::Register(loader); ST_CoverageUnion::Register(loader); diff --git a/test/sql/geos/st_coverageclean.test b/test/sql/geos/st_coverageclean.test new file mode 100644 index 00000000..69ba834f --- /dev/null +++ b/test/sql/geos/st_coverageclean.test @@ -0,0 +1,18 @@ +require spatial + +# As per st_isvalid, we trust that GEOS knows what it's doing, so we're not interested in testing this too much + +query I +SELECT ST_CoverageClean( [ + ST_GeomFromText('POLYGON ((533135.006 124828.779, 533140.387 124828.514, 533142.884 124828.615, 533146.44 124829.532, 533150.1 124830.7, 533152.1 124831.7, 533154.2 124832.85, 533157.35 124834.65, 533160.7 124836.85, 533161.35 124837.3, 533161.75 124837.64, 533170.25 124843.55, 533173.05 124845.5, 533191.4 124860.95, 533195.3 124863.32, 533194.65 124864.55, 533193.95 124870.85, 533193.91 124871.2, 533193 124879.25, 533192.81 124881.72, 533192.5 124885.65, 533191.8 124894.05, 533191.63 124897.53, 533191.51 124899.98, 533191.4 124902.2, 533191.32 124903.77, 533191.01 124910.41, 533190.91 124912.57, 533190.589 124913.407, 533190.69 124913.52, 533191 124913.82, 533191.2 124913.99, 533191.4 124914.15, 533191.61 124914.31, 533191.82 124914.45, 533192.04 124914.58, 533192.27 124914.71, 533209.378 124922.281, 533233.851 124933.109, 533234.178 124933.251, 533241.486 124936.502, 533256.269 124943.011, 533272.689 124950.307, 533280.96 124953.37, 533310.314 124966.309, 533327.77 124974.03, 533328.15 124973.2, 533338.87 124949.39, 533344.25 124937.45, 533345.6 124934.43, 533348.18 124928.68, 533347.38 124928.19, 533347.5 124927.8, 533336.95 124921.25, 533325.54 124916.43, 533325.35 124916.35, 533319.85 124914.1, 533315.09 124912.14, 533313.55 124911.5, 533307.35 124912.45, 533306.12 124912.1, 533296.68 124909.37, 533295.9 124909.15, 533287.2 124904.1, 533288.2 124901.35, 533281.6 124898.6, 533281.501 124898.845, 533275.72 124896.47, 533275.633 124896.434, 533275.55 124896.4, 533266.949 124892.9, 533266.369 124892.709, 533265.48 124894.96, 533260.4 124892.9, 533259.42 124892.46, 533256.7 124891.25, 533254.07 124890.13, 533251.65 124889.1, 533248.06 124887.42, 533247.8 124887.3, 533242.85 124885.2, 533242.71 124885.14, 533237.95 124882.95, 533237.16 124882.57, 533233.65 124880.9, 533232.75 124880.4, 533231.62 124879.71, 533230.05 124878.75, 533226.26 124876.52, 533226.05 124876.4, 533220.8 124873.3, 533219.71 124872.62, 533215.95 124870.25, 533210.1 124866.45, 533205.18 124863.03, 533198.3 124858.25, 533197.65 124857.8, 533197.25 124858.2, 533194.15 124855.8, 533192.8 124854.75, 533189.07 124851.79, 533188.2 124851.1, 533185.49 124848.96, 533182.95 124846.95, 533178.4 124843.6, 533175.18 124841.14, 533173.9 124840.15, 533169.8 124837, 533167 124834.9, 533164.35 124833.05, 533164 124832.85, 533160.85 124830.95, 533160.4 124830.7, 533157.15 124829.05, 533153.2 124827.35, 533151.3 124826.65, 533148.15 124825.65, 533143.05 124823.95, 533136.45 124822.05, 533135.8 124826.4, 533135.302 124827.751, 533135.1 124828.3, 533135.006 124828.779))') + , ST_GeomFromText('POLYGON ((533266.369 124892.709, 533266.949 124892.9, 533275.55 124896.4, 533275.633 124896.434, 533275.79 124896.03, 533281.975 124880.06, 533286.87 124867.42, 533289.215 124860.931, 533290.691 124856.899, 533290.339 124856.77, 533284.4 124854.6, 533281.13 124853.37, 533275.1 124869.5, 533275.02 124869.71, 533273.06 124875.11, 533270.65 124881.75, 533267.5 124890.1, 533266.55 124892.25, 533266.369 124892.709))') + , ST_GeomFromText('POLYGON ((533248.06 124887.42, 533251.65 124889.1, 533254.069 124890.13, 533262.6 124867.3, 533264.05 124863.4, 533269.449 124848.95, 533263.699 124846.76, 533262.29 124850.43, 533258.1 124861.4, 533257.3 124863.5, 533248.06 124887.42))') + , ST_GeomFromText('POLYGON ((533275.72 124896.47, 533281.501 124898.845, 533281.6 124898.6, 533291.699 124873.05, 533292.9 124869.7, 533295.339 124863.19, 533296.85 124859.15, 533291.04 124857.026, 533289.524 124861.032, 533286.87 124867.42, 533275.88 124896.06, 533275.72 124896.47))') + , ST_GeomFromText('POLYGON ((533254.069 124890.13, 533256.699 124891.25, 533259.42 124892.46, 533268.949 124867.6, 533269.699 124865.7, 533273.77 124854.73, 533275.132 124851.104, 533273.4 124850.45, 533269.449 124848.95, 533264.05 124863.4, 533262.6 124867.3, 533254.069 124890.13))') + , ST_GeomFromText('POLYGON ((533273.584 124876.936, 533281.975 124880.06, 533286.87 124867.42, 533290.691 124856.899, 533290.339 124856.77, 533286.214 124855.262, 533284.247 124860.343, 533281.686 124859.392, 533280.28 124858.87, 533279.585 124860.745, 533277.06 124867.56, 533273.584 124876.936))') + , ST_GeomFromText('POLYGON ((533259.42 124892.46, 533260.4 124892.9, 533265.48 124894.96, 533266.55 124892.25, 533267.5 124890.1, 533270.65 124881.75, 533273.06 124875.11, 533275.02 124869.71, 533275.1 124869.5, 533281.13 124853.37, 533275.132 124851.104, 533273.77 124854.73, 533269.699 124865.7, 533268.949 124867.6, 533259.42 124892.46))') + ] + , 0.5 +);; +---- +GEOMETRYCOLLECTION (POLYGON ((533251.65 124889.1, 533248.06 124887.42, 533242.85 124885.2, 533237.95 124882.95, 533237.16 124882.57, 533233.65 124880.9, 533232.75 124880.4, 533231.62 124879.71, 533230.05 124878.75, 533226.26 124876.52, 533220.8 124873.3, 533219.71 124872.62, 533215.95 124870.25, 533210.1 124866.45, 533205.18 124863.03, 533198.3 124858.25, 533197.65 124857.8, 533197.25 124858.2, 533194.15 124855.8, 533192.8 124854.75, 533189.07 124851.79, 533188.2 124851.1, 533185.49 124848.96, 533182.95 124846.95, 533178.4 124843.6, 533175.18 124841.14, 533173.9 124840.15, 533169.8 124837, 533167 124834.9, 533164.35 124833.05, 533160.85 124830.95, 533160.4 124830.7, 533157.15 124829.05, 533153.2 124827.35, 533151.3 124826.65, 533148.15 124825.65, 533143.05 124823.95, 533136.45 124822.05, 533135.8 124826.4, 533135.302 124827.751, 533135.006 124828.779, 533140.387 124828.514, 533142.884 124828.615, 533146.44 124829.532, 533150.1 124830.7, 533152.1 124831.7, 533154.2 124832.85, 533157.35 124834.65, 533160.7 124836.85, 533161.35 124837.3, 533161.75 124837.64, 533170.25 124843.55, 533173.05 124845.5, 533191.4 124860.95, 533195.3 124863.32, 533194.65 124864.55, 533193.95 124870.85, 533193 124879.25, 533192.81 124881.72, 533192.5 124885.65, 533191.8 124894.05, 533191.63 124897.53, 533191.51 124899.98, 533191.4 124902.2, 533191.32 124903.77, 533191.01 124910.41, 533190.91 124912.57, 533190.589 124913.407, 533191 124913.82, 533191.4 124914.15, 533191.82 124914.45, 533192.27 124914.71, 533209.378 124922.281, 533233.851 124933.109, 533241.486 124936.502, 533256.269 124943.011, 533272.689 124950.307, 533280.96 124953.37, 533310.314 124966.309, 533327.77 124974.03, 533328.15 124973.2, 533338.87 124949.39, 533344.25 124937.45, 533345.6 124934.43, 533348.18 124928.68, 533347.38 124928.19, 533336.95 124921.25, 533325.54 124916.43, 533319.85 124914.1, 533315.09 124912.14, 533313.55 124911.5, 533307.35 124912.45, 533306.12 124912.1, 533296.68 124909.37, 533295.9 124909.15, 533287.2 124904.1, 533288.2 124901.35, 533281.6 124898.6, 533275.72 124896.47, 533266.949 124892.9, 533266.369 124892.709, 533265.48 124894.96, 533260.4 124892.9, 533259.42 124892.46, 533256.7 124891.25, 533254.07 124890.13, 533251.65 124889.1)), POLYGON ((533266.949 124892.9, 533275.72 124896.47, 533281.975 124880.06, 533286.87 124867.42, 533289.215 124860.931, 533290.691 124856.899, 533286.214 124855.262, 533284.4 124854.6, 533281.13 124853.37, 533275.1 124869.5, 533273.06 124875.11, 533270.65 124881.75, 533267.5 124890.1, 533266.369 124892.709, 533266.949 124892.9)), POLYGON ((533251.65 124889.1, 533254.07 124890.13, 533262.6 124867.3, 533264.05 124863.4, 533269.449 124848.95, 533263.699 124846.76, 533262.29 124850.43, 533258.1 124861.4, 533257.3 124863.5, 533248.06 124887.42, 533251.65 124889.1)), POLYGON ((533281.6 124898.6, 533291.699 124873.05, 533292.9 124869.7, 533295.339 124863.19, 533296.85 124859.15, 533290.691 124856.899, 533289.215 124860.931, 533286.87 124867.42, 533281.975 124880.06, 533275.72 124896.47, 533281.6 124898.6)), POLYGON ((533256.7 124891.25, 533259.42 124892.46, 533268.949 124867.6, 533269.699 124865.7, 533273.77 124854.73, 533275.132 124851.104, 533273.4 124850.45, 533269.449 124848.95, 533264.05 124863.4, 533262.6 124867.3, 533254.07 124890.13, 533256.7 124891.25)), POLYGON EMPTY, POLYGON ((533260.4 124892.9, 533265.48 124894.96, 533266.369 124892.709, 533267.5 124890.1, 533270.65 124881.75, 533273.06 124875.11, 533275.1 124869.5, 533281.13 124853.37, 533275.132 124851.104, 533273.77 124854.73, 533269.699 124865.7, 533268.949 124867.6, 533259.42 124892.46, 533260.4 124892.9))) \ No newline at end of file