From 0f90d0ab8da4bf194895433ad140d3fc9d8644a5 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Wed, 12 Feb 2025 15:41:24 -0800 Subject: [PATCH] Fix buffer element erosion for negative distance and remove overlay deps (#1239) --- include/geos/operation/buffer/BufferBuilder.h | 5 +- .../geos/operation/buffer/BufferNodeFactory.h | 52 +++ .../geos/operation/buffer/MaximalEdgeRing.h | 104 +++++ .../geos/operation/buffer/MinimalEdgeRing.h | 80 ++++ .../geos/operation/buffer/PolygonBuilder.h | 202 ++++++++++ src/operation/buffer/BufferBuilder.cpp | 7 +- src/operation/buffer/BufferNodeFactory.cpp | 42 ++ src/operation/buffer/MaximalEdgeRing.cpp | 136 +++++++ src/operation/buffer/MinimalEdgeRing.cpp | 39 ++ src/operation/buffer/PolygonBuilder.cpp | 372 ++++++++++++++++++ tests/unit/operation/buffer/BufferOpTest.cpp | 13 + 11 files changed, 1045 insertions(+), 7 deletions(-) create mode 100644 include/geos/operation/buffer/BufferNodeFactory.h create mode 100644 include/geos/operation/buffer/MaximalEdgeRing.h create mode 100644 include/geos/operation/buffer/MinimalEdgeRing.h create mode 100644 include/geos/operation/buffer/PolygonBuilder.h create mode 100644 src/operation/buffer/BufferNodeFactory.cpp create mode 100644 src/operation/buffer/MaximalEdgeRing.cpp create mode 100644 src/operation/buffer/MinimalEdgeRing.cpp create mode 100644 src/operation/buffer/PolygonBuilder.cpp diff --git a/include/geos/operation/buffer/BufferBuilder.h b/include/geos/operation/buffer/BufferBuilder.h index 259f469ae1..b3c6cc928b 100644 --- a/include/geos/operation/buffer/BufferBuilder.h +++ b/include/geos/operation/buffer/BufferBuilder.h @@ -56,10 +56,9 @@ class PlanarGraph; namespace operation { namespace buffer { class BufferSubgraph; -} -namespace overlay { class PolygonBuilder; } + } } @@ -233,7 +232,7 @@ class GEOS_DLL BufferBuilder { * the final polygons */ void buildSubgraphs(const std::vector& subgraphList, - overlay::PolygonBuilder& polyBuilder); + PolygonBuilder& polyBuilder); /// \brief /// Return the externally-set noding::Noder OR a newly created diff --git a/include/geos/operation/buffer/BufferNodeFactory.h b/include/geos/operation/buffer/BufferNodeFactory.h new file mode 100644 index 0000000000..e2e7ab83c9 --- /dev/null +++ b/include/geos/operation/buffer/BufferNodeFactory.h @@ -0,0 +1,52 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2006 Refractions Research Inc. + * + * 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 + +#include + +#include // for inheritance + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +} +namespace geomgraph { +class Node; +} +} + +namespace geos { +namespace operation { // geos::operation +namespace buffer { // geos::operation::buffer + +/** \brief + * Creates nodes for use in the geomgraph::PlanarGraph constructed during + * buffer operations. NOTE: also used by operation::valid + */ +class GEOS_DLL BufferNodeFactory: public geomgraph::NodeFactory { +public: + BufferNodeFactory(): geomgraph::NodeFactory() {} + geomgraph::Node* createNode(const geom::Coordinate& coord) const override; + static const geomgraph::NodeFactory& instance(); +}; + + +} // namespace geos::operation::buffer +} // namespace geos::operation +} // namespace geos + diff --git a/include/geos/operation/buffer/MaximalEdgeRing.h b/include/geos/operation/buffer/MaximalEdgeRing.h new file mode 100644 index 0000000000..4ecd6a0f0c --- /dev/null +++ b/include/geos/operation/buffer/MaximalEdgeRing.h @@ -0,0 +1,104 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2006 Refractions Research Inc. + * + * 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. + * + ********************************************************************** + * + * Last port: operation/overlay/MaximalEdgeRing.java rev. 1.15 (JTS-1.10) + * + **********************************************************************/ + +#pragma once + +#include + +#include + +#include // for inheritance + +// Forward declarations +namespace geos { +namespace geom { +class GeometryFactory; +} +namespace geomgraph { +class DirectedEdge; +} +namespace operation { +namespace buffer { +class MinimalEdgeRing; +} +} +} + +namespace geos { +namespace operation { // geos::operation +namespace buffer { // geos::operation::buffer + +/** \brief + * A ring of [DirectedEdges](@ref geomgraph::DirectedEdge) which may contain nodes of degree > 2. + * + * A MaximalEdgeRing may represent two different spatial entities: + * + * - a single polygon possibly containing inversions (if the ring is oriented CW) + * - a single hole possibly containing exversions (if the ring is oriented CCW) + * + * If the MaximalEdgeRing represents a polygon, + * the interior of the polygon is strongly connected. + * + * These are the form of rings used to define polygons under some spatial data models. + * However, under the OGC SFS model, [MinimalEdgeRings](@ref MinimalEdgeRing) are required. + * A MaximalEdgeRing can be converted to a list of MinimalEdgeRings using the + * {@link #buildMinimalRings() } method. + * + * @see com.vividsolutions.jts.operation.overlay.MinimalEdgeRing + */ +class GEOS_DLL MaximalEdgeRing: public geomgraph::EdgeRing { + +public: + + MaximalEdgeRing(geomgraph::DirectedEdge* start, + const geom::GeometryFactory* geometryFactory); + // throw(const TopologyException &) + + ~MaximalEdgeRing() override = default; + + geomgraph::DirectedEdge* getNext(geomgraph::DirectedEdge* de) override; + + void setEdgeRing(geomgraph::DirectedEdge* de, geomgraph::EdgeRing* er) override; + + /// \brief + /// This function returns a newly allocated vector of + /// pointers to newly allocated MinimalEdgeRing objects. + /// + /// @deprecated pass the vector yourself instead + /// + std::vector* buildMinimalRings(); + + /// \brief + /// This function pushes pointers to newly allocated MinimalEdgeRing + /// objects to the provided vector. + /// + void buildMinimalRings(std::vector& minEdgeRings); + void buildMinimalRings(std::vector& minEdgeRings); + + /// \brief + /// For all nodes in this EdgeRing, + /// link the DirectedEdges at the node to form minimalEdgeRings + /// + void linkDirectedEdgesForMinimalEdgeRings(); +}; + + +} // namespace geos::operation::buffer +} // namespace geos::operation +} // namespace geos + diff --git a/include/geos/operation/buffer/MinimalEdgeRing.h b/include/geos/operation/buffer/MinimalEdgeRing.h new file mode 100644 index 0000000000..0b4b5c099f --- /dev/null +++ b/include/geos/operation/buffer/MinimalEdgeRing.h @@ -0,0 +1,80 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2006 Refractions Research Inc. + * + * 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. + * + ********************************************************************** + * + * Last port: operation/overlay/MinimalEdgeRing.java rev. 1.13 (JTS-1.10) + * + **********************************************************************/ + +#pragma once + + +#include + +#include // for inheritance +#include // for inlines + +#include + +// Forward declarations +namespace geos { +namespace geom { +class GeometryFactory; +} +namespace geomgraph { +class DirectedEdge; +class EdgeRing; +} +} + +namespace geos { +namespace operation { // geos::operation +namespace buffer { // geos::operation::buffer + +/** \brief + * A ring of [Edges](@ref geomgraph::Edge) with the property that no node + * has degree greater than 2. + * + * These are the form of rings required to represent polygons + * under the OGC SFS spatial data model. + * + * @see operation::buffer::MaximalEdgeRing + * + */ +class GEOS_DLL MinimalEdgeRing: public geomgraph::EdgeRing { + +public: + + MinimalEdgeRing(geomgraph::DirectedEdge* start, + const geom::GeometryFactory* geometryFactory); + + ~MinimalEdgeRing() override {}; + + geomgraph::DirectedEdge* getNext(geomgraph::DirectedEdge* de) override + { + return de->getNextMin(); + }; + + void setEdgeRing(geomgraph::DirectedEdge* de, + geomgraph::EdgeRing* er) override + { + de->setMinEdgeRing(er); + }; + +}; + + +} // namespace geos::operation::buffer +} // namespace geos::operation +} // namespace geos + diff --git a/include/geos/operation/buffer/PolygonBuilder.h b/include/geos/operation/buffer/PolygonBuilder.h new file mode 100644 index 0000000000..914a341a72 --- /dev/null +++ b/include/geos/operation/buffer/PolygonBuilder.h @@ -0,0 +1,202 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2006 Refractions Research Inc. + * + * 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. + * + ********************************************************************** + * + * Last port: operation/overlay/PolygonBuilder.java rev. 1.20 (JTS-1.10) + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class +#endif + +// Forward declarations +namespace geos { +namespace geom { +class Geometry; +class Coordinate; +class GeometryFactory; +} +namespace geomgraph { +class EdgeRing; +class Node; +class PlanarGraph; +class DirectedEdge; +} +namespace operation { +namespace buffer { +class MaximalEdgeRing; +class MinimalEdgeRing; +} +} +} + +namespace geos { +namespace operation { // geos::operation +namespace buffer { // geos::operation::buffer + +/** \brief + * Forms Polygon out of a graph of geomgraph::DirectedEdge. + * + * The edges to use are marked as being in the result Area. + */ +class GEOS_DLL PolygonBuilder { +public: + + PolygonBuilder(const geom::GeometryFactory* newGeometryFactory); + + ~PolygonBuilder(); + + /** + * Add a complete graph. + * The graph is assumed to contain one or more polygons, + * possibly with holes. + */ + void add(geomgraph::PlanarGraph* graph); + // throw(const TopologyException &) + + /** + * Add a set of edges and nodes, which form a graph. + * The graph is assumed to contain one or more polygons, + * possibly with holes. + */ + void add(const std::vector* dirEdges, + const std::vector* nodes); + // throw(const TopologyException &) + + std::vector> getPolygons(); + +private: + + const geom::GeometryFactory* geometryFactory; + + std::vector shellList; + + /** + * For all DirectedEdges in result, form them into MaximalEdgeRings + * + * @param maxEdgeRings + * Formed MaximalEdgeRings will be pushed to this vector. + * Ownership of the elements is transferred to caller. + */ + void buildMaximalEdgeRings( + const std::vector* dirEdges, + std::vector& maxEdgeRings); + // throw(const TopologyException &) + + void buildMinimalEdgeRings( + std::vector& maxEdgeRings, + std::vector& newShellList, + std::vector& freeHoleList, + std::vector& edgeRings); + + /** + * This method takes a list of MinimalEdgeRings derived from a + * MaximalEdgeRing, and tests whether they form a Polygon. + * This is the case if there is a single shell + * in the list. In this case the shell is returned. + * The other possibility is that they are a series of connected + * holes, in which case no shell is returned. + * + * @return the shell geomgraph::EdgeRing, if there is one + * @return NULL, if all the rings are holes + */ + geomgraph::EdgeRing* findShell(std::vector* minEdgeRings); + + /** + * This method assigns the holes for a Polygon (formed from a list of + * MinimalEdgeRings) to its shell. + * Determining the holes for a MinimalEdgeRing polygon serves two + * purposes: + * + * - it is faster than using a point-in-polygon check later on. + * - it ensures correctness, since if the PIP test was used the point + * chosen might lie on the shell, which might return an incorrect + * result from the PIP test + */ + void placePolygonHoles(geomgraph::EdgeRing* shell, + std::vector* minEdgeRings); + + /** + * For all rings in the input list, + * determine whether the ring is a shell or a hole + * and add it to the appropriate list. + * Due to the way the DirectedEdges were linked, + * a ring is a shell if it is oriented CW, a hole otherwise. + */ + void sortShellsAndHoles(std::vector& edgeRings, + std::vector& newShellList, + std::vector& freeHoleList); + + struct FastPIPRing { + geomgraph::EdgeRing* edgeRing; + algorithm::locate::IndexedPointInAreaLocator* pipLocator; + }; + + /** \brief + * This method determines finds a containing shell for all holes + * which have not yet been assigned to a shell. + * + * Holes which do not lie in any shell are (probably) an eroded element, + * so are simply discarded. + */ + void placeFreeHoles(std::vector& newShellList, + std::vector& freeHoleList); + + /** \brief + * Find the innermost enclosing shell geomgraph::EdgeRing containing the + * argument geomgraph::EdgeRing, if any. + * + * The innermost enclosing ring is the smallest enclosing ring. + * The algorithm used depends on the fact that: + * + * ring A contains ring B iff envelope(ring A) + * contains envelope(ring B) + * + * This routine is only safe to use if the chosen point of the hole + * is known to be properly contained in a shell + * (which is guaranteed to be the case if the hole does not touch + * its shell) + * + * @return containing geomgraph::EdgeRing, if there is one + * @return NULL if no containing geomgraph::EdgeRing is found + */ + geomgraph::EdgeRing* findEdgeRingContaining(geomgraph::EdgeRing* testEr, + std::vector& newShellList); + + std::vector> computePolygons( + std::vector& newShellList); + + /** + * Checks the current set of shells (with their associated holes) to + * see if any of them contain the point. + */ + +}; + +} // namespace geos::operation::buffer +} // namespace geos::operation +} // namespace geos + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/src/operation/buffer/BufferBuilder.cpp b/src/operation/buffer/BufferBuilder.cpp index 1fa923ccb9..a5d423e9e2 100644 --- a/src/operation/buffer/BufferBuilder.cpp +++ b/src/operation/buffer/BufferBuilder.cpp @@ -33,8 +33,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -77,7 +77,6 @@ using namespace geos::geom; using namespace geos::geomgraph; using namespace geos::noding; using namespace geos::algorithm; -using namespace geos::operation::overlay; using namespace geos::operation::linemerge; namespace { @@ -439,7 +438,7 @@ BufferBuilder::buffer(const Geometry* g, double distance) std::vector subgraphList; try { - PlanarGraph graph(OverlayNodeFactory::instance()); + PlanarGraph graph(BufferNodeFactory::instance()); graph.addEdges(edgeList.getEdges()); GEOS_CHECK_FOR_INTERRUPTS(); diff --git a/src/operation/buffer/BufferNodeFactory.cpp b/src/operation/buffer/BufferNodeFactory.cpp new file mode 100644 index 0000000000..934caae6c2 --- /dev/null +++ b/src/operation/buffer/BufferNodeFactory.cpp @@ -0,0 +1,42 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2005 Refractions Research Inc. + * + * 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 + +using namespace geos::geomgraph; + +namespace geos { +namespace operation { // geos.operation +namespace buffer { // geos.operation.buffer + +Node* +BufferNodeFactory::createNode(const geom::Coordinate& coord) const +{ + return new Node(coord, new DirectedEdgeStar()); +} + +const NodeFactory& +BufferNodeFactory::instance() +{ + static BufferNodeFactory onf; + return onf; +} + +} // namespace geos.operation.buffer +} // namespace geos.operation +} // namespace geos + diff --git a/src/operation/buffer/MaximalEdgeRing.cpp b/src/operation/buffer/MaximalEdgeRing.cpp new file mode 100644 index 0000000000..35a56f9581 --- /dev/null +++ b/src/operation/buffer/MaximalEdgeRing.cpp @@ -0,0 +1,136 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2005-2006 Refractions Research Inc. + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * + * 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. + * + ********************************************************************** + * + * Last port: operation/overlay/MaximalEdgeRing.java rev. 1.15 (JTS-1.10) + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef GEOS_DEBUG +#define GEOS_DEBUG 0 +#endif + +#if GEOS_DEBUG +#include +#endif + + +using namespace geos::geomgraph; +using namespace geos::geom; + +namespace geos { +namespace operation { // geos.operation +namespace buffer { // geos.operation.buffer + +/*public*/ +MaximalEdgeRing::MaximalEdgeRing(DirectedEdge* start, + const GeometryFactory* p_geometryFactory) +// throw(const TopologyException &) + : + EdgeRing(start, p_geometryFactory) +{ + computePoints(start); + computeRing(); +#if GEOS_DEBUG + std::cerr << "MaximalEdgeRing[" << this << "] ctor" << std::endl; +#endif +} + +/*public*/ +DirectedEdge* +MaximalEdgeRing::getNext(DirectedEdge* de) +{ + return de->getNext(); +} + +/*public*/ +void +MaximalEdgeRing::setEdgeRing(DirectedEdge* de, EdgeRing* er) +{ + de->setEdgeRing(er); +} + +/*public*/ +void +MaximalEdgeRing::linkDirectedEdgesForMinimalEdgeRings() +{ + DirectedEdge* de = startDe; + do { + Node* node = de->getNode(); + EdgeEndStar* ees = node->getEdges(); + + DirectedEdgeStar* des = detail::down_cast(ees); + + des->linkMinimalDirectedEdges(this); + + de = de->getNext(); + + } + while(de != startDe); +} + +/*public*/ +std::vector* +MaximalEdgeRing::buildMinimalRings() +{ + std::vector* minEdgeRings = new std::vector; + buildMinimalRings(*minEdgeRings); + return minEdgeRings; +} + +/*public*/ +void +MaximalEdgeRing::buildMinimalRings(std::vector& minEdgeRings) +{ + DirectedEdge* de = startDe; + do { + if(de->getMinEdgeRing() == nullptr) { + MinimalEdgeRing* minEr = new MinimalEdgeRing(de, geometryFactory); + minEdgeRings.push_back(minEr); + } + de = de->getNext(); + } + while(de != startDe); +} + +/*public*/ +void +MaximalEdgeRing::buildMinimalRings(std::vector& minEdgeRings) +{ + DirectedEdge* de = startDe; + do { + if(de->getMinEdgeRing() == nullptr) { + MinimalEdgeRing* minEr = new MinimalEdgeRing(de, geometryFactory); + minEdgeRings.push_back(minEr); + } + de = de->getNext(); + } + while(de != startDe); +} + +} // namespace geos.operation.overlay +} // namespace geos.operation +} // namespace geos diff --git a/src/operation/buffer/MinimalEdgeRing.cpp b/src/operation/buffer/MinimalEdgeRing.cpp new file mode 100644 index 0000000000..a0aeb9174e --- /dev/null +++ b/src/operation/buffer/MinimalEdgeRing.cpp @@ -0,0 +1,39 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2005 Refractions Research Inc. + * + * 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 + + +namespace geos { +namespace operation { // geos.operation +namespace buffer { // geos.operation.buffer + + +MinimalEdgeRing::MinimalEdgeRing(geomgraph::DirectedEdge* start, + const geom::GeometryFactory* p_geometryFactory) + : + geomgraph::EdgeRing(start, p_geometryFactory) +{ + computePoints(start); + computeRing(); +} + + +} // namespace geos.operation.buffer +} // namespace geos.operation +} // namespace geos + diff --git a/src/operation/buffer/PolygonBuilder.cpp b/src/operation/buffer/PolygonBuilder.cpp new file mode 100644 index 0000000000..893847f91e --- /dev/null +++ b/src/operation/buffer/PolygonBuilder.cpp @@ -0,0 +1,372 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2005 Refractions Research Inc. + * + * 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. + * + ********************************************************************** + * + * Last port: operation/overlay/PolygonBuilder.java rev. 1.20 (JTS-1.10) + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include + +#ifndef GEOS_DEBUG +#define GEOS_DEBUG 0 +#endif + +#if GEOS_DEBUG +#include +#endif + +using namespace geos::geomgraph; +using namespace geos::algorithm; +using namespace geos::geom; + +namespace geos { +namespace operation { // geos.operation +namespace buffer { // geos.operation.buffer + +PolygonBuilder::PolygonBuilder(const GeometryFactory* newGeometryFactory) + : + geometryFactory(newGeometryFactory) +{ +} + +PolygonBuilder::~PolygonBuilder() +{ + for(std::size_t i = 0, n = shellList.size(); i < n; ++i) { + delete shellList[i]; + } +} + +/*public*/ +void +PolygonBuilder::add(PlanarGraph* graph) +//throw(TopologyException *) +{ + const std::vector* eeptr = graph->getEdgeEnds(); + assert(eeptr); + const std::vector& ee = *eeptr; + + std::size_t eeSize = ee.size(); + +#if GEOS_DEBUG + std::cerr << __FUNCTION__ << ": PlanarGraph has " << eeSize << " EdgeEnds" << std::endl; +#endif + + std::vector dirEdges(eeSize); + for(std::size_t i = 0; i < eeSize; ++i) { + DirectedEdge* de = detail::down_cast(ee[i]); + dirEdges[i] = de; + } + + const auto& nodeMap = graph->getNodeMap()->nodeMap; + std::vector nodes; + nodes.reserve(nodeMap.size()); + for(const auto& nodeIt: nodeMap) { + Node* node = nodeIt.second.get(); + nodes.push_back(node); + } + + add(&dirEdges, &nodes); // might throw a TopologyException * +} + +/*public*/ +void +PolygonBuilder::add(const std::vector* dirEdges, + const std::vector* nodes) +//throw(TopologyException *) +{ + PlanarGraph::linkResultDirectedEdges(nodes->begin(), nodes->end()); + + std::vector maxEdgeRings; + buildMaximalEdgeRings(dirEdges, maxEdgeRings); + + std::vector freeHoleList; + std::vector edgeRings; + buildMinimalEdgeRings(maxEdgeRings, shellList, freeHoleList, edgeRings); + + sortShellsAndHoles(edgeRings, shellList, freeHoleList); + + std::vector indexedshellist; + for(auto const& shell : shellList) { + FastPIPRing pipRing { shell, new geos::algorithm::locate::IndexedPointInAreaLocator(*shell->getLinearRing()) }; + indexedshellist.push_back(pipRing); + } + placeFreeHoles(indexedshellist, freeHoleList); + //Assert: every hole on freeHoleList has a shell assigned to it + + for(auto const& shell : indexedshellist) { + delete shell.pipLocator; + } +} + +/*public*/ +std::vector> +PolygonBuilder::getPolygons() +{ + std::vector> resultPolyList = computePolygons(shellList); + return resultPolyList; +} + + +/*private*/ +void +PolygonBuilder::buildMaximalEdgeRings(const std::vector* dirEdges, + std::vector& maxEdgeRings) +// throw(const TopologyException &) +{ +#if GEOS_DEBUG + std::cerr << "PolygonBuilder::buildMaximalEdgeRings got " << dirEdges->size() << " dirEdges" << std::endl; +#endif + + std::vector::size_type oldSize = maxEdgeRings.size(); + + for(std::size_t i = 0, n = dirEdges->size(); i < n; i++) { + DirectedEdge* de = (*dirEdges)[i]; +#if GEOS_DEBUG + std::cerr << " dirEdge " << i << std::endl + << de->printEdge() << std::endl + << " inResult:" << de->isInResult() << std::endl + << " isArea:" << de->getLabel().isArea() << std::endl; +#endif + if(de->isInResult() && de->getLabel().isArea()) { + // if this edge has not yet been processed + if(de->getEdgeRing() == nullptr) { + MaximalEdgeRing* er; + try { + // MaximalEdgeRing constructor may throw + er = new MaximalEdgeRing(de, geometryFactory); + } + catch(util::GEOSException&) { + // cleanup if that happens (see stmlf-cases-20061020.xml) + for(std::size_t p_i = oldSize, p_n = maxEdgeRings.size(); p_i < p_n; p_i++) { + delete maxEdgeRings[p_i]; + } + //cerr << "Exception! " << e.what() << std::endl; + throw; + } + maxEdgeRings.push_back(er); + er->setInResult(); + //System.out.println("max node degree=" + er.getMaxDegree()); + } + } + } +#if GEOS_DEBUG + std::cerr << " pushed " << maxEdgeRings.size() - oldSize << " maxEdgeRings" << std::endl; +#endif +} + +/*private*/ +void +PolygonBuilder::buildMinimalEdgeRings( + std::vector& maxEdgeRings, + std::vector& newShellList, std::vector& freeHoleList, + std::vector& edgeRings) +{ + for(std::size_t i = 0, n = maxEdgeRings.size(); i < n; ++i) { + MaximalEdgeRing* er = maxEdgeRings[i]; +#if GEOS_DEBUG + std::cerr << "buildMinimalEdgeRings: maxEdgeRing " << i << " has " << er->getMaxNodeDegree() << " maxNodeDegree" << std::endl; +#endif + if(er->getMaxNodeDegree() > 2) { + er->linkDirectedEdgesForMinimalEdgeRings(); + std::vector minEdgeRings; + er->buildMinimalRings(minEdgeRings); + // at this point we can go ahead and attempt to place + // holes, if this EdgeRing is a polygon + EdgeRing* shell = findShell(&minEdgeRings); + if(shell != nullptr) { + placePolygonHoles(shell, &minEdgeRings); + newShellList.push_back(shell); + } + else { + freeHoleList.insert(freeHoleList.end(), + minEdgeRings.begin(), + minEdgeRings.end()); + } + delete er; + } + else { + edgeRings.push_back(er); + } + } +} + +/*private*/ +EdgeRing* +PolygonBuilder::findShell(std::vector* minEdgeRings) +{ + int shellCount = 0; + EdgeRing* shell = nullptr; + +#if GEOS_DEBUG + std::cerr << "PolygonBuilder::findShell got " << minEdgeRings->size() << " minEdgeRings" << std::endl; +#endif + + for(std::size_t i = 0, n = minEdgeRings->size(); i < n; ++i) { + EdgeRing* er = (*minEdgeRings)[i]; + if(! er->isHole()) { + shell = er; + ++shellCount; + } + } + + if(shellCount > 1) { + throw util::TopologyException("found two shells in MinimalEdgeRing list"); + } + + return shell; +} + +/*private*/ +void +PolygonBuilder::placePolygonHoles(EdgeRing* shell, + std::vector* minEdgeRings) +{ + for(std::size_t i = 0, n = minEdgeRings->size(); i < n; ++i) { + MinimalEdgeRing* er = (*minEdgeRings)[i]; + if(er->isHole()) { + er->setShell(shell); + } + } +} + +/*private*/ +void +PolygonBuilder::sortShellsAndHoles(std::vector& edgeRings, + std::vector& newShellList, std::vector& freeHoleList) +{ + for(std::size_t i = 0, n = edgeRings.size(); i < n; i++) { + EdgeRing* er = edgeRings[i]; + //er->setInResult(); + if(er->isHole()) { + freeHoleList.push_back(er); + } + else { + newShellList.push_back(er); + } + } +} + +/*private*/ +void +PolygonBuilder::placeFreeHoles(std::vector& newShellList, + std::vector& freeHoleList) +{ + for(std::vector::iterator + it = freeHoleList.begin(), itEnd = freeHoleList.end(); + it != itEnd; + ++it) { + EdgeRing* hole = *it; + // only place this hole if it doesn't yet have a shell + if(hole->getShell() == nullptr) { + EdgeRing* shell = findEdgeRingContaining(hole, newShellList); + /** + * If hole lies outside shell, discard it. + */ + if(shell != nullptr) { + hole->setShell(shell); + } + else { + delete hole; + } + + } + } +} + +/*private*/ +EdgeRing* +PolygonBuilder::findEdgeRingContaining(EdgeRing* testEr, + std::vector& newShellList) +{ + LinearRing* testRing = testEr->getLinearRing(); + const Envelope* testEnv = testRing->getEnvelopeInternal(); + EdgeRing* minShell = nullptr; + const Envelope* minShellEnv = nullptr; + + for(auto const& tryShell : newShellList) { + LinearRing* tryShellRing = tryShell.edgeRing->getLinearRing(); + const Envelope* tryShellEnv = tryShellRing->getEnvelopeInternal(); + // the hole envelope cannot equal the shell envelope + // (also guards against testing rings against themselves) + if(tryShellEnv->equals(testEnv)) { + continue; + } + // hole must be contained in shell + if(!tryShellEnv->contains(testEnv)) { + continue; + } + + const CoordinateSequence* tsrcs = tryShellRing->getCoordinatesRO(); + const Coordinate& testPt = operation::polygonize::EdgeRing::ptNotInList(testRing->getCoordinatesRO(), tsrcs); + + bool isContained = false; + if(tryShell.pipLocator->locate(&testPt) != Location::EXTERIOR) { + isContained = true; + } + + // check if this new containing ring is smaller than + // the current minimum ring + if(isContained) { + if(minShell == nullptr + || minShellEnv->contains(tryShellEnv)) { + minShell = tryShell.edgeRing; + minShellEnv = minShell->getLinearRing()->getEnvelopeInternal(); + } + } + } + return minShell; +} + +/*private*/ +std::vector> +PolygonBuilder::computePolygons(std::vector& newShellList) +{ +#if GEOS_DEBUG + std::cerr << "PolygonBuilder::computePolygons: got " << newShellList.size() << " shells" << std::endl; +#endif + std::vector> resultPolyList; + + // add Polygons for all shells + for(std::size_t i = 0, n = newShellList.size(); i < n; i++) { + EdgeRing* er = newShellList[i]; + auto poly = er->toPolygon(geometryFactory); + resultPolyList.push_back(std::move(poly)); + } + return resultPolyList; +} + + +} // namespace geos.operation.buffer +} // namespace geos.operation +} // namespace geos + diff --git a/tests/unit/operation/buffer/BufferOpTest.cpp b/tests/unit/operation/buffer/BufferOpTest.cpp index 63d8fb4bc1..342f95db2e 100644 --- a/tests/unit/operation/buffer/BufferOpTest.cpp +++ b/tests/unit/operation/buffer/BufferOpTest.cpp @@ -598,4 +598,17 @@ void object::test<26> "POLYGON ((0.73 0.05, 0.67 -0.13, 0.58 -0.31, 0.46 -0.46, 0.31 -0.58, 0.13 -0.67, -0.05 -0.73, -0.25 -0.75, -0.75 -0.75, -0.95 -0.73, -1.13 -0.67, -1.31 -0.58, -1.46 -0.46, -1.58 -0.31, -1.67 -0.13, -1.73 0.05, -1.75 0.25, -1.75 0.75, -1.73 0.95, -1.67 1.13, -1.58 1.31, -1.46 1.46, -1.31 1.58, -1.13 1.67, -0.95 1.73, -0.75 1.75, -0.25 1.75, -0.05 1.73, 0.13 1.67, 0.31 1.58, 0.46 1.46, 0.58 1.31, 0.67 1.13, 0.73 0.95, 0.75 0.75, 0.75 0.25, 0.73 0.05))"); } +// testElementErodedEx +// Checks that a skinny element polygon is eroded with no internal predicision reduction due to topo exes +// see https://github.com/libgeos/geos/issues/1182 +template<> +template<> +void object::test<27> +() +{ + std::string wkt = "MULTIPOLYGON (((48268.99938 -49048.29324, 44429.1 -55700.232847, 44429.1 -55107.317582, 44506.1 -54974, 44429.1 -54840, 44429.1 -51569.2, 42170.10515 -49316.27944, 48268.99938 -49048.29324)), ((43433.08324 -51823.15037, 42480.09977 -55494.96132, 42477.638798 -55504.400121, 42480.20715 -55494.547587, 42482.247919 -55485.931009, 42482.431666 -55485.976608, 43433.08324 -51823.15037)))"; + checkBuffer(wkt, -1, 0.01, +"POLYGON ((48267.2218198241 -49049.37231112561, 44430.1 -55696.500291383236, 44430.1 -55107.58561879013, 44506.96594366424 -54974.50014155032, 44507.05088958367 -54974.309530288774, 44507.09442572395 -54974.10543945913, 44507.09465615313 -54973.896756903174, 44507.05157083636 -54973.69257042533, 44506.96704607295 -54973.50177203271, 44430.1 -54839.73314639927, 44430.1 -51569.2, 44430.08071956347 -51569.00457958696, 44430.02362172434 -51568.81669475566, 44429.93090822513 -51568.64359050924, 44429.80615417933 -51568.49194189856, 42172.42282165639 -49317.178566110095, 48267.2218198241 -49049.37231112561))"); +} + } // namespace tut