From 4c3109e2df2eee515bfa863823787b22383c54a2 Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Mon, 23 Sep 2024 10:17:03 -0400 Subject: [PATCH] C API cluster functions: Return GEOSClusterInfo object --- capi/geos_c.cpp | 57 ++++++++---- capi/geos_c.h.in | 129 +++++++++++++++++++++------- capi/geos_ts_c.cpp | 79 +++++++++++++---- tests/unit/capi/GEOSClusterTest.cpp | 91 ++++++++------------ 4 files changed, 233 insertions(+), 123 deletions(-) diff --git a/capi/geos_c.cpp b/capi/geos_c.cpp index 3aa839873..633c6b07c 100644 --- a/capi/geos_c.cpp +++ b/capi/geos_c.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,7 @@ // violations. #define GEOSGeometry geos::geom::Geometry #define GEOSPreparedGeometry geos::geom::prep::PreparedGeometry +#define GEOSClusterInfo geos::operation::cluster::Clusters #define GEOSCoordSequence geos::geom::CoordinateSequence #define GEOSBufferParams geos::operation::buffer::BufferParameters #define GEOSSTRtree geos::index::strtree::TemplateSTRtree @@ -343,34 +345,59 @@ extern "C" { return GEOSNearestPoints_r(handle, g1, g2); } - unsigned* - GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, unsigned minPoints, unsigned* numClusters) + GEOSClusterInfo* + GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, unsigned minPoints) { - return GEOSClusterDBSCAN_r(handle, g, eps, minPoints, numClusters); + return GEOSClusterDBSCAN_r(handle, g, eps, minPoints); } - unsigned* - GEOSClusterGeometryDistance(const GEOSGeometry* g, double d, unsigned* numClusters) + GEOSClusterInfo* + GEOSClusterGeometryDistance(const GEOSGeometry* g, double d) { - return GEOSClusterGeometryDistance_r(handle, g, d, numClusters); + return GEOSClusterGeometryDistance_r(handle, g, d); } - unsigned* - GEOSClusterGeometryIntersects(const GEOSGeometry* g, unsigned* numClusters) + GEOSClusterInfo* + GEOSClusterGeometryIntersects(const GEOSGeometry* g) { - return GEOSClusterGeometryIntersects_r(handle, g, numClusters); + return GEOSClusterGeometryIntersects_r(handle, g); } - unsigned* - GEOSClusterEnvelopeDistance(const GEOSGeometry* g, double d, unsigned* numClusters) + GEOSClusterInfo* + GEOSClusterEnvelopeDistance(const GEOSGeometry* g, double d) { - return GEOSClusterEnvelopeDistance_r(handle, g, d, numClusters); + return GEOSClusterEnvelopeDistance_r(handle, g, d); } - unsigned* - GEOSClusterEnvelopeIntersects(const GEOSGeometry* g, unsigned* numClusters) + GEOSClusterInfo* + GEOSClusterEnvelopeIntersects(const GEOSGeometry* g) { - return GEOSClusterEnvelopeIntersects_r(handle, g, numClusters); + return GEOSClusterEnvelopeIntersects_r(handle, g); + } + + std::size_t GEOSClusterInfo_getNumClusters(const GEOSClusterInfo* clusters) + { + return GEOSClusterInfo_getNumClusters_r(handle, clusters); + } + + std::size_t GEOSClusterInfo_getClusterSize(const GEOSClusterInfo* clusters, size_t i) + { + return GEOSClusterInfo_getClusterSize_r(handle, clusters, i); + } + + const std::size_t* GEOSClusterInfo_getInputsForClusterN(const GEOSClusterInfo* clusters, size_t i) + { + return GEOSClusterInfo_getInputsForClusterN_r(handle, clusters, i); + } + + std::size_t* GEOSClusterInfo_getClustersForInputs(const GEOSClusterInfo* clusters) + { + return GEOSClusterInfo_getClustersForInputs_r(handle, clusters); + } + + void GEOSClusterInfo_destroy(GEOSClusterInfo* info) + { + GEOSClusterInfo_destroy_r(handle, info); } Geometry* diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in index 84e97103d..9aa9dc2db 100644 --- a/capi/geos_c.h.in +++ b/capi/geos_c.h.in @@ -175,6 +175,12 @@ typedef struct GEOSBufParams_t GEOSBufferParams; */ typedef struct GEOSMakeValidParams_t GEOSMakeValidParams; +/** +* Object containing information about cluster of geometries. +* \see GEOSClusterInfo_destroy() +*/ +typedef struct GEOSClusterInfo_t GEOSClusterInfo; + #endif /** \cond */ @@ -1849,38 +1855,48 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXYZ_r( void* userdata); /** \see GEOSClusterDBSCAN */ -extern unsigned GEOS_DLL* GEOSClusterDBSCAN_r( +extern GEOSClusterInfo GEOS_DLL* GEOSClusterDBSCAN_r( GEOSContextHandle_t handle, const GEOSGeometry* g, double eps, - unsigned minPoints, - unsigned* numClusters); + unsigned minPoints); /** \see GEOSClusterGeometryDistance */ -extern unsigned GEOS_DLL* GEOSClusterGeometryDistance_r( +extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryDistance_r( GEOSContextHandle_t handle, const GEOSGeometry* g, - double d, - unsigned* numClusters); + double d); /** \see GEOSClusterGeometryIntersects */ -extern unsigned GEOS_DLL* GEOSClusterGeometryIntersects_r( +extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryIntersects_r( GEOSContextHandle_t handle, - const GEOSGeometry* g, - unsigned* numClusters); + const GEOSGeometry* g); /** \see GEOSClusterEnvelopeDistance */ -extern unsigned GEOS_DLL* GEOSClusterEnvelopeDistance_r( +extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeDistance_r( GEOSContextHandle_t handle, const GEOSGeometry* g, - double d, - unsigned* numClusters); + double d); /** \see GEOSClusterEnvelopeIntersects */ -extern unsigned GEOS_DLL* GEOSClusterEnvelopeIntersects_r( +extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeIntersects_r( GEOSContextHandle_t handle, - const GEOSGeometry* g, - unsigned* numClusters); + const GEOSGeometry* g); + +/** \see GEOSClusterInfo_getNumClusters */ +extern size_t GEOS_DLL GEOSClusterInfo_getNumClusters_r(GEOSContextHandle_t, const GEOSClusterInfo* clusters); + +/** \see GEOSClusterInfo_getClusterSize */ +extern size_t GEOS_DLL GEOSClusterInfo_getClusterSize_r(GEOSContextHandle_t, const GEOSClusterInfo* clusters, size_t i); + +/** \see GEOSClusterInfo_getClustersForInputs */ +extern size_t GEOS_DLL* GEOSClusterInfo_getClustersForInputs_r(GEOSContextHandle_t, const GEOSClusterInfo* clusters); + +/** \see GEOSClusterInfo_getInputsForClusterN */ +extern const size_t GEOS_DLL* GEOSClusterInfo_getInputsForClusterN_r(GEOSContextHandle_t, const GEOSClusterInfo* clusters, size_t i); + +/** \see GEOSClusterInfo_destroy */ +extern void GEOS_DLL GEOSClusterInfo_destroy_r(GEOSContextHandle_t, GEOSClusterInfo* info); /* ========= Algorithms ========= */ @@ -3800,7 +3816,10 @@ extern GEOSGeometry GEOS_DLL *GEOSSharedPaths( * Functions for clustering geometries */ -static const unsigned GEOS_CLUSTER_NONE = 4294967295; + + + +static const size_t GEOS_CLUSTER_NONE = (size_t) -1; ///@{ /** @@ -3811,11 +3830,9 @@ static const unsigned GEOS_CLUSTER_NONE = 4294967295; * @param g a collection of geometries to be clustered * @param eps distance parameter for clustering * @param minPoints density parameter for clustering - * @param numClusters the number of clusters formed - * @return an array of cluster identifiers + * @return cluster information object */ -extern unsigned GEOS_DLL* GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, unsigned minPoints, - unsigned* numClusters); +extern GEOSClusterInfo GEOS_DLL* GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, unsigned minPoints); /** * @brief GEOSClusterGeometryDistance @@ -3824,10 +3841,9 @@ extern unsigned GEOS_DLL* GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, u * * @param g a collection of geometries to be clustered * @param d minimum distance between geometries in the same cluster - * @param numClusters the number of clusters formed - * @return an array of cluster identifiers + * @return cluster information object */ -extern unsigned GEOS_DLL* GEOSClusterGeometryDistance(const GEOSGeometry* g, double d, unsigned* numClusters); +extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryDistance(const GEOSGeometry* g, double d); /** * @brief GEOSClusterGeometryIntersects @@ -3835,10 +3851,9 @@ extern unsigned GEOS_DLL* GEOSClusterGeometryDistance(const GEOSGeometry* g, dou * Cluster geometries that intersect * * @param g a collection of geometries to be clustered - * @param numClusters the number of clusters formed - * @return an array of cluster identifiers + * @return cluster information object */ -extern unsigned GEOS_DLL* GEOSClusterGeometryIntersects(const GEOSGeometry* g, unsigned* numClusters); +extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryIntersects(const GEOSGeometry* g); /** * @brief GEOSClusterEnvelopeDistance @@ -3847,10 +3862,9 @@ extern unsigned GEOS_DLL* GEOSClusterGeometryIntersects(const GEOSGeometry* g, u * * @param g a collection of geometries to be clustered * @param d minimum envelope distance between geometries in the same cluster - * @param numClusters the number of clusters formed - * @return an array of cluster identifiers + * @return cluster information object */ -extern unsigned GEOS_DLL* GEOSClusterEnvelopeDistance(const GEOSGeometry* g, double d, unsigned* numClusters); +extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeDistance(const GEOSGeometry* g, double d); /** * @brief GEOSClusterEnvelopeIntersects @@ -3858,10 +3872,61 @@ extern unsigned GEOS_DLL* GEOSClusterEnvelopeDistance(const GEOSGeometry* g, dou * Cluster geometries whose envelopes intersect * * @param g - * @param numClusters the number of clusters formed - * @return an array of cluster identifiers + * @return cluster information object */ -extern unsigned GEOS_DLL* GEOSClusterEnvelopeIntersects(const GEOSGeometry* g, unsigned* numClusters); +extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeIntersects(const GEOSGeometry* g); + +/** + * @brief GEOSClusterInfo_getNumClusters + * + * Get the number of clusters identifed by a clustering operation + * + * @param clusters cluster information object + * @return number of clusters identifed + */ +extern size_t GEOS_DLL GEOSClusterInfo_getNumClusters(const GEOSClusterInfo* clusters); + +/** + * @brief GEOSClusterInfo_getSize + * + * Get the number of elements in the ith cluster (0-indexed) + * + * @param clusters cluster information object + * @param i cluster for which a size will be returned + * @return number of elements in the cluster + */ +extern size_t GEOS_DLL GEOSClusterInfo_getClusterSize(const GEOSClusterInfo* clusters, size_t i ); + +/** + * @brief GEOSClusterInfo_getClustersForInputs + * + * Get the cluster index associated with each input to the clustering operation. + * Inputs that do are not associated with any cluster will have an index of GEOS_CLUSTER_NONE. + * + * @param clusters cluster information object + * @return an array of cluster indices, to be freed by the caller. + */ +extern size_t GEOS_DLL* GEOSClusterInfo_getClustersForInputs(const GEOSClusterInfo* clusters); + +/** + * @brief GEOSClusterInfo_getInputsForClusterN + * @param info cluster information object + * @param i index of the cluster for which indices should be retrieved + * @return a pointer to an array of cluster indices. Size of the array is indicated by + * GEOSClusterInfo_getNumElements. The array is owner by the cluster information + * object and should not be modified or freed by the caller. + */ +extern const size_t GEOS_DLL* GEOSClusterInfo_getInputsForClusterN(const GEOSClusterInfo* clusters, size_t i); + +/** + * @brief GEOSClusterInfo_destroy + * + * Destroy a cluster information object. + * + * @param clusters cluster information object + */ +extern void GEOS_DLL GEOSClusterInfo_destroy(GEOSClusterInfo* clusters); + ///@} /* ========== Buffer related functions ========== */ diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp index d742b364b..467a45e57 100644 --- a/capi/geos_ts_c.cpp +++ b/capi/geos_ts_c.cpp @@ -140,6 +140,7 @@ #define GEOSGeometry geos::geom::Geometry #define GEOSPreparedGeometry geos::geom::prep::PreparedGeometry #define GEOSCoordSequence geos::geom::CoordinateSequence +#define GEOSClusterInfo geos::operation::cluster::Clusters #define GEOSBufferParams geos::operation::buffer::BufferParameters #define GEOSSTRtree geos::index::strtree::TemplateSTRtree #define GEOSWKTReader geos::io::WKTReader @@ -208,6 +209,7 @@ using geos::algorithm::hull::ConcaveHullOfPolygons; using geos::operation::buffer::BufferBuilder; using geos::operation::buffer::BufferParameters; using geos::operation::buffer::OffsetCurve; +using geos::operation::cluster::Clusters; using geos::operation::distance::IndexedFacetDistance; using geos::operation::geounion::CascadedPolygonUnion; using geos::operation::overlayng::OverlayNG; @@ -953,15 +955,18 @@ extern "C" { }); } - static unsigned* capi_clusters(const Geometry* g, - geos::operation::cluster::AbstractClusterFinder& finder, - unsigned* numClusters) + static Clusters* capi_clusters(const Geometry* g, + geos::operation::cluster::AbstractClusterFinder& finder) { std::vector input{g->getNumGeometries()}; for (std::size_t i = 0; i < input.size(); i++) { input[i] = g->getGeometryN(i); } + return new Clusters(finder.cluster(input)); + } + +#if 0 auto result = finder.cluster(input); if (numClusters) { @@ -976,54 +981,90 @@ extern "C" { return ids; } +#endif - unsigned* - GEOSClusterDBSCAN_r(GEOSContextHandle_t extHandle, const Geometry* g, double eps, unsigned minPoints, - unsigned* numClusters) + Clusters* + GEOSClusterDBSCAN_r(GEOSContextHandle_t extHandle, const Geometry* g, double eps, unsigned minPoints) { return execute(extHandle, [&]() { geos::operation::cluster::DBSCANClusterFinder finder(eps, minPoints); - return capi_clusters(g, finder, numClusters); + return capi_clusters(g, finder); }); } - unsigned* - GEOSClusterGeometryIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g, unsigned* numClusters) + Clusters* + GEOSClusterGeometryIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() { geos::operation::cluster::GeometryIntersectsClusterFinder finder; - return capi_clusters(g, finder, numClusters); + return capi_clusters(g, finder); }); } - unsigned* - GEOSClusterEnvelopeIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g, unsigned* numClusters) + Clusters* + GEOSClusterEnvelopeIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() { geos::operation::cluster::EnvelopeIntersectsClusterFinder finder; - return capi_clusters(g, finder, numClusters); + return capi_clusters(g, finder); }); } - unsigned* - GEOSClusterEnvelopeDistance_r(GEOSContextHandle_t extHandle, const Geometry* g, double d, unsigned* numClusters) + Clusters* + GEOSClusterEnvelopeDistance_r(GEOSContextHandle_t extHandle, const Geometry* g, double d) { return execute(extHandle, [&]() { geos::operation::cluster::EnvelopeDistanceClusterFinder finder(d); - return capi_clusters(g, finder, numClusters); + return capi_clusters(g, finder); }); } - unsigned* - GEOSClusterGeometryDistance_r(GEOSContextHandle_t extHandle, const Geometry* g, double d, unsigned* numClusters) + Clusters* + GEOSClusterGeometryDistance_r(GEOSContextHandle_t extHandle, const Geometry* g, double d) { return execute(extHandle, [&]() { geos::operation::cluster::GeometryDistanceClusterFinder finder(d); - return capi_clusters(g, finder, numClusters); + return capi_clusters(g, finder); + }); + } + + std::size_t GEOSClusterInfo_getNumClusters_r(GEOSContextHandle_t extHandle, const Clusters* clusters) + { + return execute(extHandle, 0, [&]() -> std::size_t { + return static_cast(clusters->getNumClusters()); }); } + std::size_t GEOSClusterInfo_getClusterSize_r(GEOSContextHandle_t extHandle, const Clusters* clusters, std::size_t i) + { + return execute(extHandle, 0, [&]() { + return static_cast(clusters->getSize(i)); + }); + } + + const std::size_t* GEOSClusterInfo_getInputsForClusterN_r(GEOSContextHandle_t extHandle, const Clusters* clusters, std::size_t i) + { + return execute(extHandle, [&]() { + return &*clusters->begin(i); + }); + } + + std::size_t* GEOSClusterInfo_getClustersForInputs_r(GEOSContextHandle_t extHandle, const Clusters* clusters) + { + return execute(extHandle, [&]() { + auto ids = clusters->getClusterIds(GEOS_CLUSTER_NONE); + std::size_t* ids_buf = (size_t*) malloc(ids.size() * sizeof(std::size_t)); + std::copy(ids.begin(), ids.end(), ids_buf); + return ids_buf; + }); + } + + void GEOSClusterInfo_destroy_r(GEOSContextHandle_t, Clusters* clusters) + { + delete clusters; + } + Geometry* GEOSGeomFromWKT_r(GEOSContextHandle_t extHandle, const char* wkt) { diff --git a/tests/unit/capi/GEOSClusterTest.cpp b/tests/unit/capi/GEOSClusterTest.cpp index 9a8ea015d..f96d91ce5 100644 --- a/tests/unit/capi/GEOSClusterTest.cpp +++ b/tests/unit/capi/GEOSClusterTest.cpp @@ -15,38 +15,25 @@ namespace tut { // Common data used in test cases. struct test_capicluster_data : public capitest::utility { static GEOSGeometry* construct_clusters(const GEOSGeometry* input, - unsigned numClusters, - unsigned* clusterIds) + const GEOSClusterInfo* clusters) { - GEOSGeometry* input_cloned = GEOSGeom_clone(input); - unsigned ngeoms; - GEOSGeometry** components = GEOSGeom_releaseCollection(input_cloned, &ngeoms); - GEOSGeom_destroy(input_cloned); - - // collect the indices associated with each cluster - std::vector> cluster_component_ids(numClusters); - for (unsigned i = 0; i < ngeoms; i++) { - cluster_component_ids[clusterIds[i]].push_back(i); - } + auto ngeoms = static_cast(GEOSGetNumGeometries(input)); + auto numClusters = GEOSClusterInfo_getNumClusters(clusters); + std::vector cluster_components(ngeoms); std::vector cluster_geoms(numClusters); - // assemble a GeometryCollection for each cluster - for (unsigned cluster_id = 0; cluster_id < numClusters; cluster_id++) { - const std::vector component_ids = cluster_component_ids[cluster_id]; - - std::vector cluster_component_geoms(component_ids.size()); - for (std::size_t i = 0; i < component_ids.size(); i++) { - cluster_component_geoms[i] = components[component_ids[i]]; + for (std::size_t cluster_id = 0; cluster_id < numClusters; cluster_id++) { + auto sz = GEOSClusterInfo_getClusterSize(clusters, cluster_id); + const std::size_t* indices = GEOSClusterInfo_getInputsForClusterN(clusters, cluster_id); + for (std::size_t i = 0; i < sz; i++) { + cluster_components[i] = GEOSGeom_clone(GEOSGetGeometryN(input, static_cast(indices[i]))); } - cluster_geoms[cluster_id] = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, - cluster_component_geoms.data(), - static_cast(cluster_component_geoms.size())); + cluster_components.data(), + static_cast(sz)); } - GEOSFree(components); - // combine the clusters into a single nested GeometryCollection return GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, cluster_geoms.data(), @@ -75,11 +62,10 @@ template<> void object::test<1> "POINT (0 7))"); { - unsigned numClusters = 123; - unsigned* cluster_ids = GEOSClusterEnvelopeIntersects(input_, &numClusters); - ensure_equals("two clusters by envelope intersection", numClusters, 2u); + GEOSClusterInfo* clusters = GEOSClusterEnvelopeIntersects(input_); + ensure_equals("two clusters by envelope intersection", GEOSClusterInfo_getNumClusters(clusters), 2u); - GEOSGeometry* geom_result = construct_clusters(input_, numClusters, cluster_ids); + GEOSGeometry* geom_result = construct_clusters(input_, clusters); GEOSGeometry* geom_expected = fromWKT("GEOMETRYCOLLECTION (" " GEOMETRYCOLLECTION (" " POINT (0 1)," @@ -93,31 +79,25 @@ template<> void object::test<1> GEOSGeom_destroy(geom_result); GEOSGeom_destroy(geom_expected); - GEOSFree(cluster_ids); + GEOSClusterInfo_destroy(clusters); } { - unsigned numClusters = 123; - unsigned* cluster_ids = GEOSClusterEnvelopeDistance(input_, 6, &numClusters); - ensure_equals("one cluster by envelope distance", numClusters, 1u); - - GEOSFree(cluster_ids); + GEOSClusterInfo* clusters = GEOSClusterEnvelopeDistance(input_, 6); + ensure_equals("one cluster by envelope distance", GEOSClusterInfo_getNumClusters(clusters), 1u); + GEOSClusterInfo_destroy(clusters); } { - unsigned numClusters = 123; - unsigned* cluster_ids = GEOSClusterGeometryIntersects(input_, &numClusters); - ensure_equals("four clusters by geometry intersection", numClusters, 4u); - - GEOSFree(cluster_ids); + GEOSClusterInfo* clusters = GEOSClusterGeometryIntersects(input_); + ensure_equals("four clusters by geometry intersection", GEOSClusterInfo_getNumClusters(clusters), 4u); + GEOSClusterInfo_destroy(clusters); } { - unsigned numClusters = 123; - unsigned* cluster_ids = GEOSClusterGeometryDistance(input_, 0.2, &numClusters); - ensure_equals("three clusters by distance", numClusters, 3u); - - GEOSFree(cluster_ids); + GEOSClusterInfo* clusters = GEOSClusterGeometryDistance(input_, 0.2); + ensure_equals("three clusters by distance", GEOSClusterInfo_getNumClusters(clusters), 3u); + GEOSClusterInfo_destroy(clusters); } } @@ -139,29 +119,26 @@ void object::test<2>() ")"); { - unsigned numClusters = 123; - unsigned* cluster_ids = GEOSClusterDBSCAN(input_, 1.01, 5, &numClusters); - ensure_equals("two clusters with minPoints = 5", numClusters, 2u); - - GEOSFree(cluster_ids); + GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 1.01, 5); + ensure_equals("two clusters with minPoints = 5", GEOSClusterInfo_getNumClusters(clusters), 2u); + GEOSClusterInfo_destroy(clusters); } { - unsigned numClusters = 123; - unsigned* cluster_ids = GEOSClusterDBSCAN(input_, 1.01, 2, &numClusters); - ensure_equals("one cluster with minPoints = 2", numClusters, 1u); - - GEOSFree(cluster_ids); + GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 1.01, 2); + ensure_equals("one cluster with minPoints = 2", GEOSClusterInfo_getNumClusters(clusters), 1u); + GEOSClusterInfo_destroy(clusters); } { - unsigned numClusters = 123; - unsigned* cluster_ids = GEOSClusterDBSCAN(input_, 1.01, 20, &numClusters); - ensure_equals("no clusters with minPoints = 20", numClusters, 0u); + GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 1.01, 20); + ensure_equals("no clusters with minPoints = 20", GEOSClusterInfo_getNumClusters(clusters), 0u); + auto* cluster_ids = GEOSClusterInfo_getClustersForInputs(clusters); for (int i = 0; i < GEOSGetNumGeometries(input_); i++) { ensure_equals(cluster_ids[i], GEOS_CLUSTER_NONE); } + GEOSClusterInfo_destroy(clusters); GEOSFree(cluster_ids); } }