[geos-commits] [SCM] GEOS branch 3.14 updated. 08e7d5412fb5bca73046252b42f0586aaab6017d

git at osgeo.org git at osgeo.org
Thu Oct 23 09:36: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, 3.14 has been updated
       via  08e7d5412fb5bca73046252b42f0586aaab6017d (commit)
      from  86ba6f9aa0d694ce87637ab5ecd264c304074606 (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 08e7d5412fb5bca73046252b42f0586aaab6017d
Author: Daniel Baston <dbaston at gmail.com>
Date:   Thu Oct 23 12:35:54 2025 -0400

    CircularArcs: Fix incorrect Envelope calculation (#1314)

diff --git a/NEWS.md b/NEWS.md
index 5555b42ff..6d871d531 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -5,6 +5,7 @@
 - Fixes/Improvements:
   - Make floating-point exceptions optional for geosop (GH-1305, Maxim Kochetkov)
   - GridIntersection: Fix crash for certain polygons outside grid extent (Dan Baston)
+  - Fix incorrect envelope calculation for arcs (GH-1314, Dan Baston)
 
 
 ## Changes in 3.14.0
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:
 NEWS.md                                   |  1 +
 include/geos/geom/Quadrant.h              | 18 +++++++
 src/algorithm/CircularArcs.cpp            | 22 +++++---
 tests/unit/algorithm/CircularArcsTest.cpp | 14 ++++++
 tests/unit/geom/QuadrantTest.cpp          | 83 +++++++++++++++++++++++++++++++
 5 files changed, 130 insertions(+), 8 deletions(-)
 create mode 100644 tests/unit/geom/QuadrantTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list