From 1d9f998d8cc7eb3b82b2ffff595716e3fc5dfb28 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Mon, 30 Sep 2024 13:19:11 -0700 Subject: [PATCH 1/3] Fix ConcaveHullOfPolygons nested shell handling (#1169) --- .../algorithm/hull/ConcaveHullOfPolygons.h | 4 - .../algorithm/hull/OuterShellsExtracter.h | 75 +++++++++++ src/algorithm/hull/ConcaveHullOfPolygons.cpp | 18 +-- src/algorithm/hull/OuterShellsExtracter.cpp | 123 ++++++++++++++++++ .../hull/ConcaveHullOfPolygonsTest.cpp | 22 ++++ 5 files changed, 222 insertions(+), 20 deletions(-) create mode 100644 include/geos/algorithm/hull/OuterShellsExtracter.h create mode 100644 src/algorithm/hull/OuterShellsExtracter.cpp diff --git a/include/geos/algorithm/hull/ConcaveHullOfPolygons.h b/include/geos/algorithm/hull/ConcaveHullOfPolygons.h index 861e9a37d8..1c99ab9ed2 100644 --- a/include/geos/algorithm/hull/ConcaveHullOfPolygons.h +++ b/include/geos/algorithm/hull/ConcaveHullOfPolygons.h @@ -124,10 +124,6 @@ class GEOS_DLL ConcaveHullOfPolygons { std::unique_ptr createEmptyHull(); - static void extractShellRings( - const Geometry* polygons, - std::vector& rings); - void buildHullTris(); /** diff --git a/include/geos/algorithm/hull/OuterShellsExtracter.h b/include/geos/algorithm/hull/OuterShellsExtracter.h new file mode 100644 index 0000000000..ad0a054b05 --- /dev/null +++ b/include/geos/algorithm/hull/OuterShellsExtracter.h @@ -0,0 +1,75 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +namespace geos { +namespace geom { +class Coordinate; +class CoordinateSequence; +class Envelope; +class Geometry; +class GeometryCollection; +class GeometryFactory; +class LinearRing; +class Polygon; +} +} + +using geos::geom::Geometry; +using geos::geom::LinearRing; + +namespace geos { +namespace algorithm { // geos::algorithm +namespace hull { // geos::algorithm::hull + +/** + * Extracts the rings of outer shells from a polygonal geometry. + * Outer shells are the shells of polygon elements which + * are not nested inside holes of other polygons. + * + * \author Martin Davis + */ +class OuterShellsExtracter { +private: + + OuterShellsExtracter(const Geometry& g); + + void extractOuterShells(std::vector& outerShells); + + bool isOuter(const LinearRing& shell, std::vector& outerShells); + + bool covers(const LinearRing& shellA, const LinearRing& shellB); + + bool isPointInRing(const LinearRing& shell, const LinearRing& shellRing); + + static void extractShellRings(const Geometry& polygons, std::vector& shells); + + static bool envelopeAreaComparator( + const LinearRing* g1, + const LinearRing* g2); + + const Geometry& geom; + +public: + static void extractShells(const Geometry* polygons, std::vector& shells); + +}; + +} // geos::algorithm::hull +} // geos::algorithm +} // geos + diff --git a/src/algorithm/hull/ConcaveHullOfPolygons.cpp b/src/algorithm/hull/ConcaveHullOfPolygons.cpp index a774cf6f0f..623a7662ab 100644 --- a/src/algorithm/hull/ConcaveHullOfPolygons.cpp +++ b/src/algorithm/hull/ConcaveHullOfPolygons.cpp @@ -13,6 +13,7 @@ **********************************************************************/ #include +#include #include #include #include @@ -179,26 +180,11 @@ ConcaveHullOfPolygons::createEmptyHull() return geomFactory->createPolygon(); } -/* private static */ -void -ConcaveHullOfPolygons::extractShellRings(const Geometry* polygons, std::vector& rings) -{ - rings.clear(); - for (std::size_t i = 0; i < polygons->getNumGeometries(); i++) { - const Geometry* consGeom = polygons->getGeometryN(i); - const Polygon* consPoly = static_cast(consGeom); - const LinearRing* lr = consPoly->getExteriorRing(); - rings.push_back(lr); - } - return; -} - - /* private */ void ConcaveHullOfPolygons::buildHullTris() { - extractShellRings(inputPolygons, polygonRings); + OuterShellsExtracter::extractShells(inputPolygons, polygonRings); std::unique_ptr frame = createFrame(inputPolygons->getEnvelopeInternal()); ConstrainedDelaunayTriangulator::triangulatePolygon(frame.get(), triList); //System.out.println(tris); diff --git a/src/algorithm/hull/OuterShellsExtracter.cpp b/src/algorithm/hull/OuterShellsExtracter.cpp new file mode 100644 index 0000000000..69c94121d6 --- /dev/null +++ b/src/algorithm/hull/OuterShellsExtracter.cpp @@ -0,0 +1,123 @@ + +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include + +using geos::algorithm::PointLocation; +using geos::geom::Polygon; +using geos::geom::Coordinate; + +namespace geos { +namespace algorithm { +namespace hull { + +/* public static */ +void +OuterShellsExtracter::extractShells(const Geometry* polygons, std::vector& shells) +{ + OuterShellsExtracter extracter(*polygons); + extracter.extractOuterShells(shells); +} + +/* private */ +OuterShellsExtracter::OuterShellsExtracter(const geom::Geometry& g) + : geom(g) +{ +} + +/* private */ +void +OuterShellsExtracter::extractOuterShells(std::vector& outerShells) +{ + std::vector shells; + extractShellRings(geom, shells); + //-- sort shells in order of increasing envelope area + std::sort(shells.begin(), shells.end(), envelopeAreaComparator); + + //-- Scan shells by decreasing area to ensure that shells are added before any nested shells + for (auto i = shells.rbegin(); i != shells.rend(); ++i) { + const LinearRing* shell = *i; + if (outerShells.size() == 0 + || isOuter(*shell, outerShells)) { + outerShells.push_back(shell); + } + } +} + +bool +OuterShellsExtracter::envelopeAreaComparator( + const LinearRing* g1, + const LinearRing* g2) +{ + double area1 = g1->getEnvelopeInternal()->getArea(); + double area2 = g2->getEnvelopeInternal()->getArea(); + if (area1 < area2) + return true; + else + return false; +} + +/* private */ +bool +OuterShellsExtracter::isOuter(const LinearRing& shell, std::vector& outerShells) +{ + for (const LinearRing* outShell : outerShells) { + if (covers(*outShell, shell)) { + return false; + } + } + return true; +} + +/* private */ +bool +OuterShellsExtracter::covers(const LinearRing& shellA, const LinearRing& shellB) +{ + //-- if shellB envelope is not covered then shell is not covered + if (! shellA.getEnvelopeInternal()->covers(shellB.getEnvelopeInternal())) + return false; + //-- if a shellB point lies inside shellA, shell is covered (since shells do not overlap) + if (isPointInRing(shellB, shellA)) + return true; + return false; +} + +bool +OuterShellsExtracter::isPointInRing(const LinearRing& shell, const LinearRing& shellRing) +{ + //TODO: optimize this with cached index + const Coordinate* pt = shell.getCoordinate(); + return PointLocation::isInRing(*pt, shellRing.getCoordinatesRO()); +} + +void +OuterShellsExtracter::extractShellRings(const Geometry& polygons, std::vector& shells) +{ + shells.clear(); + for (std::size_t i = 0; i < polygons.getNumGeometries(); i++) { + const Geometry* consGeom = polygons.getGeometryN(i); + const Polygon* consPoly = static_cast(consGeom); + const LinearRing* lr = consPoly->getExteriorRing(); + shells.push_back(lr); + } +} + + +} // geos::algorithm::hull +} // geos::algorithm +} // geos \ No newline at end of file diff --git a/tests/unit/algorithm/hull/ConcaveHullOfPolygonsTest.cpp b/tests/unit/algorithm/hull/ConcaveHullOfPolygonsTest.cpp index 574e2da385..83505123e7 100644 --- a/tests/unit/algorithm/hull/ConcaveHullOfPolygonsTest.cpp +++ b/tests/unit/algorithm/hull/ConcaveHullOfPolygonsTest.cpp @@ -191,5 +191,27 @@ void object::test<7>() "POLYGON ((6 9, 8 9, 9 5, 8 0, 6 0, 5 0, 1 0, 1 4, 1 5, 1 9, 5 9, 6 9))"); } +// testPolygonHole +template<> +template<> +void object::test<8>() +{ + checkHullByLenRatio( + "MULTIPOLYGON (((1 1, 10 3, 19 1, 16 8, 19 7, 19 19, 10 20, 8 17, 1 19, 1 1), (3 4, 5 10, 3 16, 9 14, 14 15, 15 9, 13 5, 3 4)))", + 0.9, + "POLYGON ((10 20, 19 19, 19 7, 19 1, 10 3, 1 1, 1 19, 10 20), (13 5, 15 9, 14 15, 9 14, 3 16, 5 10, 3 4, 13 5))" ); +} + + +// testPolygonNestedPoly +template<> +template<> +void object::test<9>() +{ + checkHullByLenRatio( + "MULTIPOLYGON (((1 1, 10 3, 19 1, 16 8, 19 7, 19 19, 10 20, 8 17, 1 19, 1 1), (3 4, 5 10, 3 16, 9 14, 14 15, 15 9, 13 5, 3 4)), ((6 10, 7 13, 10 12, 12 13, 13 11, 11 9, 13 8, 9 6, 6 6, 7 8, 6 10)))", + 0.9, + "MULTIPOLYGON (((10 20, 19 19, 19 7, 19 1, 10 3, 1 1, 1 19, 10 20), (13 5, 15 9, 14 15, 9 14, 3 16, 5 10, 3 4, 13 5)), ((7 13, 10 12, 12 13, 13 11, 11 9, 13 8, 9 6, 6 6, 7 8, 6 10, 7 13)))" ); +} } // namespace tut From 6cca51c5ed50b59680ad5f92157afa825e3c4134 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Mon, 30 Sep 2024 13:19:48 -0700 Subject: [PATCH 2/3] Update NEWS --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 3e2bf5ee19..fbd9f90952 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ - Centroid: Fix crash on polygons with empty holes (GH-1075, Dan Baston) - MinimumClearance: Fix crash on NaN inputs (GH-1082, Dan Baston) - GEOSRelatePatternMatch: Fix crash on invalid DE-9IM pattern (GH-1089, Dan Baston) + - Fix ConcaveHullOfPolygons nested shell handling (GH-1169, Martin Davis) ## Changes in 3.11.4 2024-06-05 From 246ec0eabea9779c81bd9aca7a00752d948d7e21 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Tue, 1 Oct 2024 13:38:53 -0700 Subject: [PATCH 3/3] Bump up to actions/upload-artifact@v4 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 132ddef3e6..a5152fbc5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: - name: 'Upload Valgrind Log' if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: valgrind-log path: build.cmake/Testing/Temporary/MemoryChecker.**.log