[geos-commits] [SCM] GEOS branch main updated. 2a5ce498a499916cf9b8e3c830b3e644d03f2c9f

git at osgeo.org git at osgeo.org
Wed Jul 15 06:19:30 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  2a5ce498a499916cf9b8e3c830b3e644d03f2c9f (commit)
      from  fdc70563162eb1784929c68263f4ad67ce809761 (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 2a5ce498a499916cf9b8e3c830b3e644d03f2c9f
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Jul 15 09:19:04 2026 -0400

    RayCrossingCounter: Fix some incorrect results for curved geometries (#1479)

diff --git a/include/geos/geom/CircularArc.h b/include/geos/geom/CircularArc.h
index a2a4dd151..86f2b2c23 100644
--- a/include/geos/geom/CircularArc.h
+++ b/include/geos/geom/CircularArc.h
@@ -209,11 +209,6 @@ public:
     double distance(const CoordinateXY& p1, const CoordinateXY& p2) const;
     double distance(const CircularArc& other) const;
 
-    /// Return true if the arc is pointing positive in the y direction
-    /// at the location of a specified point. The point is assumed to
-    /// be on the arc.
-    bool isUpwardAtPoint(const CoordinateXY& q) const;
-
     CircularArc reverse() const;
 
     // Split an arc at a specified point.
diff --git a/src/algorithm/RayCrossingCounter.cpp b/src/algorithm/RayCrossingCounter.cpp
index 3d212beb6..7377d11c7 100644
--- a/src/algorithm/RayCrossingCounter.cpp
+++ b/src/algorithm/RayCrossingCounter.cpp
@@ -102,7 +102,7 @@ RayCrossingCounter::processSequence(const geom::CoordinateSequence& seq, bool is
 
     if (isLinear) {
         for(std::size_t i = 1; i < seq.size(); i++) {
-            const geom::CoordinateXY& p1 = seq.getAt<geom::CoordinateXY>(i-1);;
+            const geom::CoordinateXY& p1 = seq.getAt<geom::CoordinateXY>(i-1);
             const geom::CoordinateXY& p2 = seq.getAt<geom::CoordinateXY>(i);
 
             countSegment(p1, p2);
@@ -190,21 +190,34 @@ RayCrossingCounter::countSegment(const geom::CoordinateXY& p1,
 
 bool
 RayCrossingCounter::shouldCountCrossing(const geom::CircularArc& arc, const geom::CoordinateXY& q) {
+    const auto& c = arc.getCenter();
+
     // To avoid double-counting shared vertices, we count an intersection point if
     // a) is in the interior of the arc
     // b) is at the starting point of the arc, and the arc is directed upward at that point
     // c) is at the ending point of the arc is directed downward at that point
     if (q.equals2D(arc.p0())) {
-        return arc.isUpwardAtPoint(q);
+        if (arc.isCCW()) {
+            return q.x > c.x || (q.x == c.x && q.y < c.y);
+        } else {
+            return q.x < c.x || (q.x == c.x && q.y < c.y);
+        }
     } else if (q.equals2D(arc.p2())) {
-        return !arc.isUpwardAtPoint(q);
+        if (arc.isCCW()) {
+            return q.x < c.x || (q.x == c.x && q.y < c.y);
+        } else {
+            return q.x > c.x || (q.x == c.x && q.y < c.y);
+        }
     } else {
-        return true;
+        // Ignore rays tangent to the arc interior. This is topologically equivalent
+        // to the ray intersecting adjacent upward and downward segments, which would
+        // cancel out.
+        return q.x != arc.getCenter().x;
     }
 }
 
-/// Return an array of 0-2 intersection points between an arc and a horizontal
-/// ray extending righward from a point. If fewer than 2 intersection points exist,
+/// Return an array of 0-2 unique intersection points between an arc and a horizontal
+/// ray extending rightward from a point. If fewer than 2 intersection points exist,
 /// some Coordinates in the returned array will be equal to CoordinateXY::getNull().
 std::array<geom::CoordinateXY, 2>
 RayCrossingCounter::pointsIntersectingHorizontalRay(const geom::CircularArc& arc, const geom::CoordinateXY& origin) {
@@ -241,7 +254,7 @@ RayCrossingCounter::pointsIntersectingHorizontalRay(const geom::CircularArc& arc
     if (intPt1.x >= origin.x && arc.containsPointOnCircle(intPt1)) {
         ret[pos++] = intPt1;
     }
-    if (intPt2.x >= origin.x && arc.containsPointOnCircle(intPt2)) {
+    if (intPt2 != intPt1 && intPt2.x >= origin.x && arc.containsPointOnCircle(intPt2)) {
         ret[pos++] = intPt2;
     }
 
@@ -270,7 +283,7 @@ RayCrossingCounter::countArc(const geom::CircularArc& arc)
         return;
     }
 
-    // Evaluate all arcs whose enveleope is to the right of the test point.
+    // Evaluate all arcs whose envelope is to the right of the test point.
     if (arcEnvelope.getMaxY() >= point.y && arcEnvelope.getMinY() <= point.y) {
         if (arc.containsPoint(point)) {
             isPointOnSegment = true;
diff --git a/src/geom/CircularArc.cpp b/src/geom/CircularArc.cpp
index 038fd1d18..44414c0ad 100644
--- a/src/geom/CircularArc.cpp
+++ b/src/geom/CircularArc.cpp
@@ -393,21 +393,6 @@ CircularArc::addLinearizedPoints(CoordinateSequence& seq, const algorithm::Curve
     seq.add(*getCoordinateSequence(), getCoordinatePosition() + 2, getCoordinatePosition() + 2);
 }
 
-bool
-CircularArc::isUpwardAtPoint(const CoordinateXY& q) const
-{
-    auto quad = geom::Quadrant::quadrant(getCenter(), q);
-    bool isUpward;
-
-    if (getOrientation() == algorithm::Orientation::CLOCKWISE) {
-        isUpward = (quad == geom::Quadrant::SW || quad == geom::Quadrant::NW);
-    } else {
-        isUpward = (quad == geom::Quadrant::SE || quad == geom::Quadrant::NE);
-    }
-
-    return isUpward;
-}
-
 CircularArc
 CircularArc::reverse() const
 {
diff --git a/tests/unit/algorithm/LocatePointInRingTest.cpp b/tests/unit/algorithm/LocatePointInRingTest.cpp
index 1a70b9d15..aed2943a5 100644
--- a/tests/unit/algorithm/LocatePointInRingTest.cpp
+++ b/tests/unit/algorithm/LocatePointInRingTest.cpp
@@ -295,6 +295,71 @@ void object::test<12>()
     runPtLocator(Location::BOUNDARY, CoordinateXY(11.230120879533454, 62.84897119848748), wkt);
 }
 
+template<>
+template<>
+void object::test<13>()
+{
+    std::string wkt = "CURVEPOLYGON(COMPOUNDCURVE ("
+        "(220 140, 60 140, 60 260, 220 240),"
+        "CIRCULARSTRING (220 240, 230 230, 220 220, 190 230, 160 220, 155 210, 160 200, 174.18861169915812 207.4341649025257, 190 210),"
+        "(190 210, 190 180, 160 180, 160 160, 220 160, 220 140)))";
+
+    for (int y = 195; y <= 245; y++) {
+        runPtLocator(Location::INTERIOR, {80, static_cast<double>(y)}, wkt);
+    }
+}
+
+template<>
+template<>
+void object::test<14>()
+{
+    std::string wkt = "CURVEPOLYGON(COMPOUNDCURVE ((-10 100, 0 110, 10 100), CIRCULARSTRING (10 100, 35 75, 10 50, 35 25, 10 0), (10 0, 0 -10, -10 0), CIRCULARSTRING (-10 0, -35 25, -10 50, -35 75, -10 100)))";
+
+    runPtLocator(Location::EXTERIOR, {-20, 0},  wkt);
+    runPtLocator(Location::INTERIOR, {0, 0},  wkt);
+    runPtLocator(Location::EXTERIOR, {-20, 0},  wkt);
+
+    runPtLocator(Location::EXTERIOR, {-20, 50},  wkt);
+    runPtLocator(Location::INTERIOR, {0, 50},  wkt);
+    runPtLocator(Location::EXTERIOR, {-20, 50},  wkt);
+
+    runPtLocator(Location::EXTERIOR, {-20, 100},  wkt);
+    runPtLocator(Location::INTERIOR, {0, 100},  wkt);
+    runPtLocator(Location::EXTERIOR, {-20, 100},  wkt);
+}
+
+template<>
+template<>
+void object::test<15>()
+{
+    std::string wkt = "CURVEPOLYGON(COMPOUNDCURVE (CIRCULARSTRING(0 0, 10 10, 20 0), (20 0, 30 10, 40 0), CIRCULARSTRING (40 0, 50 -10, 60 0), (60 0, 70 -10, 80 0), CIRCULARSTRING (80 0, 70 10, 80 20), (80 20, 100 20), CIRCULARSTRING (100 20, 120 0, 100 -20), (100 -20, 100 0), CIRCULARSTRING (100 0, 50 -50, 0 0)))";
+
+    // scanline at y = 0
+    runPtLocator(Location::EXTERIOR, {-10, 0}, wkt);
+    runPtLocator(Location::INTERIOR, {10, 0}, wkt);
+    runPtLocator(Location::INTERIOR, {30, 0}, wkt);
+    runPtLocator(Location::EXTERIOR, {50, 0}, wkt);
+    runPtLocator(Location::EXTERIOR, {70, 0}, wkt);
+    runPtLocator(Location::INTERIOR, {90, 0}, wkt);
+    runPtLocator(Location::INTERIOR, {110, 0}, wkt);
+    runPtLocator(Location::EXTERIOR, {130, 0}, wkt);
+
+    // scanline at y = 5
+    runPtLocator(Location::EXTERIOR, {0, 5}, wkt);
+    runPtLocator(Location::INTERIOR, {10, 5}, wkt);
+    runPtLocator(Location::EXTERIOR, {20, 5}, wkt);
+    runPtLocator(Location::INTERIOR, {30, 5}, wkt);
+    runPtLocator(Location::EXTERIOR, {40, 5}, wkt);
+    runPtLocator(Location::EXTERIOR, {50, 5}, wkt);
+    runPtLocator(Location::EXTERIOR, {60, 5}, wkt);
+    runPtLocator(Location::EXTERIOR, {70, 5}, wkt);
+    runPtLocator(Location::INTERIOR, {75, 5}, wkt);
+    runPtLocator(Location::INTERIOR, {80, 5}, wkt);
+    runPtLocator(Location::INTERIOR, {100, 5}, wkt);
+    runPtLocator(Location::INTERIOR, {110, 5}, wkt);
+    runPtLocator(Location::EXTERIOR, {120, 5}, wkt);
+    runPtLocator(Location::EXTERIOR, {125, 5}, wkt);
+}
 
 } // namespace tut
 
diff --git a/tests/unit/algorithm/locate/SimplePointInAreaLocatorTest.cpp b/tests/unit/algorithm/locate/SimplePointInAreaLocatorTest.cpp
index 0330c1fed..798df3ca6 100644
--- a/tests/unit/algorithm/locate/SimplePointInAreaLocatorTest.cpp
+++ b/tests/unit/algorithm/locate/SimplePointInAreaLocatorTest.cpp
@@ -37,7 +37,6 @@ void object::test<1>()
     checkLocation(*g, {1, 1}, Location::BOUNDARY); // vertex of hole
     checkLocation(*g, {2, 1}, Location::BOUNDARY); // boundary of hole
     checkLocation(*g, {2.5, 1.5}, Location::EXTERIOR); // inside hole
-
 }
 
 }

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

Summary of changes:
 include/geos/geom/CircularArc.h                    |  5 --
 src/algorithm/RayCrossingCounter.cpp               | 29 +++++++---
 src/geom/CircularArc.cpp                           | 15 -----
 tests/unit/algorithm/LocatePointInRingTest.cpp     | 65 ++++++++++++++++++++++
 .../locate/SimplePointInAreaLocatorTest.cpp        |  1 -
 5 files changed, 86 insertions(+), 29 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list