From 6237980962a9c2d09987a0695280670a6f18a804 Mon Sep 17 00:00:00 2001 From: Petr Belohlavek Date: Wed, 5 Aug 2026 20:34:27 +0200 Subject: [PATCH 1/3] Add regression unit tests for #1405 (non-commutative polygon intersection) Signed-off-by: Petr Belohlavek --- tests/unit/edgegraph/EdgeGraphTest.cpp | 19 ++++++++++++ .../overlayng/OverlayNGRobustTest.cpp | 30 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/tests/unit/edgegraph/EdgeGraphTest.cpp b/tests/unit/edgegraph/EdgeGraphTest.cpp index 9d96139e34..a14d443a1c 100644 --- a/tests/unit/edgegraph/EdgeGraphTest.cpp +++ b/tests/unit/edgegraph/EdgeGraphTest.cpp @@ -170,6 +170,25 @@ void object::test<5> () checkNextPrev(*graph); } +// testSimilarEdgesDirection +template<> +template<> +void object::test<6> () +{ + EdgeGraph graph; + HalfEdge* e1 = addEdge(graph, 1, 1, 0, 0.5); + addEdge(graph, 1, 1, 0, 0.49999999999999994); + addEdge(graph, 1, 1, 0, 1); + + HalfEdge* eUpper = findEdge(graph, 1, 1, 0, 0.5); + HalfEdge* eLower = findEdge(graph, 1, 1, 0, 0.49999999999999994); + ensure("edges with distinct direction points must not compare equal", eUpper->compareTo(eLower) != 0); + ensure("edge comparison must be antisymmetric", eUpper->compareTo(eLower) == -eLower->compareTo(eUpper)); + + checkNodeValid(e1); + checkNextPrev(graph); +} + } // namespace tut diff --git a/tests/unit/operation/overlayng/OverlayNGRobustTest.cpp b/tests/unit/operation/overlayng/OverlayNGRobustTest.cpp index d48518cf92..5a27cdeb7f 100644 --- a/tests/unit/operation/overlayng/OverlayNGRobustTest.cpp +++ b/tests/unit/operation/overlayng/OverlayNGRobustTest.cpp @@ -61,6 +61,17 @@ struct test_overlayngrobust_data { ensure_NO_THROW( OverlayNGRobust::Overlay(geom_a.get(), geom_b.get(), opCode) ); } + void + checkOverlaySymmetricArea(const std::string& a, const std::string& b, int opCode, double expectedArea) + { + std::unique_ptr geom_a = r.read(a); + std::unique_ptr geom_b = r.read(b); + double areaAB = OverlayNGRobust::Overlay(geom_a.get(), geom_b.get(), opCode)->getArea(); + double areaBA = OverlayNGRobust::Overlay(geom_b.get(), geom_a.get(), opCode)->getArea(); + ensure_equals("overlay is not commutative", areaAB, areaBA, 1e-12); + ensure_equals("unexpected overlay area", areaAB, expectedArea, 1e-12); + } + std::unique_ptr double2geom(const std::vector& x, const std::vector& y) { @@ -109,7 +120,26 @@ void object::test<2> () checkOverlaySuccess(a, b, OverlayNG::INTERSECTION); } +// 2026-08-05 Intersection of polygons isn't commutative (https://github.com/libgeos/geos/issues/1405) +template<> +template<> +void object::test<3> () +{ + set_test_name("Intersection of triangles with an extremely similar edge"); + const std::string a = "POLYGON ((1 1, 0 0.5, 0 0, 1 1))"; + const std::string b = "POLYGON ((1 1, 0 0.49999999999999994, 0 1, 1 1))"; + checkOverlaySymmetricArea(a, b, OverlayNG::INTERSECTION, 0.0); +} +template<> +template<> +void object::test<4> () +{ + set_test_name("Union of triangles with an extremely similar edge"); + const std::string a = "POLYGON ((1 1, 0 0.5, 0 0, 1 1))"; + const std::string b = "POLYGON ((1 1, 0 0.49999999999999994, 0 1, 1 1))"; + checkOverlaySymmetricArea(a, b, OverlayNG::UNION, 0.5); +} #if 0 /** From 9f360dd06342061e4181c261760dc59f88388583 Mon Sep 17 00:00:00 2001 From: Petr Belohlavek Date: Wed, 5 Aug 2026 20:35:15 +0200 Subject: [PATCH 2/3] Use equals2D in EdgeEnd::compareDirection and HalfEdge::compareAngularDirection Signed-off-by: Petr Belohlavek --- src/edgegraph/HalfEdge.cpp | 11 +++-------- src/geomgraph/EdgeEnd.cpp | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/edgegraph/HalfEdge.cpp b/src/edgegraph/HalfEdge.cpp index 54b5658e0e..5468b6a343 100644 --- a/src/edgegraph/HalfEdge.cpp +++ b/src/edgegraph/HalfEdge.cpp @@ -179,17 +179,12 @@ HalfEdge::findLowest() const int HalfEdge::compareAngularDirection(const HalfEdge* e) const { - double dx = directionX(); - double dy = directionY(); - double dx2 = e->directionX(); - double dy2 = e->directionY(); - // same vector - if (dx == dx2 && dy == dy2) + if (directionPt().equals2D(e->directionPt())) return 0; - int quadrant = geom::Quadrant::quadrant(dx, dy); - int quadrant2 = geom::Quadrant::quadrant(dx2, dy2); + int quadrant = geom::Quadrant::quadrant(directionX(), directionY()); + int quadrant2 = geom::Quadrant::quadrant(e->directionX(), e->directionY()); /** * If the direction vectors are in different quadrants, diff --git a/src/geomgraph/EdgeEnd.cpp b/src/geomgraph/EdgeEnd.cpp index 18f9a99887..0122199076 100644 --- a/src/geomgraph/EdgeEnd.cpp +++ b/src/geomgraph/EdgeEnd.cpp @@ -160,7 +160,7 @@ int EdgeEnd::compareDirection(const EdgeEnd* e) const { assert(e); - if(dx == e->dx && dy == e->dy) { + if(p1.equals2D(e->p1)) { return 0; } From 491fcbd9f4300a2ccca66e8e7c4b7681f97a274c Mon Sep 17 00:00:00 2001 From: Petr Belohlavek Date: Wed, 12 Aug 2026 19:41:53 +0200 Subject: [PATCH 3/3] Assert EdgeEnd::compareDirection HalfEdge::compareAngularDirection originate in the same point Signed-off-by: Petr Belohlavek --- src/edgegraph/HalfEdge.cpp | 4 ++-- src/geomgraph/EdgeEnd.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/edgegraph/HalfEdge.cpp b/src/edgegraph/HalfEdge.cpp index 5468b6a343..cdafbd7348 100644 --- a/src/edgegraph/HalfEdge.cpp +++ b/src/edgegraph/HalfEdge.cpp @@ -179,6 +179,8 @@ HalfEdge::findLowest() const int HalfEdge::compareAngularDirection(const HalfEdge* e) const { + assert(orig().equals2D(e->orig())); + // same vector if (directionPt().equals2D(e->directionPt())) return 0; @@ -267,5 +269,3 @@ HalfEdge::toStringNode(const HalfEdge* he, std::ostream& os) } // namespace geos.edgegraph } // namespace geos - - diff --git a/src/geomgraph/EdgeEnd.cpp b/src/geomgraph/EdgeEnd.cpp index 0122199076..d9c094bea2 100644 --- a/src/geomgraph/EdgeEnd.cpp +++ b/src/geomgraph/EdgeEnd.cpp @@ -160,6 +160,8 @@ int EdgeEnd::compareDirection(const EdgeEnd* e) const { assert(e); + assert(p0.equals2D(e->p0)); + if(p1.equals2D(e->p1)) { return 0; } @@ -215,4 +217,3 @@ operator<< (std::ostream& os, const EdgeEnd& ee) } // namespace geos.geomgraph } // namespace geos -