From ac8b32fb40945f21b02440a8b5f4976f7f696367 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Wed, 22 May 2024 21:14:08 -0700 Subject: [PATCH] Add CoveragePolygonValidator section performance optimization (#1099) --- include/geos/coverage/CoveragePolygon.h | 57 ++++++++ .../geos/coverage/CoveragePolygonValidator.h | 38 +++--- include/geos/coverage/CoverageRing.h | 2 + src/coverage/CoveragePolygon.cpp | 76 +++++++++++ src/coverage/CoveragePolygonValidator.cpp | 124 ++++++++---------- src/coverage/CoverageRing.cpp | 10 ++ 6 files changed, 217 insertions(+), 90 deletions(-) create mode 100644 include/geos/coverage/CoveragePolygon.h create mode 100644 src/coverage/CoveragePolygon.cpp diff --git a/include/geos/coverage/CoveragePolygon.h b/include/geos/coverage/CoveragePolygon.h new file mode 100644 index 0000000000..e56668c456 --- /dev/null +++ b/include/geos/coverage/CoveragePolygon.h @@ -0,0 +1,57 @@ +/********************************************************************** + * + * 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 + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class Envelope; +class Polygon; +} +} + +using geos::geom::CoordinateXY; +using geos::geom::Envelope; +using geos::geom::Polygon; +using geos::algorithm::locate::IndexedPointInAreaLocator; + +namespace geos { // geos +namespace coverage { // geos::coverage + +class GEOS_DLL CoveragePolygon { + + // Members + const Polygon* m_polygon; + Envelope polyEnv; + mutable std::unique_ptr m_locator; + +public: + CoveragePolygon(const Polygon* poly); + + bool intersectsEnv(const Envelope& env) const; + bool intersectsEnv(const CoordinateXY& p) const; + bool contains(const CoordinateXY& p) const; + +private: + IndexedPointInAreaLocator& getLocator() const; + +}; + +} // namespace geos::coverage +} // namespace geos + diff --git a/include/geos/coverage/CoveragePolygonValidator.h b/include/geos/coverage/CoveragePolygonValidator.h index c0150c895c..8f635de385 100644 --- a/include/geos/coverage/CoveragePolygonValidator.h +++ b/include/geos/coverage/CoveragePolygonValidator.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -195,11 +196,10 @@ class GEOS_DLL CoveragePolygonValidator { // Members const Geometry* targetGeom; std::vector adjGeoms; - std::vector m_adjPolygons; + //std::vector m_adjPolygons; const GeometryFactory* geomFactory; double gapWidth = 0.0; - std::map> adjPolygonLocators; - // std::vector> coverageRingStore; + std::vector> m_adjCovPolygons; std::deque coverageRingStore; std::vector> localCoordinateSequences; std::deque coverageRingSegmentStore; @@ -273,6 +273,8 @@ class GEOS_DLL CoveragePolygonValidator { private: + static std::vector> + toCoveragePolygons(const std::vector polygons); static std::vector extractPolygons(std::vector& geoms); /* private */ @@ -336,34 +338,26 @@ class GEOS_DLL CoveragePolygonValidator { * to an adjacent polygon. * * @param targetRings the rings with segments to test - * @param adjPolygons the adjacent polygons + * @param adjCovPolygons the adjacent polygons */ void markInvalidInteriorSegments( std::vector& targetRings, - std::vector& adjPolygons); + std::vector>& adjCovPolygons); + + void markInvalidInteriorSection( + CoverageRing& ring, + std::size_t iStart, + std::size_t iEnd, + std::vector>& adjCovPolygons ); + + void markInvalidInteriorSegment( + CoverageRing& ring, std::size_t i, CoveragePolygon* adjPoly); void checkTargetRings( std::vector& targetRings, std::vector& adjRngs, const Envelope& targetEnv); - /** - * Tests if a coordinate is in the interior of some adjacent polygon. - * Uses the cached Point-In-Polygon indexed locators, for performance. - * - * @param p the coordinate to test - * @param adjPolygons the list of polygons - * @return true if the point is in the interior - */ - bool isInteriorVertex(const Coordinate& p, - std::vector& adjPolygons); - - - bool polygonContainsPoint(std::size_t index, - const Polygon* poly, const Coordinate& pt); - - IndexedPointInAreaLocator* getLocator(std::size_t index, const Polygon* poly); - std::unique_ptr createInvalidLines(std::vector& rings); std::vector createRings(const Geometry* geom); diff --git a/include/geos/coverage/CoverageRing.h b/include/geos/coverage/CoverageRing.h index 45a348e9f5..76bd44aaf4 100644 --- a/include/geos/coverage/CoverageRing.h +++ b/include/geos/coverage/CoverageRing.h @@ -83,6 +83,8 @@ class GEOS_DLL CoverageRing : public noding::BasicSegmentString { CoverageRing(const LinearRing* ring, bool isShell); + geom::Envelope getEnvelope(std::size_t start, std::size_t end); + /** * Tests if all rings have known status (matched or invalid) * for all segments. diff --git a/src/coverage/CoveragePolygon.cpp b/src/coverage/CoveragePolygon.cpp new file mode 100644 index 0000000000..46ee29c7cf --- /dev/null +++ b/src/coverage/CoveragePolygon.cpp @@ -0,0 +1,76 @@ +/********************************************************************** + * + * 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. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include + +using geos::algorithm::locate::IndexedPointInAreaLocator; +using geos::geom::CoordinateXY; +using geos::geom::Envelope; +using geos::geom::Location; +using geos::geom::Polygon; + +namespace geos { // geos +namespace coverage { // geos.coverage + +/* public */ +CoveragePolygon::CoveragePolygon(const Polygon* poly) + : m_polygon(poly) +{ + //-- cache polygon envelope for maximum performance + polyEnv = *(poly->getEnvelopeInternal()); +} + +/* public */ +bool +CoveragePolygon::intersectsEnv(const Envelope& env) const +{ + return polyEnv.intersects(env); +} + +/* public */ +bool +CoveragePolygon::intersectsEnv(const CoordinateXY& p) const +{ + return polyEnv.intersects(p); +} + +/* public */ +bool +CoveragePolygon::contains(const CoordinateXY& p) const +{ + if (! intersectsEnv(p)) + return false; + IndexedPointInAreaLocator& pia = getLocator(); + return Location::INTERIOR == pia.locate(&p); +} + +/* private */ +IndexedPointInAreaLocator& +CoveragePolygon::getLocator() const +{ + if (m_locator == nullptr) { + m_locator = std::make_unique(*m_polygon); + } + return *m_locator; +} + +} // namespace geos.coverage +} // namespace geos + + diff --git a/src/coverage/CoveragePolygonValidator.cpp b/src/coverage/CoveragePolygonValidator.cpp index a996439030..7690e25420 100644 --- a/src/coverage/CoveragePolygonValidator.cpp +++ b/src/coverage/CoveragePolygonValidator.cpp @@ -13,7 +13,9 @@ **********************************************************************/ #include + #include +#include #include #include @@ -31,7 +33,7 @@ using geos::algorithm::locate::IndexedPointInAreaLocator; using geos::algorithm::Orientation; -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; using geos::geom::Envelope; using geos::geom::Geometry; using geos::geom::GeometryFactory; @@ -88,10 +90,10 @@ CoveragePolygonValidator::setGapWidth(double p_gapWidth) std::unique_ptr CoveragePolygonValidator::validate() { - m_adjPolygons = extractPolygons(adjGeoms); - + std::vector adjPolygons = extractPolygons(adjGeoms); + m_adjCovPolygons = toCoveragePolygons(adjPolygons); std::vector targetRings = createRings(targetGeom); - std::vector adjRings = createRings(m_adjPolygons); + std::vector adjRings = createRings(adjPolygons); /** * Mark matching segments first. @@ -106,6 +108,15 @@ CoveragePolygonValidator::validate() return createInvalidLines(targetRings); } +/* private static */ +std::vector> +CoveragePolygonValidator::toCoveragePolygons(const std::vector polygons) { + std::vector> covPolys; + for (const Polygon* poly : polygons) { + covPolys.push_back( std::make_unique(poly) ); + } + return covPolys; +} /* private */ void @@ -132,10 +143,9 @@ CoveragePolygonValidator::checkTargetRings( * Do further checks to see if any of them are are invalid. */ markInvalidInteractingSegments(targetRings, adjRings, gapWidth); - markInvalidInteriorSegments(targetRings, m_adjPolygons); + markInvalidInteriorSegments(targetRings, m_adjCovPolygons); } - /* private static */ std::vector CoveragePolygonValidator::extractPolygons(std::vector& geoms) @@ -246,85 +256,63 @@ CoveragePolygonValidator::markInvalidInteractingSegments( void CoveragePolygonValidator::markInvalidInteriorSegments( std::vector& targetRings, - std::vector& adjPolygons) + std::vector>& adjCovPolygons ) { for (CoverageRing* ring : targetRings) { - for (std::size_t i = 0; i < ring->size() - 1; i++) { - //-- skip check for segments with known state. - if (ring->isKnown(i)) - continue; - - /** - * Check if vertex is in interior of an adjacent polygon. - * If so, the segments on either side are in the interior. - * Mark them invalid, unless they are already matched. - */ - const Coordinate& p = ring->getCoordinate(i); - if (isInteriorVertex(p, adjPolygons)) { - ring->markInvalid(i); - //-- previous segment may be interior (but may also be matched) - std::size_t iPrev = i == 0 ? ring->size()-2 : i-1; - if (! ring->isKnown(iPrev)) - ring->markInvalid(iPrev); - } + std::size_t stride = 1000; //-- RING_SECTION_STRIDE; + for (std::size_t i = 0; i < ring->size() - 1; i += stride) { + std::size_t iEnd = i + stride; + if (iEnd >= ring->size()) + iEnd = ring->size() - 1; + markInvalidInteriorSection(*ring, i, iEnd, adjCovPolygons); } } } - /* private */ -bool -CoveragePolygonValidator::isInteriorVertex( - const Coordinate& p, - std::vector& adjPolygons) +void +CoveragePolygonValidator::markInvalidInteriorSection( + CoverageRing& ring, + std::size_t iStart, + std::size_t iEnd, + std::vector>& adjCovPolygons ) { - /** - * There should not be too many adjacent polygons, - * and hopefully not too many segments with unknown status - * so a linear scan should not be too inefficient - */ - //TODO: try a spatial index? - for (std::size_t i = 0; i < adjPolygons.size(); i++) { - const Polygon* adjPoly = adjPolygons[i]; - if (polygonContainsPoint(i, adjPoly, p)) - return true; + Envelope sectionEnv = ring.getEnvelope(iStart, iEnd); + //TODO: is it worth indexing polygons? + for (auto& adjPoly : adjCovPolygons) { + if (adjPoly->intersectsEnv(sectionEnv)) { + //-- test vertices in section + for (std::size_t i = iStart; i < iEnd; i++) { + markInvalidInteriorSegment(ring, i, adjPoly.get()); + } + } } - return false; } - /* private */ -bool -CoveragePolygonValidator::polygonContainsPoint(std::size_t index, - const Polygon* poly, const Coordinate& pt) +void +CoveragePolygonValidator::markInvalidInteriorSegment( + CoverageRing& ring, std::size_t i, CoveragePolygon* adjPoly) { - if (! poly->getEnvelopeInternal()->intersects(pt)) - return false; - IndexedPointInAreaLocator* pia = getLocator(index, poly); - return Location::INTERIOR == pia->locate(&pt); -} - + //-- skip check for segments with known state. + if (ring.isKnown(i)) + return; -/* private */ -IndexedPointInAreaLocator* -CoveragePolygonValidator::getLocator(std::size_t index, const Polygon* poly) -{ - auto it = adjPolygonLocators.find(index); - // found locator already constructed - if (it != adjPolygonLocators.end()) { - return (it->second).get(); - } - // construct new locator for this polygon - else { - IndexedPointInAreaLocator* ipia = new IndexedPointInAreaLocator(*poly); - adjPolygonLocators.emplace(std::piecewise_construct, - std::forward_as_tuple(index), - std::forward_as_tuple(ipia)); - return ipia; + /** + * Check if vertex is in interior of an adjacent polygon. + * If so, the segments on either side are in the interior. + * Mark them invalid, unless they are already matched. + */ + const CoordinateXY& p = ring.getCoordinate(i); + if (adjPoly->contains(p)) { + ring.markInvalid(i); + //-- previous segment may be interior (but may also be matched) + std::size_t iPrev = i == 0 ? ring.size()-2 : i-1; + if (! ring.isKnown(iPrev)) + ring.markInvalid(iPrev); } } - /* private */ std::unique_ptr CoveragePolygonValidator::createInvalidLines(std::vector& rings) diff --git a/src/coverage/CoverageRing.cpp b/src/coverage/CoverageRing.cpp index 8f258d9d59..9a331291f5 100644 --- a/src/coverage/CoverageRing.cpp +++ b/src/coverage/CoverageRing.cpp @@ -73,6 +73,16 @@ CoverageRing::CoverageRing(const LinearRing* ring, bool isShell) algorithm::Orientation::isCCW(ring->getCoordinatesRO()) != isShell) {} +/* public */ +geom::Envelope CoverageRing::getEnvelope(std::size_t start, std::size_t end) +{ + geom::Envelope env; + for (std::size_t i = start; i < end; i++) { + env.expandToInclude(getCoordinate(i)); + } + return env; +} + /* public */ bool