Skip to content

Commit

Permalink
Port JTS locationtech/jts#772 GeometryFixer support for unnested holes
Browse files Browse the repository at this point in the history
  • Loading branch information
pramsey committed Sep 7, 2021
1 parent 8ac9e91 commit cb6a2c3
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 46 deletions.
53 changes: 42 additions & 11 deletions include/geos/geom/util/GeometryFixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,20 @@ namespace util { // geos.geom.util
* * Empty atomic geometries are valid and are returned unchanged
* * Empty elements are removed from collections
* * <code>Point</code>: keep valid coordinate, or EMPTY
* * <code>LineString</code>: fix coordinate list
* * <code>LinearRing</code>: fix coordinate list, return as valid ring or else <code>LineString</code>
* * <code>Polygon</code>: transform into a valid polygon, preserving as much of the extent and vertices as possible
* * <code>MultiPolygon</code>: fix each polygon, then ensure result is non-overlapping (via union)
* * <code>GeometryCollection</code>: fix each element
* * <code>LineString</code>: coordinates are fixed
* * <code>LinearRing</code>: coordinates are fixed, Keep valid ring or else convert into <code>LineString</code>
* * <code>Polygon</code>: transform into a valid polygon,
* * preserving as much of the extent and vertices as possible.
* * Rings are fixed to ensure they are valid</li>
* * Holes intersecting the shell are subtracted from the shell</li>
* * Holes outside the shell are converted into polygons</li>
* * <code>MultiPolygon</code>: each polygon is fixed,
* then result made non-overlapping (via union)</li>
* * <code>GeometryCollection</code>: each element is fixed</li>
* * Collapsed lines and polygons are handled as follows,
*
* depending on the <code>keepCollapsed</code> setting:
*
* * <code>false</code>: (default) collapses are converted to empty geometries
* * <code>true</code>: collapses are converted to a valid geometry of lower dimension
* depending on the <code>keepCollapsed</code> setting:
* * <code>false</code>: (default) collapses are converted to empty geometries
* * <code>true</code>: collapses are converted to a valid geometry of lower dimension
*
* @author Martin Davis
*/
Expand Down Expand Up @@ -123,12 +126,40 @@ class GEOS_DLL GeometryFixer {
std::unique_ptr<geom::Geometry> fixMultiLineString(const MultiLineString* geom) const;
std::unique_ptr<geom::Geometry> fixPolygon(const geom::Polygon* geom) const;
std::unique_ptr<geom::Geometry> fixPolygonElement(const geom::Polygon* geom) const;
std::unique_ptr<geom::Geometry> fixHoles(const geom::Polygon* geom) const;
std::vector<std::unique_ptr<Geometry>> fixHoles(const geom::Polygon* geom) const;
std::unique_ptr<geom::Geometry> removeHoles(const geom::Geometry* shell, const geom::Geometry* holes) const;
std::unique_ptr<geom::Geometry> fixRing(const geom::LinearRing* ring) const;
std::unique_ptr<geom::Geometry> fixMultiPolygon(const geom::MultiPolygon* geom) const;
std::unique_ptr<geom::Geometry> fixCollection(const geom::GeometryCollection* geom) const;

void classifyHoles(
const Geometry* shell,
std::vector<std::unique_ptr<Geometry>>& holesFixed,
std::vector<const Geometry*>& holes,
std::vector<const Geometry*>& 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<Geometry> difference(
const Geometry* shell,
std::vector<const Geometry*>& 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<Geometry> unionGeometry(
std::vector<const Geometry*>& polys) const;

};

} // namespace geos.geom.util
Expand Down
100 changes: 76 additions & 24 deletions src/geom/util/GeometryFixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
#include <geos/geom/util/GeometryFixer.h>
#include <geos/geom/Geometry.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/geom/prep/PreparedGeometry.h>
#include <geos/geom/prep/PreparedGeometryFactory.h>
#include <geos/operation/overlayng/OverlayNG.h>
#include <geos/operation/overlayng/OverlayNGRobust.h>
#include <geos/operation/buffer/BufferOp.h>
#include <geos/operation/valid/RepeatedPointRemover.h>
#include <geos/util/UnsupportedOperationException.h>
#include <geos/operation/union/UnaryUnionOp.h>

using geos::operation::valid::RepeatedPointRemover;
using geos::operation::overlayng::OverlayNGRobust;
using geos::operation::geounion::UnaryUnionOp;

namespace geos {
namespace geom { // geos.geom
Expand Down Expand Up @@ -248,52 +253,99 @@ GeometryFixer::fixPolygonElement(const Polygon* p_geom) const
std::unique_ptr<LineString> 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<Geometry> fixedHoles = fixHoles(p_geom);
std::unique_ptr<Geometry> result = removeHoles(fixShell.get(), fixedHoles.get());
return result;

//--- fix holes, classify, and construct shell-true holes
std::vector<std::unique_ptr<Geometry>> holesFixed = fixHoles(p_geom);
std::vector<const Geometry*> holes;
std::vector<const Geometry*> shells;

classifyHoles(fixShell.get(), holesFixed, holes, shells);
std::unique_ptr<Geometry> 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<Geometry>
GeometryFixer::fixHoles(const Polygon* p_geom) const
std::vector<std::unique_ptr<Geometry>>
GeometryFixer::fixHoles(const Polygon* poly) const
{
std::vector<std::unique_ptr<Geometry>> holes;

for (std::size_t i = 0; i < p_geom->getNumInteriorRing(); i++) {
std::unique_ptr<Geometry> 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<Geometry> 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<Geometry> h(holes.at(0).release());
return h;
/* private */
void
GeometryFixer::classifyHoles(
const Geometry* shell,
std::vector<std::unique_ptr<Geometry>>& holesFixed,
std::vector<const Geometry*>& holes,
std::vector<const Geometry*>& shells) const
{
std::unique_ptr<prep::PreparedGeometry> 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<GeometryCollection> holesGeom = (factory->createGeometryCollection(std::move(holes)));
return operation::overlayng::OverlayNGRobust::Union(holesGeom.get());
}


/* private */
std::unique_ptr<Geometry>
GeometryFixer::removeHoles(const Geometry* shell, const Geometry* holes) const
GeometryFixer::difference(
const Geometry* shell,
std::vector<const Geometry*>& 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<Geometry> holesUnion = unionGeometry(holes);
return OverlayNGRobust::Difference(shell, holesUnion.get());
}


/* private */
std::unique_ptr<Geometry>
GeometryFixer::unionGeometry(std::vector<const Geometry*>& 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<Geometry>
GeometryFixer::fixRing(const LinearRing* ring) const
Expand Down Expand Up @@ -322,7 +374,7 @@ GeometryFixer::fixMultiPolygon(const MultiPolygon* p_geom) const
return factory->createMultiPolygon();
}
std::unique_ptr<GeometryCollection> polysGeom = (factory->createGeometryCollection(std::move(polys)));
return operation::overlayng::OverlayNGRobust::Union(polysGeom.get());
return OverlayNGRobust::Union(polysGeom.get());
}

/* private */
Expand Down
26 changes: 15 additions & 11 deletions tests/unit/geom/util/GeometryFixerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct test_geometryfixer_data {
}

void checkFix(const Geometry* input, bool keepCollapse, const std::string& wktExpected) {

std::unique_ptr<Geometry> actual;
if (keepCollapse) {
GeometryFixer fixer(input);
Expand Down Expand Up @@ -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<Point> createPoint(double x, double y) {
Expand All @@ -123,12 +121,11 @@ struct test_geometryfixer_data {
}
};

typedef test_group<test_geometryfixer_data> group;
typedef test_group<test_geometryfixer_data, 255> group;
typedef group::object object;

group test_geometryfixer_group("geos::geom::util::GeometryFixer");


template<>
template<>
void object::test<1>()
Expand Down Expand Up @@ -170,7 +167,7 @@ void object::test<5>()
checkFix(pt.get() , "POINT EMPTY");
}

//----------------------------------------
//----------------------------------------

// testMultiPointNaN
template<>
Expand Down Expand Up @@ -208,7 +205,7 @@ void object::test<9>()
"MULTIPOINT EMPTY");
}

//----------------------------------------
//----------------------------------------

// testLineStringEmpty
template<>
Expand Down Expand Up @@ -585,6 +582,7 @@ void object::test<47>()
"GEOMETRYCOLLECTION EMPTY");
}


// testGCWithAllEmpty
template<>
template<>
Expand All @@ -595,9 +593,6 @@ void object::test<48>()
"GEOMETRYCOLLECTION (POINT EMPTY, LINESTRING EMPTY, POLYGON EMPTY)");
}


//----------------------------------------

template<>
template<>
void object::test<49>()
Expand All @@ -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))",
Expand All @@ -627,5 +632,4 @@ void object::test<52>()




} // namespace tut

0 comments on commit cb6a2c3

Please sign in to comment.