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

git at osgeo.org git at osgeo.org
Wed Mar 18 05:35:02 PDT 2026


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  f1c154d67d6f0a71e76b7ced2f014d74b4f64b19 (commit)
       via  14f2d5c249ca70e9be8a8ae4d8198bcc6302e139 (commit)
       via  b2f92977431b1e20e3e325f6607940198675cccc (commit)
       via  fb2a200dede22869f47923ec37beeb34dc5b2803 (commit)
       via  0e20c737cdeb4ac240477a786087548b31a01277 (commit)
       via  59ff30ae5c7ffee252949b7cf16662f95cbef52d (commit)
       via  b040a784c29565807d660067b45df300216137b1 (commit)
      from  62b3524601c63abb662ee7c8cae428c24fe92af0 (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 f1c154d67d6f0a71e76b7ced2f014d74b4f64b19
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 18 08:34:37 2026 -0400

    GeometryPrecisionReducer: Preserve M in pointwise mode (#1401)

diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index e21dcbdb1..630f1763f 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -5621,6 +5621,8 @@ extern GEOSGeometry GEOS_DLL *GEOSSnap(
 * So the combination GEOS_PREC_NO_TOPO || GEOS_PREC_KEEP_COLLAPSED
 * has the same semantics as GEOS_PREC_NO_TOPO
 *
+* Z and M values in the input are preserved.
+*
 * \param g Input geometry
 * \param gridSize cell size of grid to round coordinates to,
 *        or 0 for FLOATING precision
diff --git a/include/geos/precision/GeometryPrecisionReducer.h b/include/geos/precision/GeometryPrecisionReducer.h
index de8afb120..c885d14a3 100644
--- a/include/geos/precision/GeometryPrecisionReducer.h
+++ b/include/geos/precision/GeometryPrecisionReducer.h
@@ -68,7 +68,7 @@ private:
     bool useAreaReducer;
     bool isPointwise;
 
-    geom::GeometryFactory::Ptr createFactory(
+    static geom::GeometryFactory::Ptr createFactory(
         const geom::GeometryFactory& oldGF,
         const geom::PrecisionModel& newPM);
 
diff --git a/include/geos/precision/PointwisePrecisionReducerTransformer.h b/include/geos/precision/PointwisePrecisionReducerTransformer.h
index 9ea06717f..cd92beb85 100644
--- a/include/geos/precision/PointwisePrecisionReducerTransformer.h
+++ b/include/geos/precision/PointwisePrecisionReducerTransformer.h
@@ -44,7 +44,7 @@ private:
     const geom::PrecisionModel& targetPM;
 
     std::unique_ptr<geom::CoordinateSequence> reducePointwise(
-        const geom::CoordinateSequence* coordinates);
+        const geom::CoordinateSequence* coordinates) const;
 
 public:
 
diff --git a/include/geos/precision/PrecisionReducerTransformer.h b/include/geos/precision/PrecisionReducerTransformer.h
index 49d094913..2997149fe 100644
--- a/include/geos/precision/PrecisionReducerTransformer.h
+++ b/include/geos/precision/PrecisionReducerTransformer.h
@@ -50,9 +50,9 @@ private:
     const geom::PrecisionModel& targetPM;
     bool isRemoveCollapsed;
 
-    std::unique_ptr<geom::Geometry> reduceArea(const geom::Geometry* geom);
+    std::unique_ptr<geom::Geometry> reduceArea(const geom::Geometry* geom) const;
 
-    void extend(
+    static void extend(
         geom::CoordinateSequence& coords,
         std::size_t minLength);
 
diff --git a/src/precision/PointwisePrecisionReducerTransformer.cpp b/src/precision/PointwisePrecisionReducerTransformer.cpp
index 1b867b4b2..63985fdb1 100644
--- a/src/precision/PointwisePrecisionReducerTransformer.cpp
+++ b/src/precision/PointwisePrecisionReducerTransformer.cpp
@@ -49,7 +49,7 @@ PointwisePrecisionReducerTransformer::transformCoordinates(const CoordinateSeque
     (void)(parent); // ignore unused variable
 
     if (coords->isEmpty()) {
-        return detail::make_unique<CoordinateSequence>(0u, coords->getDimension());
+        return detail::make_unique<CoordinateSequence>(0u, coords->hasZ(), coords->hasM());
     }
 
     return reducePointwise(coords);
@@ -58,13 +58,13 @@ PointwisePrecisionReducerTransformer::transformCoordinates(const CoordinateSeque
 
 /* private */
 std::unique_ptr<CoordinateSequence>
-PointwisePrecisionReducerTransformer::reducePointwise(const CoordinateSequence* coordinates)
+PointwisePrecisionReducerTransformer::reducePointwise(const CoordinateSequence* coordinates) const
 {
-    auto coordReduce = detail::make_unique<CoordinateSequence>();
+    auto coordReduce = detail::make_unique<CoordinateSequence>(0, coordinates->hasZ(), coordinates->hasM());
     coordReduce->reserve(coordinates->size());
 
     // copy coordinates and reduce
-    coordinates->forEach<Coordinate>([&coordReduce, this](Coordinate coord) {
+    coordinates->forEach([&coordReduce, this](auto coord) {
         targetPM.makePrecise(coord);
         coordReduce->add(coord);
     });
diff --git a/src/precision/PrecisionReducerTransformer.cpp b/src/precision/PrecisionReducerTransformer.cpp
index 74339e6c0..79148b2fe 100644
--- a/src/precision/PrecisionReducerTransformer.cpp
+++ b/src/precision/PrecisionReducerTransformer.cpp
@@ -169,7 +169,7 @@ PrecisionReducerTransformer::transformMultiPolygon(
 
 // private
 std::unique_ptr<Geometry>
-PrecisionReducerTransformer::reduceArea(const Geometry* geom)
+PrecisionReducerTransformer::reduceArea(const Geometry* geom) const
 {
     return operation::overlayng::PrecisionReducer::reducePrecision(geom, &targetPM);
 }
diff --git a/tests/unit/precision/GeometryPrecisionReducerTest.cpp b/tests/unit/precision/GeometryPrecisionReducerTest.cpp
index 7c2161ddf..2550a34bf 100644
--- a/tests/unit/precision/GeometryPrecisionReducerTest.cpp
+++ b/tests/unit/precision/GeometryPrecisionReducerTest.cpp
@@ -106,7 +106,7 @@ struct test_gpr_data {
 
         // std::cout << *actual << std::endl;
 
-        ensure_equals_geometry(expected.get(), actual.get());
+        ensure_equals_geometry_xyzm(expected.get(), actual.get());
         if (samePM)
             ensure("Factories are not the same", actual->getFactory() == g->getFactory());
         else
@@ -411,5 +411,34 @@ void object::test<28> ()
 
 
 
+template<>
+template<>
+void object::test<34>()
+{
+    set_test_name("empty LineString ZM pointwise");
+
+    checkReducePointwise("LINESTRING ZM EMPTY", "LINESTRING ZM EMPTY");
+}
+
+template<>
+template<>
+void object::test<35>()
+{
+    set_test_name("LineString ZM pointwise");
+
+    checkReducePointwise("LINESTRING ZM (1.23 4.56 7.89 9.87, 2.34 5.67 0.21 0.24)",
+                  "LINESTRING ZM (1 5 7.89 9.87, 2 6 0.21 0.24)");
+}
+
+template<>
+template<>
+void object::test<36>()
+{
+    set_test_name("LineString ZM");
+
+    checkReduce("LINESTRING ZM (1.23 4.56 7.89 9.87, 2.34 5.67 0.21 0.24)",
+               "LINESTRING ZM (1 5 7.89 9.87, 2 6 0.21 0.24)");
+}
+
 } // namespace tut
 

commit 14f2d5c249ca70e9be8a8ae4d8198bcc6302e139
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 11 14:05:32 2026 -0400

    C API: Correct description of GEOSisClosed

diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 29e7ed528..e21dcbdb1 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -3356,9 +3356,13 @@ extern char GEOS_DLL GEOSHasM(const GEOSGeometry* g);
 
 /**
 * Tests whether the input geometry is closed.
-* A closed geometry is a LineString, MultiLineString, CircularString,
-* CompoundCurve, or MultiCurve with the start and end points having 
-* the same XY values. Z and M values are not considered.
+* A closed geometry is either:
+* - a non-empty LineString, CircularString, or CompoundCurve whose
+*   start and end points having the same XY values; or
+* - a non-empty MultiLineString or MultiCurve whose elements are 
+*   all closed.
+*  
+* Z and M values are not considered.
 *
 * \param g The geometry to test
 * \return 1 on true, 0 on false, 2 on exception

commit b2f92977431b1e20e3e325f6607940198675cccc
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 11 12:29:40 2026 -0400

    C API: Add comments on curve support

diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 45828c4ac..29e7ed528 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -3656,6 +3656,11 @@ extern GEOSGeometry GEOS_DLL *GEOSRemoveRepeatedPoints(
 
 /**
 * Calculate the area of a geometry.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
+* Area is calculated in the XY plane; Z and M values are ignored.
+*
 * \param[in] g Input geometry
 * \param[out] area Pointer to be filled in with area result
 * \return 1 on success, 0 on exception.
@@ -3667,6 +3672,9 @@ extern int GEOS_DLL GEOSArea(
 
 /**
 * Calculate the length of a geometry.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * Length is calculated in the XY plane; Z and M values are ignored.
 * \param[in] g Input geometry
 * \param[out] length Pointer to be filled in with length result
@@ -4762,6 +4770,8 @@ GEOSCoverageClean(
 *
 * Z and M values in the input coordinates are ignored.
 *
+* Cuved geometry types are supported as of GEOS 3.13.
+*
 * \param g The geometry to calculate an envelope for
 * \return A newly allocated polygonal envelope or point. NULL on exception.
 * Caller is responsible for freeing with GEOSGeom_destroy().
@@ -5211,6 +5221,8 @@ extern GEOSGeometry GEOS_DLL * GEOSVoronoiDiagram(
 * Z and M values of input coordinates will be preserved.
 * Z and M values at constructed points will be interpolated.
 *
+* Curved geometry types are supported since GEOS 3.15.
+*
 * \param g The input geometry
 * \return The noded geometry or NULL on exception
 * Caller is responsible for freeing with GEOSGeom_destroy().
@@ -5423,6 +5435,9 @@ extern GEOSGeometry GEOS_DLL *GEOSLineSubstring(
 * of the sequences. Converts CCW rings to CW. Reverses direction
 * of LineStrings. Z and M values of the input coordinates are
 * preserved.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param g The input geometry
 * \return The reversed geometry
 * Caller is responsible for freeing with GEOSGeom_destroy().
@@ -5480,6 +5495,8 @@ extern GEOSGeometry GEOS_DLL *GEOSTopologyPreserveSimplify(
 * Z values on the retained coordinates will be preserved.
 * M values will not be preserved.
 *
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param g The input geometry
 * \return The distinct points
 * Caller is responsible for freeing with GEOSGeom_destroy().
@@ -5515,6 +5532,9 @@ extern int GEOS_DLL GEOSHilbertCode(
 * Apply XY coordinate transform callback to all coordinates in a copy of
 * input geometry.  If the callback returns an error, returned geometry will be
 * NULL.  Z and M values, if present, are not modified by this function.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param[in] g Input geometry
 * \param[in] callback a function to be executed for each coordinate in the
                 geometry.  The callback takes 3 parameters: x and y coordinate
@@ -5534,6 +5554,9 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXY(
 * Apply XYZ coordinate transform callback to all coordinates in a copy of
 * input geometry.  If the callback returns an error, returned geometry will be
 * NULL.  M values, if present, are not modified by this function.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param[in] g Input geometry
 * \param[in] callback a function to be executed for each coordinate in the
                 geometry.  The callback takes 4 parameters: x, y and z coordinate
@@ -5736,6 +5759,9 @@ extern char GEOS_DLL GEOSCoveredBy(const GEOSGeometry* g1, const GEOSGeometry* g
 * Unlike GEOSEquals(), geometries that are topologically equivalent but have different
 * representations (e.g., LINESTRING (0 0, 1 1) and MULTILINESTRING ((0 0, 1 1)) ) are not
 * considered equal by GEOSEqualsExact().
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param g1 Input geometry
 * \param g2 Input geometry
 * \param tolerance Tolerance to determine vertex equality
@@ -5749,11 +5775,13 @@ extern char GEOS_DLL GEOSEqualsExact(
     double tolerance);
 
 /**
- * Determine pointwise equality of two geometries by checking
- * that the structure, ordering, and values of all vertices are
- * identical in all dimensions. NaN values are considered to be
- * equal to other NaN values.
- *
+* Determine pointwise equality of two geometries by checking
+* that the structure, ordering, and values of all vertices are
+* identical in all dimensions. NaN values are considered to be
+* equal to other NaN values.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param g1 Input geometry
 * \param g2 Input geometry
 * \returns 1 on true, 0 on false, 2 on exception

commit fb2a200dede22869f47923ec37beeb34dc5b2803
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 11 12:27:26 2026 -0400

    GEOSGeomGetLength: Support CircularString and CompoundCurve

diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index eecf7f513..45828c4ac 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -3403,10 +3403,12 @@ extern void GEOS_DLL GEOSGeom_setUserData(GEOSGeometry* g, void* userData);
 * - Rings start with their smallest coordinate
 *   (using XY ordering)
 * - Polygon shell rings are oriented CW, and holes CCW
-* - Collection elements are sorted by their first coordinate
+* - Collection elements are sorted by their geometry type and first coordinate
 *
 * Coordinate Z and M values are not modified during normalization.
 *
+* Curved geometry types are supported since GEOS 3.15.
+*
 * Use before calling \ref GEOSEqualsExact to avoid false "not equal" results.
 * \param g Input geometry
 * \return 0 on success or -1 on exception
@@ -3676,10 +3678,14 @@ extern int GEOS_DLL GEOSLength(
     double *length);
 
 /**
-* Calculate the length of a LineString.
-* Only works for LineString inputs, returns exception otherwise.
+* Calculate the length of a LineString, CircularString, or CompoundCurve.
+*
+* Returns an exception if a different geometry type is provided.
 * Length is calculated in the XY plane; Z and M values are ignored.
 *
+* CircularString and CompoundCurve arguments are supported since
+* GEOS 3.15.
+*
 * \param[in] g Input geometry
 * \param[out] length Pointer to be filled in with length result
 * \return 1 on success, 0 on exception.
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index aeac05f10..e57a00343 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -2146,16 +2146,16 @@ extern "C" {
     }
 
     /*
-     * Call only on LINESTRING
-     * return 0 on exception, otherwise 1
+     * Call only on LineString, CircularString, or CompoundCurve
+     * Returns NULL on exception
      */
     int
     GEOSGeomGetLength_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* length)
     {
         return execute(extHandle, 0, [&]() {
-            const LineString* ls = dynamic_cast<const LineString*>(g1);
+            const Curve* ls = dynamic_cast<const Curve*>(g1);
             if(!ls) {
-                throw IllegalArgumentException("Argument is not a LineString");
+                throw IllegalArgumentException("Argument is not a Curve (LineString, CircularString, or CompoundCurve)");
             }
             *length = ls->getLength();
             return 1;
diff --git a/tests/unit/capi/GEOSCircularStringTest.cpp b/tests/unit/capi/GEOSCircularStringTest.cpp
index de7e67378..43a5dc30b 100644
--- a/tests/unit/capi/GEOSCircularStringTest.cpp
+++ b/tests/unit/capi/GEOSCircularStringTest.cpp
@@ -7,6 +7,7 @@
 #include <cmath>
 
 #include "capi_test_utils.h"
+#include "geos/constants.h"
 
 namespace tut {
 //
@@ -134,4 +135,15 @@ void object::test<7>()
     ensure_equals(GEOSGeom_getDimensions(cs_), 1);
 }
 
+template<>
+template<>
+void object::test<8>()
+{
+    set_test_name("GEOSGeomGetLength");
+
+    double length = -1;
+    ensure_equals(GEOSGeomGetLength(cs_, &length), 1);
+    ensure_equals(length, geos::MATH_PI*1.5*5);
+}
+
 } // namespace tut
diff --git a/tests/unit/capi/GEOSCompoundCurveTest.cpp b/tests/unit/capi/GEOSCompoundCurveTest.cpp
index 81cf06600..a43aa63f9 100644
--- a/tests/unit/capi/GEOSCompoundCurveTest.cpp
+++ b/tests/unit/capi/GEOSCompoundCurveTest.cpp
@@ -3,6 +3,7 @@
 #include <tut/tut.hpp>
 // geos
 #include <geos_c.h>
+#include <geos/constants.h>
 // std
 #include <cmath>
 
@@ -130,4 +131,15 @@ void object::test<7>()
     ensure_equals(GEOSGeom_getDimensions(cc_), 1);
 }
 
+template<>
+template<>
+void object::test<8>()
+{
+    set_test_name("GEOSGeomGetLength");
+
+    double length = -1;
+    ensure_equals(GEOSGeomGetLength(cc_, &length), 1);
+    ensure_equals(length, geos::MATH_PI*1.5*5 + 5);
+}
+
 } // namespace tut

commit 0e20c737cdeb4ac240477a786087548b31a01277
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 11 12:13:08 2026 -0400

    C API: Document/test coordinate accessors for curved types

diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 47bbd44d2..eecf7f513 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -3060,9 +3060,12 @@ extern double GEOS_DLL GEOSGeom_getPrecision(const GEOSGeometry *g);
 extern int GEOS_DLL GEOSGetNumInteriorRings(const GEOSGeometry* g);
 
 /**
-* Returns the number of points, for a LineString input, or
-* an exception otherwise.
-* \param g Input LineString geometry
+* Return the number of points in a linear geometry.
+*
+* CircularString inputs are supported since GEOS 3.14.
+* CompoundCurve inputs are supported since GEOS 3.15.
+*
+* \param g Input geometry, must be a LineString, CircularString, or CompoundCurve
 * \return Number of points, -1 on exception
 * \since 2.2
 */
@@ -3146,7 +3149,7 @@ extern int GEOS_DLL GEOSGetNumCoordinates(
 
 /**
 * Return the coordinate sequence underlying the
-* given geometry (Must be a LineString, LinearRing or Point).
+* given geometry (Must be a LineString, LinearRing, CircularString, or Point).
 * Do not directly free the coordinate sequence, it is owned by
 * the parent geometry.
 * \param g Input geometry
@@ -3159,9 +3162,9 @@ extern const GEOSCoordSequence GEOS_DLL *GEOSGeom_getCoordSeq(
 /**
 * Return the planar dimensionality of the geometry.
 *
-* - 0 for point, multipoint
-* - 1 for linestring, multilinestring
-* - 2 for polygon, multipolygon
+* - 0 for Point, MultiPoint
+* - 1 for LineString, MultiLineString, CircularString, CompoundCurve, MultiCurve
+* - 2 for Polygon, MultiPolygon, CurvePolygon, MultiSurface
 *
 * \see geos::geom::Dimension::DimensionType
 * \param g Input geometry
@@ -3175,7 +3178,8 @@ extern int GEOS_DLL GEOSGeom_getDimensions(
 * Return the cartesian dimension of the geometry.
 *
 * - 2 for XY data
-* - 3 for XYZ data
+* - 3 for XYZ or XYM data
+* - 4 for XYZM data
 *
 * \param g Input geometry
 * \return The dimension
@@ -3270,8 +3274,12 @@ extern int GEOS_DLL GEOSGeom_getExtent(
     double* ymax);
 
 /**
-* Return the N'th point of a LineString
-* \param g Input geometry, must be a LineString
+* Return the N'th point of a linear geometry.
+*
+* CircularString inputs are supported since GEOS 3.14.
+* CompoundCurve inputs are supported since GEOS 3.15.
+*
+* \param g Input geometry, must be a LineString, CircularString, or CompoundCurve
 * \param n Index of desired point (zero based)
 * \return A Point geometry.
 *         Caller must free with GEOSGeom_destroy()
@@ -3281,8 +3289,12 @@ extern int GEOS_DLL GEOSGeom_getExtent(
 extern GEOSGeometry GEOS_DLL *GEOSGeomGetPointN(const GEOSGeometry *g, int n);
 
 /**
-* Return the first point of a LineString
-* \param g Input geometry, must be a LineString
+* Return the first point of a linear geometry.
+*
+* CircularString inputs are supported since GEOS 3.14.
+* CompoundCurve inputs are supported since GEOS 3.15.
+*
+* \param g Input geometry, must be a LineString, CircularString, or CompoundCurve
 * \return A Point geometry.
 *         Caller must free with GEOSGeom_destroy()
 *         NULL on exception.
@@ -3291,8 +3303,12 @@ extern GEOSGeometry GEOS_DLL *GEOSGeomGetPointN(const GEOSGeometry *g, int n);
 extern GEOSGeometry GEOS_DLL *GEOSGeomGetStartPoint(const GEOSGeometry *g);
 
 /**
-* Return the last point of a LineString
-* \param g Input geometry, must be a LineString
+* Return the last point of a linear geometry.
+*
+* CircularString inputs are supported since GEOS 3.14.
+* CompoundCurve inputs are supported since GEOS 3.15.
+*
+* \param g Input geometry, must be a LineString, CircularString, or CompoundCurve
 * \return A Point geometry.
 *         Caller must free with GEOSGeom_destroy()
 *         NULL on exception.
@@ -3300,7 +3316,6 @@ extern GEOSGeometry GEOS_DLL *GEOSGeomGetStartPoint(const GEOSGeometry *g);
 */
 extern GEOSGeometry GEOS_DLL *GEOSGeomGetEndPoint(const GEOSGeometry *g);
 
-
 /**
 * Tests whether the input geometry is empty. If the geometry or any
 * component is non-empty, the geometry is non-empty. An empty geometry
@@ -3341,9 +3356,10 @@ extern char GEOS_DLL GEOSHasM(const GEOSGeometry* g);
 
 /**
 * Tests whether the input geometry is closed.
-* A closed geometry is a linestring or multilinestring
-* with the start and end points having the same XY values.
-* Z and M values are not considered.
+* A closed geometry is a LineString, MultiLineString, CircularString,
+* CompoundCurve, or MultiCurve with the start and end points having 
+* the same XY values. Z and M values are not considered.
+*
 * \param g The geometry to test
 * \return 1 on true, 0 on false, 2 on exception
 * \since 3.3
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index 78a96a612..aeac05f10 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -2067,16 +2067,16 @@ extern "C" {
     }
 
     /*
-     * Call only on LINESTRING
+     * Call only on LineString, CircularString, or CompoundCurve
      * Returns NULL on exception
      */
     Geometry*
     GEOSGeomGetPointN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n)
     {
         return execute(extHandle, [&]() {
-            const SimpleCurve* ls = dynamic_cast<const SimpleCurve*>(g1);
+            const Curve* ls = dynamic_cast<const Curve*>(g1);
             if(!ls) {
-                throw IllegalArgumentException("Argument is not a SimpleCurve");
+                throw IllegalArgumentException("Argument is not a Curve (LineString, CircularString, or CompoundCurve)");
             }
             if(n < 0) {
                 throw IllegalArgumentException("Index must be non-negative.");
@@ -2086,15 +2086,16 @@ extern "C" {
     }
 
     /*
-     * Call only on LINESTRING
+     * Call only on LineString, CircularString, or CompoundCurve
+     * Returns NULL on exception
      */
     Geometry*
     GEOSGeomGetStartPoint_r(GEOSContextHandle_t extHandle, const Geometry* g1)
     {
         return execute(extHandle, [&]() {
-            const SimpleCurve* ls = dynamic_cast<const SimpleCurve*>(g1);
+            const Curve* ls = dynamic_cast<const Curve*>(g1);
             if(!ls) {
-                throw IllegalArgumentException("Argument is not a SimpleCurve");
+                throw IllegalArgumentException("Argument is not a Curve (LineString, CircularString, or CompoundCurve)");
             }
 
             return ls->getStartPoint().release();
@@ -2102,15 +2103,16 @@ extern "C" {
     }
 
     /*
-     * Call only on LINESTRING
+     * Call only on LineString, CircularString, or CompoundCurve
+     * Returns NULL on exception
      */
     Geometry*
     GEOSGeomGetEndPoint_r(GEOSContextHandle_t extHandle, const Geometry* g1)
     {
         return execute(extHandle, [&]() {
-            const SimpleCurve* ls = dynamic_cast<const SimpleCurve*>(g1);
+            const Curve* ls = dynamic_cast<const Curve*>(g1);
             if(!ls) {
-                throw IllegalArgumentException("Argument is not a SimpleCurve");
+                throw IllegalArgumentException("Argument is not a Curve (LineString, CircularString, or CompoundCurve)");
             }
             return ls->getEndPoint().release();
         });
@@ -2161,15 +2163,16 @@ extern "C" {
     }
 
     /*
-     * Call only on LINESTRING
+     * Call only on LineString, CircularString, or CompoundCurve
+     * Returns NULL on exception
      */
     int
     GEOSGeomGetNumPoints_r(GEOSContextHandle_t extHandle, const Geometry* g1)
     {
         return execute(extHandle, -1, [&]() {
-            const SimpleCurve* ls = dynamic_cast<const SimpleCurve*>(g1);
+            const Curve* ls = dynamic_cast<const Curve*>(g1);
             if(!ls) {
-                throw IllegalArgumentException("Argument is not a SimpleCurve");
+                throw IllegalArgumentException("Argument is not a Curve (LineString, CircularString, or CompoundCurve)");
             }
             return static_cast<int>(ls->getNumPoints());
         });
diff --git a/tests/unit/capi/GEOSCircularStringTest.cpp b/tests/unit/capi/GEOSCircularStringTest.cpp
new file mode 100644
index 000000000..de7e67378
--- /dev/null
+++ b/tests/unit/capi/GEOSCircularStringTest.cpp
@@ -0,0 +1,137 @@
+// Test Suite for C-API CircularString functions
+
+#include <tut/tut.hpp>
+// geos
+#include <geos_c.h>
+// std
+#include <cmath>
+
+#include "capi_test_utils.h"
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used in test cases.
+struct test_capicircularstring_data : public capitest::utility {
+
+    GEOSGeometry* cs_;
+
+    test_capicircularstring_data() {
+        cs_ = fromWKT("CIRCULARSTRING ZM (-5 0 6 7, 4 3 7 8 , 0 -5 8 9)");
+    }
+
+    ~test_capicircularstring_data() override {
+        GEOSGeom_destroy(cs_);
+    }
+};
+
+typedef test_group<test_capicircularstring_data> group;
+typedef group::object object;
+
+group test_capicircularstring_group("capi::GEOSCircularString");
+
+//
+// Test Cases
+//
+
+template<>
+template<>
+void object::test<1>()
+{
+    set_test_name("GEOSisClosed");
+
+    ensure_equals(GEOSisClosed(cs_), 0);
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    set_test_name("GEOSGeomGetPointN");
+
+    double x, y, z, m;
+    result_ = GEOSGeomGetPointN(cs_, 1);
+    GEOSGeomGetX(result_, &x);
+    GEOSGeomGetY(result_, &y);
+    GEOSGeomGetZ(result_, &z);
+    GEOSGeomGetM(result_, &m);
+
+    ensure_equals(x, 4);
+    ensure_equals(y, 3);
+    ensure_equals(z, 7);
+    ensure_equals(m, 8);
+}
+
+template<>
+template<>
+void object::test<3>()
+{
+    set_test_name("GEOSGeomGetStartPoint");
+
+    double x, y, z, m;
+    result_ = GEOSGeomGetStartPoint(cs_);
+    GEOSGeomGetX(result_, &x);
+    GEOSGeomGetY(result_, &y);
+    GEOSGeomGetZ(result_, &z);
+    GEOSGeomGetM(result_, &m);
+
+    ensure_equals(x, -5);
+    ensure_equals(y, 0);
+    ensure_equals(z, 6);
+    ensure_equals(m, 7);
+}
+
+template<>
+template<>
+void object::test<4>()
+{
+    set_test_name("GEOSGeomGetEndPoint");
+
+    double x, y, z, m;
+    result_ = GEOSGeomGetEndPoint(cs_);
+    GEOSGeomGetX(result_, &x);
+    GEOSGeomGetY(result_, &y);
+    GEOSGeomGetZ(result_, &z);
+    GEOSGeomGetM(result_, &m);
+
+    ensure_equals(x, 0);
+    ensure_equals(y, -5);
+    ensure_equals(z, 8);
+    ensure_equals(m, 9);
+}
+
+template<>
+template<>
+void object::test<5>()
+{
+    set_test_name("GEOSGeomGetNumPoints");
+
+    ensure_equals(GEOSGeomGetNumPoints(cs_), 3);
+}
+
+template<>
+template<>
+void object::test<6>()
+{
+    set_test_name("GEOSGeom_getCoordSeq");
+
+    const GEOSCoordSequence* seq = GEOSGeom_getCoordSeq(cs_);
+    ensure(seq);
+
+    unsigned int size = 0;
+    ensure(GEOSCoordSeq_getSize(seq, &size));
+    ensure_equals(size, 3u);
+}
+
+template<>
+template<>
+void object::test<7>()
+{
+    set_test_name("GEOSGeom_getDimensions");
+
+    ensure_equals(GEOSGeom_getDimensions(cs_), 1);
+}
+
+} // namespace tut
diff --git a/tests/unit/capi/GEOSCompoundCurveTest.cpp b/tests/unit/capi/GEOSCompoundCurveTest.cpp
new file mode 100644
index 000000000..81cf06600
--- /dev/null
+++ b/tests/unit/capi/GEOSCompoundCurveTest.cpp
@@ -0,0 +1,133 @@
+// Test Suite for C-API CompoundCurve functions
+
+#include <tut/tut.hpp>
+// geos
+#include <geos_c.h>
+// std
+#include <cmath>
+
+#include "capi_test_utils.h"
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used in test cases.
+struct test_capicompoundcurve_data : public capitest::utility {
+
+    GEOSGeometry* cc_;
+
+    test_capicompoundcurve_data() {
+        cc_ = fromWKT("COMPOUNDCURVE(CIRCULARSTRING ZM (-5 0 6 7, 4 3 7 8 , 0 -5 8 9), (0 -5 8 9, -5 -5 9 10))");
+    }
+
+    ~test_capicompoundcurve_data() override {
+        GEOSGeom_destroy(cc_);
+    }
+};
+
+typedef test_group<test_capicompoundcurve_data> group;
+typedef group::object object;
+
+group test_capicompoundcurve_group("capi::GEOSCompoundCurve");
+
+//
+// Test Cases
+//
+
+template<>
+template<>
+void object::test<1>()
+{
+    set_test_name("GEOSisClosed");
+
+    ensure_equals(GEOSisClosed(cc_), 0);
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    set_test_name("GEOSGeomGetPointN");
+
+    double x, y, z, m;
+    result_ = GEOSGeomGetPointN(cc_, 1);
+    GEOSGeomGetX(result_, &x);
+    GEOSGeomGetY(result_, &y);
+    GEOSGeomGetZ(result_, &z);
+    GEOSGeomGetM(result_, &m);
+
+    ensure_equals(x, 4);
+    ensure_equals(y, 3);
+    ensure_equals(z, 7);
+    ensure_equals(m, 8);
+}
+
+template<>
+template<>
+void object::test<3>()
+{
+    set_test_name("GEOSGeomGetStartPoint");
+
+    double x, y, z, m;
+    result_ = GEOSGeomGetStartPoint(cc_);
+    GEOSGeomGetX(result_, &x);
+    GEOSGeomGetY(result_, &y);
+    GEOSGeomGetZ(result_, &z);
+    GEOSGeomGetM(result_, &m);
+
+    ensure_equals(x, -5);
+    ensure_equals(y, 0);
+    ensure_equals(z, 6);
+    ensure_equals(m, 7);
+}
+
+template<>
+template<>
+void object::test<4>()
+{
+    set_test_name("GEOSGeomGetEndPoint");
+
+    double x, y, z, m;
+    result_ = GEOSGeomGetEndPoint(cc_);
+    GEOSGeomGetX(result_, &x);
+    GEOSGeomGetY(result_, &y);
+    GEOSGeomGetZ(result_, &z);
+    GEOSGeomGetM(result_, &m);
+
+    ensure_equals(x, -5);
+    ensure_equals(y, -5);
+    ensure_equals(z, 9);
+    ensure_equals(m, 10);
+}
+
+template<>
+template<>
+void object::test<5>()
+{
+    set_test_name("GEOSGeomGetNumPoints");
+
+    ensure_equals(GEOSGeomGetNumPoints(cc_), 5);
+}
+
+template<>
+template<>
+void object::test<6>()
+{
+    set_test_name("GEOSGeom_getCoordSeq");
+
+    const GEOSCoordSequence* seq = GEOSGeom_getCoordSeq(cc_);
+    ensure(!seq);
+}
+
+template<>
+template<>
+void object::test<7>()
+{
+    set_test_name("GEOSGeom_getDimensions");
+
+    ensure_equals(GEOSGeom_getDimensions(cc_), 1);
+}
+
+} // namespace tut
diff --git a/tests/unit/capi/capi_test_utils.h b/tests/unit/capi/capi_test_utils.h
index 0b8d03284..7cc711ed7 100644
--- a/tests/unit/capi/capi_test_utils.h
+++ b/tests/unit/capi/capi_test_utils.h
@@ -37,7 +37,7 @@ namespace capitest {
 #endif
         }
 
-        ~utility()
+        virtual ~utility()
         {
             if (wktw_)     GEOSWKTWriter_destroy(wktw_);
             if (geom1_)    GEOSGeom_destroy(geom1_);

commit 59ff30ae5c7ffee252949b7cf16662f95cbef52d
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 11 12:09:28 2026 -0400

    CompoundCurve: Support getPointN

diff --git a/include/geos/geom/CompoundCurve.h b/include/geos/geom/CompoundCurve.h
index ae22670e8..cb9243d9e 100644
--- a/include/geos/geom/CompoundCurve.h
+++ b/include/geos/geom/CompoundCurve.h
@@ -74,6 +74,8 @@ public:
 
     std::size_t getNumPoints() const override;
 
+    std::unique_ptr<Point> getPointN(std::size_t n) const override;
+
     std::unique_ptr<Point> getStartPoint() const override;
 
     bool hasCurvedComponents() const override;
diff --git a/include/geos/geom/Curve.h b/include/geos/geom/Curve.h
index f9968fb9e..807760919 100644
--- a/include/geos/geom/Curve.h
+++ b/include/geos/geom/Curve.h
@@ -55,13 +55,16 @@ public:
     /// \brief
     /// Return the end point of the Curve
     /// or NULL if this is an EMPTY Curve.
-    ///
     virtual std::unique_ptr<Point> getEndPoint() const = 0;
 
+    /// \brief
+    /// Return the n'th point of the Curve
+    /// or NULL if this is an EMPTY Curve.
+    virtual std::unique_ptr<Point> getPointN(std::size_t n) const = 0;
+
     /// \brief
     /// Return the start point of the Curve
     /// or NULL if this is an EMPTY Curve.
-    ///
     virtual std::unique_ptr<Point> getStartPoint() const = 0;
 
     /// Returns true if the first and last coordinate in the Curve are the same
diff --git a/include/geos/geom/SimpleCurve.h b/include/geos/geom/SimpleCurve.h
index 6fe96b40c..da24b0125 100644
--- a/include/geos/geom/SimpleCurve.h
+++ b/include/geos/geom/SimpleCurve.h
@@ -81,7 +81,7 @@ public:
 
     std::size_t getNumPoints() const override;
 
-    virtual std::unique_ptr<Point> getPointN(std::size_t n) const;
+    std::unique_ptr<Point> getPointN(std::size_t n) const override;
 
     /// \brief
     /// Return the start point of the LineString
diff --git a/src/geom/CompoundCurve.cpp b/src/geom/CompoundCurve.cpp
index d2e268f9b..70916aa9c 100644
--- a/src/geom/CompoundCurve.cpp
+++ b/src/geom/CompoundCurve.cpp
@@ -397,6 +397,22 @@ CompoundCurve::normalizeClosed()
     curves = std::move(newCurves);
 }
 
+std::unique_ptr<Point>
+CompoundCurve::getPointN(std::size_t n) const
+{
+    for (const auto& curve : curves) {
+        if (n < curve->getNumPoints()) {
+            return curve->getCoordinatesRO()->applyAt(n, [this](const auto& c) {
+                return getFactory()->createPoint(c);
+            });
+        }
+
+        n -= curve->getNumPoints();
+    }
+
+    return nullptr;
+}
+
 std::unique_ptr<CompoundCurve>
 CompoundCurve::reverse() const
 {
diff --git a/tests/unit/geom/CompoundCurveTest.cpp b/tests/unit/geom/CompoundCurveTest.cpp
index ac33abade..c7def3e63 100644
--- a/tests/unit/geom/CompoundCurveTest.cpp
+++ b/tests/unit/geom/CompoundCurveTest.cpp
@@ -83,6 +83,7 @@ void object::test<1>()
 
     ensure("getStartPoint", cc->getStartPoint() == nullptr);
     ensure("getEndPoint", cc->getEndPoint() == nullptr);
+    ensure("getPointN", cc->getPointN(3) == nullptr);
 }
 
 // Basic Geometry API
@@ -101,7 +102,7 @@ void object::test<2>()
     ensure_equals("getLength", cc_->getLength(), geos::MATH_PI + 2);
     ensure_equals("getNumGeometries", cc_->getNumGeometries(), 1u);
     ensure_equals("getNumCurves", cc_->getNumCurves(), 2u);
-    ensure_equals("getNumPoints", cc_->getNumPoints(), 5u); // FIXME should this be 5 or 4?
+    ensure_equals("getNumPoints", cc_->getNumPoints(), 5u);
     geos::geom::Envelope expected(0, 2, 0, 2);
     ensure("getEnvelopeInternal", cc_->getEnvelopeInternal()->equals(&expected));
 
@@ -130,6 +131,7 @@ void object::test<2>()
 
     ensure_equals_geometry(cc_->getStartPoint().get(), factory_->createPoint(CoordinateXY{0, 0}).get());
     ensure_equals_geometry(cc_->getEndPoint().get(), factory_->createPoint(CoordinateXY{2, 2}).get());
+    ensure_equals_geometry(cc_->getPointN(4).get(), factory_->createPoint(CoordinateXY{2, 2}).get());
 }
 
 // Operations

commit b040a784c29565807d660067b45df300216137b1
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 11 12:01:02 2026 -0400

    GEOSGeom_getExtent: Update docs/tests for curves

diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 12d30c9f4..47bbd44d2 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -3186,7 +3186,12 @@ extern int GEOS_DLL GEOSGeom_getCoordinateDimension(
     const GEOSGeometry* g);
 
 /**
-* Finds the minimum X value in the geometry.
+* Gets the minimum X value of the geometry's envelope.
+* For linear geometry types, this is the same as the minimum X
+* coordinate value.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param[in] g Input geometry
 * \param[out] value Pointer to place result
 * \return 0 on exception
@@ -3196,7 +3201,12 @@ extern int GEOS_DLL GEOSGeom_getCoordinateDimension(
 extern int GEOS_DLL GEOSGeom_getXMin(const GEOSGeometry* g, double* value);
 
 /**
-* Finds the minimum Y value in the geometry.
+* Gets the minimum Y value of the geometry's envelope.
+* For linear geometry types, this is the same as the minimum Y
+* coordinate value.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param[in] g Input geometry
 * \param[out] value Pointer to place result
 * \return 0 on exception
@@ -3206,7 +3216,12 @@ extern int GEOS_DLL GEOSGeom_getXMin(const GEOSGeometry* g, double* value);
 extern int GEOS_DLL GEOSGeom_getYMin(const GEOSGeometry* g, double* value);
 
 /**
-* Finds the maximum X value in the geometry.
+* Get the maximum X value of the geometry's envelope.
+* For linear geometry types, this is the same as the maximum X
+* coordinate value.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param[in] g Input geometry
 * \param[out] value Pointer to place result
 * \return 0 on exception
@@ -3216,7 +3231,12 @@ extern int GEOS_DLL GEOSGeom_getYMin(const GEOSGeometry* g, double* value);
 extern int GEOS_DLL GEOSGeom_getXMax(const GEOSGeometry* g, double* value);
 
 /**
-* Finds the maximum Y value in the geometry.
+* Gets the maximum Y value of the geometry's envelope.
+* For linear geometry types, this is the same as the maximum Y
+* coordinate value.
+*
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param[in] g Input geometry
 * \param[out] value Pointer to place result
 * \return 0 on exception
@@ -3226,9 +3246,13 @@ extern int GEOS_DLL GEOSGeom_getXMax(const GEOSGeometry* g, double* value);
 extern int GEOS_DLL GEOSGeom_getYMax(const GEOSGeometry* g, double* value);
 
 /**
-* Finds the extent (minimum and maximum X and Y value) of the geometry.
+* Finds the extent (minimum and maximum X and Y value) of the
+* geometry's envelope.
+*
 * Raises an exception for empty geometry input.
 *
+* Curved geometry types are supported since GEOS 3.13.
+*
 * \param[in] g Input geometry
 * \param[out] xmin Pointer to place result for minimum X value
 * \param[out] ymin Pointer to place result for minimum Y value
diff --git a/tests/unit/capi/GEOSGeom_extentTest.cpp b/tests/unit/capi/GEOSGeom_extentTest.cpp
index 49a703c5e..a722b5903 100644
--- a/tests/unit/capi/GEOSGeom_extentTest.cpp
+++ b/tests/unit/capi/GEOSGeom_extentTest.cpp
@@ -56,5 +56,37 @@ void object::test<2>()
     ensure_equals(GEOSGeom_getExtent(geom1_, &d, &d, &d, &d), 0);
 }
 
+template<>
+template<>
+void object::test<3>()
+{
+    set_test_name("curved inputs");
+
+    input_ = fromWKT("CIRCULARSTRING (-5 0, 4 3, 0 -5)");
+
+    {
+        double xmin, ymin, xmax, ymax = -1;
+        ensure_equals(GEOSGeom_getXMin(input_, &xmin), 1);
+        ensure_equals(GEOSGeom_getXMax(input_, &xmax), 1);
+        ensure_equals(GEOSGeom_getYMin(input_, &ymin), 1);
+        ensure_equals(GEOSGeom_getYMax(input_, &ymax), 1);
+
+        ensure_equals(xmin, -5);
+        ensure_equals(xmax, 5);
+        ensure_equals(ymin, -5);
+        ensure_equals(ymax, 5);
+    }
+
+    {
+        double xmin, ymin, xmax, ymax = -1;
+
+        ensure_equals(GEOSGeom_getExtent(input_, &xmin, &ymin, &xmax, &ymax), 1);
+        ensure_equals(xmin, -5);
+        ensure_equals(xmax, 5);
+        ensure_equals(ymin, -5);
+        ensure_equals(ymax, 5);
+    }
+}
+
 } // namespace tut
 

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

Summary of changes:
 capi/geos_c.h.in                                   | 140 ++++++++++++++-----
 capi/geos_ts_c.cpp                                 |  35 ++---
 include/geos/geom/CompoundCurve.h                  |   2 +
 include/geos/geom/Curve.h                          |   7 +-
 include/geos/geom/SimpleCurve.h                    |   2 +-
 include/geos/precision/GeometryPrecisionReducer.h  |   2 +-
 .../PointwisePrecisionReducerTransformer.h         |   2 +-
 .../geos/precision/PrecisionReducerTransformer.h   |   4 +-
 src/geom/CompoundCurve.cpp                         |  16 +++
 .../PointwisePrecisionReducerTransformer.cpp       |   8 +-
 src/precision/PrecisionReducerTransformer.cpp      |   2 +-
 tests/unit/capi/GEOSCircularStringTest.cpp         | 149 +++++++++++++++++++++
 tests/unit/capi/GEOSCompoundCurveTest.cpp          | 145 ++++++++++++++++++++
 tests/unit/capi/GEOSGeom_extentTest.cpp            |  32 +++++
 tests/unit/capi/capi_test_utils.h                  |   2 +-
 tests/unit/geom/CompoundCurveTest.cpp              |   4 +-
 .../precision/GeometryPrecisionReducerTest.cpp     |  31 ++++-
 17 files changed, 522 insertions(+), 61 deletions(-)
 create mode 100644 tests/unit/capi/GEOSCircularStringTest.cpp
 create mode 100644 tests/unit/capi/GEOSCompoundCurveTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list