From c37391cf135ebe358a80a91d56c0daac4115214b Mon Sep 17 00:00:00 2001 From: Paul Ramsey Date: Wed, 14 Jun 2023 11:55:57 -0700 Subject: [PATCH] Overlay with mixed dimension (#923) For overlay operations (union/difference/intersection/symdifference) support for GeometryCollection has been missing, at least for those cases of GeometryCollection that contain a mixture of types (point/line/polygon). This fix tests inputs to HeuristicOverlay (formerly a wide-ranging set of shims to work around issues with the old overlay code) and for those that are mixed-type collections, processes the component dimensions separately, with appropriate logic for the overlay operation being run. Fixes GH-797. --- include/geos/geom/Geometry.h | 3 + include/geos/geom/HeuristicOverlay.h | 68 +++- src/geom/Geometry.cpp | 28 ++ src/geom/HeuristicOverlay.cpp | 387 ++++++++++++++++++++--- tests/unit/geom/DimensionTest.cpp | 17 + tests/unit/geom/HeuristicOverlayTest.cpp | 207 ++++++++++++ 6 files changed, 672 insertions(+), 38 deletions(-) create mode 100644 tests/unit/geom/HeuristicOverlayTest.cpp diff --git a/include/geos/geom/Geometry.h b/include/geos/geom/Geometry.h index 8ba3587c1e..38343cee2f 100644 --- a/include/geos/geom/Geometry.h +++ b/include/geos/geom/Geometry.h @@ -364,6 +364,9 @@ class GEOS_DLL Geometry { return isDimensionStrict(Dimension::A); } + bool isMixedDimension() const; + bool isMixedDimension(Dimension::DimensionType* baseDim) const; + bool isCollection() const { int t = getGeometryTypeId(); return t == GEOS_GEOMETRYCOLLECTION || diff --git a/include/geos/geom/HeuristicOverlay.h b/include/geos/geom/HeuristicOverlay.h index b6146bbfd2..bc1217a785 100644 --- a/include/geos/geom/HeuristicOverlay.h +++ b/include/geos/geom/HeuristicOverlay.h @@ -20,16 +20,80 @@ #pragma once #include +#include +#include + + #include // for unique_ptr +#include -namespace geos { -namespace geom { // geos::geom +namespace geos { +namespace geom { class Geometry; +class GeometryFactory; +} +} + + +namespace geos { +namespace geom { // geos::geom std::unique_ptr GEOS_DLL HeuristicOverlay(const Geometry* g0, const Geometry* g1, int opCode); +class StructuredCollection { + +public: + + StructuredCollection(const Geometry* g) + : factory(g->getFactory()) + , pt_union(nullptr) + , line_union(nullptr) + , poly_union(nullptr) + { + readCollection(g); + unionByDimension(); + }; + + StructuredCollection() + : factory(nullptr) + , pt_union(nullptr) + , line_union(nullptr) + , poly_union(nullptr) + {}; + + void readCollection(const Geometry* g); + const Geometry* getPolyUnion() const { return poly_union.get(); } + const Geometry* getLineUnion() const { return line_union.get(); } + const Geometry* getPointUnion() const { return pt_union.get(); } + + std::unique_ptr doUnion(const StructuredCollection& a) const; + std::unique_ptr doIntersection(const StructuredCollection& a) const; + std::unique_ptr doSymDifference(const StructuredCollection& a) const; + std::unique_ptr doDifference(const StructuredCollection& a) const; + std::unique_ptr doUnaryUnion() const; + + static void toVector(const Geometry* g, std::vector& v); + void unionByDimension(void); + + +private: + + const GeometryFactory* factory; + std::vector pts; + std::vector lines; + std::vector polys; + std::unique_ptr pt_union; + std::unique_ptr line_union; + std::unique_ptr poly_union; + +}; + + + + + } // namespace geos::geom } // namespace geos diff --git a/src/geom/Geometry.cpp b/src/geom/Geometry.cpp index d514e4d36f..dfd37113ea 100644 --- a/src/geom/Geometry.cpp +++ b/src/geom/Geometry.cpp @@ -160,6 +160,34 @@ Geometry::getCentroid() const return std::unique_ptr(getFactory()->createPoint(centPt)); } +/* public */ +bool +Geometry::isMixedDimension() const +{ + Dimension::DimensionType baseDim = Dimension::DONTCARE; + return isMixedDimension(&baseDim); +} + +/* public */ +bool +Geometry::isMixedDimension(Dimension::DimensionType* baseDim) const +{ + if (isCollection()) { + for (std::size_t i = 0; i < getNumGeometries(); i++) { + if (getGeometryN(i)->isMixedDimension(baseDim)) + return true; + } + return false; + } + else { + if (*baseDim == Dimension::DONTCARE) { + *baseDim = getDimension(); + return false; + } + return *baseDim != getDimension(); + } +} + /*public*/ bool Geometry::getCentroid(CoordinateXY& ret) const diff --git a/src/geom/HeuristicOverlay.cpp b/src/geom/HeuristicOverlay.cpp index 05aa2b6078..2d3d51304c 100644 --- a/src/geom/HeuristicOverlay.cpp +++ b/src/geom/HeuristicOverlay.cpp @@ -25,6 +25,14 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include namespace geos { namespace geom { // geos::geom @@ -37,48 +45,355 @@ HeuristicOverlay(const Geometry* g0, const Geometry* g1, int opCode) { std::unique_ptr ret; -/**************************************************************************/ - -/* -* overlayng::OverlayNGRobust carries out the following steps -* -* 1. Perform overlay operation using PrecisionModel(float). -* If no exception return result. -* 2. Perform overlay operation using SnappingNoder(tolerance), starting -* with a very very small tolerance and increasing it for 5 iterations. -* The SnappingNoder moves only nodes that are within tolerance of -* other nodes and lines, leaving all the rest undisturbed, for a very -* clean result, if it manages to create one. -* If a result is found with no exception, return. -* 3. Perform overlay operation using a PrecisionModel(scale), which -* uses a SnapRoundingNoder. Every vertex will be noded to the snapping -* grid, resulting in a modified geometry. The SnapRoundingNoder approach -* reliably produces results, assuming valid inputs. -* -* Running overlayng::OverlayNGRobust at this stage should guarantee -* that none of the other heuristics are ever needed. -*/ - if (g0 == nullptr && g1 == nullptr) { - return std::unique_ptr(nullptr); + /* + * overlayng::OverlayNGRobust does not currently handle + * GeometryCollection (collections of mixed dimension) + * so we handle that case here. + */ + if ((g0->isMixedDimension() && !g0->isEmpty()) || + (g1->isMixedDimension() && !g1->isEmpty())) + { + StructuredCollection s0(g0); + StructuredCollection s1(g1); + switch (opCode) { + case OverlayNG::UNION: + return s0.doUnion(s1); + case OverlayNG::DIFFERENCE: + return s0.doDifference(s1); + case OverlayNG::SYMDIFFERENCE: + return s0.doSymDifference(s1); + case OverlayNG::INTERSECTION: + return s0.doIntersection(s1); } - else if (g0 == nullptr) { - // Use a unary union for the one-parameter case, as the pairwise - // union with one parameter is very intolerant to invalid - // collections and multi-polygons. - ret = OverlayNGRobust::Union(g1); + } + + /* + * overlayng::OverlayNGRobust carries out the following steps + * + * 1. Perform overlay operation using PrecisionModel(float). + * If no exception return result. + * 2. Perform overlay operation using SnappingNoder(tolerance), starting + * with a very very small tolerance and increasing it for 5 iterations. + * The SnappingNoder moves only nodes that are within tolerance of + * other nodes and lines, leaving all the rest undisturbed, for a very + * clean result, if it manages to create one. + * If a result is found with no exception, return. + * 3. Perform overlay operation using a PrecisionModel(scale), which + * uses a SnapRoundingNoder. Every vertex will be noded to the snapping + * grid, resulting in a modified geometry. The SnapRoundingNoder approach + * reliably produces results, assuming valid inputs. + * + * Running overlayng::OverlayNGRobust at this stage should guarantee + * that none of the other heuristics are ever needed. + */ + if (g0 == nullptr && g1 == nullptr) { + return std::unique_ptr(nullptr); + } + else if (g0 == nullptr) { + // Use a unary union for the one-parameter case, as the pairwise + // union with one parameter is very intolerant to invalid + // collections and multi-polygons. + ret = OverlayNGRobust::Union(g1); + } + else if (g1 == nullptr) { + // Use a unary union for the one-parameter case, as the pairwise + // union with one parameter is very intolerant to invalid + // collections and multi-polygons. + ret = OverlayNGRobust::Union(g0); + } + else { + ret = OverlayNGRobust::Overlay(g0, g1, opCode); + } + + return ret; +} + +/* public */ +void +StructuredCollection::readCollection(const Geometry* g) +{ + if (!factory) factory = g->getFactory(); + if (g->isCollection()) { + for (std::size_t i = 0; i < g->getNumGeometries(); i++) { + readCollection(g->getGeometryN(i)); } - else if (g1 == nullptr) { - // Use a unary union for the one-parameter case, as the pairwise - // union with one parameter is very intolerant to invalid - // collections and multi-polygons. - ret = OverlayNGRobust::Union(g0); + } + else { + if (g->isEmpty()) return; + switch (g->getGeometryTypeId()) { + case GEOS_POINT: + pts.push_back(g); + break; + case GEOS_LINESTRING: + lines.push_back(g); + break; + case GEOS_POLYGON: + polys.push_back(g); + break; + default: + throw util::IllegalArgumentException("cannot process unexpected collection"); + } + } +} + +/* public static */ +void +StructuredCollection::toVector(const Geometry* g, std::vector& v) +{ + if (!g || g->isEmpty()) return; + if (g->isCollection()) { + for (std::size_t i = 0; i < g->getNumGeometries(); i++) { + toVector(g->getGeometryN(i), v); } - else { - ret = OverlayNGRobust::Overlay(g0, g1, opCode); + } + else { + switch (g->getGeometryTypeId()) { + case GEOS_POINT: + case GEOS_LINESTRING: + case GEOS_POLYGON: + v.push_back(g); + break; + default: + return; } + } +} + + +/* public */ +void +StructuredCollection::unionByDimension(void) +{ + /* + * Remove duplication within each dimension, so that there + * is only one object covering any particular space within + * that dimension. + * This makes reasoning about the collection-on-collection + * operations a little easier later on. + */ + std::unique_ptr pt_col = factory->createMultiPoint(pts); + std::unique_ptr line_col = factory->createMultiLineString(lines); + std::unique_ptr poly_col = factory->createMultiPolygon(polys); + + pt_union = OverlayNGRobust::Union(static_cast(pt_col.get())); + line_union = OverlayNGRobust::Union(static_cast(line_col.get())); + poly_union = OverlayNGRobust::Union(static_cast(poly_col.get())); + + // io::WKTWriter w; + // std::cout << "line_col " << w.write(*line_col) << std::endl; + // std::cout << "line_union " << w.write(*line_union) << std::endl; + + if (! pt_union->isPuntal()) + throw util::IllegalArgumentException("union of points not puntal"); + if (! line_union->isLineal()) + throw util::IllegalArgumentException("union of lines not lineal"); + if (! poly_union->isPolygonal()) + throw util::IllegalArgumentException("union of polygons not polygonal"); +} + +/* public */ +std::unique_ptr +StructuredCollection::doUnaryUnion() const +{ + /* + * Before output, we clean up the components to remove spatial + * duplication. Points that lines pass through. Lines that are covered + * by polygonal areas already. Provides a "neater" output that still + * covers all the area it should. + */ + std::unique_ptr pts_less_lines = OverlayNGRobust::Overlay( + pt_union.get(), + line_union.get(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pts_less_polys_lines = OverlayNGRobust::Overlay( + pts_less_lines.get(), + poly_union.get(), + OverlayNG::DIFFERENCE); + + std::unique_ptr lines_less_polys = OverlayNGRobust::Overlay( + line_union.get(), + poly_union.get(), + OverlayNG::DIFFERENCE); + + std::vector geoms; + toVector(pts_less_polys_lines.get(), geoms); + toVector(lines_less_polys.get(), geoms); + toVector(poly_union.get(), geoms); + + return factory->buildGeometry(geoms.begin(), geoms.end()); +} + + +/* public */ +std::unique_ptr +StructuredCollection::doUnion(const StructuredCollection& a) const +{ + + auto poly_union_poly = OverlayNGRobust::Overlay( + a.getPolyUnion(), + poly_union.get(), + OverlayNG::UNION); + + auto line_union_line = OverlayNGRobust::Overlay( + a.getLineUnion(), + line_union.get(), + OverlayNG::UNION); + + auto pt_union_pt = OverlayNGRobust::Overlay( + a.getPointUnion(), + pt_union.get(), + OverlayNG::UNION); + + StructuredCollection c; + c.readCollection(poly_union_poly.get()); + c.readCollection(line_union_line.get()); + c.readCollection(pt_union_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); +} + + +std::unique_ptr +StructuredCollection::doIntersection(const StructuredCollection& a) const +{ + std::unique_ptr poly_inter_poly = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPolyUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr poly_inter_line = OverlayNGRobust::Overlay( + poly_union.get(), + a.getLineUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr poly_inter_pt = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPointUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr line_inter_poly = OverlayNGRobust::Overlay( + line_union.get(), + a.getPolyUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr line_inter_line = OverlayNGRobust::Overlay( + line_union.get(), + a.getLineUnion(), + OverlayNG::INTERSECTION); - return ret; + std::unique_ptr line_inter_pt = OverlayNGRobust::Overlay( + line_union.get(), + a.getPointUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr pt_inter_pt = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPointUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr pt_inter_line = OverlayNGRobust::Overlay( + pt_union.get(), + a.getLineUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr pt_inter_poly = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPolyUnion(), + OverlayNG::INTERSECTION); + + // io::WKTWriter w; + // std::cout << "poly_inter_poly " << w.write(*poly_inter_poly) << std::endl; + // std::cout << "poly_union.get() " << w.write(poly_union.get()) << std::endl; + // std::cout << "a.getLineUnion() " << w.write(a.getLineUnion()) << std::endl; + // std::cout << "poly_inter_line " << w.write(*poly_inter_line) << std::endl; + // std::cout << "poly_inter_pt " << w.write(*poly_inter_pt) << std::endl; + // std::cout << "line_inter_line " << w.write(*line_inter_line) << std::endl; + // std::cout << "line_inter_pt " << w.write(*line_inter_pt) << std::endl; + // std::cout << "pt_inter_pt " << w.write(*pt_inter_pt) << std::endl; + + StructuredCollection c; + c.readCollection(poly_inter_poly.get()); + c.readCollection(poly_inter_line.get()); + c.readCollection(poly_inter_pt.get()); + c.readCollection(line_inter_poly.get()); + c.readCollection(line_inter_line.get()); + c.readCollection(line_inter_pt.get()); + c.readCollection(pt_inter_poly.get()); + c.readCollection(pt_inter_line.get()); + c.readCollection(pt_inter_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); } + +std::unique_ptr +StructuredCollection::doDifference(const StructuredCollection& a) const +{ + std::unique_ptr poly_diff_poly = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPolyUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr line_diff_poly = OverlayNGRobust::Overlay( + line_union.get(), + a.getPolyUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_diff_poly = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPolyUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr line_diff_poly_line = OverlayNGRobust::Overlay( + line_diff_poly.get(), + a.getLineUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_diff_poly_line = OverlayNGRobust::Overlay( + pt_diff_poly.get(), + line_diff_poly_line.get(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_diff_poly_line_pt = OverlayNGRobust::Overlay( + pt_diff_poly_line.get(), + a.getPointUnion(), + OverlayNG::DIFFERENCE); + + StructuredCollection c; + c.readCollection(poly_diff_poly.get()); + c.readCollection(line_diff_poly_line.get()); + c.readCollection(pt_diff_poly_line_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); +} + +std::unique_ptr +StructuredCollection::doSymDifference(const StructuredCollection& a) const +{ + std::unique_ptr poly_symdiff_poly = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPolyUnion(), + OverlayNG::SYMDIFFERENCE); + + std::unique_ptr line_symdiff_line = OverlayNGRobust::Overlay( + line_union.get(), + a.getLineUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_symdiff_pt = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPointUnion(), + OverlayNG::DIFFERENCE); + + StructuredCollection c; + c.readCollection(poly_symdiff_poly.get()); + c.readCollection(line_symdiff_line.get()); + c.readCollection(pt_symdiff_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); +} + + } // namespace geos::geom } // namespace geos diff --git a/tests/unit/geom/DimensionTest.cpp b/tests/unit/geom/DimensionTest.cpp index c37d54ff9d..5ac000d166 100644 --- a/tests/unit/geom/DimensionTest.cpp +++ b/tests/unit/geom/DimensionTest.cpp @@ -5,6 +5,7 @@ #include // geos #include +#include #include #include @@ -137,5 +138,21 @@ void object::test<5> } } +template<> +template<> +void object::test<6> +() +{ + using geos::geom::Dimension; + geos::io::WKTReader reader; + auto geom = reader.read("GEOMETRYCOLLECTION(POINT(1 1), LINESTRING(2 2, 3 3))"); + Dimension::DimensionType d = geom->getDimension(); + // std::cout << d << std::endl; + // getDimension() finds the highest dimension in the collection + ensure(d == Dimension::L); +} + + + } // namespace tut diff --git a/tests/unit/geom/HeuristicOverlayTest.cpp b/tests/unit/geom/HeuristicOverlayTest.cpp new file mode 100644 index 0000000000..c687e8a69b --- /dev/null +++ b/tests/unit/geom/HeuristicOverlayTest.cpp @@ -0,0 +1,207 @@ +// +// Test Suite for geos::geom::HeuristicOverlay + +#include + +// geos +#include +#include +#include +#include +#include +#include +#include +#include + +// std +#include +#include + +namespace tut { +// +// Test Group +// + +using geos::operation::overlayng::OverlayNG; + +struct test_heuristic_data { + + WKTReader reader_; + WKTWriter writer_; + + test_heuristic_data() {} + + void checkOverlay( + const std::string& wkt1, + const std::string& wkt2, + int opCode, + const std::string& wkt_expected) + { + std::unique_ptr g1 = reader_.read(wkt1); + std::unique_ptr g2 = reader_.read(wkt2); + std::unique_ptr expected = reader_.read(wkt_expected); + std::unique_ptr actual = HeuristicOverlay(g1.get(), g2.get(), opCode); + + // std::cout << "expected:" << std::endl << writer_.write(*expected) << std::endl; + // std::cout << "actual:" << std::endl << writer_.write(*actual) << std::endl; + + ensure_equals_geometry(expected.get(), actual.get()); + } + + +}; + +typedef test_group group; +typedef group::object object; + +group test_heuristic_data("geos::geom::HeuristicOverlay"); + +// +// Test Cases +// + +// +// These tests exercise the special cast code in HeuristicOverlay +// for GeometryCollection in which the contents are "mixed dimension", +// such as points and lines or lines and polygons in the same collection. +// For those cases the result of the overlay might be a matter of +// interpretation, depending on the inputs and the opinions of the +// end user. The implementation just tries to generate a visually +// defensible, simplified answer. +// + +template<> +template<> +void object::test<1> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(1 1, 2 2))", + "GEOMETRYCOLLECTION(POINT(10 10), LINESTRING(11 11, 12 12))", + OverlayNG::UNION, + "GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(1 1, 2 2), POINT(10 10), LINESTRING(11 11, 12 12))" + ); +} + +template<> +template<> +void object::test<2> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(1 1, 2 2))", + "POLYGON((-10 -10, -10 10, 10 10, 10 -10, -10 -10))", + OverlayNG::UNION, + "POLYGON((-10 -10, -10 10, 10 10, 10 -10, -10 -10))" + ); +} + +template<> +template<> +void object::test<3> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POINT(0.5 0.5), LINESTRING(0 0, 2 2), POLYGON((0 0, 1 0, 1 1, 0 1, 0 0)))", + "GEOMETRYCOLLECTION(LINESTRING(0.5 0.5, 0.5 4), POINT(2 0))", + OverlayNG::UNION, + "GEOMETRYCOLLECTION (POINT (2 0), LINESTRING (0.5 1, 0.5 4), LINESTRING (1 1, 2 2), POLYGON ((0 1, 1 1, 1 0, 0 0, 0 1)))" + ); +} + +template<> +template<> +void object::test<4> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(20 20, 30 30))", + "GEOMETRYCOLLECTION(POLYGON((9 9, 21 9, 21 21, 9 21, 9 9)), POINT(5 5))", + OverlayNG::DIFFERENCE, + "GEOMETRYCOLLECTION (LINESTRING (21 21, 30 30), POLYGON ((10 0, 0 0, 0 10, 9 10, 9 9, 10 9, 10 0)))" + ); +} + +template<> +template<> +void object::test<5> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(20 20, 30 30))", + "GEOMETRYCOLLECTION(POLYGON((9 9, 21 9, 21 21, 9 21, 9 9)), POINT(5 5))", + OverlayNG::INTERSECTION, + "GEOMETRYCOLLECTION (POINT (5 5), LINESTRING(20 20, 21 21), POLYGON ((10 10, 10 9, 9 9, 9 10, 10 10)))" + ); +} + +template<> +template<> +void object::test<6> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(20 20, 30 30))", + "GEOMETRYCOLLECTION(POLYGON((9 9, 21 9, 21 21, 9 21, 9 9)), POINT(5 5))", + OverlayNG::SYMDIFFERENCE, + "GEOMETRYCOLLECTION (LINESTRING (21 21, 30 30), POLYGON ((0 0, 0 10, 9 10, 9 9, 10 9, 10 0, 0 0)), POLYGON ((9 10, 9 21, 21 21, 21 9, 10 9, 10 10, 9 10)))" + ); +} + + +template<> +template<> +void object::test<7> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))", + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))", + OverlayNG::UNION, + "POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))" + ); +} + +template<> +template<> +void object::test<8> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))", + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POINT(20 20))", + OverlayNG::DIFFERENCE, + "GEOMETRYCOLLECTION EMPTY" + ); +} + +template<> +template<> +void object::test<9> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))", + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))", + OverlayNG::INTERSECTION, + "POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))" + ); +} + + +template<> +template<> +void object::test<10> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POINT EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 5, 6 6))", + "GEOMETRYCOLLECTION(POLYGON((2 2, 12 2, 12 12, 2 12, 2 2)), LINESTRING EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 6, 6 5))", + OverlayNG::INTERSECTION, + "GEOMETRYCOLLECTION (POINT (11 11), POLYGON ((10 10, 10 2, 2 2, 2 10, 10 10)))" + ); +} + +template<> +template<> +void object::test<11> () +{ + checkOverlay( + "GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)), POINT EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 5, 6 6))", + "GEOMETRYCOLLECTION(POLYGON((2 2, 12 2, 12 12, 2 12, 2 2)), LINESTRING EMPTY, MULTIPOINT(4 4, 11 11), LINESTRING(5 6, 6 5))", + OverlayNG::UNION, + "POLYGON ((2 12, 12 12, 12 2, 10 2, 10 0, 0 0, 0 10, 2 10, 2 12))" + ); +} + +} // namespace tut