[geos-commits] [SCM] GEOS branch main updated. 47435e4f5d4163d01e77116b289de5b5c57778c6

git at osgeo.org git at osgeo.org
Thu Oct 23 09:32:22 PDT 2025


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

The branch, main has been updated
       via  47435e4f5d4163d01e77116b289de5b5c57778c6 (commit)
      from  32c23d1fe658c575e187579ae49dc821df2651b6 (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 47435e4f5d4163d01e77116b289de5b5c57778c6
Author: Daniel Baston <dbaston at gmail.com>
Date:   Thu Oct 23 12:31:58 2025 -0400

    CircularArcs: Fix incorrect Envelope calculation (#1314)

diff --git a/include/geos/geom/Quadrant.h b/include/geos/geom/Quadrant.h
index 6705f1f2e..d7a9e9519 100644
--- a/include/geos/geom/Quadrant.h
+++ b/include/geos/geom/Quadrant.h
@@ -119,6 +119,24 @@ public:
         }
     };
 
+    /** Return a measure that increases monotonically with counterclockwise
+     *  angle and avoids trigonometric calculations. Values are consistent
+     *  with the numeric quadrant codes
+     *
+     * @param p0 circle center coordinate
+     * @param p1 coordinate for which pseudoangle should be calculated
+     */
+    static double pseudoAngle(const CoordinateXY& p0, const CoordinateXY& p1)
+    {
+        const double dx = p1.x - p0.x;
+        const double dy = p1.y - p0.y;
+
+        const double k = dx / (std::abs(dx) + std::abs(dy));
+        const double w = 2 + (dy > 0 ? 3 - k : 1 + k);
+
+        return w >= 4 ? w - 4 : w;
+    }
+
     /**
      * Returns true if the quadrants are 1 and 3, or 2 and 4
      */
diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp
index 949f18509..068dfd38c 100644
--- a/src/algorithm/CircularArcs.cpp
+++ b/src/algorithm/CircularArcs.cpp
@@ -57,6 +57,8 @@ void
 CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1,
                              const geom::CoordinateXY& p2)
 {
+    using geom::Quadrant;
+
     e.expandToInclude(p0);
     e.expandToInclude(p1);
     e.expandToInclude(p2);
@@ -73,22 +75,24 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co
         return;
     }
 
-    auto orientation = Orientation::index(center, p0, p1);
-
     //* 1 | 0
     //* --+--
     //* 2 | 3
+    const auto pa0 = Quadrant::pseudoAngle(center, p0);
+    const auto pa1 = Quadrant::pseudoAngle(center, p1);
+    const auto pa2 = Quadrant::pseudoAngle(center, p2);
 
-    using geom::Quadrant;
-
-    auto q0 = geom::Quadrant::quadrant(center, p0);
-    auto q2 = geom::Quadrant::quadrant(center, p2);
+    auto q0 = static_cast<int>(pa0);
+    auto q2 = static_cast<int>(pa2);
     double R = center.distance(p1);
 
     if (q0 == q2) {
-        // Start and end quadrants are the same. Either the arc crosses all of
+        // Start and end quadrants are the same. Either the arc crosses all
         // the axes, or none of the axes.
-        if (Orientation::index(center, p1, p2) != orientation) {
+
+        const bool isBetween = pa1 > std::min(pa0, pa2) && pa1 < std::max(pa0, pa2);
+
+        if (!isBetween) {
             e.expandToInclude({center.x, center.y + R});
             e.expandToInclude({center.x - R, center.y});
             e.expandToInclude({center.x, center.y - R});
@@ -98,6 +102,8 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co
         return;
     }
 
+    auto orientation = Orientation::index(p0, p1, p2);
+
     if (orientation == Orientation::CLOCKWISE) {
         std::swap(q0, q2);
     }
diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp
index 475261d4f..e40aa3435 100644
--- a/tests/unit/algorithm/CircularArcsTest.cpp
+++ b/tests/unit/algorithm/CircularArcsTest.cpp
@@ -215,5 +215,19 @@ void object::test<13>()
                   3, 4, 3, 4);
 }
 
+template<>
+template<>
+void object::test<14>()
+{
+    set_test_name("envelope: GH #1313");
+
+    CoordinateXY p0{2, 0};
+    CoordinateXY p1{4, 2};
+    CoordinateXY p2{2, 1};
+
+    checkEnvelope(p0, p1, p2,
+    2, -1.0811388300841898, 5.08113883008419,2.08113883008419);
+}
+
 }
 
diff --git a/tests/unit/geom/QuadrantTest.cpp b/tests/unit/geom/QuadrantTest.cpp
new file mode 100644
index 000000000..bc23d1094
--- /dev/null
+++ b/tests/unit/geom/QuadrantTest.cpp
@@ -0,0 +1,83 @@
+#include <tut/tut.hpp>
+
+#include <geos/geom/Quadrant.h>
+
+using geos::geom::Quadrant;
+
+namespace tut {
+
+struct test_quadrant_data {
+
+    static std::string quadrantName(int quadrant) {
+        switch (quadrant) {
+            case Quadrant::NE: return "NE";
+                case Quadrant::NW: return "NW";
+                case Quadrant::SE: return "SE";
+                case Quadrant::SW: return "SW";
+        }
+        return "unknown";
+    }
+
+    static void checkQuadrant(double dx, double dy, int expected) {
+        int quadrant = Quadrant::quadrant({0, 0}, {dx, dy});
+        ensure_equals(quadrant, expected);
+    }
+
+    static void checkPseudoAngleMatchesQuadrant(double dx, double dy) {
+        int quadrant = Quadrant::quadrant({0, 0}, {dx, dy});
+        double pa = Quadrant::pseudoAngle({0, 0}, {dx, dy});
+
+        if (quadrant != static_cast<int>(pa)) {
+            std::stringstream ss;
+            ss << "relative coordinate (" << dx << ", " << dy << ") expected to be in quadrant " << quadrantName(quadrant) << " but pseudoAngle " << pa << " corresponds to quadrant " << quadrantName(static_cast<int>(pa));
+            fail(ss.str());
+        }
+
+        ensure_equals(quadrant, static_cast<int>(pa));
+    }
+};
+
+typedef test_group<test_quadrant_data> group;
+typedef group::object object;
+
+group test_quadrant_group("geos::geom::Quadrant");
+
+template<>
+template<>
+void object::test<1>()
+{
+    checkQuadrant(1, 1, Quadrant::NE);
+    checkQuadrant(1, -1, Quadrant::SE);
+    checkQuadrant(-1, -1, Quadrant::SW);
+    checkQuadrant(-1, 1, Quadrant::NW);
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    static constexpr double eps = 1e-8;
+
+    // center of each quadrant
+    checkPseudoAngleMatchesQuadrant(1, 1);
+    checkPseudoAngleMatchesQuadrant(1, -1);
+    checkPseudoAngleMatchesQuadrant(-1, -1);
+    checkPseudoAngleMatchesQuadrant(-1, 1);
+
+    // near axes
+    checkPseudoAngleMatchesQuadrant(1, eps); // +X
+    checkPseudoAngleMatchesQuadrant(1, -eps); // +X
+    checkPseudoAngleMatchesQuadrant(eps, 1); // +Y
+    checkPseudoAngleMatchesQuadrant(-eps, 1); // +Y
+    checkPseudoAngleMatchesQuadrant(-1, eps); // +X
+    checkPseudoAngleMatchesQuadrant(-1, -eps); // +X
+    checkPseudoAngleMatchesQuadrant(eps, -1); // -Y
+    checkPseudoAngleMatchesQuadrant(-eps, -1); // -Y
+
+    // axes
+    checkPseudoAngleMatchesQuadrant(1, 0); // +X
+    //checkPseudoAngleMatchesQuadrant(0, 1); // +Y
+    //checkPseudoAngleMatchesQuadrant(-1, 0); // +X
+    //checkPseudoAngleMatchesQuadrant(0, -1); // -Y
+}
+}

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

Summary of changes:
 include/geos/geom/Quadrant.h              | 18 +++++++
 src/algorithm/CircularArcs.cpp            | 22 +++++---
 tests/unit/algorithm/CircularArcsTest.cpp | 14 ++++++
 tests/unit/geom/QuadrantTest.cpp          | 83 +++++++++++++++++++++++++++++++
 4 files changed, 129 insertions(+), 8 deletions(-)
 create mode 100644 tests/unit/geom/QuadrantTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list