From afffbd0de6544897af3eeb55c65b0af5e677d940 Mon Sep 17 00:00:00 2001 From: Dan Baston Date: Fri, 31 Oct 2025 09:21:27 -0400 Subject: [PATCH 1/3] Add test for PostGIS ticket #5832 --- tests/unit/capi/GEOSIntersectsTest.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit/capi/GEOSIntersectsTest.cpp b/tests/unit/capi/GEOSIntersectsTest.cpp index fe11fd70e5..7c22ec46a1 100644 --- a/tests/unit/capi/GEOSIntersectsTest.cpp +++ b/tests/unit/capi/GEOSIntersectsTest.cpp @@ -256,5 +256,17 @@ void object::test<13>() ensure_equals(GEOSIntersects(geom2_, geom1_), 0); } +template<> +template<> +void object::test<14>() +{ + set_test_name("PostGIS ticket 5832"); + + geom1_ = fromWKT("CURVEPOLYGON(COMPOUNDCURVE((25492739.7449 6677441.1816,25492771.1213 6677416.7832),CIRCULARSTRING(25492771.1213 6677416.7832,25492832.3384 6677400.8583,25492885.9851 6677434.3719),(25492885.9851 6677434.3719,25492900.7986 6677455.7498),CIRCULARSTRING(25492900.7986 6677455.7498,25492901.1068 6677457.3233,25492900.1601 6677458.6175,25492822.2626 6677477.2365,25492747.0232 6677449.7828),(25492747.0232 6677449.7828,25492739.7748 6677444.3615),CIRCULARSTRING(25492739.7748 6677444.3615,25492738.9731 6677442.7789,25492739.7449 6677441.1816)))"); + geom2_ = fromWKT("POINT (25492818 6677399.98)"); + + ensure_equals(GEOSIntersects(geom1_, geom2_), 1); +} + } // namespace tut From 919c7ff9cb7cf6d8c29ea488d3abf626153cc9a6 Mon Sep 17 00:00:00 2001 From: Dan Baston Date: Fri, 31 Oct 2025 09:36:14 -0400 Subject: [PATCH 2/3] Geometry::intersection: handle MultiSurface PIP --- src/geom/Geometry.cpp | 11 +++++------ tests/unit/capi/GEOSIntersectsTest.cpp | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/geom/Geometry.cpp b/src/geom/Geometry.cpp index a34c7bf352..a134ec7544 100644 --- a/src/geom/Geometry.cpp +++ b/src/geom/Geometry.cpp @@ -324,12 +324,11 @@ Geometry::intersects(const Geometry* g) const } auto typ = getGeometryTypeId(); - if (typ == GEOS_CURVEPOLYGON && g->getGeometryTypeId() == GEOS_POINT) { - auto loc = locate::SimplePointInAreaLocator::locatePointInSurface(*g->getCoordinate(), *detail::down_cast(this)); - return loc != Location::EXTERIOR; - } else if (typ == GEOS_POINT && g->getGeometryTypeId() == GEOS_CURVEPOLYGON) { - auto loc = locate::SimplePointInAreaLocator::locatePointInSurface(*getCoordinate(), *detail::down_cast(g)); - return loc != Location::EXTERIOR; + if ((typ == GEOS_CURVEPOLYGON || typ == GEOS_MULTISURFACE) && g->getGeometryTypeId() == GEOS_POINT) { + return locate::SimplePointInAreaLocator::isContained(*g->getCoordinate(), this); + } + if (typ == GEOS_POINT && (g->getGeometryTypeId() == GEOS_CURVEPOLYGON || g->getGeometryTypeId() == GEOS_MULTISURFACE)) { + return locate::SimplePointInAreaLocator::isContained(*getCoordinate(), g); } #if USE_RELATENG diff --git a/tests/unit/capi/GEOSIntersectsTest.cpp b/tests/unit/capi/GEOSIntersectsTest.cpp index 7c22ec46a1..8fde1879b9 100644 --- a/tests/unit/capi/GEOSIntersectsTest.cpp +++ b/tests/unit/capi/GEOSIntersectsTest.cpp @@ -262,7 +262,7 @@ void object::test<14>() { set_test_name("PostGIS ticket 5832"); - geom1_ = fromWKT("CURVEPOLYGON(COMPOUNDCURVE((25492739.7449 6677441.1816,25492771.1213 6677416.7832),CIRCULARSTRING(25492771.1213 6677416.7832,25492832.3384 6677400.8583,25492885.9851 6677434.3719),(25492885.9851 6677434.3719,25492900.7986 6677455.7498),CIRCULARSTRING(25492900.7986 6677455.7498,25492901.1068 6677457.3233,25492900.1601 6677458.6175,25492822.2626 6677477.2365,25492747.0232 6677449.7828),(25492747.0232 6677449.7828,25492739.7748 6677444.3615),CIRCULARSTRING(25492739.7748 6677444.3615,25492738.9731 6677442.7789,25492739.7449 6677441.1816)))"); + geom1_ = fromWKT("MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((25492739.7449 6677441.1816,25492771.1213 6677416.7832),CIRCULARSTRING(25492771.1213 6677416.7832,25492832.3384 6677400.8583,25492885.9851 6677434.3719),(25492885.9851 6677434.3719,25492900.7986 6677455.7498),CIRCULARSTRING(25492900.7986 6677455.7498,25492901.1068 6677457.3233,25492900.1601 6677458.6175,25492822.2626 6677477.2365,25492747.0232 6677449.7828),(25492747.0232 6677449.7828,25492739.7748 6677444.3615),CIRCULARSTRING(25492739.7748 6677444.3615,25492738.9731 6677442.7789,25492739.7449 6677441.1816))))"); geom2_ = fromWKT("POINT (25492818 6677399.98)"); ensure_equals(GEOSIntersects(geom1_, geom2_), 1); From 038b90c43989e41294cd1c88576a86872bde53e9 Mon Sep 17 00:00:00 2001 From: Dan Baston Date: Fri, 31 Oct 2025 10:25:01 -0400 Subject: [PATCH 3/3] CurvePolygon PIP: apply to collections and contains, disjoint predicates --- .../locate/SimplePointInAreaLocator.h | 4 +++ .../locate/SimplePointInAreaLocator.cpp | 30 ++++++++++++++++ src/geom/Geometry.cpp | 26 ++++++++++---- tests/unit/capi/GEOSContainsTest.cpp | 35 +++++++++++++++++++ tests/unit/capi/GEOSDisjointTest.cpp | 14 ++++++++ tests/unit/capi/GEOSIntersectsTest.cpp | 5 +-- 6 files changed, 106 insertions(+), 8 deletions(-) diff --git a/include/geos/algorithm/locate/SimplePointInAreaLocator.h b/include/geos/algorithm/locate/SimplePointInAreaLocator.h index be77ff9d61..11fe95d7d5 100644 --- a/include/geos/algorithm/locate/SimplePointInAreaLocator.h +++ b/include/geos/algorithm/locate/SimplePointInAreaLocator.h @@ -87,6 +87,10 @@ class GEOS_DLL SimplePointInAreaLocator : public PointOnGeometryLocator { static bool isContained(const geom::CoordinateXY& p, const geom::Geometry* geom); + static bool isAnyPointContained(const geom::Geometry& pt, const geom::Geometry& areaGeom); + + static bool isEveryPointContained(const geom::Geometry& pt, const geom::Geometry& areaGeom); + SimplePointInAreaLocator(const geom::Geometry* p_g) : g(p_g) { } diff --git a/src/algorithm/locate/SimplePointInAreaLocator.cpp b/src/algorithm/locate/SimplePointInAreaLocator.cpp index 0a7b8e3437..d0894ec4d9 100644 --- a/src/algorithm/locate/SimplePointInAreaLocator.cpp +++ b/src/algorithm/locate/SimplePointInAreaLocator.cpp @@ -46,6 +46,36 @@ SimplePointInAreaLocator::isContained(const CoordinateXY& p, const Geometry* geo return Location::EXTERIOR != locate(p, geom); } +bool +SimplePointInAreaLocator::isAnyPointContained(const geom::Geometry& pt, const geom::Geometry& area) +{ + if (pt.getNumGeometries() > 1) { + for (size_t i = 0; i < pt.getNumGeometries(); i++ ) { + if (isAnyPointContained(*pt.getGeometryN(i), area)) { + return true; + } + } + return false; + } + + return isContained(*pt.getCoordinate(), &area); +} + +bool +SimplePointInAreaLocator::isEveryPointContained(const geom::Geometry &pt, const geom::Geometry &area) +{ + if (pt.getNumGeometries() > 1) { + for (size_t i = 0; i < pt.getNumGeometries(); i++ ) { + if (!isEveryPointContained(*pt.getGeometryN(i), area)) { + return false; + } + } + return true; + } + + return isContained(*pt.getCoordinate(), &area); +} + geom::Location SimplePointInAreaLocator::locateInGeometry(const CoordinateXY& p, const Geometry* geom) { diff --git a/src/geom/Geometry.cpp b/src/geom/Geometry.cpp index a134ec7544..ceaba5db69 100644 --- a/src/geom/Geometry.cpp +++ b/src/geom/Geometry.cpp @@ -261,6 +261,10 @@ Geometry::getEnvelope() const bool Geometry::disjoint(const Geometry* g) const { + if (hasCurvedComponents() || g->hasCurvedComponents()) { + return !intersects(g); + } + #if USE_RELATENG return operation::relateng::RelateNG::disjoint(this, g); #else @@ -323,12 +327,13 @@ Geometry::intersects(const Geometry* g) const return predicate::RectangleIntersects::intersects(*p, *this); } - auto typ = getGeometryTypeId(); - if ((typ == GEOS_CURVEPOLYGON || typ == GEOS_MULTISURFACE) && g->getGeometryTypeId() == GEOS_POINT) { - return locate::SimplePointInAreaLocator::isContained(*g->getCoordinate(), this); - } - if (typ == GEOS_POINT && (g->getGeometryTypeId() == GEOS_CURVEPOLYGON || g->getGeometryTypeId() == GEOS_MULTISURFACE)) { - return locate::SimplePointInAreaLocator::isContained(*getCoordinate(), g); + if (hasCurvedComponents() || g->hasCurvedComponents()) { + if (getDimension() == Dimension::A && g->getDimension() == Dimension::P) { + return locate::SimplePointInAreaLocator::isAnyPointContained(*g, *this); + } + if (g->getDimension() == Dimension::A && getDimension() == Dimension::P) { + return locate::SimplePointInAreaLocator::isAnyPointContained(*this, *g); + } } #if USE_RELATENG @@ -425,6 +430,15 @@ Geometry::within(const Geometry* g) const bool Geometry::contains(const Geometry* g) const { + if (hasCurvedComponents() || g->hasCurvedComponents()) { + if (getDimension() == Dimension::A && g->getDimension() == Dimension::P) { + return locate::SimplePointInAreaLocator::isEveryPointContained(*g, *this); + } + if (g->getDimension() == Dimension::A && getDimension() == Dimension::P) { + return locate::SimplePointInAreaLocator::isEveryPointContained(*this, *g); + } + } + #if USE_RELATENG return operation::relateng::RelateNG::contains(this, g); #else diff --git a/tests/unit/capi/GEOSContainsTest.cpp b/tests/unit/capi/GEOSContainsTest.cpp index 3d377e8295..9d7b2f96a6 100644 --- a/tests/unit/capi/GEOSContainsTest.cpp +++ b/tests/unit/capi/GEOSContainsTest.cpp @@ -201,6 +201,41 @@ void object::test<6>() ensure_equals("curved geometry not supported", GEOSContains(geom1_, geom2_), 2); } +template<> +template<> +void object::test<7>() +{ + set_test_name("Single-point multipoint contained by MultiSurface"); + + geom1_ = fromWKT("MULTISURFACE(POLYGON ((100 100, 200 100, 200 200, 100 100)), CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 2 0), (2 0, 0 0))))"); + geom2_ = fromWKT("MULTIPOINT ((0.1556955 0.5355459))"); + + ensure_equals(GEOSContains(geom1_, geom2_), 1); +} + +template<> +template<> +void object::test<8>() +{ + set_test_name("Only 1 part of 2-point MultiPoint contained by MultiSurface"); + + geom1_ = fromWKT("MULTISURFACE(POLYGON ((100 100, 200 100, 200 200, 100 100)), CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 2 0), (2 0, 0 0))))"); + geom2_ = fromWKT("MULTIPOINT ((0.1556955 0.5355459), (500 500))"); + + ensure_equals(GEOSContains(geom1_, geom2_), 0); +} + +template<> +template<> +void object::test<9>() +{ + set_test_name("MultiPoint contained by MultiSurface"); + + geom1_ = fromWKT("MULTISURFACE(POLYGON ((100 100, 200 100, 200 200, 100 100)), CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 2 0), (2 0, 0 0))))"); + geom2_ = fromWKT("MULTIPOINT ((0.1556955 0.5355459), (199 101))"); + + ensure_equals(GEOSContains(geom1_, geom2_), 1); +} } // namespace tut diff --git a/tests/unit/capi/GEOSDisjointTest.cpp b/tests/unit/capi/GEOSDisjointTest.cpp index f17faca3aa..469b757923 100644 --- a/tests/unit/capi/GEOSDisjointTest.cpp +++ b/tests/unit/capi/GEOSDisjointTest.cpp @@ -42,5 +42,19 @@ void object::test<2>() ensure_equals("curved geometry not supported", GEOSDisjoint(geom2_, geom1_), 2); } +template<> +template<> +void object::test<3>() +{ + set_test_name("MultiSurface / MultiPoint PIP"); + + geom1_ = fromWKT("MULTISURFACE(POLYGON ((100 100, 200 100, 200 200, 100 100)), CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 2 0), (2 0, 0 0))))"); + geom2_ = fromWKT("MULTIPOINT ((5000 5000), (0.1556955 0.5355459))"); + + ensure_equals(GEOSDisjoint(geom1_, geom2_), 0); + ensure_equals(GEOSDisjoint(geom2_, geom1_), 0); +} + + } // namespace tut diff --git a/tests/unit/capi/GEOSIntersectsTest.cpp b/tests/unit/capi/GEOSIntersectsTest.cpp index 8fde1879b9..e4f6d12562 100644 --- a/tests/unit/capi/GEOSIntersectsTest.cpp +++ b/tests/unit/capi/GEOSIntersectsTest.cpp @@ -236,8 +236,8 @@ template<> template<> void object::test<12>() { - geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 2 0), (2 0, 0 0)))"); - geom2_ = fromWKT("POINT (0.1556955 0.5355459)"); + geom1_ = fromWKT("MULTISURFACE(POLYGON ((100 100, 200 100, 200 200, 100 100)), CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 2 0), (2 0, 0 0))))"); + geom2_ = fromWKT("MULTIPOINT ((5000 5000), (0.1556955 0.5355459))"); // PostGIS would return false here because geom2 is inside geom1 // but outside the linearized form of geom1 @@ -266,6 +266,7 @@ void object::test<14>() geom2_ = fromWKT("POINT (25492818 6677399.98)"); ensure_equals(GEOSIntersects(geom1_, geom2_), 1); + ensure_equals(GEOSIntersects(geom2_, geom1_), 1); } } // namespace tut