[geos-commits] [SCM] GEOS branch main updated. 2e9e1b5bdeb2af6cd4b4e0161bc810e5e273235e
git at osgeo.org
git at osgeo.org
Thu Oct 1 05:39:02 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 2e9e1b5bdeb2af6cd4b4e0161bc810e5e273235e (commit)
from e0ff2cd9eda39cf7a3283e6321b0c69eca5db49b (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 2e9e1b5bdeb2af6cd4b4e0161bc810e5e273235e
Author: Daniel Baston <dbaston at gmail.com>
Date: Thu Oct 1 08:38:43 2026 -0400
CircularArcIntersector: use Orient2D to check for arc control points on line (#1546)
Resolves https://github.com/libgeos/geos/issues/1539
diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp
index 79ab344b9..f08bafa54 100644
--- a/src/algorithm/CircularArcIntersector.cpp
+++ b/src/algorithm/CircularArcIntersector.cpp
@@ -142,21 +142,21 @@ CircularArcIntersector::intersects(const CircularArc& arc, const CoordinateSeque
return;
}
- // Check for exact endpoint-endpoint or endpoint-control point intersections
- // If found, replace the computed intersection points with an exact endpoint
+ // Check if an endpoint or control point of the arc is exactly on the line.
+ // If found, replace the computed intersection point with the exact control point.
const CoordinateXY& ap0 = arc.p0<CoordinateXY>();
const CoordinateXY& ap1 = arc.p1<CoordinateXY>();
const CoordinateXY& ap2 = arc.p2<CoordinateXY>();
const CoordinateXY& bp0 = seq.getAt<CoordinateXY>(segPos0);
const CoordinateXY& bp1 = seq.getAt<CoordinateXY>(segPos1);
- if (ap0 == bp0 || ap0 == bp1) {
+ if (Orientation::index(bp0, bp1, ap0) == Orientation::COLLINEAR) {
closestPoint(isect0, isect1, nPointsIntersectingLine, ap0) = ap0;
}
- if (ap1 == bp0 || ap1 == bp1) {
+ if (Orientation::index(bp0, bp1, ap1) == Orientation::COLLINEAR) {
closestPoint(isect0, isect1, nPointsIntersectingLine, ap1) = ap1;
}
- if (ap2 == bp0 || ap2 == bp1) {
+ if (Orientation::index(bp0, bp1, ap2) == Orientation::COLLINEAR) {
closestPoint(isect0, isect1, nPointsIntersectingLine, ap2) = ap2;
}
diff --git a/tests/unit/algorithm/CircularArcIntersectorTest.cpp b/tests/unit/algorithm/CircularArcIntersectorTest.cpp
index 5d0123c17..6e5805b08 100644
--- a/tests/unit/algorithm/CircularArcIntersectorTest.cpp
+++ b/tests/unit/algorithm/CircularArcIntersectorTest.cpp
@@ -29,9 +29,19 @@ struct test_circulararcintersector_data {
using XYZM = CoordinateXYZM;
static constexpr double NaN = geos::DoubleNotANumber;
+ double eps = 1e-8;
+ bool toleranceIsDistance = true;
using ArcOrPoint = std::variant<XY, XYZ, XYM, XYZM, CircularArc>;
+ void setTolerance(double value) {
+ eps = value;
+ }
+
+ void setToleranceIsDistance(bool value) {
+ toleranceIsDistance = value;
+ }
+
static std::string to_string(CircularArcIntersector::intersection_type t)
{
switch (t) {
@@ -96,11 +106,11 @@ struct test_circulararcintersector_data {
}
template<typename C1, typename C2>
- static void checkIntersection(C1 p0, C1 p1, C1 p2,
- C2 q0, C2 q1, C2 q2,
- CircularArcIntersector::intersection_type result,
- const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
- const ArcOrPoint& i1 = CoordinateXYZM::getNull())
+ void checkIntersection(C1 p0, C1 p1, C1 p2,
+ C2 q0, C2 q1, C2 q2,
+ CircularArcIntersector::intersection_type result,
+ const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
+ const ArcOrPoint& i1 = CoordinateXYZM::getNull())
{
CoordinateSequence cs1(3, C1::template has<Ordinate::Z>(), C1::template has<Ordinate::M>());
cs1.setAt(p0, 0);
@@ -119,11 +129,11 @@ struct test_circulararcintersector_data {
}
template<typename C1, typename C2>
- static void checkIntersectionArcSeg(C1 p0, C1 p1, C1 p2,
- C2 q0, C2 q1,
- CircularArcIntersector::intersection_type result,
- const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
- const ArcOrPoint& i1 = CoordinateXYZM::getNull())
+ void checkIntersectionArcSeg(C1 p0, C1 p1, C1 p2,
+ C2 q0, C2 q1,
+ CircularArcIntersector::intersection_type result,
+ const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
+ const ArcOrPoint& i1 = CoordinateXYZM::getNull())
{
CoordinateSequence cs1(3, C1::template has<Ordinate::Z>(), C1::template has<Ordinate::M>());
cs1.setAt(p0, 0);
@@ -140,11 +150,11 @@ struct test_circulararcintersector_data {
}
template<typename C1, typename C2>
- static void checkIntersectionSegArc(C1 p0, C1 p1,
- C2 q0, C2 q1, C2 q2,
- CircularArcIntersector::intersection_type result,
- const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
- const ArcOrPoint& i1 = CoordinateXYZM::getNull())
+ void checkIntersectionSegArc(C1 p0, C1 p1,
+ C2 q0, C2 q1, C2 q2,
+ CircularArcIntersector::intersection_type result,
+ const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
+ const ArcOrPoint& i1 = CoordinateXYZM::getNull())
{
CoordinateSequence seg(2, C1::template has<Ordinate::Z>(), C1::template has<Ordinate::M>());
seg.setAt(p0, 0);
@@ -161,11 +171,11 @@ struct test_circulararcintersector_data {
}
template<typename C1, typename C2>
- static void checkIntersectionSegSeg(C1 p0, C1 p1,
- C2 q0, C2 q1,
- CircularArcIntersector::intersection_type result,
- const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
- const ArcOrPoint& i1 = CoordinateXYZM::getNull())
+ void checkIntersectionSegSeg(C1 p0, C1 p1,
+ C2 q0, C2 q1,
+ CircularArcIntersector::intersection_type result,
+ const ArcOrPoint& i0 = CoordinateXYZM::getNull(),
+ const ArcOrPoint& i1 = CoordinateXYZM::getNull())
{
CoordinateSequence seg0(2, C1::template has<Ordinate::Z>(), C1::template has<Ordinate::M>());
seg0.setAt(p0, 0);
@@ -179,7 +189,7 @@ struct test_circulararcintersector_data {
}
- static bool pointWithinTolerance(const CoordinateXYZM& actual, const CoordinateXYZM& expected, double tol)
+ static bool pointWithinTolerance(const CoordinateXYZM& actual, const CoordinateXYZM& expected, double eps, bool isDistance)
{
if (std::isnan(actual.z) != std::isnan(expected.z)) {
return false;
@@ -189,23 +199,23 @@ struct test_circulararcintersector_data {
return false;
}
- if (!std::isnan(expected.z) && std::abs(actual.z - expected.z) > tol * std::abs(expected.z)) {
+ if (!std::isnan(expected.z) && std::abs(actual.z - expected.z) > eps * std::abs(expected.z)) {
return false;
}
- if (!std::isnan(expected.m) && std::abs(actual.m - expected.m) > tol * std::abs(expected.m)) {
+ if (!std::isnan(expected.m) && std::abs(actual.m - expected.m) > eps * std::abs(expected.m)) {
return false;
}
- if (actual.distance(expected) < tol) {
+ if (isDistance && actual.distance(expected) < eps) {
return true;
}
- if (std::abs(actual.x - expected.x) > tol * std::abs(expected.x)) {
+ if (std::abs(actual.x - expected.x) > eps * std::abs(expected.x)) {
return false;
}
- if (std::abs(actual.y - expected.y) > tol * std::abs(expected.y)) {
+ if (std::abs(actual.y - expected.y) > eps * std::abs(expected.y)) {
return false;
}
@@ -213,7 +223,7 @@ struct test_circulararcintersector_data {
}
template<typename T1, typename T2>
- static void checkIntersection(const T1& a0,
+ void checkIntersection(const T1& a0,
const T2& a1,
CircularArcIntersector::intersection_type result,
const ArcOrPoint& p0 = CoordinateXYZM::getNull(),
@@ -292,11 +302,9 @@ struct test_circulararcintersector_data {
equal = false;
}
- constexpr double eps = 1e-8;
-
if (equal) {
for (std::size_t i = 0; i < actualPoints.size(); i++) {
- if (!pointWithinTolerance(actualPoints[i], expectedPoints[i], eps)) {
+ if (!pointWithinTolerance(actualPoints[i], expectedPoints[i], eps, toleranceIsDistance)) {
equal = false;
}
}
@@ -309,7 +317,7 @@ struct test_circulararcintersector_data {
equal = false;
}
- if (!pointWithinTolerance(XYZM(actualArcs[i].getCenter()), XYZM(expectedArcs[i].getCenter()), eps)) {
+ if (!pointWithinTolerance(XYZM(actualArcs[i].getCenter()), XYZM(expectedArcs[i].getCenter()), eps, toleranceIsDistance)) {
equal = false;
}
@@ -317,7 +325,7 @@ struct test_circulararcintersector_data {
actualArcs[i].getCoordinateSequence()->getAt(actualArcs[i].getCoordinatePosition(), actual0);
expectedArcs[i].getCoordinateSequence()->getAt(expectedArcs[i].getCoordinatePosition(), expected0);
- if (!pointWithinTolerance(actual0, expected0, eps)) {
+ if (!pointWithinTolerance(actual0, expected0, eps, toleranceIsDistance)) {
equal = false;
}
@@ -325,7 +333,7 @@ struct test_circulararcintersector_data {
actualArcs[i].getCoordinateSequence()->getAt(actualArcs[i].getCoordinatePosition(), actual2);
expectedArcs[i].getCoordinateSequence()->getAt(expectedArcs[i].getCoordinatePosition(), expected2);
- if (!pointWithinTolerance(actual2, expected2, eps)) {
+ if (!pointWithinTolerance(actual2, expected2, eps, toleranceIsDistance)) {
equal = false;
}
}
@@ -1753,6 +1761,56 @@ void object::test<87>()
ensure_equals(arc.p2(), arcOut.p2());
}
+template<>
+template<>
+void object::test<88>()
+{
+ // From https://github.com/libgeos/geos/issues/1539
+ set_test_name("arc endpoint intersects interior of horizontal line");
+ setTolerance(0);
+ setToleranceIsDistance(false);
+
+ // clockwise arc
+ checkIntersectionArcSeg(XY{1, 10}, XY{3, 7}, XY{1, 5},
+ XY{0, 10}, XY{10, 10},
+ CircularArcIntersector::ONE_POINT_INTERSECTION,
+ XY{1, 10});
+
+ // counter-clockwise arc
+ checkIntersectionArcSeg(XY{1, 5}, XY{3, 7}, XY{1, 10},
+ XY{0, 10}, XY{10, 10},
+ CircularArcIntersector::ONE_POINT_INTERSECTION,
+ XY{1, 10});
+}
+
+template<>
+template<>
+void object::test<89>()
+{
+ set_test_name("arc endpoint intersects interior of vertical line");
+ setTolerance(0);
+ setToleranceIsDistance(false);
+
+ checkIntersectionArcSeg(XY{-7, 3}, XY{8, 11}, XY{1043, 6},
+ XY{-7, 0}, XY{-7, 5},
+ CircularArcIntersector::ONE_POINT_INTERSECTION,
+ XY{-7, 3});
+}
+
+template<>
+template<>
+void object::test<90>()
+{
+ set_test_name("arc control point intersects interior of diagonal line");
+ setTolerance(0);
+ setToleranceIsDistance(false);
+
+ checkIntersectionArcSeg(XY{0 , 0}, XY{208, 22}, XY{140355444245, 3882200024},
+ XY{104, 11}, XY{2080, 220},
+ CircularArcIntersector::ONE_POINT_INTERSECTION,
+ XY{208, 22});
+}
+
// TODO: check Z values of arc result centerpoints
// TODO: add tests for seg/seg
diff --git a/tests/unit/operation/split/GeometrySplitterTest.cpp b/tests/unit/operation/split/GeometrySplitterTest.cpp
index 8209abca4..97192839b 100644
--- a/tests/unit/operation/split/GeometrySplitterTest.cpp
+++ b/tests/unit/operation/split/GeometrySplitterTest.cpp
@@ -936,4 +936,15 @@ void object::test<77>()
"GEOMETRYCOLLECTION (CURVEPOLYGON (COMPOUNDCURVE ((5 0, 3 0, 3 5, 5 5), CIRCULARSTRING (5 5, 7 1, 5 0))), POLYGON ((3 0, 0 0, 0 5, 3 5, 3 0)))");
}
+template<>
+template<>
+void object::test<78>()
+{
+ set_test_name("split Polygon with CircularString (#1539)");
+
+ testSplit("POLYGON ((0 5, 0 10, 10 10, 10 5, 0 5))",
+ "CIRCULARSTRING (1 10, 3 7, 1 5)",
+ "GEOMETRYCOLLECTION (CURVEPOLYGON (COMPOUNDCURVE ((0 5, 0 10, 1 10), CIRCULARSTRING (1 10, 3 7, 1 5), (1 5, 0 5))), CURVEPOLYGON (COMPOUNDCURVE ((1 10, 10 10, 10 5, 1 5), CIRCULARSTRING (1 5, 3 7, 1 10))))");
+}
+
}
diff --git a/tests/xmltester/tests/general/TestOverlayCurvesLA.xml b/tests/xmltester/tests/general/TestOverlayCurvesLA.xml
index 1cef3bf89..1c6c60340 100644
--- a/tests/xmltester/tests/general/TestOverlayCurvesLA.xml
+++ b/tests/xmltester/tests/general/TestOverlayCurvesLA.xml
@@ -117,7 +117,7 @@
<test>
<op name="union" arg1="A" arg2="B">
- GEOMETRYCOLLECTION (CURVEPOLYGON (CIRCULARSTRING (60 80, 100 120, 120 180, 140 120, 180 80, 120 90, 60 80)), CIRCULARSTRING (60 220, 120 270, 180 220, 156.0555127546399 190.91673086804016, 120 180, 83.94448724536011 190.91673086804016, 60 220), POLYGON ((100 240, 140 240, 120 220, 100 240)), LINESTRING (60 40, 60 80, 60 80.00000000000001, 60 220), LINESTRING (60 220, 60 300))
+ GEOMETRYCOLLECTION (CURVEPOLYGON (CIRCULARSTRING (60 80, 100 120, 120 180, 140 120, 180 80, 120 90, 60 80)), CIRCULARSTRING (60 220, 120 270, 180 220, 156.0555127546399 190.91673086804016, 120 180, 83.94448724536011 190.91673086804016, 60 220), POLYGON ((100 240, 140 240, 120 220, 100 240)), LINESTRING (60 40, 60 80, 60 220), LINESTRING (60 220, 60 300))
</op>
</test>
@@ -129,7 +129,7 @@
<test>
<op name="symdifference" arg1="A" arg2="B">
- GEOMETRYCOLLECTION (CURVEPOLYGON (CIRCULARSTRING (60 80, 100 120, 120 180, 140 120, 180 80, 120 90, 60 80)), CIRCULARSTRING (60 220, 120 270, 180 220, 156.0555127546399 190.91673086804016, 120 180, 83.94448724536011 190.91673086804016, 60 220), POLYGON ((100 240, 140 240, 120 220, 100 240)), LINESTRING (60 40, 60 80, 60 80.00000000000001, 60 220), LINESTRING (60 220, 60 300))
+ GEOMETRYCOLLECTION (CURVEPOLYGON (CIRCULARSTRING (60 80, 100 120, 120 180, 140 120, 180 80, 120 90, 60 80)), CIRCULARSTRING (60 220, 120 270, 180 220, 156.0555127546399 190.91673086804016, 120 180, 83.94448724536011 190.91673086804016, 60 220), POLYGON ((100 240, 140 240, 120 220, 100 240)), LINESTRING (60 40, 60 80, 60 220), LINESTRING (60 220, 60 300))
</op>
</test>
-----------------------------------------------------------------------
Summary of changes:
src/algorithm/CircularArcIntersector.cpp | 10 +-
.../unit/algorithm/CircularArcIntersectorTest.cpp | 124 +++++++++++++++------
.../unit/operation/split/GeometrySplitterTest.cpp | 11 ++
.../tests/general/TestOverlayCurvesLA.xml | 4 +-
4 files changed, 109 insertions(+), 40 deletions(-)
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list