[geos-commits] [SCM] GEOS branch main updated. 4b364c0312ab3b396377afc3bba0a7fd70cf2f51
git at osgeo.org
git at osgeo.org
Thu Oct 29 07:28: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 4b364c0312ab3b396377afc3bba0a7fd70cf2f51 (commit)
from 4d1795abc69d644d97c820639e3519065419407e (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 4b364c0312ab3b396377afc3bba0a7fd70cf2f51
Author: Daniel Baston <dbaston at gmail.com>
Date: Thu Oct 29 10:28:09 2026 -0400
CircularArcs: Fix missing candidates in closestPointsArcSegment (#1577)
Check for case where closest points are found on segment interior, arc
endpoint, or segment/arc tangency missed by intersection test.
Co-authored-by: Jeroen Bloemscheer <jbloemscheer at gmail.com>
diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp
index f997f19f0..4c403f942 100644
--- a/src/algorithm/CircularArcs.cpp
+++ b/src/algorithm/CircularArcs.cpp
@@ -502,7 +502,7 @@ CircularArcs::closestPointsArcSegment(const CoordinateXY& c, double r,
}
// Calculate distance between line and circle center
- const CoordinateXY d = geom::LineSegment::closestPoint(q0, q1, c);
+ const CoordinateXY d = LineSegment::closestPoint(q0, q1, c);
const double dist_cd = d.distance(c);
if (dist_cd <= r) {
@@ -516,14 +516,18 @@ CircularArcs::closestPointsArcSegment(const CoordinateXY& c, double r,
return {isect1, isect1};
}
- // Closest point is one of the segment endpoints.
- const auto e0 = closestPointArcPoint(c, r, p0, p2, isCCW, q0);
- const auto e1 = closestPointArcPoint(c, r, p0, p2, isCCW, q1);
- if (e0.distance(q0) < e1.distance(q1)) {
- return {e0, q0};
- }
+ // Closest point should be a segment or arc endpoint
+ std::array<std::array<CoordinateXY, 2>, 5> candidates;
+ candidates[0] = {closestPointArcPoint(c, r, p0, p2, isCCW, q0), q0};
+ candidates[1] = {closestPointArcPoint(c, r, p0, p2, isCCW, q1), q1};
+ candidates[2] = {p0, LineSegment::closestPoint(q0, q1, p0)};
+ candidates[3] = {p2, LineSegment::closestPoint(q0, q1, p2)};
+ // But we could have also missed a tangency point
+ candidates[4] = {closestPointArcPoint(c, r, p0, p2, isCCW, d), d};
- return {e1, q1};
+ return *std::min_element(candidates.begin(), candidates.end(), [](const auto& a, const auto& b) {
+ return a[0].distance(a[1]) < b[0].distance(b[1]);
+ });
}
// Find closest point on circle
@@ -541,10 +545,10 @@ CircularArcs::closestPointsArcSegment(const CoordinateXY& c, double r,
const auto e1 = LineSegment::closestPoint(q0, q1, p2);
if (e0.distance(p0) < e1.distance(p2)) {
- return {e0, p0};
+ return {p0, e0};
}
- return {e1, p2};
+ return {p2, e1};
}
double
diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp
index a19ccb50d..b1de7b939 100644
--- a/tests/unit/algorithm/CircularArcsTest.cpp
+++ b/tests/unit/algorithm/CircularArcsTest.cpp
@@ -1,6 +1,7 @@
#include <tut/tut.hpp>
#include <geos/geom/Coordinate.h>
+#include <geos/geom/CircularString.h>
#include <geos/algorithm/CircularArcs.h>
#include <geos/geom/CircularArc.h>
#include <geos/geom/Quadrant.h>
@@ -1006,8 +1007,7 @@ set_test_name("getMidpointAngle");
template<>
template<>
-void object::test<71>()
-{
+void object::test<71>() {
set_test_name("containsAngle");
using geos::algorithm::Orientation;
@@ -1043,4 +1043,90 @@ void object::test<71>()
checkAngleContained(MATH_PI, MATH_PI/4, 9*MATH_PI/4, BOTH_);
}
+template<>
+template<>
+void object::test<72>()
+{
+ set_test_name("arc-seg distance, segment inside circle, closest to arc endpoint");
+
+ // The segment lies inside the circle and does not reach the arc, so the
+ // closest pair joins the arc endpoint at 70 degrees to the segment interior.
+ const double r = 10;
+ CircularArc arc = CircularArc::create(XY{r*std::cos(MATH_PI/3), r*std::sin(MATH_PI/3)},
+ XY{r*std::cos(13*MATH_PI/36), r*std::sin(13*MATH_PI/36)},
+ XY{r*std::cos(7*MATH_PI/18), r*std::sin(7*MATH_PI/18)});
+ XY q0{2, 0};
+ XY q1{2, 9.5};
+
+ ensure_equals("distance", arc.distance(q0, q1), arc.p2().x - 2, 1e-12);
+}
+
+template<>
+template<>
+void object::test<73>()
+{
+ set_test_name("arc-seg distance, segment outside circle, closest to segment endpoint");
+
+ // The closest pair joins a segment endpoint to the arc interior.
+ CircularArc arc = CircularArc::create(XY{-3.48172, 2.91175}, XY{9.45735, -6.02278}, XY{1.44247, -0.448891});
+ XY q0{4.48559, 4.09741};
+ XY q1{-5.45188, -9.20177};
+
+ ensure_equals("distance", arc.distance(q0, q1), 0.2835665009749685, 1e-9);
+
+ const auto pts = CircularArcs::closestPointsArcSegment(arc.getCenter(), arc.getRadius(),
+ arc.p0(), arc.p2(), arc.isCCW(), q0, q1);
+ ensure("first point is on the arc", std::abs(pts[0].distance(arc.getCenter()) - arc.getRadius()) < 1e-9);
+ ensure_equals("closest points distance", pts[0].distance(pts[1]), 0.2835665009749685, 1e-9);
+}
+
+template<>
+template<>
+void object::test<74>()
+{
+ set_test_name("arc-seg distance, segment tangent to arc");
+
+ // The segment is tangent to the circle at (4, 3), which lies on the arc,
+ // but roundoff hides the intersection from circleIntersectsSegment.
+ CircularArc arc = CircularArc::create(XY{0, -5}, XY{4, -3}, XY{0, 5});
+ XY q0{7, -1};
+ XY q1{1, 7};
+
+ ensure_equals("distance", arc.distance(q0, q1), 0, 1e-12);
+ ensure_equals("distance (reversed segment)", arc.distance(q1, q0), 0, 1e-12);
+}
+
+template<>
+template<>
+void object::test<75>()
+{
+ set_test_name("arc-seg distance, segment nearly tangent to arc");
+
+ // Found by differential testing against NetTopologySuite.Proofs ARC_SEGMENT_DISTANCE.
+ CircularArc arc = CircularArc::create(XY{-81.85405104117687, -125.94712311622965},
+ XY{118.46299847713223, -81.33910481556457},
+ XY{73.85498017646715, 118.97794470274454});
+ XY q0{176.08930671152348, -164.72607692939968};
+ XY q1{97.98804198508518, 227.31432409657776};
+
+ ensure_equals("distance", arc.distance(q0, q1), 0, 1e-9);
+ ensure_equals("distance (reversed segment)", arc.distance(q1, q0), 0, 1e-9);
+}
+
+template<>
+template<>
+void object::test<76>()
+{
+ set_test_name("arc-seg closest points, segment outside circle, closest to arc endpoint");
+
+ // Closest pair joins the arc endpoint (5, 0) to the segment endpoint (10, -1).
+ CircularArc arc = CircularArc::create(XY{5, 0}, XY{3, 4}, XY{0, 5});
+ XY q0{10, -10};
+ XY q1{10, -1};
+
+ const auto pts = arc.closestPoints(q0, q1);
+ ensure_equals("first point is on the arc", pts[0], XY{5, 0});
+ ensure_equals("second point is on the segment", pts[1], XY{10, -1});
+}
+
}
-----------------------------------------------------------------------
Summary of changes:
src/algorithm/CircularArcs.cpp | 24 +++++----
tests/unit/algorithm/CircularArcsTest.cpp | 90 ++++++++++++++++++++++++++++++-
2 files changed, 102 insertions(+), 12 deletions(-)
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list