[geos-commits] [SCM] GEOS branch 3.14 updated. 2b48c043c11a15beb8d2e0ca4efe004be171bd42

git at osgeo.org git at osgeo.org
Mon Mar 9 07:43:59 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, 3.14 has been updated
       via  2b48c043c11a15beb8d2e0ca4efe004be171bd42 (commit)
       via  fcb9b9242b1675a549ad90ac4502a1638f25e083 (commit)
       via  4929cd265831da7db2d92d3e62cd39f654f6a594 (commit)
       via  c94148de2df6a038e2482fa695a21b41c565e661 (commit)
       via  bb6b293bc0e29a54979b0d59d41de08a9619ca53 (commit)
       via  9cce057baae8dd044981cdd696eb541eacfafc4a (commit)
       via  d93a5397e614d4298342f3851af11c11dbd1780f (commit)
      from  25e08d264b9a1eb2267e76207da7995cf8c8254c (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 2b48c043c11a15beb8d2e0ca4efe004be171bd42
Author: Daniel Baston <dbaston at gmail.com>
Date:   Mon Mar 9 09:41:03 2026 -0400

    NEWS updates

diff --git a/NEWS.md b/NEWS.md
index c77fa6a50..08b2802a4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,11 +1,13 @@
-
 ## Changes in 3.14.2
-2025-xx-xx
+2026-xx-xx
 
 - Fixes/Improvements:
   - Relax other floating-point exception handling with other compilers (GH-1333, Mike Taves)
   - Quiet FP_DIVBYZERO exception from CGAlgorithmsDD::intersection (GH-1235, Paul Ramsey)
   - Avoid crash on buffer of geometry with only invalid coordinates (GH-1335, Dan Baston)
+  - Fix some cases of dropped M values in overlay (GH-1364/GH-1388, Dan Baston)
+  - GEOSClusterDBSCAN fix unassigned clusters with minPoints <= 1 (GH-1386, Dan Baston)
+  - Fix crash in GEOSConvexHull (GH-1358, Dan Baston)
 
 ## Changes in 3.14.1
 2025-10-27

commit fcb9b9242b1675a549ad90ac4502a1638f25e083
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat Mar 7 21:01:55 2026 -0500

    SegmentNodeList: Preserve Z/M in mixed-dimension overlay (#1388)

diff --git a/src/noding/SegmentNodeList.cpp b/src/noding/SegmentNodeList.cpp
index 962f4a7c3..46caa5358 100644
--- a/src/noding/SegmentNodeList.cpp
+++ b/src/noding/SegmentNodeList.cpp
@@ -280,6 +280,14 @@ SegmentNodeList::createSplitEdgePts(const SegmentNode* ei0, const SegmentNode* e
     pts->add(*edgeCoords, ei0->segmentIndex + 1, ei1->segmentIndex);
     if (useIntPt1) {
         pts->add(ei1->coord);
+    } else if (ei1->coord.equals2D(lastSegStartPt)) {
+        // Can we pick up Z/M values from ei1 that weren't present in edgeCoords?
+        if (constructZ && !edgeCoords->hasZ() && !std::isnan(ei1->coord.z)) {
+            pts->setZ(pts->size() - 1, ei1->coord.z);
+        }
+        if (constructM && !edgeCoords->hasM() && !std::isnan(ei1->coord.m)) {
+            pts->setM(pts->size() - 1, ei1->coord.m);
+        }
     }
 
     assert(pts->size() == npts);
diff --git a/tests/unit/capi/GEOSSharedPathsTest.cpp b/tests/unit/capi/GEOSSharedPathsTest.cpp
index e1708d938..4316500ed 100644
--- a/tests/unit/capi/GEOSSharedPathsTest.cpp
+++ b/tests/unit/capi/GEOSSharedPathsTest.cpp
@@ -99,5 +99,40 @@ void object::test<4>()
     ensure("curved geometry not supported", result_ == nullptr);
 }
 
+template<>
+template<>
+void object::test<5>() {
+    set_test_name("Z/M values taken from first input");
+
+    geom1_ = fromWKT("LINESTRING ZM (0 1 5 4, 0 0 6 5, 1 0 7 6, 1 1 8 7)");
+    geom2_ = fromWKT("LINESTRING ZM (0 -1 3 8, 0 0 2 9, 1 0 1 10, 1 -1 0 11)");
+
+    ensure(geom1_);
+    ensure(geom2_);
+
+    result_ = GEOSSharedPaths(geom1_, geom2_);
+
+    expected_ = fromWKT("GEOMETRYCOLLECTION(MULTILINESTRING ZM ((0 0 6 5, 1 0 7 6)), MULTILINESTRING EMPTY)");
+    ensure_geometry_equals_identical(result_, expected_);
+}
+
+template<>
+template<>
+void object::test<6>() {
+    set_test_name("Z/M values taken from second input");
+
+    geom1_ = fromWKT("LINESTRING (0 1, 0 0, 1 0, 1 1)");
+    geom2_ = fromWKT("LINESTRING ZM (0 -1 3 8, 0 0 2 9, 1 0 1 10, 1 -1 0 11)");
+
+    ensure(geom1_);
+    ensure(geom2_);
+
+    result_ = GEOSSharedPaths(geom1_, geom2_);
+
+    expected_ = fromWKT("GEOMETRYCOLLECTION(MULTILINESTRING ZM ((0 0 2 9, 1 0 1 10)), MULTILINESTRING EMPTY)");
+    ensure_geometry_equals_identical(result_, expected_);
+}
+
+
 } // namespace tut
 
diff --git a/tests/unit/operation/overlayng/OverlayNGZTest.cpp b/tests/unit/operation/overlayng/OverlayNGZTest.cpp
index 9769a7be0..e9ee46377 100644
--- a/tests/unit/operation/overlayng/OverlayNGZTest.cpp
+++ b/tests/unit/operation/overlayng/OverlayNGZTest.cpp
@@ -294,4 +294,26 @@ void object::test<19>()
                       "POINT M (5 5 99)");
 }
 
+template<>
+template<>
+void object::test<20>()
+{
+    set_test_name("LINESTRING / LINESTRING ZM intersection");
+
+    checkIntersection("LINESTRING (0 0, 1 0)",
+        "LINESTRING ZM (0 0 2 9, 1 0 5 7)",
+        "LINESTRING ZM (0 0 2 9, 1 0 5 7)");
+}
+
+template<>
+template<>
+void object::test<21>()
+{
+    set_test_name("LINESTRING M / LINESTRING Z intersection");
+
+    checkIntersection("LINESTRING M (0 0 9, 1 0 7)",
+        "LINESTRING Z (1 0 5, 0 0 2)",
+        "LINESTRING ZM (0 0 2 9, 1 0 5 7)");
+}
+
 } // namespace tut

commit 4929cd265831da7db2d92d3e62cd39f654f6a594
Author: Daniel Baston <dbaston at gmail.com>
Date:   Mon Jan 12 10:20:23 2026 -0500

    CoordinateSequence: Add Z/M accessors

diff --git a/include/geos/geom/CoordinateSequence.h b/include/geos/geom/CoordinateSequence.h
index 2d952f137..0fb3b604a 100644
--- a/include/geos/geom/CoordinateSequence.h
+++ b/include/geos/geom/CoordinateSequence.h
@@ -337,6 +337,80 @@ public:
         return m_vect[index * stride() + 1];
     }
 
+    /**
+     * Returns ordinate Z of the specified coordinate.
+     *
+     * @param index
+     * @return the value of the Z ordinate in the index'th coordinate, or NaN if the
+     *         CoordinateSequence does not store Z values
+     */
+    double getZ(std::size_t index) const
+    {
+        return getOrdinate(index, Z);
+    }
+
+    /**
+     * Returns ordinate M of the specified coordinate.
+     *
+     * @param index
+     * @return the value of the M ordinate in the index'th coordinate, or NaN if the
+     *         CoordinateSequence does not store M values
+     */
+    double getM(std::size_t index) const
+    {
+        return getOrdinate(index, M);
+    }
+
+    /**
+     * Set the X value of the specified coordinate.
+     *
+     * @param index
+     * @param x the new X value
+     */
+    void setX(std::size_t index, double x)
+    {
+        m_vect[index * stride()] = x;
+    }
+
+    /**
+     * Set the Y value of the specified coordinate.
+     *
+     * @param index
+     * @param y the new Y value
+     */
+    void setY(std::size_t index, double y)
+    {
+        m_vect[index * stride() + 1] = y;
+    }
+
+    /**
+     * Set the Z value of the specified coordinate.
+     *
+     * Has no effect if the CoordinateSequence does not store Z values.
+     *
+     * @param index
+     * @param z the new Z value
+     */
+    void setZ(std::size_t index, double z)
+    {
+        if (hasZ())
+            setOrdinate(index, Z, z);
+    }
+
+    /**
+     * Set the M value of the specified coordinate.
+     *
+     * Has no effect if the CoordinateSequence does not store M values.
+     *
+     * @param index
+     * @param m the new M value
+     */
+    void setM(std::size_t index, double m)
+    {
+        if (hasM())
+            setOrdinate(index, M, m);
+    }
+
     /// Return last Coordinate in the sequence
     template<typename T=Coordinate>
     const T& back() const
diff --git a/tests/unit/geom/CoordinateSequenceTest.cpp b/tests/unit/geom/CoordinateSequenceTest.cpp
index 4786c20c8..b0d28fc6d 100644
--- a/tests/unit/geom/CoordinateSequenceTest.cpp
+++ b/tests/unit/geom/CoordinateSequenceTest.cpp
@@ -1536,4 +1536,41 @@ void object::test<56>
     ensure_equals_xyz(seq1.getAt(3), Coordinate{10, 11, DoubleNotANumber});
 }
 
+template<>
+template<>
+void object::test<60>()
+{
+    set_test_name("Z/M accessors on XY sequence");
+
+    CoordinateSequence seq(0, false, false);
+    seq.add(CoordinateXY{0, 0});
+    seq.add(CoordinateXY{3, 0});
+
+    seq.setZ(0, 500);
+    seq.setM(0, 501);
+
+    ensure(std::isnan(seq.getZ(0)));
+    ensure(std::isnan(seq.getM(0)));
+}
+
+template<>
+template<>
+void object::test<61>()
+{
+    set_test_name("Z/M accessors on XYZM sequence");
+
+    CoordinateSequence seq(0, true, true);
+    seq.add(CoordinateXY{0, 0});
+    seq.add(CoordinateXY{3, 0});
+
+    seq.setZ(0, 500);
+    seq.setM(0, 501);
+
+    ensure_equals(seq.getZ(0), 500);
+    ensure_equals(seq.getM(0), 501);
+
+    ensure(std::isnan(seq.getZ(1)));
+    ensure(std::isnan(seq.getM(1)));
+}
+
 } // namespace tut

commit c94148de2df6a038e2482fa695a21b41c565e661
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat Mar 7 20:10:21 2026 -0500

    DBSCAN: Fix unassigned clusters with minPoints <= 1 (#1386)

diff --git a/src/operation/cluster/DBSCANClusterFinder.cpp b/src/operation/cluster/DBSCANClusterFinder.cpp
index 1a87eb887..15da6c012 100644
--- a/src/operation/cluster/DBSCANClusterFinder.cpp
+++ b/src/operation/cluster/DBSCANClusterFinder.cpp
@@ -44,8 +44,10 @@ Clusters DBSCANClusterFinder::process(const std::vector<const geom::Geometry*> &
                       index::strtree::TemplateSTRtree<std::size_t> & tree,
                       UnionFind & uf) {
 
-    std::vector<bool> in_a_cluster(components.size());
-    std::vector<bool> is_in_core(components.size());
+    const bool allInputsInCluster = m_minPoints <= 1;
+
+    std::vector<bool> in_a_cluster(components.size(), allInputsInCluster);
+    std::vector<bool> is_in_core(components.size(), allInputsInCluster);
 
     std::vector<size_t> hits;
     std::vector<size_t> neighbors;
@@ -59,7 +61,7 @@ Clusters DBSCANClusterFinder::process(const std::vector<const geom::Geometry*> &
         });
 
         if (hits.size() < m_minPoints) {
-            // We didn't find enough points do do anything even if they're all within eps.
+            // We didn't find enough points to do anything even if they're all within eps.
             continue;
         }
 
diff --git a/tests/unit/capi/GEOSClusterTest.cpp b/tests/unit/capi/GEOSClusterTest.cpp
index f96d91ce5..c8136720f 100644
--- a/tests/unit/capi/GEOSClusterTest.cpp
+++ b/tests/unit/capi/GEOSClusterTest.cpp
@@ -105,6 +105,8 @@ template<>
 template<>
 void object::test<2>()
 {
+    set_test_name("DBSCAN");
+
     input_ = fromWKT(
                  "GEOMETRYCOLLECTION ("
                  "POINT (0 0),"
@@ -118,6 +120,24 @@ void object::test<2>()
                  "POINT ( 3 0.1)"
                  ")");
 
+    {
+        GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 0.01, 0);
+        ensure_equals("nine clusters with eps=0.01, minPoints = 0", GEOSClusterInfo_getNumClusters(clusters), 9u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 0.01, 1);
+        ensure_equals("nine clusters with eps=0.01, minPoints = 1", GEOSClusterInfo_getNumClusters(clusters), 9u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+
+    {
+        GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 0.1, 1);
+        ensure_equals("five clusters with eps=0.1, minPoints = 1", GEOSClusterInfo_getNumClusters(clusters), 5u);
+        GEOSClusterInfo_destroy(clusters);
+    }
+
     {
         GEOSClusterInfo* clusters = GEOSClusterDBSCAN(input_, 1.01, 5);
         ensure_equals("two clusters with minPoints = 5", GEOSClusterInfo_getNumClusters(clusters), 2u);

commit bb6b293bc0e29a54979b0d59d41de08a9619ca53
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue Jan 13 16:50:29 2026 -0500

    LineDissolver: Create geometries with dimensions matching input (#1373)

diff --git a/include/geos/dissolve/LineDissolver.h b/include/geos/dissolve/LineDissolver.h
index cfae61f80..42ff86c3b 100644
--- a/include/geos/dissolve/LineDissolver.h
+++ b/include/geos/dissolve/LineDissolver.h
@@ -83,6 +83,8 @@ private:
     std::vector<std::unique_ptr<LineString>> lines;
     std::stack<HalfEdge*> nodeEdgeStack;
     DissolveHalfEdge* ringStartEdge = nullptr;
+    bool constructZ = false;
+    bool constructM = false;
 
 
     void computeResult();
diff --git a/src/dissolve/LineDissolver.cpp b/src/dissolve/LineDissolver.cpp
index 2c036cc0d..66a4322b8 100644
--- a/src/dissolve/LineDissolver.cpp
+++ b/src/dissolve/LineDissolver.cpp
@@ -87,6 +87,9 @@ LineDissolver::add(const LineString* lineString)
         factory = lineString->getFactory();
     }
     const CoordinateSequence* seq = lineString->getCoordinatesRO();
+    constructZ |= seq->hasZ();
+    constructM |= seq->hasM();
+
     bool doneStart = false;
     for (std::size_t i = 1; i < seq->size(); i++) {
         CoordinateXYZM orig, dest;
@@ -235,7 +238,7 @@ LineDissolver::buildLine(HalfEdge* eStart)
 void
 LineDissolver::buildRing(HalfEdge* eStartRing)
 {
-    std::unique_ptr<CoordinateSequence> line(new CoordinateSequence(0, 4));
+    std::unique_ptr<CoordinateSequence> line(new CoordinateSequence(0, constructZ, constructM));
     HalfEdge* e = eStartRing;
 
     // add first node
diff --git a/tests/unit/coverage/CoverageCleanerTest.cpp b/tests/unit/coverage/CoverageCleanerTest.cpp
index f36f1f887..de0b634db 100644
--- a/tests/unit/coverage/CoverageCleanerTest.cpp
+++ b/tests/unit/coverage/CoverageCleanerTest.cpp
@@ -77,6 +77,8 @@ struct test_coveragecleaner_data {
     {
         ensure_equals("checkEqual sizes", actual.size(), expected.size());
         for (std::size_t i = 0; i < actual.size(); i++) {
+            ensure_equals("hasZ does not match", actual[i]->hasZ(), expected[i]->hasZ());
+            ensure_equals("hasM does not match", actual[i]->hasM(), expected[i]->hasM());
             ensure_equals_geometry(actual[i], expected[i]);
         }
     }

commit 9cce057baae8dd044981cdd696eb541eacfafc4a
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue Jan 13 16:56:28 2026 -0500

    OverlayEdgeRing: Preserve M values when closing ring (#1372)
    
    Resolves https://github.com/libgeos/geos/issues/1365

diff --git a/src/operation/overlayng/OverlayEdgeRing.cpp b/src/operation/overlayng/OverlayEdgeRing.cpp
index f45d46418..683f340d1 100644
--- a/src/operation/overlayng/OverlayEdgeRing.cpp
+++ b/src/operation/overlayng/OverlayEdgeRing.cpp
@@ -122,7 +122,7 @@ void
 OverlayEdgeRing::closeRing(CoordinateSequence& pts)
 {
     if(pts.size() > 0) {
-        pts.add(pts.getAt(0), false);
+        pts.closeRing(false);
     }
 }
 
diff --git a/tests/unit/capi/GEOSGeom_setPrecisionTest.cpp b/tests/unit/capi/GEOSGeom_setPrecisionTest.cpp
index 4640b19e7..4c604a183 100644
--- a/tests/unit/capi/GEOSGeom_setPrecisionTest.cpp
+++ b/tests/unit/capi/GEOSGeom_setPrecisionTest.cpp
@@ -326,5 +326,20 @@ void object::test<23>()
     checkPrecision(wkt,  100000, "LINESTRING ( 700000 200000,  700000 1400000)");
 }
 
+template<>
+template<>
+void object::test<24>()
+{
+    // https://github.com/libgeos/geos/issues/1365{
+    set_test_name("M value retained on last point");
+
+    input_ = fromWKT("POLYGON ZM ((0 0 0 0, 0 1 1 1, 1 1 2 3, 1 0 4 5, 0 0 6 7))");
+    expected_ = fromWKT("POLYGON ZM ((0 1 1 1, 1 1 2 3, 1 0 4 5, 0 0 6 7, 0 1 1 1))");
+
+    result_ = GEOSGeom_setPrecision(input_, 0.1, 0);
+
+    ensure(GEOSEqualsIdentical(result_, expected_));
+}
+
 } // namespace tut
 

commit d93a5397e614d4298342f3851af11c11dbd1780f
Author: Daniel Baston <dbaston at gmail.com>
Date:   Mon Mar 9 09:26:05 2026 -0400

    ConvexHull: improve robustness of polarCompare

diff --git a/src/algorithm/ConvexHull.cpp b/src/algorithm/ConvexHull.cpp
index 7d8e34374..1ed617438 100644
--- a/src/algorithm/ConvexHull.cpp
+++ b/src/algorithm/ConvexHull.cpp
@@ -63,13 +63,21 @@ private:
     polarCompare(const Coordinate* o, const Coordinate* p,
                  const Coordinate* q)
     {
-        int orient = Orientation::index(*o, *p, *q);
+        // To use this comparator in std::sort it must provide a stable ordering, such that
+        // if cmp(a, b) is true then cmp(b, a) is false. Unfortunately Orientation::index may
+        // not provide this guarantee when the inputs differ by many orders of magnitude. To
+        // guard against this, we normalize the order of P and Q before calling OrientationIndex
+        // and flip the result if the inputs were flipped.
+        const bool swap = geom::CoordinateLessThan()(p, q);
+
+        const int orient = swap ? Orientation::index(*o, *q, *p) : Orientation::index(*o, *p, *q);
 
         if(orient == Orientation::COUNTERCLOCKWISE) {
-            return 1;
+            return swap ? -1 : 1;
         }
+
         if(orient == Orientation::CLOCKWISE) {
-            return -1;
+            return swap ? 1 : -1;
         }
 
         /**
@@ -102,7 +110,7 @@ public:
     RadiallyLessThen(const Coordinate* c): origin(c) {}
 
     bool
-    operator()(const Coordinate* p1, const Coordinate* p2)
+    operator()(const Coordinate* p1, const Coordinate* p2) const
     {
         return (polarCompare(origin, p1, p2) == -1);
     }
diff --git a/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp b/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp
index f535fce86..3634752e9 100644
--- a/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp
+++ b/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp
@@ -129,5 +129,20 @@ void object::test<5>
     ensure("curved geometry not supported", result_ == nullptr);
 }
 
+template<>
+template<>
+void object::test<6>
+()
+{
+    set_test_name("https://github.com/libgeos/geos/issues/1072");
+
+    geom1_ =  fromWKT("LINESTRING(7777777777777777770 7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777770 1, 1 7 1,-2 1 2)");
+    geom2_ =  GEOSSingleSidedBuffer(geom1_, 1.0, 64, 1, 1.0, 1);
+    result_ =  GEOSMinimumBoundingCircle(geom2_, NULL, NULL);
+
+    // no segfault
+}
+
+
 } // namespace tut
 

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

Summary of changes:
 NEWS.md                                           |  6 +-
 include/geos/dissolve/LineDissolver.h             |  2 +
 include/geos/geom/CoordinateSequence.h            | 74 +++++++++++++++++++++++
 src/algorithm/ConvexHull.cpp                      | 16 +++--
 src/dissolve/LineDissolver.cpp                    |  5 +-
 src/noding/SegmentNodeList.cpp                    |  8 +++
 src/operation/cluster/DBSCANClusterFinder.cpp     |  8 ++-
 src/operation/overlayng/OverlayEdgeRing.cpp       |  2 +-
 tests/unit/capi/GEOSClusterTest.cpp               | 20 ++++++
 tests/unit/capi/GEOSGeom_setPrecisionTest.cpp     | 15 +++++
 tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp | 15 +++++
 tests/unit/capi/GEOSSharedPathsTest.cpp           | 35 +++++++++++
 tests/unit/coverage/CoverageCleanerTest.cpp       |  2 +
 tests/unit/geom/CoordinateSequenceTest.cpp        | 37 ++++++++++++
 tests/unit/operation/overlayng/OverlayNGZTest.cpp | 22 +++++++
 15 files changed, 256 insertions(+), 11 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list