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

git at osgeo.org git at osgeo.org
Thu Apr 2 05:36:44 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  f92148794f989e64940eba0074c4cec9083b54c7 (commit)
       via  f9657108f95a58051ddd38d2f64e9a2af9d39bce (commit)
      from  4a24df60ce8b4b36ded1b06d4507a1578cb4da77 (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 f92148794f989e64940eba0074c4cec9083b54c7
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue Mar 31 09:24:47 2026 -0400

    ArcIntersectionAdder: Avoid processing some trivial intersections

diff --git a/include/geos/noding/ArcIntersectionAdder.h b/include/geos/noding/ArcIntersectionAdder.h
index e71822df4..0d606a60f 100644
--- a/include/geos/noding/ArcIntersectionAdder.h
+++ b/include/geos/noding/ArcIntersectionAdder.h
@@ -33,6 +33,11 @@ public:
     void processIntersections(SegmentString& e0, std::size_t segIndex0, SegmentString& e1, std::size_t segIndex1) override;
 
 private:
+    static bool isAdjacentSegments(std::size_t i1, std::size_t i2);
+
+    bool isTrivialIntersection(const PathString* e0, std::size_t segIndex0,
+                               const PathString* e1, std::size_t segIndex1) const;
+
     algorithm::CircularArcIntersector& m_intersector;
 };
 
diff --git a/src/noding/ArcIntersectionAdder.cpp b/src/noding/ArcIntersectionAdder.cpp
index 61b9adae1..0232e2ae3 100644
--- a/src/noding/ArcIntersectionAdder.cpp
+++ b/src/noding/ArcIntersectionAdder.cpp
@@ -35,6 +35,10 @@ ArcIntersectionAdder::processIntersections(ArcString& e0, std::size_t segIndex0,
         return;
     }
 
+    if (isTrivialIntersection(&e0, segIndex0, &e1, segIndex1)) {
+        return;
+    }
+
     for (std::uint8_t i = 0; i < m_intersector.getNumPoints(); i++) {
         detail::down_cast<NodableArcString*>(&e0)->addIntersection(m_intersector.getPoint(i), segIndex0);
         detail::down_cast<NodableArcString*>(&e1)->addIntersection(m_intersector.getPoint(i), segIndex1);
@@ -73,6 +77,33 @@ ArcIntersectionAdder::processIntersections(ArcString& e0, std::size_t segIndex0,
 
 }
 
+bool
+ArcIntersectionAdder::isAdjacentSegments(std::size_t i1, std::size_t i2)
+{
+    return (i1 > i2 ? i1 - i2 : i2 - i1) == 1;
+}
+
+bool
+ArcIntersectionAdder::isTrivialIntersection(const PathString *e0, std::size_t segIndex0,
+                                            const PathString *e1, std::size_t segIndex1) const
+{
+    if (e0 != e1) {
+        return false;
+    }
+
+    // TODO: If two ArcStrings form a circle they may have a trivial intersection with
+    // two points.
+    if (m_intersector.getNumPoints() != 1) {
+        return false;
+    }
+
+    if (isAdjacentSegments(segIndex0, segIndex1)) {
+        return true;
+    }
+
+    return false;
+}
+
 void
 ArcIntersectionAdder::processIntersections(SegmentString& e0, std::size_t segIndex0, SegmentString& e1, std::size_t segIndex1)
 {
@@ -90,11 +121,13 @@ ArcIntersectionAdder::processIntersections(SegmentString& e0, std::size_t segInd
         return;
     }
 
-    // todo collinear?
+    if (isTrivialIntersection(&e0, segIndex0, &e1, segIndex1)) {
+        return;
+    }
+
+    // TODO: Handle collinear segments
 
     static_cast<NodedSegmentString&>(e0).addIntersection(m_intersector.getPoint(0), segIndex0);
-
-
 }
 
 }
\ No newline at end of file

commit f9657108f95a58051ddd38d2f64e9a2af9d39bce
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Mar 25 08:30:24 2026 -0400

    NodableArcString: Split output ArcStrings at node points

diff --git a/include/geos/noding/NodableArcString.h b/include/geos/noding/NodableArcString.h
index b7e438f8f..505cbbba6 100644
--- a/include/geos/noding/NodableArcString.h
+++ b/include/geos/noding/NodableArcString.h
@@ -33,7 +33,7 @@ public:
         m_adds[segmentIndex].push_back(intPt);
     }
 
-    std::unique_ptr<ArcString> getNoded();
+    void getNoded(std::vector<std::unique_ptr<ArcString>>& splitArcs);
 
 private:
     std::map<size_t, std::vector<geom::CoordinateXYZM>> m_adds;
diff --git a/src/noding/GeometryNoder.cpp b/src/noding/GeometryNoder.cpp
index 7580b6e07..208f5885f 100644
--- a/src/noding/GeometryNoder.cpp
+++ b/src/noding/GeometryNoder.cpp
@@ -127,10 +127,14 @@ GeometryNoder::toGeometry(std::vector<std::unique_ptr<PathString>>& nodedEdges)
     for(auto& path :  nodedEdges) {
         const auto& coords = path->getCoordinates();
 
+        bool isLinear = dynamic_cast<SegmentString*>(path.get());
+
+        // TODO: Make OrientedCoordinateArray not require strict equality of arc control points
+
         OrientedCoordinateArray oca1(*coords);
         // Check if an equivalent edge is known
         if(ocas.insert(oca1).second) {
-            if (dynamic_cast<SegmentString*>(path.get())) {
+            if (isLinear) {
                 lines.push_back(geomFact->createLineString(coords));
             } else {
                 resultArcs = true;
diff --git a/src/noding/NodableArcString.cpp b/src/noding/NodableArcString.cpp
index 35969cac5..bbecddf99 100644
--- a/src/noding/NodableArcString.cpp
+++ b/src/noding/NodableArcString.cpp
@@ -3,7 +3,7 @@
  * GEOS - Geometry Engine Open Source
  * http://geos.osgeo.org
  *
- * Copyright (C) 2025 ISciences, LLC
+ * Copyright (C) 2025-2026 ISciences, LLC
  *
  * This is free software; you can redistribute and/or modify it under
  * the terms of the GNU Lesser General Public Licence as published
@@ -13,8 +13,10 @@
  **********************************************************************/
 
 #include <geos/noding/NodableArcString.h>
+#include <geos/algorithm/Angle.h>
 
-#define DEBUG_NODABLE_ARC_STRING 0
+using geos::geom::CoordinateXYZM;
+using geos::geom::CircularArc;
 
 namespace geos::noding {
 
@@ -36,96 +38,165 @@ NodableArcString::NodableArcString(std::vector<geom::CircularArc> arcs, const st
 {
 }
 
-std::unique_ptr<ArcString>
-NodableArcString::getNoded() {
+static
+std::vector<CoordinateXYZM> prepareArcPoints(const CircularArc& arc, std::vector<CoordinateXYZM> splitPoints)
+{
+    const bool isCCW = arc.getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE;
+    const geom::CoordinateXY& center = arc.getCenter();
+    const double paStart = geom::Quadrant::pseudoAngle(center, arc.p0());
 
+    std::vector<CoordinateXYZM> retained;
+    // Add starting point of input arc
+    {
+        CoordinateXYZM p0;
+        arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition(), p0);
+        retained.push_back(p0);
+    }
+
+    std::sort(splitPoints.begin(), splitPoints.end(), [&center, paStart, isCCW](const auto& p0, const auto& p1) {
+        double pa0 = geom::Quadrant::pseudoAngle(center, p0);
+        double pa1 = geom::Quadrant::pseudoAngle(center, p1);
+
+        if (isCCW) {
+            return pseudoAngleDiffCCW(paStart, pa0) < pseudoAngleDiffCCW(paStart, pa1);
+        } else {
+            return pseudoAngleDiffCCW(paStart, pa0) > pseudoAngleDiffCCW(paStart, pa1);
+        }
+    });
+
+    // Add ending point of input arc
+    {
+        CoordinateXYZM p2;
+        arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition() + 2, p2);
+        splitPoints.push_back(p2);
+    }
+
+    for (const auto& p2 : splitPoints) {
+        auto& p0 = retained.back();
+
+        if (p2.equals2D(p0)) {
+            if (std::isnan(p0.z) && !std::isnan(p2.z)) {
+                p0.z = p2.z;
+            }
+            if (std::isnan(p0.m) && !std::isnan(p2.m)) {
+                p0.m = p2.m;
+            }
+            continue;
+        }
+
+        if (!arc.containsPointOnCircle(p2)) {
+            continue;
+        }
+
+        const auto p1 = algorithm::CircularArcs::getMidpoint(p0, p2, center, arc.getRadius(), isCCW);
+
+        if (p1.equals2D(p0) || p1.equals2D(p2)) {
+            continue;
+        }
+
+        // Reject split point where sub-arc midpoint doesn't fall inside arc
+        if (!arc.containsPointOnCircle(p1)) {
+            continue;
+        }
+
+        // Reject split point where computed doesn't fall between endpoints
+        const double t0 = algorithm::Angle::normalizePositive(arc.theta0());
+        const double t1 = algorithm::Angle::normalizePositive(algorithm::CircularArcs::getAngle(p1, center));
+        const double t2 = algorithm::Angle::normalizePositive(algorithm::CircularArcs::getAngle(p2, center));
+
+        if (algorithm::Angle::isWithinCCW(t1, t0, t2) != isCCW) {
+            continue;
+        }
+
+        retained.push_back(p2);
+    }
+
+    // Make sure that endpoint of split arc is unchanged
+    {
+        CoordinateXYZM p2;
+        arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition() + 2, p2);
+        CoordinateXYZM& back = retained.back();
+
+        if (!back.equals2D(p2)) {
+            back.x = p2.x;
+            back.y = p2.y;
+        }
+        if (std::isnan(back.z) && !std::isnan(p2.z)) {
+            back.z = p2.z;
+        }
+        if (std::isnan(back.m) && !std::isnan(p2.m)) {
+            back.m = p2.m;
+        }
+    }
+
+    return retained;
+}
+
+void
+NodableArcString::getNoded(std::vector<std::unique_ptr<ArcString>>& splitArcs) {
     auto dstSeq = std::make_unique<geom::CoordinateSequence>(0, m_constructZ, m_constructM);
+    std::vector<geom::CircularArc> arcs;
 
-        std::vector<geom::CircularArc> arcs;
-        for (size_t i = 0; i < m_arcs.size(); i++) {
-            if (const auto it = m_adds.find(i); it == m_adds.end()) {
-                // No nodes added, just copy the coordinates into the sequence.
-                const geom::CoordinateSequence* srcSeq = m_arcs[i].getCoordinateSequence();
-                std::size_t srcPos = m_arcs[i].getCoordinatePosition();
-                std::size_t dstPos = dstSeq->getSize();
-                dstSeq->add(*srcSeq, srcPos, srcPos + 2, false);
-                arcs.emplace_back(*dstSeq, dstPos);
-            } else {
-                std::vector<geom::CoordinateXYZM>& splitPoints = it->second;
+    for (size_t arcIndex = 0; arcIndex < m_arcs.size(); arcIndex++) {
+        const CircularArc& toSplit = m_arcs[arcIndex];
+        const geom::CoordinateXY& center = toSplit.getCenter();
+        const double radius = toSplit.getRadius();
+        const int orientation = toSplit.getOrientation();
 
-                // TODO check split point actually inside arc?
+        bool arcIsSplit = true;
+        std::vector<CoordinateXYZM> arcPoints;
+        const auto it = m_adds.find(arcIndex);
+        if (it == m_adds.end()) {
+            arcIsSplit = false;
+        } else {
+            arcPoints = prepareArcPoints(toSplit, it->second);
 
-                const geom::CircularArc& toSplit = m_arcs[i];
-                const geom::CoordinateXY& center = toSplit.getCenter();
-                const double radius = toSplit.getRadius();
-                const int orientation = toSplit.getOrientation();
-                const bool isCCW = orientation == algorithm::Orientation::COUNTERCLOCKWISE;
-                const double paStart = geom::Quadrant::pseudoAngle(center, toSplit.p0());
-
-                std::sort(splitPoints.begin(), splitPoints.end(), [&center, paStart, isCCW](const auto& p0, const auto& p1) {
-                    double pa0 = geom::Quadrant::pseudoAngle(center, p0);
-                    double pa1 = geom::Quadrant::pseudoAngle(center, p1);
-
-                    if (isCCW) {
-                        return pseudoAngleDiffCCW(paStart, pa0) < pseudoAngleDiffCCW(paStart, pa1);
-                    } else {
-                        return pseudoAngleDiffCCW(paStart, pa0) > pseudoAngleDiffCCW(paStart, pa1);
-                    }
-                });
-
-#if DEBUG_NODABLE_ARC_STRING
-                std::cout << "Splitting " << toSplit.toString() << " " << (isCCW ? "CCW" : "CW") << " paStart " << paStart << " paStop " << geom::Quadrant::pseudoAngle(center, toSplit.p2()) << std::endl;
-                for (const auto& splitPt : splitPoints)
-                {
-                    const double pa = geom::Quadrant::pseudoAngle(center, splitPt);
-                    std::cout << "  " << splitPt.toString() << "  (pa " << pa << " diff " << pseudoAngleDiffCCW(paStart, pa) << ")" << std::endl;
-                }
-#endif
-
-                // Add first point of split arc
-                std::size_t dstPos = dstSeq->getSize();
-                dstSeq->add(*toSplit.getCoordinateSequence(), toSplit.getCoordinatePosition(), toSplit.getCoordinatePosition());
-                geom::CoordinateXYZM p0, p2;
-                dstSeq->getAt(dstPos, p0);
-
-                // Add intermediate points of split arc
-                for (const auto& splitPoint : splitPoints) {
-                    if (arcs.empty()) {
-                        if (splitPoint.equals2D(p0)) {
-                            continue;
-                        }
-                    } else if (splitPoint.equals2D(arcs.back().p2())) {
-                        continue;
-                    }
-
-                    geom::CoordinateXYZM midpoint(algorithm::CircularArcs::getMidpoint(p0, splitPoint, center, radius, isCCW));
-                    midpoint.z = (p0.z + splitPoint.z) / 2;
-                    midpoint.m = (p0.m + splitPoint.m) / 2;
-
-                    dstSeq->add(midpoint);
-                    dstSeq->add(splitPoint);
-
-                    p0 = splitPoint;
-
-                    arcs.emplace_back(*dstSeq, dstPos, center, radius, orientation);
-                    dstPos = dstSeq->getSize() - 1;
-                }
-
-                // Add last point of split arc
-                toSplit.getCoordinateSequence()->getAt(toSplit.getCoordinatePosition() + 2, p2);
-                if (arcs.empty() || !arcs.back().p2().equals2D(p2)) {
-                    geom::CoordinateXYZM midpoint(algorithm::CircularArcs::getMidpoint(p0, p2, center, radius, isCCW));
-                    midpoint.z = (p0.z + p2.z) / 2;
-                    midpoint.m = (p0.m + p2.m) / 2;
-
-                    dstSeq->add(midpoint);
-                    dstSeq->add(p2);
-                    arcs.emplace_back(*dstSeq, dstPos, center, radius, orientation);
-                }
+            if (arcPoints.size() == 2) {
+                // All added nodes collapsed
+                arcIsSplit = false;
             }
         }
 
-        return std::make_unique<NodableArcString>(std::move(arcs), std::move(dstSeq), m_constructZ, m_constructM, nullptr);
+        if (!arcIsSplit) {
+            // No nodes added, just copy the coordinates into the sequence.
+            const geom::CoordinateSequence* srcSeq = m_arcs[arcIndex].getCoordinateSequence();
+            std::size_t srcPos = m_arcs[arcIndex].getCoordinatePosition();
+            dstSeq->add(*srcSeq, srcPos, srcPos + 2, false);
+            std::size_t dstPos = dstSeq->getSize() - 3;
+            arcs.emplace_back(*dstSeq, dstPos, center, radius, orientation);
+
+            continue;
+        }
+
+        const bool isCCW = orientation == algorithm::Orientation::COUNTERCLOCKWISE;
+
+        for (std::size_t i = 1; i < arcPoints.size(); i++) {
+            const CoordinateXYZM& p0 = arcPoints[i - 1];
+            const CoordinateXYZM& p2 = arcPoints[i];
+
+            CoordinateXYZM p1(algorithm::CircularArcs::getMidpoint(p0, p2, center, radius, isCCW));
+            p1.z = (p0.z + p2.z) / 2;
+            p1.m = (p0.m + p2.m) / 2;
+
+            if (dstSeq->isEmpty()) {
+                dstSeq->add(p0);
+            }
+            dstSeq->add(p1);
+            dstSeq->add(p2);
+
+            const std::size_t dstPos = dstSeq->getSize() - 3;
+            arcs.emplace_back(*dstSeq, dstPos, center, radius, orientation);
+
+            // Finish the ArcString, start a new one.
+            splitArcs.push_back(std::make_unique<NodableArcString>(std::move(arcs), std::move(dstSeq), m_constructZ, m_constructM, nullptr));
+            dstSeq = std::make_unique<geom::CoordinateSequence>(0, m_constructZ, m_constructM);
+            arcs.clear();
+        }
     }
 
+    if (!arcs.empty()) {
+        splitArcs.push_back(std::make_unique<NodableArcString>(std::move(arcs), std::move(dstSeq), m_constructZ, m_constructM, nullptr));
+    }
+}
+
 }
\ No newline at end of file
diff --git a/src/noding/SimpleNoder.cpp b/src/noding/SimpleNoder.cpp
index 496de6070..a35c0ee83 100644
--- a/src/noding/SimpleNoder.cpp
+++ b/src/noding/SimpleNoder.cpp
@@ -85,8 +85,12 @@ SimpleNoder::getNodedPaths()
                 nodedPaths.push_back(std::move(segString));
             }
         } else {
+            std::vector<std::unique_ptr<ArcString>> tmp;
             auto* nas = detail::down_cast<NodableArcString*>(ps);
-            nodedPaths.push_back(nas->getNoded());
+            nas->getNoded(tmp);
+            for (auto& arcString : tmp) {
+                nodedPaths.push_back(std::move(arcString));
+            }
         }
     }
 
diff --git a/tests/unit/capi/GEOSNodeTest.cpp b/tests/unit/capi/GEOSNodeTest.cpp
index e1356e44e..8feb5df96 100644
--- a/tests/unit/capi/GEOSNodeTest.cpp
+++ b/tests/unit/capi/GEOSNodeTest.cpp
@@ -212,8 +212,12 @@ void object::test<9>()
     ensure(result_ != nullptr);
 
     expected_ = fromWKT("MULTICURVE ("
-                        "CIRCULARSTRING (0 0, 0.0340741737 0.2588190451, 0.1339745962 0.5, 1 1, 1.8660254038 0.5, 1.9659258263 0.2588190451, 2 0),"
-                        "CIRCULARSTRING (0 1, 0.0340741737 0.7411809549, 0.1339745962 0.5, 1 0, 1.8660254038 0.5, 1.9659258263 0.7411809549, 2 1))");
+                        "CIRCULARSTRING (0 0, 0.0340741737 0.2588190451, 0.1339745962 0.5),"
+                        "CIRCULARSTRING (0.1339745962 0.5, 1 1, 1.8660254038 0.5),"
+                        "CIRCULARSTRING (1.8660254038 0.5, 1.9659258263 0.2588190451, 2 0),"
+                        "CIRCULARSTRING (0 1, 0.0340741737 0.7411809549, 0.1339745962 0.5),"
+                        "CIRCULARSTRING (0.1339745962 0.5, 1 0, 1.8660254038 0.5),"
+                        "CIRCULARSTRING (1.8660254038 0.5, 1.9659258263 0.7411809549, 2 1))");
 
     ensure_geometry_equals_exact(result_, expected_, 1e-8);
 }
@@ -222,7 +226,7 @@ template<>
 template<>
 void object::test<10>()
 {
-    set_test_name("CIRCULARSTRING ZM intersecting CIRCULARSTRING M");
+    set_test_name("CIRCULARSTRING ZM intersecting CIRCULARSTRING M at endpoint and interior");
 
     input_ = fromWKT("MULTICURVE (CIRCULARSTRING ZM (-1 0 3 4, 0 1 2 5, 1 0 4 7), CIRCULARSTRING M (-1 2 9, 0 1 13, -1 0 17))");
     ensure(input_);
@@ -231,12 +235,12 @@ void object::test<10>()
     ensure(result_ != nullptr);
 
     expected_ = fromWKT("MULTICURVE ZM ("
-        "CIRCULARSTRING ZM (-1 0 3 4, -0.7071067812 0.7071067812 2.5 6.5, -5.5511151231e-17 1 2 9, 0.7071067812 0.7071067812 3 8, 1 0 4 7),"
-        "CIRCULARSTRING ZM (-1 2 NaN 9, -0.2928932188 1.7071067812 NaN 9, -5.5511151231e-17 1 2 9, -0.2928932188 0.2928932188 2.5 6.5, -1 0 3 4))");
+        "CIRCULARSTRING ZM (-1 0 3 4, -0.7071067812 0.7071067812 2.5 6.5, -5.5511151231e-17 1 2 9),"
+        "CIRCULARSTRING ZM (-5.5511151231e-17 1 2 9, 0.7071067812 0.7071067812 3 8, 1 0 4 7),"
+        "CIRCULARSTRING ZM (-1 2 NaN 9, -0.2928932188 1.7071067812 NaN 9, -5.5511151231e-17 1 2 9),"
+        "CIRCULARSTRING ZM (-5.5511151231e-17 1 2 9, -0.2928932188 0.2928932188 2.5 6.5, -1 0 3 4))");
 
-    ensure_equals(GEOSGetNumGeometries(result_), 2);
-    ensure_equals("Noded arc 1 should have 5 points", GEOSGeomGetNumPoints(GEOSGetGeometryN(result_, 0)), 5);
-    ensure_equals("Noded arc 2 should have 5 points", GEOSGeomGetNumPoints(GEOSGetGeometryN(result_, 1)), 5);
+    ensure_equals(GEOSGetNumGeometries(result_), 4);
 
     ensure_equals_exact_geometry_xyzm(reinterpret_cast<Geometry*>(result_),
                                       reinterpret_cast<Geometry*>(expected_), 1e-8);
@@ -255,7 +259,8 @@ void object::test<11>()
     ensure(result_ != nullptr);
 
     expected_ = fromWKT("MULTICURVE ZM ("
-                        "CIRCULARSTRING ZM (-5 0 3 4, -3.5355 3.5355 3 6, 0 5 3 8, 2.2361 4.4721 3.5 7.5, 4 3 4 7),"
+                        "CIRCULARSTRING ZM (-5 0 3 4, -3.5355 3.5355 3 6, 0 5 3 8),"
+                        "CIRCULARSTRING ZM (0 5 3 8, 2.2361 4.4721 3.5 7.5, 4 3 4 7),"
                         "LINESTRING ZM (0 0 NaN 7, 0 5 3 8),"
                         "LINESTRING ZM (0 5 3 8, 0 10 NaN 13))");
 
@@ -274,13 +279,17 @@ void object::test<12>()
         "CIRCULARSTRING (-4 3, 0 5, 4 3))");
 
     result_ = GEOSNode(input_);
+    ensure(result_);
 
     expected_ = fromWKT("MULTICURVE ("
-                        "CIRCULARSTRING (-5.0000000000000000 0.0000000000000000, -4.7434164902525691 1.5811388300841900, -4.0000000000000000 3.0000000000000000, 0.0000000000000003 5.0000000000000000, 4.0000000000000000 3.0000000000000000, 4.7434164902525691 1.5811388300841898, 5.0000000000000000 0.0000000000000000),"
-                        "CIRCULARSTRING (-4.0000000000000000 3.0000000000000000, 0.0000000000000003 5.0000000000000000, 4.0000000000000000 3.0000000000000000))");
+                        "CIRCULARSTRING (-5 0, -4.7434164902525691 1.5811388300841900, -4 3),"
+                        "CIRCULARSTRING (-4 3, 0 5, 4 3),"
+                        "CIRCULARSTRING (-4 3, 0 5, 4 3)," // FIXME this component is not removed by OrientedCoordinateArray because
+                                                           // its control point differs slightly from the previous one
+                        "CIRCULARSTRING (4 3, 4.7434164902525691 1.5811388300841898, 5 0))");
 
-    ensure_equals_exact_geometry_xyzm(reinterpret_cast<Geometry*>(result_),
-                                      reinterpret_cast<Geometry*>(expected_), 1e-4);
+    ensure_equals_geometry_xyzm(reinterpret_cast<Geometry*>(result_),
+                                reinterpret_cast<Geometry*>(expected_), 1e-4);
 }
 
 template<>
@@ -349,7 +358,7 @@ void object::test<16>()
     result_ = GEOSNode(input_);
     ensure(result_ != nullptr);
 
-    expected_ = fromWKT("MULTICURVE Z (CIRCULARSTRING Z (-5 0 3, -1.5811388301 4.7434164903 4.5, 4 3 6), (0 0 NaN, 4 3 6))");
+    expected_ = fromWKT("MULTICURVE Z (CIRCULARSTRING Z (-5 0 3, -4 3 5, 4 3 6), (0 0 NaN, 4 3 6))");
 
     ensure_equals_exact_geometry_xyzm(reinterpret_cast<Geometry*>(result_),
                                       reinterpret_cast<Geometry*>(expected_), 1e-4);
@@ -367,5 +376,27 @@ void object::test<17>()
     ensure(result_);
 }
 
+template<>
+template<>
+void object::test<18>()
+{
+    set_test_name("Multi-section CircularStrings with single interior intersection");
+
+    input_ = fromWKT("MULTICURVE ("
+        "CIRCULARSTRING (-5 0, -4 3, 0 5, 3 4, 4 3),"
+        "CIRCULARSTRING (3 5, 4 4, 2 3))"
+    );
+
+    result_ = GEOSNode(input_);
+    ensure(result_);
+
+    ensure_equals(GEOSGetNumGeometries(result_), 4);
+
+    expected_ = fromWKT("MULTICURVE (CIRCULARSTRING (-5 0, -4 3, 0 5, 2.1154124423 4.5304558489, 3.8335130689 3.2100120795), CIRCULARSTRING (3.8335130689 3.2100120795, 3.918163858 3.1061216946, 4 3), CIRCULARSTRING (3 5, 3.9016950794 4.3308190803, 3.8335130689 3.2100120795), CIRCULARSTRING (3.8335130689 3.2100120795, 2.9674441042 2.6624775822, 2 3))");
+    ensure_equals_exact_geometry_xyzm(reinterpret_cast<Geometry*>(result_),
+                                      reinterpret_cast<Geometry*>(expected_), 1e-4);
+
+}
+
 } // namespace tut
 
diff --git a/tests/unit/noding/NodableArcStringTest.cpp b/tests/unit/noding/NodableArcStringTest.cpp
index 7c90e3831..4bdedad95 100644
--- a/tests/unit/noding/NodableArcStringTest.cpp
+++ b/tests/unit/noding/NodableArcStringTest.cpp
@@ -8,6 +8,7 @@ using geos::geom::CircularArc;
 using geos::geom::Ordinate;
 using geos::geom::CoordinateXY;
 using geos::geom::CoordinateXYZM;
+using geos::noding::ArcString;
 using geos::noding::NodableArcString;
 
 namespace tut {
@@ -20,7 +21,7 @@ struct test_nodablearcstring_data {
     using XYZM = CoordinateXYZM;
 
     static void test_add_points(const CircularArc& arc, const std::vector<CoordinateXY>& coords,
-                                const std::vector<CircularArc>& expected, bool reversed=false) {
+                                const std::vector<std::vector<CircularArc>>& expected, bool reversed=false) {
         std::vector<CircularArc> arcs;
         arcs.push_back(arc);
         NodableArcString nas(arcs, nullptr, false, false, nullptr);
@@ -29,20 +30,27 @@ struct test_nodablearcstring_data {
             nas.addIntersection(coord, 0);
         }
 
-        auto noded = nas.getNoded();
+        std::vector<std::unique_ptr<ArcString>> noded;
+        nas.getNoded(noded);
 
-        ensure_equals(noded->getSize(), expected.size());
+        ensure_equals(noded.size(), expected.size());
 
-        for (std::size_t i = 0; i < expected.size(); i++) {
-            ensure_arc_equals(noded->getArc(i), expected[i], 1e-8);
+        for (std::size_t i = 0; i < noded.size(); i++) {
+            for (std::size_t j = 0; j < noded[i]->getSize(); j++) {
+                ensure_arc_equals(noded[i]->getArc(j), expected[i][j], 1e-8);
+            }
         }
 
         if (!reversed) {
             const auto revArc = arc.reverse();
 
-            std::vector<CircularArc> revExpected;
+            std::vector<std::vector<CircularArc>> revExpected;
             for (const auto& x : expected) {
-                revExpected.push_back(x.reverse());
+                revExpected.push_back(x);
+                std::reverse(revExpected.back().begin(), revExpected.back().end());
+                for (auto& y : revExpected.back()) {
+                    y = y.reverse();
+                }
             }
             std::reverse(revExpected.begin(), revExpected.end());
 
@@ -74,12 +82,12 @@ void object::test<1>()
     coords.emplace_back(-3, 4);
     coords.emplace_back(-4, 3);
 
-    std::vector<CircularArc> expected;
-    expected.push_back(CircularArc::create(XY{-5, 0}, {-4, 3}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{-4, 3}, {-3, 4}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{-3, 4}, {3, 4}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{3, 4}, {4, 3}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{4, 3}, {5, 0}, {0, 0}, 5, Orientation::CLOCKWISE));
+    std::vector<std::vector<CircularArc>> expected;
+    expected.push_back({CircularArc::create(XY{-5, 0}, {-4, 3}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{-4, 3}, {-3, 4}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{-3, 4}, {3, 4}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{3, 4}, {4, 3}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{4, 3}, {5, 0}, {0, 0}, 5, Orientation::CLOCKWISE)});
 
     test_add_points(in, coords, expected);
 }
@@ -99,13 +107,13 @@ void object::test<2>()
     coords.emplace_back(3, 4);
     coords.emplace_back(5, 0);
 
-    std::vector<CircularArc> expected;
-    expected.push_back(CircularArc::create(XY{0, 5}, {3, 4}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{3, 4}, {4, 3}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{4, 3}, {5, 0}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{5, 0}, {4, -3}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{4, -3}, {3, -4}, {0, 0}, 5, Orientation::CLOCKWISE));
-    expected.push_back(CircularArc::create(XY{3, -4}, {0, -5}, {0, 0}, 5, Orientation::CLOCKWISE));
+    std::vector<std::vector<CircularArc>> expected;
+    expected.push_back({CircularArc::create(XY{0, 5}, {3, 4}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{3, 4}, {4, 3}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{4, 3}, {5, 0}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{5, 0}, {4, -3}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{4, -3}, {3, -4}, {0, 0}, 5, Orientation::CLOCKWISE)});
+    expected.push_back({CircularArc::create(XY{3, -4}, {0, -5}, {0, 0}, 5, Orientation::CLOCKWISE)});
 
     test_add_points(in, coords, expected);
 }
@@ -118,8 +126,8 @@ void object::test<3>()
     CircularArc in = CircularArc::create(CoordinateXY{-1, 0}, CoordinateXY{0, 1}, CoordinateXY{1, 0});
 
     std::vector<CoordinateXY> coords;
-    std::vector<CircularArc> expected;
-    expected.push_back(in);
+    std::vector<std::vector<CircularArc>> expected;
+    expected.push_back({in});
     test_add_points(in, coords, expected);
 }
 
@@ -147,15 +155,16 @@ void object::test<4>()
 
     nas.addIntersection( intPt, 0);
 
-    auto noded = nas.getNoded();
+    std::vector<std::unique_ptr<ArcString>> noded;
+    nas.getNoded(noded);
 
-    ensure_equals(noded->getSize(), 2u);
-    const CircularArc& arc0 = noded->getArc(0);
+    ensure_equals(noded.size(), 2u);
+    const CircularArc& arc0 = noded[0]->getArc(0);
     ensure_arc_equals(arc0, CircularArc::create(p0, intPt, arc.getCenter(), arc.getRadius(), arc.getOrientation()), 1e-8);
     ensure_equals(arc0.p1<CoordinateXYZM>().z, (p0.z + intPt.z) / 2);
     ensure_equals(arc0.p1<CoordinateXYZM>().m, (p0.m + intPt.m) / 2);
 
-    const CircularArc& arc1 = noded->getArc(1);
+    const CircularArc& arc1 = noded[1]->getArc(0);
     ensure_arc_equals(arc1, CircularArc::create(intPt, p2, arc.getCenter(), arc.getRadius(), arc.getOrientation()), 1e-8);
     ensure_equals(arc1.p1<CoordinateXYZM>().z, (intPt.z + p2.z) / 2);
     ensure_equals(arc1.p1<CoordinateXYZM>().m, (intPt.m + p2.m) / 2);

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

Summary of changes:
 include/geos/noding/ArcIntersectionAdder.h |   5 +
 include/geos/noding/NodableArcString.h     |   2 +-
 src/noding/ArcIntersectionAdder.cpp        |  39 ++++-
 src/noding/GeometryNoder.cpp               |   6 +-
 src/noding/NodableArcString.cpp            | 243 +++++++++++++++++++----------
 src/noding/SimpleNoder.cpp                 |   6 +-
 tests/unit/capi/GEOSNodeTest.cpp           |  61 ++++++--
 tests/unit/noding/NodableArcStringTest.cpp |  61 +++++---
 8 files changed, 290 insertions(+), 133 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list