[geos-commits] [SCM] GEOS branch main updated. b2af4472de4195a8e1cde955addf8693c3e05c04

git at osgeo.org git at osgeo.org
Wed Jan 22 16:54:58 PST 2025


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".

The branch, main has been updated
       via  b2af4472de4195a8e1cde955addf8693c3e05c04 (commit)
      from  af08e9d5318ad823072d17908058c6b1aa9125b9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit b2af4472de4195a8e1cde955addf8693c3e05c04
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Jan 22 19:54:31 2025 -0500

    Add clustering functions to C API (#1154)
    
    * Add clustering functions to C API
    
    * C API cluster functions: Return ids instead of GEOSGeometry*
    
    * C API cluster functions: Return GEOSClusterInfo object

diff --git a/capi/geos_c.cpp b/capi/geos_c.cpp
index 39d34ff5e..633c6b07c 100644
--- a/capi/geos_c.cpp
+++ b/capi/geos_c.cpp
@@ -24,6 +24,7 @@
 #include <geos/io/GeoJSONReader.h>
 #include <geos/io/GeoJSONWriter.h>
 #include <geos/operation/buffer/BufferParameters.h>
+#include <geos/operation/cluster/Clusters.h>
 #include <geos/util/Interrupt.h>
 
 #include <stdexcept>
@@ -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<void*>
@@ -343,6 +345,61 @@ extern "C" {
         return GEOSNearestPoints_r(handle, g1, g2);
     }
 
+    GEOSClusterInfo*
+    GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, unsigned minPoints)
+    {
+        return GEOSClusterDBSCAN_r(handle, g, eps, minPoints);
+    }
+
+    GEOSClusterInfo*
+    GEOSClusterGeometryDistance(const GEOSGeometry* g, double d)
+    {
+        return GEOSClusterGeometryDistance_r(handle, g, d);
+    }
+
+    GEOSClusterInfo*
+    GEOSClusterGeometryIntersects(const GEOSGeometry* g)
+    {
+        return GEOSClusterGeometryIntersects_r(handle, g);
+    }
+
+    GEOSClusterInfo*
+    GEOSClusterEnvelopeDistance(const GEOSGeometry* g, double d)
+    {
+        return GEOSClusterEnvelopeDistance_r(handle, g, d);
+    }
+
+    GEOSClusterInfo*
+    GEOSClusterEnvelopeIntersects(const GEOSGeometry* g)
+    {
+        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*
     GEOSGeomFromWKT(const char* wkt)
     {
diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index bd3b3e36c..761f69889 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 */
@@ -1848,6 +1854,50 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXYZ_r(
     GEOSTransformXYZCallback callback,
     void* userdata);
 
+/** \see GEOSClusterDBSCAN */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterDBSCAN_r(
+    GEOSContextHandle_t handle,
+    const GEOSGeometry* g,
+    double eps,
+    unsigned minPoints);
+
+/** \see GEOSClusterGeometryDistance */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryDistance_r(
+    GEOSContextHandle_t handle,
+    const GEOSGeometry* g,
+    double d);
+
+/** \see GEOSClusterGeometryIntersects */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryIntersects_r(
+    GEOSContextHandle_t handle,
+    const GEOSGeometry* g);
+
+/** \see GEOSClusterEnvelopeDistance */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeDistance_r(
+    GEOSContextHandle_t handle,
+    const GEOSGeometry* g,
+    double d);
+
+/** \see GEOSClusterEnvelopeIntersects */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeIntersects_r(
+    GEOSContextHandle_t handle,
+    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 ========= */
 
 /** \see GEOSOrientationIndex */
@@ -3759,6 +3809,123 @@ extern GEOSGeometry GEOS_DLL *GEOSClipByRect(
 extern GEOSGeometry GEOS_DLL *GEOSSharedPaths(
     const GEOSGeometry* g1,
     const GEOSGeometry* g2);
+///@}
+
+/* ========== Clustering functions ========== */
+/** @name Clustering
+* Functions for clustering geometries
+*/
+
+
+
+
+static const size_t GEOS_CLUSTER_NONE = (size_t) -1;
+
+///@{
+/**
+ * @brief GEOSClusterDBSCAN
+ *
+ * Cluster geometries using the DBSCAN algorithm
+ *
+ * @param g a collection of geometries to be clustered
+ * @param eps distance parameter for clustering
+ * @param minPoints density parameter for clustering
+ * @return cluster information object
+ */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterDBSCAN(const GEOSGeometry* g, double eps, unsigned minPoints);
+
+/**
+ * @brief GEOSClusterGeometryDistance
+ *
+ * Cluster geometries according to a distance threshold
+ *
+ * @param g a collection of geometries to be clustered
+ * @param d minimum distance between geometries in the same cluster
+ * @return cluster information object
+ */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryDistance(const GEOSGeometry* g, double d);
+
+/**
+ * @brief GEOSClusterGeometryIntersects
+ *
+ * Cluster geometries that intersect
+ *
+ * @param g a collection of geometries to be clustered
+ * @return cluster information object
+ */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterGeometryIntersects(const GEOSGeometry* g);
+
+/**
+ * @brief GEOSClusterEnvelopeDistance
+ *
+ * Cluster geometries according to an envelope distance threshold
+ *
+ * @param g a collection of geometries to be clustered
+ * @param d minimum envelope distance between geometries in the same cluster
+ * @return cluster information object
+ */
+extern GEOSClusterInfo GEOS_DLL* GEOSClusterEnvelopeDistance(const GEOSGeometry* g, double d);
+
+/**
+ * @brief GEOSClusterEnvelopeIntersects
+ *
+ * Cluster geometries whose envelopes intersect
+ *
+ * @param g
+ * @return cluster information object
+ */
+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 clusters 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);
 
 ///@}
 
@@ -4975,9 +5142,9 @@ extern char GEOS_DLL GEOSEqualsIdentical(
 
 /**
 * Calculate the [DE9IM](https://en.wikipedia.org/wiki/DE-9IM) string for a geometry pair
-* and compare against a DE9IM pattern. 
+* and compare against a DE9IM pattern.
 * Returns true if the computed matrix matches the pattern.
-* The pattern is a 9-character string 
+* The pattern is a 9-character string
 * containing symbols in the set "012TF*".
 * "012F" match the corresponding dimension symbol;
 * "T" matches any non-empty dimension; "*" matches any dimension.
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index b77d872ba..ace450b49 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -73,6 +73,11 @@
 #include <geos/operation/buffer/BufferOp.h>
 #include <geos/operation/buffer/BufferParameters.h>
 #include <geos/operation/buffer/OffsetCurve.h>
+#include <geos/operation/cluster/DBSCANClusterFinder.h>
+#include <geos/operation/cluster/EnvelopeDistanceClusterFinder.h>
+#include <geos/operation/cluster/EnvelopeIntersectsClusterFinder.h>
+#include <geos/operation/cluster/GeometryDistanceClusterFinder.h>
+#include <geos/operation/cluster/GeometryIntersectsClusterFinder.h>
 #include <geos/operation/distance/DistanceOp.h>
 #include <geos/operation/distance/IndexedFacetDistance.h>
 #include <geos/operation/linemerge/LineMerger.h>
@@ -135,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<void*>
 #define GEOSWKTReader geos::io::WKTReader
@@ -203,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;
@@ -948,6 +955,98 @@ extern "C" {
         });
     }
 
+    static Clusters* capi_clusters(const Geometry* g,
+                                   geos::operation::cluster::AbstractClusterFinder& finder)
+    {
+        std::vector<const Geometry*> input{g->getNumGeometries()};
+        for (std::size_t i = 0; i < input.size(); i++) {
+            input[i] = g->getGeometryN(i);
+        }
+
+        return new Clusters(finder.cluster(input));
+    }
+
+    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);
+        });
+    }
+
+    Clusters*
+    GEOSClusterGeometryIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g)
+    {
+        return execute(extHandle, [&]() {
+            geos::operation::cluster::GeometryIntersectsClusterFinder finder;
+            return capi_clusters(g, finder);
+        });
+    }
+
+    Clusters*
+    GEOSClusterEnvelopeIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g)
+    {
+        return execute(extHandle, [&]() {
+            geos::operation::cluster::EnvelopeIntersectsClusterFinder finder;
+            return capi_clusters(g, finder);
+        });
+    }
+
+    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);
+        });
+    }
+
+    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);
+        });
+    }
+
+    std::size_t GEOSClusterInfo_getNumClusters_r(GEOSContextHandle_t extHandle, const Clusters* clusters)
+    {
+        return execute(extHandle, 0, [&]() -> std::size_t {
+            return static_cast<unsigned>(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<unsigned>(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
new file mode 100644
index 000000000..f96d91ce5
--- /dev/null
+++ b/tests/unit/capi/GEOSClusterTest.cpp
@@ -0,0 +1,148 @@
+//
+// Test Suite for C-API GEOSCluster*
+
+#include <tut/tut.hpp>
+// geos
+#include <geos_c.h>
+
+#include "capi_test_utils.h"
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used in test cases.
+struct test_capicluster_data : public capitest::utility {
+    static GEOSGeometry* construct_clusters(const GEOSGeometry* input,
+                                            const GEOSClusterInfo* clusters)
+    {
+        auto ngeoms = static_cast<std::size_t>(GEOSGetNumGeometries(input));
+        auto numClusters = GEOSClusterInfo_getNumClusters(clusters);
+
+        std::vector<GEOSGeometry*> cluster_components(ngeoms);
+        std::vector<GEOSGeometry*> cluster_geoms(numClusters);
+
+        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<int>(indices[i])));
+            }
+            cluster_geoms[cluster_id] = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION,
+                                                                  cluster_components.data(),
+                                                                  static_cast<unsigned>(sz));
+        }
+
+        // combine the clusters into a single nested GeometryCollection
+        return GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION,
+                                         cluster_geoms.data(),
+                                         static_cast<unsigned>(cluster_geoms.size()));
+    }
+};
+
+typedef test_group<test_capicluster_data> group;
+typedef group::object object;
+
+group test_capicluster_group("capi::GEOSCluster");
+
+//
+// Test Cases
+//
+template<>
+template<> void object::test<1>
+()
+{
+    input_ = fromWKT(
+                 "GEOMETRYCOLLECTION ("
+                 "POINT (0 1),"
+                 "LINESTRING (0 0, 0 0.1),"
+                 "LINESTRING (0 0, 1.0 1.0),"
+                 "POINT (0.9 1.0),"
+                 "POINT (0 7))");
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterEnvelopeIntersects(input_);
+        ensure_equals("two clusters by envelope intersection", GEOSClusterInfo_getNumClusters(clusters), 2u);
+
+        GEOSGeometry* geom_result = construct_clusters(input_, clusters);
+        GEOSGeometry* geom_expected = fromWKT("GEOMETRYCOLLECTION ("
+                                              "  GEOMETRYCOLLECTION ("
+                                              "    POINT (0 1),"
+                                              "    LINESTRING (0 0, 0 0.1),"
+                                              "    LINESTRING (0 0, 1.0 1.0),"
+                                              "    POINT (0.9 1.0)),"
+                                              "  GEOMETRYCOLLECTION ("
+                                              "    POINT (0 7)))");
+
+        ensure_geometry_equals_identical(geom_expected, geom_result);
+        GEOSGeom_destroy(geom_result);
+        GEOSGeom_destroy(geom_expected);
+
+        GEOSClusterInfo_destroy(clusters);
+    }
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterEnvelopeDistance(input_, 6);
+        ensure_equals("one cluster by envelope distance", GEOSClusterInfo_getNumClusters(clusters), 1u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterGeometryIntersects(input_);
+        ensure_equals("four clusters by geometry intersection", GEOSClusterInfo_getNumClusters(clusters), 4u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterGeometryDistance(input_, 0.2);
+        ensure_equals("three clusters by distance", GEOSClusterInfo_getNumClusters(clusters), 3u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    input_ = fromWKT(
+                 "GEOMETRYCOLLECTION ("
+                 "POINT (0 0),"
+                 "POINT (-1 0),"
+                 "POINT (-1 -0.1),"
+                 "POINT (-1 0.1),"
+                 "POINT (1 0),"
+                 "POINT (2 0),"
+                 "POINT (3  0),"
+                 "POINT ( 3 -0.1),"
+                 "POINT ( 3 0.1)"
+                 ")");
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 1.01, 5);
+        ensure_equals("two clusters with minPoints = 5", GEOSClusterInfo_getNumClusters(clusters), 2u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 1.01, 2);
+        ensure_equals("one cluster with minPoints = 2", GEOSClusterInfo_getNumClusters(clusters), 1u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+
+    {
+        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);
+    }
+}
+
+
+} // namespace tut
+

-----------------------------------------------------------------------

Summary of changes:
 capi/geos_c.cpp                     |  57 ++++++++++++
 capi/geos_c.h.in                    | 171 +++++++++++++++++++++++++++++++++++-
 capi/geos_ts_c.cpp                  |  99 +++++++++++++++++++++
 tests/unit/capi/GEOSClusterTest.cpp | 148 +++++++++++++++++++++++++++++++
 4 files changed, 473 insertions(+), 2 deletions(-)
 create mode 100644 tests/unit/capi/GEOSClusterTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list