diff --git a/include/geos/geom/util/GeometryFixer.h b/include/geos/geom/util/GeometryFixer.h index 499885787a..9ffc4de135 100644 --- a/include/geos/geom/util/GeometryFixer.h +++ b/include/geos/geom/util/GeometryFixer.h @@ -54,17 +54,20 @@ namespace util { // geos.geom.util * * Empty atomic geometries are valid and are returned unchanged * * Empty elements are removed from collections * * Point: keep valid coordinate, or EMPTY - * * LineString: fix coordinate list - * * LinearRing: fix coordinate list, return as valid ring or else LineString - * * Polygon: transform into a valid polygon, preserving as much of the extent and vertices as possible - * * MultiPolygon: fix each polygon, then ensure result is non-overlapping (via union) - * * GeometryCollection: fix each element + * * LineString: coordinates are fixed + * * LinearRing: coordinates are fixed, Keep valid ring or else convert into LineString + * * Polygon: transform into a valid polygon, + * * preserving as much of the extent and vertices as possible. + * * Rings are fixed to ensure they are valid + * * Holes intersecting the shell are subtracted from the shell + * * Holes outside the shell are converted into polygons + * * MultiPolygon: each polygon is fixed, + * then result made non-overlapping (via union) + * * GeometryCollection: each element is fixed * * Collapsed lines and polygons are handled as follows, - * - * depending on the keepCollapsed setting: - * - * * false: (default) collapses are converted to empty geometries - * * true: collapses are converted to a valid geometry of lower dimension + * depending on the keepCollapsed setting: + * * false: (default) collapses are converted to empty geometries + * * true: collapses are converted to a valid geometry of lower dimension * * @author Martin Davis */ @@ -123,12 +126,40 @@ class GEOS_DLL GeometryFixer { std::unique_ptr fixMultiLineString(const MultiLineString* geom) const; std::unique_ptr fixPolygon(const geom::Polygon* geom) const; std::unique_ptr fixPolygonElement(const geom::Polygon* geom) const; - std::unique_ptr fixHoles(const geom::Polygon* geom) const; + std::vector> fixHoles(const geom::Polygon* geom) const; std::unique_ptr removeHoles(const geom::Geometry* shell, const geom::Geometry* holes) const; std::unique_ptr fixRing(const geom::LinearRing* ring) const; std::unique_ptr fixMultiPolygon(const geom::MultiPolygon* geom) const; std::unique_ptr fixCollection(const geom::GeometryCollection* geom) const; + void classifyHoles( + const Geometry* shell, + std::vector>& holesFixed, + std::vector& holes, + std::vector& shells) const; + + /** + * Subtracts a list of polygonal geometries from a polygonal geometry. + * + * @param shell polygonal geometry for shell + * @param holes polygonal geometries to subtract + * @return the result geometry + */ + std::unique_ptr difference( + const Geometry* shell, + std::vector& holes) const; + + /** + * Unions a list of polygonal geometries. + * Optimizes case of zero or one input geometries. + * Requires that the inputs are net new objects. + * + * @param polys the polygonal geometries to union + * @return the union of the inputs + */ + std::unique_ptr unionGeometry( + std::vector& polys) const; + }; } // namespace geos.geom.util diff --git a/src/geom/util/GeometryFixer.cpp b/src/geom/util/GeometryFixer.cpp index 634380f4a7..67a3026589 100644 --- a/src/geom/util/GeometryFixer.cpp +++ b/src/geom/util/GeometryFixer.cpp @@ -15,13 +15,18 @@ #include #include #include +#include +#include #include #include #include #include #include +#include using geos::operation::valid::RepeatedPointRemover; +using geos::operation::overlayng::OverlayNGRobust; +using geos::operation::geounion::UnaryUnionOp; namespace geos { namespace geom { // geos.geom @@ -248,52 +253,99 @@ GeometryFixer::fixPolygonElement(const Polygon* p_geom) const std::unique_ptr line(factory->createLineString(*cs)); return fixLineString(line.get()); } - //-- if not allowing collapses then return empty polygon + //--- if not allowing collapses then return empty polygon return nullptr; } - // if no holes then done + //--- if no holes then done if (p_geom->getNumInteriorRing() == 0) { return fixShell; } - std::unique_ptr fixedHoles = fixHoles(p_geom); - std::unique_ptr result = removeHoles(fixShell.get(), fixedHoles.get()); - return result; + + //--- fix holes, classify, and construct shell-true holes + std::vector> holesFixed = fixHoles(p_geom); + std::vector holes; + std::vector shells; + + classifyHoles(fixShell.get(), holesFixed, holes, shells); + std::unique_ptr polyWithHoles = difference(fixShell.get(), holes); + if (shells.empty()) { + return polyWithHoles; + } + + //--- if some holes converted to shells, union all shells + shells.push_back(polyWithHoles.get()); + return unionGeometry(shells); } /* private */ -std::unique_ptr -GeometryFixer::fixHoles(const Polygon* p_geom) const +std::vector> +GeometryFixer::fixHoles(const Polygon* poly) const { std::vector> holes; - - for (std::size_t i = 0; i < p_geom->getNumInteriorRing(); i++) { - std::unique_ptr holeRep = fixRing(p_geom->getInteriorRingN(i)); - // Do not preserve null/empty holes - if (holeRep != nullptr && !holeRep->isEmpty()) { + for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) { + std::unique_ptr holeRep = fixRing(poly->getInteriorRingN(i)); + if (holeRep != nullptr) { holes.emplace_back(holeRep.release()); } } - if (holes.empty()) - return nullptr; + return holes; +} - if (holes.size() == 1) { - std::unique_ptr h(holes.at(0).release()); - return h; +/* private */ +void +GeometryFixer::classifyHoles( + const Geometry* shell, + std::vector>& holesFixed, + std::vector& holes, + std::vector& shells) const +{ + std::unique_ptr shellPrep = + prep::PreparedGeometryFactory::prepare(shell); + + for (auto& hole : holesFixed) { + const Geometry* cptrHole = hole.get(); + if (shellPrep->intersects(cptrHole)) { + holes.push_back(cptrHole); + } + else { + shells.push_back(cptrHole); + } } - std::unique_ptr holesGeom = (factory->createGeometryCollection(std::move(holes))); - return operation::overlayng::OverlayNGRobust::Union(holesGeom.get()); } + /* private */ std::unique_ptr -GeometryFixer::removeHoles(const Geometry* shell, const Geometry* holes) const +GeometryFixer::difference( + const Geometry* shell, + std::vector& holes) const { - if (holes == nullptr || holes->isEmpty()) + if (holes.empty()) return shell->clone(); - return operation::overlayng::OverlayNGRobust::Overlay(shell, holes, - operation::overlayng::OverlayNG::DIFFERENCE); + if (holes.size() == 1) + return OverlayNGRobust::Difference(shell, holes[0]); + std::unique_ptr holesUnion = unionGeometry(holes); + return OverlayNGRobust::Difference(shell, holesUnion.get()); +} + + +/* private */ +std::unique_ptr +GeometryFixer::unionGeometry(std::vector& polys) const +{ + if (polys.empty()) { + return factory->createPolygon(geom->getCoordinateDimension()); + } + if (polys.size() == 1) { + return (polys[0])->clone(); + } + + UnaryUnionOp op(polys); + return op.Union(); + // return OverlayNGRobust::Union(polys); } + /* private */ std::unique_ptr GeometryFixer::fixRing(const LinearRing* ring) const @@ -322,7 +374,7 @@ GeometryFixer::fixMultiPolygon(const MultiPolygon* p_geom) const return factory->createMultiPolygon(); } std::unique_ptr polysGeom = (factory->createGeometryCollection(std::move(polys))); - return operation::overlayng::OverlayNGRobust::Union(polysGeom.get()); + return OverlayNGRobust::Union(polysGeom.get()); } /* private */ diff --git a/tests/unit/geom/util/GeometryFixerTest.cpp b/tests/unit/geom/util/GeometryFixerTest.cpp index f724b13fc9..1a4d7c4de9 100644 --- a/tests/unit/geom/util/GeometryFixerTest.cpp +++ b/tests/unit/geom/util/GeometryFixerTest.cpp @@ -58,6 +58,7 @@ struct test_geometryfixer_data { } void checkFix(const Geometry* input, bool keepCollapse, const std::string& wktExpected) { + std::unique_ptr actual; if (keepCollapse) { GeometryFixer fixer(input); @@ -110,9 +111,6 @@ struct test_geometryfixer_data { std::string actualWKT = wktwriter_.write(actual.get()); std::string expectedWKT = wktwriter_.write(expected.get()); ensure_equals(actualWKT, expectedWKT); - - - // checkEqualXYZ(expected, actual); } std::unique_ptr createPoint(double x, double y) { @@ -123,12 +121,11 @@ struct test_geometryfixer_data { } }; -typedef test_group group; +typedef test_group group; typedef group::object object; group test_geometryfixer_group("geos::geom::util::GeometryFixer"); - template<> template<> void object::test<1>() @@ -170,7 +167,7 @@ void object::test<5>() checkFix(pt.get() , "POINT EMPTY"); } - //---------------------------------------- +//---------------------------------------- // testMultiPointNaN template<> @@ -208,7 +205,7 @@ void object::test<9>() "MULTIPOINT EMPTY"); } - //---------------------------------------- +//---------------------------------------- // testLineStringEmpty template<> @@ -585,6 +582,7 @@ void object::test<47>() "GEOMETRYCOLLECTION EMPTY"); } + // testGCWithAllEmpty template<> template<> @@ -595,9 +593,6 @@ void object::test<48>() "GEOMETRYCOLLECTION (POINT EMPTY, LINESTRING EMPTY, POLYGON EMPTY)"); } - - //---------------------------------------- - template<> template<> void object::test<49>() @@ -607,9 +602,19 @@ void object::test<49>() "MULTIPOLYGON Z(((10 10 1, 10 90 1, 50 50 5, 10 10 1)), ((50 50 5, 90 90 9, 90 10 9, 50 50 5)))"); } +// testPolygonHoleOverlapAndOutsideOverlap template<> template<> void object::test<50>() +{ + checkFix( + "POLYGON ((50 90, 80 90, 80 10, 50 10, 50 90), (70 80, 90 80, 90 20, 70 20, 70 80), (40 80, 40 50, 0 50, 0 80, 40 80), (30 40, 10 40, 10 60, 30 60, 30 40), (60 70, 80 70, 80 30, 60 30, 60 70))", + "MULTIPOLYGON (((10 40, 10 50, 0 50, 0 80, 40 80, 40 50, 30 50, 30 40, 10 40)), ((70 80, 70 70, 60 70, 60 30, 70 30, 70 20, 80 20, 80 10, 50 10, 50 90, 80 90, 80 80, 70 80)))"); +} + +template<> +template<> +void object::test<51>() { checkFixZ( "POLYGON Z ((10 90 1, 60 90 6, 60 10 6, 10 10 1, 10 90 1), (20 80 2, 90 80 9, 90 20 9, 20 20 2, 20 80 2))", @@ -627,5 +632,4 @@ void object::test<52>() - } // namespace tut