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