[geos-commits] [SCM] GEOS branch master updated. 73ca614ccce4b4e773d4ad0d7a9aa7f707665793

git at osgeo.org git at osgeo.org
Tue May 28 18:37:55 PDT 2019


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".

The branch, master has been updated
       via  73ca614ccce4b4e773d4ad0d7a9aa7f707665793 (commit)
       via  c6b3d82d4c3954288758344c6d46d1893bdb2850 (commit)
       via  b73ab76f0c4d4b0aa146a321f1685917f8d0629c (commit)
       via  5033a1e2409c20c0d00c58ccf9b2905cbb214865 (commit)
       via  e6de5e9c97d006d6add04f6d1ec23f3c4d66163c (commit)
       via  cd12871fc0fd86118b31d7e7e65711d1296b8a3c (commit)
       via  909bc961ea7ba314222e37f3ea8755e5c41864d1 (commit)
       via  263d05cac7c69dd1f2d9458ef8b54dbb5324c06c (commit)
       via  4f3d339e749c0ac13adb851789a8f3d8ef30c72f (commit)
       via  0387a9fdeee3906e8386d9f83c195a2133dfafe0 (commit)
       via  c5c826b340045c6e1581afb7587f41acf57237fa (commit)
       via  3c5b5af760d860aeb80773433c0832f623734fbe (commit)
       via  7919420cac16ebd32501c37bed9f4d6bb249ca0a (commit)
       via  ff01eef205592e467821b822aa3992e70f4ef6fc (commit)
       via  72db1ce1c418f0451ded95f8d921a33f6827558c (commit)
      from  c0f70dbc73906ae4c0b90954dfb3e030b7694d9d (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 73ca614ccce4b4e773d4ad0d7a9aa7f707665793
Merge: c0f70db c6b3d82
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue May 28 21:37:41 2019 -0400

    Merge branch 'coverage-union'


commit c6b3d82d4c3954288758344c6d46d1893bdb2850
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue May 28 20:30:24 2019 -0400

    Add missing GEOS_DLL for MSVC

diff --git a/include/geos/operation/polygonize/HoleAssigner.h b/include/geos/operation/polygonize/HoleAssigner.h
index 254f6df..82dbe1f 100644
--- a/include/geos/operation/polygonize/HoleAssigner.h
+++ b/include/geos/operation/polygonize/HoleAssigner.h
@@ -34,7 +34,7 @@ namespace polygonize {
  *
  * @author mdavis
  */
-class HoleAssigner {
+class GEOS_DLL HoleAssigner {
 public:
     /**
      * Assigns hole rings to shell rings

commit b73ab76f0c4d4b0aa146a321f1685917f8d0629c
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat May 18 22:22:59 2019 -0400

    Fix compilation on gcc 4.8

diff --git a/include/geos/operation/polygonize/HoleAssigner.h b/include/geos/operation/polygonize/HoleAssigner.h
index a40380f..254f6df 100644
--- a/include/geos/operation/polygonize/HoleAssigner.h
+++ b/include/geos/operation/polygonize/HoleAssigner.h
@@ -44,7 +44,7 @@ public:
     static void assignHolesToShells(std::vector<EdgeRing*> & holes, std::vector<EdgeRing*> & shells);
 
 private:
-    explicit HoleAssigner(std::vector<EdgeRing*> & shells) : m_shells{shells} {
+    explicit HoleAssigner(std::vector<EdgeRing*> & shells) : m_shells(shells) {
         buildIndex();
     }
 

commit 5033a1e2409c20c0d00c58ccf9b2905cbb214865
Merge: e6de5e9 b15fd11
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat May 18 22:06:50 2019 -0400

    Merge branch 'master' into coverage-union


commit e6de5e9c97d006d6add04f6d1ec23f3c4d66163c
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat May 18 22:02:32 2019 -0400

    Switch Travis builds to xenial

diff --git a/.travis.yml b/.travis.yml
index 7f6d5b0..77f2a46 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,7 @@
 # by the Free Software Foundation.
 # See the COPYING file for more information.
 #
-dist: trusty
+dist: xenial
 sudo: false
 
 language: cpp

commit cd12871fc0fd86118b31d7e7e65711d1296b8a3c
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat May 18 21:59:12 2019 -0400

    Add HoleAssigner to automake files

diff --git a/include/geos/operation/polygonize/Makefile.am b/include/geos/operation/polygonize/Makefile.am
index 4329213..d55ed0d 100644
--- a/include/geos/operation/polygonize/Makefile.am
+++ b/include/geos/operation/polygonize/Makefile.am
@@ -10,6 +10,7 @@ geosdir = $(includedir)/geos/operation/polygonize
 geos_HEADERS = \
     BuildArea.h \
     EdgeRing.h \
+    HoleAssigner.h \
     PolygonizeDirectedEdge.h \
     PolygonizeEdge.h \
     PolygonizeGraph.h \
diff --git a/src/operation/polygonize/Makefile.am b/src/operation/polygonize/Makefile.am
index 0e8d417..dbbfa9f 100644
--- a/src/operation/polygonize/Makefile.am
+++ b/src/operation/polygonize/Makefile.am
@@ -12,6 +12,7 @@ liboppolygonize_la_SOURCES = \
     PolygonizeEdge.cpp \
     PolygonizeGraph.cpp \
     Polygonizer.cpp \
+    HoleAssigner.cpp \
     BuildArea.cpp \
     EdgeRing.cpp 
 

commit 909bc961ea7ba314222e37f3ea8755e5c41864d1
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat May 18 21:58:52 2019 -0400

    NEWS entry

diff --git a/NEWS b/NEWS
index b014dce..82f0a20 100644
--- a/NEWS
+++ b/NEWS
@@ -5,11 +5,14 @@ Changes in 3.8.0
   - CAPI: GEOSBuildArea (#952, Even Rouault)
   - CAPI: GEOSMakeValid (#952, Even Rouault)
   - CAPI: GEOSPolygonize_valid (#727, Dan Baston)
+  - CAPI: GEOSCoverageUnion (Dan Baston)
 
 - Improvements:
   - Improve performance and robustness of GEOSPointOnSurface (Martin Davis)
   - Improve performance of GEOSPolygonize for cases with many potential
     holes (#748, Dan Baston)
+  - Improve performance of GEOSPolygonize for cases with many or complex
+    shells (Dan Baston, Martin Davis)
 
 
 Changes in 3.7.2

commit 263d05cac7c69dd1f2d9458ef8b54dbb5324c06c
Author: Daniel Baston <dbaston at gmail.com>
Date:   Sat May 18 21:47:17 2019 -0400

    Rework c5c826b340 to match what ultimately landed in JTS

diff --git a/include/geos/operation/polygonize/EdgeRing.h b/include/geos/operation/polygonize/EdgeRing.h
index b40f1c2..0ef3b01 100644
--- a/include/geos/operation/polygonize/EdgeRing.h
+++ b/include/geos/operation/polygonize/EdgeRing.h
@@ -13,7 +13,7 @@
  *
  **********************************************************************
  *
- * Last port: operation/polygonize/EdgeRing.java rev. 974
+ * Last port: operation/polygonize/EdgeRing.java 0b3c7e3eb0d3e
  *
  **********************************************************************/
 
@@ -30,6 +30,7 @@
 
 #include <memory>
 #include <vector>
+#include <geos/geom/Location.h>
 
 #ifdef _MSC_VER
 #pragma warning(push)
@@ -72,7 +73,7 @@ private:
     // cache the following data for efficiency
     std::unique_ptr<geom::LinearRing> ring;
     std::unique_ptr<geom::CoordinateSequence> ringPts;
-    std::unique_ptr<algorithm::locate::IndexedPointInAreaLocator> ringLocator;
+    std::unique_ptr<algorithm::locate::PointOnGeometryLocator> ringLocator;
 
     std::unique_ptr<std::vector<geom::Geometry*>> holes;
 
@@ -95,6 +96,13 @@ private:
                         bool isForward,
                         geom::CoordinateSequence* coordList);
 
+    algorithm::locate::PointOnGeometryLocator* getLocator() {
+        if (ringLocator == nullptr) {
+            ringLocator.reset(new algorithm::locate::IndexedPointInAreaLocator(*getRingInternal()));
+        }
+        return ringLocator.get();
+    }
+
 public:
     /** \brief
      * Adds a DirectedEdge which is known to form part of this ring.
@@ -106,7 +114,7 @@ public:
     /**
      * \brief
      * Find the innermost enclosing shell EdgeRing
-     * containing the argument EdgeRing, if any.
+     * containing this, if any.
      *
      * The innermost enclosing ring is the <i>smallest</i> enclosing ring.
      * The algorithm used depends on the fact that:
@@ -121,9 +129,7 @@ public:
      * @return containing EdgeRing, if there is one
      * @return null if no containing EdgeRing is found
      */
-    static EdgeRing* findEdgeRingContaining(
-        EdgeRing* testEr,
-        geos::index::strtree::STRtree* shellIndex);
+    EdgeRing* findEdgeRingContaining(const std::vector<EdgeRing*> & erList);
 
     /**
      * \brief
@@ -236,13 +242,6 @@ public:
         return isHole() ? shell : this;
     }
 
-    algorithm::locate::IndexedPointInAreaLocator* getLocator() {
-        if (ringLocator == nullptr) {
-            ringLocator.reset(new algorithm::locate::IndexedPointInAreaLocator(*getRingInternal()));
-        }
-        return ringLocator.get();
-    }
-
     /** \brief
      * Tests whether this ring is an outer hole.
      * A hole is an outer hole if it is not contained by any shell.
@@ -266,56 +265,22 @@ public:
         return getOuterHole() != nullptr;
     }
 
-    EdgeRing* getOuterHole() const {
-        if (isHole()) {
-            return nullptr;
-        }
-
-        // A shell is an outer shell if any edge is also in an outer hole.
-        // A hole is an outer shell if it is not contained by a shell.
-        for (auto& de : deList) {
-            auto adjRing = (dynamic_cast<PolygonizeDirectedEdge*>(de->getSym()))->getRing();
-            if (adjRing->isOuterHole()) {
-                return adjRing;
-            }
-        }
-
-        return nullptr;
-    }
+    /** \brief
+     * Gets the outer hole of a shell, if it has one.
+     * An outer hole is one that is not contained in any other shell.
+     *
+     * Each disjoint connected group of shells is surrounded by
+     * an outer hole.
+     *
+     * @return the outer hole edge ring, or nullptr
+     */
+    EdgeRing* getOuterHole() const;
 
     /** \brief
      * Updates the included status for currently non-included shells
      * based on whether they are adjacent to an included shell.
      */
-    void updateIncludedRecursive() {
-        visitedByUpdateIncludedRecursive = true;
-
-        if (isHole()) {
-            return;
-        }
-
-        for (const auto& de : deList) {
-            auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
-
-            if (adjShell != nullptr) {
-                if (!adjShell->isIncludedSet() && !adjShell->visitedByUpdateIncludedRecursive) {
-                    adjShell->updateIncludedRecursive();
-                }
-            }
-        }
-
-        for (const auto& de : deList) {
-            auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
-
-            if (adjShell != nullptr) {
-                if (adjShell->isIncludedSet()) {
-                    setIncluded(!adjShell->isIncluded());
-                    return;
-                }
-            }
-        }
-
-    }
+    void updateIncludedRecursive();
 
     /** \brief
      * Adds a hole to the polygon formed by this ring.
@@ -369,6 +334,10 @@ public:
      * Caller gets ownership of ring.
      */
     std::unique_ptr<geom::LinearRing> getRingOwnership();
+
+    bool isInRing(const geom::Coordinate & pt) {
+        return geom::Location::EXTERIOR != getLocator()->locate(&pt);
+    }
 };
 
 } // namespace geos::operation::polygonize
diff --git a/include/geos/operation/polygonize/HoleAssigner.h b/include/geos/operation/polygonize/HoleAssigner.h
new file mode 100644
index 0000000..a40380f
--- /dev/null
+++ b/include/geos/operation/polygonize/HoleAssigner.h
@@ -0,0 +1,66 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2019 Daniel Baston <dbaston at gmail.com>
+ *
+ * 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/polygonize/HoleAssigner.java 0b3c7e3eb0d3e
+ *
+ **********************************************************************/
+
+#ifndef GEOS_OP_POLYGONIZE_HOLEASSIGNER_H
+#define GEOS_OP_POLYGONIZE_HOLEASSIGNER_H
+
+#include <geos/operation/polygonize/EdgeRing.h>
+#include <geos/index/strtree/STRtree.h>
+
+#include <vector>
+
+namespace geos {
+namespace operation {
+namespace polygonize {
+
+/**
+ * Assigns hole rings to shell rings during polygonization.
+ * Uses spatial indexing to improve performance of shell lookup.
+ *
+ * @author mdavis
+ */
+class HoleAssigner {
+public:
+    /**
+     * Assigns hole rings to shell rings
+     * @param holes list of hole rings to assign
+     * @param shells list of shell rings
+     */
+    static void assignHolesToShells(std::vector<EdgeRing*> & holes, std::vector<EdgeRing*> & shells);
+
+private:
+    explicit HoleAssigner(std::vector<EdgeRing*> & shells) : m_shells{shells} {
+        buildIndex();
+    }
+
+    void assignHolesToShells(std::vector<EdgeRing*> & holes);
+    void assignHoleToShell(EdgeRing* holeER);
+    std::vector<EdgeRing*> findShells(const geom::Envelope & ringEnv);
+
+    EdgeRing* findEdgeRingContaining(EdgeRing* testER);
+
+    void buildIndex();
+
+    std::vector<EdgeRing*>& m_shells;
+    geos::index::strtree::STRtree m_shellIndex;
+};
+}
+}
+}
+
+#endif
diff --git a/include/geos/operation/polygonize/Polygonizer.h b/include/geos/operation/polygonize/Polygonizer.h
index 2fa9a32..e816e9e 100644
--- a/include/geos/operation/polygonize/Polygonizer.h
+++ b/include/geos/operation/polygonize/Polygonizer.h
@@ -14,7 +14,7 @@
  *
  **********************************************************************
  *
- * Last port: operation/polygonize/Polygonizer.java rev. 974
+ * Last port: operation/polygonize/Polygonizer.java 0b3c7e3eb0d3e
  *
  **********************************************************************/
 
@@ -38,7 +38,9 @@
 namespace geos {
 namespace geom {
 class Geometry;
+
 class LineString;
+
 class Polygon;
 }
 namespace operation {
@@ -46,11 +48,6 @@ namespace polygonize {
 class EdgeRing;
 }
 }
-namespace index {
-namespace strtree {
-    class STRtree;
-}
-}
 }
 
 namespace geos {
@@ -117,11 +114,6 @@ private:
 
     void findShellsAndHoles(const std::vector<EdgeRing*>& edgeRingList);
 
-    static void assignHolesToShells(const std::vector<EdgeRing*>& holeList,
-                                    std::vector<EdgeRing*>& shellList);
-
-    static void assignHoleToShell(EdgeRing* holeER, geos::index::strtree::STRtree* shellIndex);
-
     void findDisjointShells();
 
     static void findOuterShells(std::vector<EdgeRing*>& shellList);
diff --git a/src/operation/polygonize/EdgeRing.cpp b/src/operation/polygonize/EdgeRing.cpp
index 680927f..c98f576 100644
--- a/src/operation/polygonize/EdgeRing.cpp
+++ b/src/operation/polygonize/EdgeRing.cpp
@@ -13,7 +13,7 @@
  *
  **********************************************************************
  *
- * Last port: operation/polygonize/EdgeRing.java rev. 974
+ * Last port: operation/polygonize/EdgeRing.java 0b3c7e3eb0d3e
  *
  **********************************************************************/
 
@@ -51,25 +51,19 @@ namespace polygonize { // geos.operation.polygonize
 
 /*public*/
 EdgeRing*
-EdgeRing::findEdgeRingContaining(EdgeRing* testEr,
-                                 geos::index::strtree::STRtree* shellIndex)
+EdgeRing::findEdgeRingContaining(const std::vector<EdgeRing*> & erList)
 {
-    const LinearRing* testRing = testEr->getRingInternal();
+    const LinearRing* testRing = getRingInternal();
     if(! testRing) {
         return nullptr;
     }
     const Envelope* testEnv = testRing->getEnvelopeInternal();
-    EdgeRing* minShell = nullptr;
-    const Envelope* minShellEnv = nullptr;
+    EdgeRing* minRing = nullptr;
+    const Envelope* minRingEnv = nullptr;
 
-    std::vector<void*> shellList;
-    shellIndex->query(testEnv, shellList);
-
-    for(void* hit : shellList) {
-        EdgeRing* tryShell = static_cast<EdgeRing*>(hit);
-
-        auto tryShellRing = tryShell->getRingInternal();
-        auto tryShellEnv = tryShellRing->getEnvelopeInternal();
+    for(auto& tryEdgeRing : erList) {
+        auto tryRing = tryEdgeRing->getRingInternal();
+        auto tryShellEnv = tryRing->getEnvelopeInternal();
         // the hole envelope cannot equal the shell envelope
         // (also guards against testing rings against themselves)
         if (tryShellEnv->equals(testEnv)) {
@@ -80,20 +74,18 @@ EdgeRing::findEdgeRingContaining(EdgeRing* testEr,
             continue;
         }
 
-        auto tryCoords = tryShellRing->getCoordinatesRO();
+        auto tryCoords = tryRing->getCoordinatesRO();
         Coordinate testPt = ptNotInList(testRing->getCoordinatesRO(), tryCoords); // TODO: don't copy testPt !
 
-        bool isContained = tryShell->getLocator()->locate(&testPt) == geom::Location::INTERIOR;
-
         // check if this new containing ring is smaller than the current minimum ring
-        if(isContained) {
-            if(minShell == nullptr || minShellEnv->contains(tryShellEnv)) {
-                minShell = tryShell;
-                minShellEnv = minShell->getRingInternal()->getEnvelopeInternal();
+        if(tryEdgeRing->isInRing(testPt)) {
+            if(minRing == nullptr || minRingEnv->contains(tryShellEnv)) {
+                minRing = tryEdgeRing;
+                minRingEnv = minRing->getRingInternal()->getEnvelopeInternal();
             }
         }
     }
-    return minShell;
+    return minRing;
 }
 
 std::vector<PolygonizeDirectedEdge*>
@@ -297,6 +289,56 @@ EdgeRing::addEdge(const CoordinateSequence* coords, bool isForward,
     }
 }
 
+EdgeRing*
+EdgeRing::getOuterHole() const {
+    // Only shells can have outer holes
+    if (isHole()) {
+        return nullptr;
+    }
+
+    // A shell is an outer shell if any edge is also in an outer hole.
+    // A hole is an outer shell if it is not contained by a shell.
+    for (auto& de : deList) {
+        auto adjRing = (dynamic_cast<PolygonizeDirectedEdge*>(de->getSym()))->getRing();
+        if (adjRing->isOuterHole()) {
+            return adjRing;
+        }
+    }
+
+    return nullptr;
+}
+
+void
+EdgeRing::updateIncludedRecursive() {
+    visitedByUpdateIncludedRecursive = true;
+
+    if (isHole()) {
+        return;
+    }
+
+    for (const auto& de : deList) {
+        auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
+
+        if (adjShell != nullptr) {
+            if (!adjShell->isIncludedSet() && !adjShell->visitedByUpdateIncludedRecursive) {
+                adjShell->updateIncludedRecursive();
+            }
+        }
+    }
+
+    for (const auto& de : deList) {
+        auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
+
+        if (adjShell != nullptr) {
+            if (adjShell->isIncludedSet()) {
+                setIncluded(!adjShell->isIncluded());
+                return;
+            }
+        }
+    }
+
+}
+
 } // namespace geos.operation.polygonize
 } // namespace geos.operation
 } // namespace geos
diff --git a/src/operation/polygonize/HoleAssigner.cpp b/src/operation/polygonize/HoleAssigner.cpp
new file mode 100644
index 0000000..9a06123
--- /dev/null
+++ b/src/operation/polygonize/HoleAssigner.cpp
@@ -0,0 +1,83 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2019 Daniel Baston <dbaston at gmail.com>
+ *
+ * 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/polygonize/HoleAssigner.java 0b3c7e3eb0d3e
+ *
+ **********************************************************************/
+
+#include <geos/operation/polygonize/HoleAssigner.h>
+#include <geos/util/Interrupt.h>
+
+namespace geos {
+namespace operation {
+namespace polygonize {
+
+void
+HoleAssigner::buildIndex() {
+    for (EdgeRing* shell : m_shells) {
+        m_shellIndex.insert(shell->getRingInternal()->getEnvelopeInternal(), shell);
+    }
+}
+
+void
+HoleAssigner::assignHolesToShells(std::vector<EdgeRing*> & holes, std::vector<EdgeRing*> & shells)
+{
+    HoleAssigner assigner(shells);
+    assigner.assignHolesToShells(holes);
+
+}
+
+void HoleAssigner::assignHolesToShells(std::vector<EdgeRing*> & holes) {
+    for (const auto& holeER : holes) {
+        assignHoleToShell(holeER);
+        GEOS_CHECK_FOR_INTERRUPTS();
+    }
+}
+
+void
+HoleAssigner::assignHoleToShell(EdgeRing* holeER)
+{
+    EdgeRing* shell = findEdgeRingContaining(holeER);
+
+    if(shell != nullptr) {
+        shell->addHole(holeER);
+    }
+}
+
+std::vector<EdgeRing*>
+HoleAssigner::findShells(const geom::Envelope& e) {
+    std::vector<void*> shellsVoid;
+    m_shellIndex.query(&e, shellsVoid);
+
+    // TODO turn AbstractSTRtree::query into a template and remove this
+    std::vector<EdgeRing*> shells{shellsVoid.size()};
+    for (size_t i = 0; i < shellsVoid.size(); i++) {
+        shells[i] = static_cast<EdgeRing*>(shellsVoid[i]);
+    }
+
+    return shells;
+}
+
+EdgeRing*
+HoleAssigner::findEdgeRingContaining(EdgeRing* testEr) {
+    const geos::geom::Envelope* e = testEr->getRingInternal()->getEnvelopeInternal();
+
+    std::vector<EdgeRing*> candidateShells = findShells(*e);
+
+    return testEr->findEdgeRingContaining(candidateShells);
+}
+
+}
+}
+}
diff --git a/src/operation/polygonize/Polygonizer.cpp b/src/operation/polygonize/Polygonizer.cpp
index 7bb811f..ee2d0c5 100644
--- a/src/operation/polygonize/Polygonizer.cpp
+++ b/src/operation/polygonize/Polygonizer.cpp
@@ -14,13 +14,14 @@
  *
  **********************************************************************
  *
- * Last port: operation/polygonize/Polygonizer.java rev. 974
+ * Last port: operation/polygonize/Polygonizer.java 0b3c7e3eb0d3e
  *
  **********************************************************************/
 
 #include <geos/operation/polygonize/Polygonizer.h>
 #include <geos/operation/polygonize/PolygonizeGraph.h>
 #include <geos/operation/polygonize/EdgeRing.h>
+#include <geos/operation/polygonize/HoleAssigner.h>
 #include <geos/geom/LineString.h>
 #include <geos/geom/Geometry.h>
 #include <geos/geom/Polygon.h>
@@ -253,7 +254,7 @@ Polygonizer::polygonize()
     cerr << "                           " << shellList.size() << " shells" << endl;
 #endif
 
-    assignHolesToShells(holeList, shellList);
+    HoleAssigner::assignHolesToShells(holeList, shellList);
 
     bool includeAll = true;
     if (extractOnlyPolygonal) {
@@ -299,31 +300,6 @@ Polygonizer::findShellsAndHoles(const vector<EdgeRing*>& edgeRingList)
     }
 }
 
-/* private */
-void
-Polygonizer::assignHolesToShells(const vector<EdgeRing*>& holeList, vector<EdgeRing*>& shellList)
-{
-    geos::index::strtree::STRtree shellIndex;
-    for (const auto& shell : shellList) {
-        shellIndex.insert(shell->getRingInternal()->getEnvelopeInternal(), shell);
-    }
-
-    for(const auto& holeER : holeList) {
-        assignHoleToShell(holeER, &shellIndex);
-        GEOS_CHECK_FOR_INTERRUPTS();
-    }
-}
-
-/* private */
-void
-Polygonizer::assignHoleToShell(EdgeRing* holeER, geos::index::strtree::STRtree* shellIndex)
-{
-    EdgeRing* shell = EdgeRing::findEdgeRingContaining(holeER, shellIndex);
-
-    if(shell != nullptr) {
-        shell->addHole(holeER);
-    }
-}
 
 void
 Polygonizer::findDisjointShells() {

commit 4f3d339e749c0ac13adb851789a8f3d8ef30c72f
Author: Daniel Baston <dbaston at gmail.com>
Date:   Thu May 16 12:28:26 2019 -0400

    Fix compilation for gcc 4.8

diff --git a/src/operation/union/CoverageUnion.cpp b/src/operation/union/CoverageUnion.cpp
index d1ab989..2735f6d 100644
--- a/src/operation/union/CoverageUnion.cpp
+++ b/src/operation/union/CoverageUnion.cpp
@@ -94,7 +94,7 @@ std::unique_ptr<Geometry> CoverageUnion::polygonize(const GeometryFactory* gf) {
         throw geos::util::TopologyException("CoverageUnion cannot process incorrectly noded inputs.");
     }
 
-    auto polygons{p.getPolygons()};
+    auto polygons = p.getPolygons();
     segment_geoms.reset();
 
     if (polygons->size() == 1) {

commit 0387a9fdeee3906e8386d9f83c195a2133dfafe0
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed May 15 17:32:20 2019 -0400

    Add GEOSCoverageUnion CAPI test

diff --git a/tests/unit/capi/GEOSCoverageUnionTest.cpp b/tests/unit/capi/GEOSCoverageUnionTest.cpp
new file mode 100644
index 0000000..992a685
--- /dev/null
+++ b/tests/unit/capi/GEOSCoverageUnionTest.cpp
@@ -0,0 +1,107 @@
+//
+// Test Suite for C-API GEOSCoverageUnion
+
+#include <tut/tut.hpp>
+// geos
+#include <geos_c.h>
+// std
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used in test cases.
+struct test_capicoverageunion_data {
+    static void
+    notice(const char *fmt, ...) {
+        std::fprintf(stdout, "NOTICE: ");
+
+        va_list ap;
+        va_start(ap, fmt);
+        std::vfprintf(stdout, fmt, ap);
+        va_end(ap);
+
+        std::fprintf(stdout, "\n");
+    }
+
+    test_capicoverageunion_data() {
+        m_context = initGEOS_r(notice, notice);
+        m_reader = GEOSWKTReader_create_r(m_context);
+    }
+
+    ~test_capicoverageunion_data() {
+        GEOSWKTReader_destroy_r(m_context, m_reader);
+        finishGEOS_r(m_context);
+    }
+
+    GEOSContextHandle_t m_context;
+    GEOSWKTReader* m_reader;
+};
+
+
+typedef test_group<test_capicoverageunion_data> group;
+typedef group::object object;
+
+group test_capicoverageunion_group("capi::GEOSCoverageUnion");
+
+//
+// Test Cases
+//
+
+
+template<>
+template<> void object::test<1>
+()
+{
+    // Adjacent inputs
+    std::vector<std::string> wkt{
+            "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
+            "POLYGON ((1 0, 1 1, 2 1, 2 0, 1 0))"
+    };
+
+    auto g1 = GEOSWKTReader_read_r(m_context, m_reader, wkt[0].c_str());
+    auto g2 = GEOSWKTReader_read_r(m_context, m_reader, wkt[1].c_str());
+
+    GEOSGeometry* geoms[2] = { g1, g2 };
+
+    auto input = GEOSGeom_createCollection_r(m_context, GEOS_GEOMETRYCOLLECTION, geoms, 2);
+    auto result = GEOSCoverageUnion_r(m_context, input);
+
+    ensure( result != nullptr );
+    ensure( GEOSGeomTypeId_r(m_context, result) == GEOS_POLYGON );
+
+    GEOSGeom_destroy_r(m_context, input);
+    GEOSGeom_destroy_r(m_context, result);
+}
+
+template<>
+template<>
+void object::test<2>
+() {
+    // Overlapping inputs (error)
+    std::vector<std::string> wkt{
+            "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
+            "POLYGON ((1 0, 0.9 1, 2 1, 2 0, 1 0))"
+    };
+
+    auto g1 = GEOSWKTReader_read_r(m_context, m_reader, wkt[0].c_str());
+    auto g2 = GEOSWKTReader_read_r(m_context, m_reader, wkt[1].c_str());
+
+    GEOSGeometry* geoms[2] = { g1, g2 };
+
+    auto input = GEOSGeom_createCollection_r(m_context, GEOS_GEOMETRYCOLLECTION, geoms, 2);
+    auto result = GEOSCoverageUnion_r(m_context, input);
+
+    ensure( result == nullptr );
+
+    GEOSGeom_destroy_r(m_context, input);
+}
+
+
+} // namespace tut
+

commit c5c826b340045c6e1581afb7587f41acf57237fa
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed May 15 17:31:46 2019 -0400

    Polygonizer performance improvements
    
    - Use STRtree for assigning holes to shells
    - Use IndexedPointInAreaLocator for testing shell-in-shell containment
    - Remove potential N^2 search for setting shell inclusion

diff --git a/include/geos/operation/polygonize/EdgeRing.h b/include/geos/operation/polygonize/EdgeRing.h
index f7edb17..b40f1c2 100644
--- a/include/geos/operation/polygonize/EdgeRing.h
+++ b/include/geos/operation/polygonize/EdgeRing.h
@@ -22,6 +22,7 @@
 #define GEOS_OP_POLYGONIZE_EDGERING_H
 
 #include <geos/export.h>
+#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
 #include <geos/operation/polygonize/PolygonizeDirectedEdge.h>
 #include <geos/geom/Geometry.h>
 #include <geos/geom/LinearRing.h>
@@ -46,6 +47,11 @@ class Coordinate;
 namespace planargraph {
 class DirectedEdge;
 }
+namespace index {
+namespace strtree {
+class STRtree;
+}
+}
 }
 
 namespace geos {
@@ -66,14 +72,16 @@ private:
     // cache the following data for efficiency
     std::unique_ptr<geom::LinearRing> ring;
     std::unique_ptr<geom::CoordinateSequence> ringPts;
+    std::unique_ptr<algorithm::locate::IndexedPointInAreaLocator> ringLocator;
 
     std::unique_ptr<std::vector<geom::Geometry*>> holes;
 
-    const EdgeRing* shell = nullptr;
+    EdgeRing* shell = nullptr;
     bool is_hole;
     bool is_processed = false;
     bool is_included_set = false;
     bool is_included = false;
+    bool visitedByUpdateIncludedRecursive = false;
 
     /** \brief
      * Computes the list of coordinates which are contained in this ring.
@@ -115,7 +123,7 @@ public:
      */
     static EdgeRing* findEdgeRingContaining(
         EdgeRing* testEr,
-        std::vector<EdgeRing*>* shellList);
+        geos::index::strtree::STRtree* shellIndex);
 
     /**
      * \brief
@@ -158,7 +166,6 @@ public:
 
     ~EdgeRing();
 
-
     void build(PolygonizeDirectedEdge* startDE);
 
     /** \brief
@@ -174,10 +181,16 @@ public:
         return is_hole;
     }
 
+    /* Indicates whether we know if the ring should be included in a polygonizer
+     * output of only polygons.
+     */
     bool isIncludedSet() const {
         return is_included_set;
     }
 
+    /* Indicates whether the ring should be included in a polygonizer output of
+     * only polygons.
+     */
     bool isIncluded() const {
         return is_included;
     }
@@ -200,90 +213,110 @@ public:
      *
      *  @param shell the shell ring
      */
-     void setShell(const EdgeRing* shellRing) {
+    void setShell(EdgeRing* shellRing) {
         shell = shellRing;
-     }
-
-     /** \brief
-      * Tests whether this ring has a shell assigned to it.
-      *
-      * @return true if the ring has a shell
-      */
-     bool hasShell() const {
-         return shell != nullptr;
-     }
-
-     /** \brief
-      * Gets the shell for this ring. The shell is the ring itself if it is
-      * not a hole, otherwise it is the parent shell.
-      *
-      * @return the shell for the ring
-      */
-     const EdgeRing* getShell() const {
-         return isHole() ? shell : this;
-     }
-
-     /** \brief
-      * Tests whether this ring is an outer hole.
-      * A hole is an outer hole if it is not contained by any shell.
-      *
-      * @return true if the ring is an outer hole.
-      */
-      bool isOuterHole() const {
-          if (!isHole()) {
-              return false;
-          }
-
-          return !hasShell();
-      }
-
-      /** \brief
-       * Tests whether this ring is an outer shell.
-       *
-       * @return true if the ring is an outer shell.
-       */
-       bool isOuterShell() const {
-           return getOuterHole() != nullptr;
-       }
-
-        EdgeRing* getOuterHole() const {
-           if (isHole()) {
-               return nullptr;
-           }
-
-           // A shell is an outer shell if any edge is also in an outer hole.
-           // A hole is an outer shell if it is not contained by a shell.
-           for (auto& de : deList) {
-               auto adjRing = (dynamic_cast<PolygonizeDirectedEdge*>(de->getSym()))->getRing();
-               if (adjRing->isOuterHole()) {
-                   return adjRing;
-               }
-           }
-
-           return nullptr;
-       }
-
-       /** \brief
-        * Updates the included status for currently non-included shells
-        * based on whether they are adjacent to an included shell.
-        */
-        void updateIncluded() {
-            if (isHole()) {
-                return;
+    }
+
+    /** \brief
+     * Tests whether this ring has a shell assigned to it.
+     *
+     * @return true if the ring has a shell
+     */
+    bool hasShell() const {
+        return shell != nullptr;
+    }
+
+    /** \brief
+     * Gets the shell for this ring. The shell is the ring itself if it is
+     * not a hole, otherwise it is the parent shell.
+     *
+     * @return the shell for the ring
+     */
+    EdgeRing* getShell() {
+        return isHole() ? shell : this;
+    }
+
+    algorithm::locate::IndexedPointInAreaLocator* getLocator() {
+        if (ringLocator == nullptr) {
+            ringLocator.reset(new algorithm::locate::IndexedPointInAreaLocator(*getRingInternal()));
+        }
+        return ringLocator.get();
+    }
+
+    /** \brief
+     * Tests whether this ring is an outer hole.
+     * A hole is an outer hole if it is not contained by any shell.
+     *
+     * @return true if the ring is an outer hole.
+     */
+    bool isOuterHole() const {
+        if (!isHole()) {
+            return false;
+        }
+
+        return !hasShell();
+    }
+
+    /** \brief
+     * Tests whether this ring is an outer shell.
+     *
+     * @return true if the ring is an outer shell.
+     */
+    bool isOuterShell() const {
+        return getOuterHole() != nullptr;
+    }
+
+    EdgeRing* getOuterHole() const {
+        if (isHole()) {
+            return nullptr;
+        }
+
+        // A shell is an outer shell if any edge is also in an outer hole.
+        // A hole is an outer shell if it is not contained by a shell.
+        for (auto& de : deList) {
+            auto adjRing = (dynamic_cast<PolygonizeDirectedEdge*>(de->getSym()))->getRing();
+            if (adjRing->isOuterHole()) {
+                return adjRing;
             }
+        }
 
-            for (const auto& de : deList) {
-                auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
+        return nullptr;
+    }
 
-                if (adjShell != nullptr && adjShell->isIncludedSet()) {
-                    // adjacent ring has been processed, so set included to inverse of adjacent included
+    /** \brief
+     * Updates the included status for currently non-included shells
+     * based on whether they are adjacent to an included shell.
+     */
+    void updateIncludedRecursive() {
+        visitedByUpdateIncludedRecursive = true;
+
+        if (isHole()) {
+            return;
+        }
+
+        for (const auto& de : deList) {
+            auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
+
+            if (adjShell != nullptr) {
+                if (!adjShell->isIncludedSet() && !adjShell->visitedByUpdateIncludedRecursive) {
+                    adjShell->updateIncludedRecursive();
+                }
+            }
+        }
+
+        for (const auto& de : deList) {
+            auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
+
+            if (adjShell != nullptr) {
+                if (adjShell->isIncludedSet()) {
                     setIncluded(!adjShell->isIncluded());
                     return;
                 }
-
             }
         }
 
+    }
+
     /** \brief
      * Adds a hole to the polygon formed by this ring.
      *
diff --git a/include/geos/operation/polygonize/Polygonizer.h b/include/geos/operation/polygonize/Polygonizer.h
index f6e412b..2fa9a32 100644
--- a/include/geos/operation/polygonize/Polygonizer.h
+++ b/include/geos/operation/polygonize/Polygonizer.h
@@ -46,6 +46,11 @@ namespace polygonize {
 class EdgeRing;
 }
 }
+namespace index {
+namespace strtree {
+    class STRtree;
+}
+}
 }
 
 namespace geos {
@@ -115,10 +120,9 @@ private:
     static void assignHolesToShells(const std::vector<EdgeRing*>& holeList,
                                     std::vector<EdgeRing*>& shellList);
 
-    static void assignHoleToShell(EdgeRing* holeER,
-                                  std::vector<EdgeRing*>& shellList);
+    static void assignHoleToShell(EdgeRing* holeER, geos::index::strtree::STRtree* shellIndex);
 
-    static void findDisjointShells(std::vector<EdgeRing*>& shellList);
+    void findDisjointShells();
 
     static void findOuterShells(std::vector<EdgeRing*>& shellList);
 
diff --git a/src/operation/polygonize/EdgeRing.cpp b/src/operation/polygonize/EdgeRing.cpp
index 730f594..680927f 100644
--- a/src/operation/polygonize/EdgeRing.cpp
+++ b/src/operation/polygonize/EdgeRing.cpp
@@ -30,6 +30,9 @@
 #include <geos/algorithm/Orientation.h>
 #include <geos/util/IllegalArgumentException.h>
 #include <geos/util.h> // TODO: drop this, includes too much
+#include <geos/index/strtree/STRtree.h>
+#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
+#include <geos/geom/Location.h>
 
 #include <vector>
 #include <cassert>
@@ -49,7 +52,7 @@ namespace polygonize { // geos.operation.polygonize
 /*public*/
 EdgeRing*
 EdgeRing::findEdgeRingContaining(EdgeRing* testEr,
-                                 vector<EdgeRing*>* shellList)
+                                 geos::index::strtree::STRtree* shellIndex)
 {
     const LinearRing* testRing = testEr->getRingInternal();
     if(! testRing) {
@@ -59,7 +62,12 @@ EdgeRing::findEdgeRingContaining(EdgeRing* testEr,
     EdgeRing* minShell = nullptr;
     const Envelope* minShellEnv = nullptr;
 
-    for(EdgeRing* tryShell : *shellList) {
+    std::vector<void*> shellList;
+    shellIndex->query(testEnv, shellList);
+
+    for(void* hit : shellList) {
+        EdgeRing* tryShell = static_cast<EdgeRing*>(hit);
+
         auto tryShellRing = tryShell->getRingInternal();
         auto tryShellEnv = tryShellRing->getEnvelopeInternal();
         // the hole envelope cannot equal the shell envelope
@@ -75,7 +83,7 @@ EdgeRing::findEdgeRingContaining(EdgeRing* testEr,
         auto tryCoords = tryShellRing->getCoordinatesRO();
         Coordinate testPt = ptNotInList(testRing->getCoordinatesRO(), tryCoords); // TODO: don't copy testPt !
 
-        bool isContained = PointLocation::isInRing(testPt, tryCoords);
+        bool isContained = tryShell->getLocator()->locate(&testPt) == geom::Location::INTERIOR;
 
         // check if this new containing ring is smaller than the current minimum ring
         if(isContained) {
diff --git a/src/operation/polygonize/Polygonizer.cpp b/src/operation/polygonize/Polygonizer.cpp
index bae99f4..7bb811f 100644
--- a/src/operation/polygonize/Polygonizer.cpp
+++ b/src/operation/polygonize/Polygonizer.cpp
@@ -25,6 +25,7 @@
 #include <geos/geom/Geometry.h>
 #include <geos/geom/Polygon.h>
 #include <geos/util/Interrupt.h>
+#include <geos/index/strtree/STRtree.h>
 // std
 #include <vector>
 
@@ -256,7 +257,7 @@ Polygonizer::polygonize()
 
     bool includeAll = true;
     if (extractOnlyPolygonal) {
-        findDisjointShells(shellList);
+        findDisjointShells();
         includeAll = false;
     }
     polyList = extractPolygons(shellList, includeAll);
@@ -302,18 +303,22 @@ Polygonizer::findShellsAndHoles(const vector<EdgeRing*>& edgeRingList)
 void
 Polygonizer::assignHolesToShells(const vector<EdgeRing*>& holeList, vector<EdgeRing*>& shellList)
 {
+    geos::index::strtree::STRtree shellIndex;
+    for (const auto& shell : shellList) {
+        shellIndex.insert(shell->getRingInternal()->getEnvelopeInternal(), shell);
+    }
+
     for(const auto& holeER : holeList) {
-        assignHoleToShell(holeER, shellList);
+        assignHoleToShell(holeER, &shellIndex);
         GEOS_CHECK_FOR_INTERRUPTS();
     }
 }
 
 /* private */
 void
-Polygonizer::assignHoleToShell(EdgeRing* holeER,
-                               vector<EdgeRing*>& shellList)
+Polygonizer::assignHoleToShell(EdgeRing* holeER, geos::index::strtree::STRtree* shellIndex)
 {
-    EdgeRing* shell = EdgeRing::findEdgeRingContaining(holeER, &shellList);
+    EdgeRing* shell = EdgeRing::findEdgeRingContaining(holeER, shellIndex);
 
     if(shell != nullptr) {
         shell->addHole(holeER);
@@ -321,23 +326,16 @@ Polygonizer::assignHoleToShell(EdgeRing* holeER,
 }
 
 void
-Polygonizer::findDisjointShells(vector<EdgeRing*> & shells)
-{
-    findOuterShells(shells);
-
-    bool isMoreToScan;
-    do {
-        isMoreToScan = false;
-        for (EdgeRing* er : shells) {
-            if (er->isIncludedSet()) {
-                continue;
-            }
-            er->updateIncluded();
-            if (!er->isIncludedSet()) {
-                isMoreToScan = true;
-            }
+Polygonizer::findDisjointShells() {
+    findOuterShells(shellList);
+
+    for (EdgeRing *er : shellList) {
+        if (!er->isIncludedSet()) {
+            er->updateIncludedRecursive();
         }
-    } while (isMoreToScan);
+    }
+
+    return;
 }
 
 void
diff --git a/src/operation/union/CoverageUnion.cpp b/src/operation/union/CoverageUnion.cpp
index e7afdb1..d1ab989 100644
--- a/src/operation/union/CoverageUnion.cpp
+++ b/src/operation/union/CoverageUnion.cpp
@@ -57,8 +57,6 @@ void CoverageUnion::extractSegments(const Polygon* p) {
     for (size_t i = 0; i < p->getNumInteriorRing(); i++) {
         extractSegments(p->getInteriorRingN(i));
     }
-
-
 }
 
 void CoverageUnion::extractSegments(const LineString* ls) {
diff --git a/tests/unit/operation/polygonize/PolygonizeTest.cpp b/tests/unit/operation/polygonize/PolygonizeTest.cpp
index 065722c..510172f 100644
--- a/tests/unit/operation/polygonize/PolygonizeTest.cpp
+++ b/tests/unit/operation/polygonize/PolygonizeTest.cpp
@@ -55,7 +55,7 @@ struct test_polygonizetest_data {
     printAll(std::ostream& os, T& cnt)
     {
         for(typename T::iterator i = cnt.begin(), e = cnt.end(); i != e; ++i) {
-            os << **i;
+            os << **i << std::endl;
         }
     }
 
@@ -127,7 +127,9 @@ struct test_polygonizetest_data {
 
         bool ok = compare(expectGeoms, *retGeoms);
         if(! ok) {
-            cout << "OBTAINED(" << retGeoms->size() << "): ";
+            cout << "EXPECTED(" << expectGeoms.size() << "): " << std::endl;
+            printAll(cout, expectGeoms);
+            cout << "OBTAINED(" << retGeoms->size() << "): " << std::endl;
             printAll(cout, *retGeoms);
             cout << endl;
 
@@ -249,6 +251,8 @@ template<>
 template<>
 void object::test<6>()
 {
+    // Two adjacent squares, but since we only get polygonal output
+    // we only get one of the squares back.
     std::vector<std::string> inp{
             "LINESTRING (10 10, 10 20, 20 20)",
             "LINESTRING (20 20, 20 10)",

commit 3c5b5af760d860aeb80773433c0832f623734fbe
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue May 14 14:55:08 2019 -0400

    Add GEOSCoverageUnion to C API

diff --git a/capi/geos_c.cpp b/capi/geos_c.cpp
index 11f2e81..d606940 100644
--- a/capi/geos_c.cpp
+++ b/capi/geos_c.cpp
@@ -506,6 +506,12 @@ extern "C" {
     }
 
     Geometry*
+    GEOSCoverageUnion(const Geometry* g)
+    {
+        return GEOSCoverageUnion_r(handle, g);
+    }
+
+    Geometry*
     GEOSNode(const Geometry* g)
     {
         return GEOSNode_r(handle, g);
diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 0cd0ef1..da42049 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -572,6 +572,11 @@ extern GEOSGeometry GEOS_DLL *GEOSUnion_r(GEOSContextHandle_t handle,
                                           const GEOSGeometry* g2);
 extern GEOSGeometry GEOS_DLL *GEOSUnaryUnion_r(GEOSContextHandle_t handle,
                                           const GEOSGeometry* g);
+/* GEOSCoverageUnion is an optimized union algorithm for polygonal inputs that are correctly
+ * noded and do not overlap. It will not generate an error (return NULL) for inputs that
+ * do not satisfy this constraint. */
+extern GEOSGeometry GEOS_DLL *GEOSCoverageUnion_r(GEOSContextHandle_t handle,
+                                                  const GEOSGeometry* g);
 /* @deprecated in 3.3.0: use GEOSUnaryUnion_r instead */
 extern GEOSGeometry GEOS_DLL *GEOSUnionCascaded_r(GEOSContextHandle_t handle,
                                                   const GEOSGeometry* g);
@@ -1603,6 +1608,11 @@ extern GEOSGeometry GEOS_DLL *GEOSBoundary(const GEOSGeometry* g);
 extern GEOSGeometry GEOS_DLL *GEOSUnion(const GEOSGeometry* g1, const GEOSGeometry* g2);
 extern GEOSGeometry GEOS_DLL *GEOSUnaryUnion(const GEOSGeometry* g);
 
+/* GEOSCoverageUnion is an optimized union algorithm for polygonal inputs that are correctly
+ * noded and do not overlap. It will not generate an error (return NULL) for inputs that
+ * do not satisfy this constraint. */
+extern GEOSGeometry GEOS_DLL *GEOSCoverageUnion(const GEOSGeometry *g);
+
 /* @deprecated in 3.3.0: use GEOSUnaryUnion instead */
 extern GEOSGeometry GEOS_DLL *GEOSUnionCascaded(const GEOSGeometry* g);
 extern GEOSGeometry GEOS_DLL *GEOSPointOnSurface(const GEOSGeometry* g);
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index 5744252..ed86bf0 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -67,6 +67,7 @@
 #include <geos/operation/relate/RelateOp.h>
 #include <geos/operation/sharedpaths/SharedPathsOp.h>
 #include <geos/operation/union/CascadedPolygonUnion.h>
+#include <geos/operation/union/CoverageUnion.h>
 #include <geos/operation/valid/IsValidOp.h>
 #include <geos/operation/valid/MakeValid.h>
 #include <geos/precision/GeometryPrecisionReducer.h>
@@ -2146,6 +2147,32 @@ extern "C" {
     }
 
     Geometry*
+    GEOSCoverageUnion_r(GEOSContextHandle_t extHandle, const Geometry* g)
+    {
+        if(0 == extHandle) {
+            return NULL;
+        }
+
+        GEOSContextHandleInternal_t* handle = 0;
+        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
+        if(0 == handle->initialized) {
+            return NULL;
+        }
+
+        try {
+            return geos::operation::geounion::CoverageUnion::Union(g).release();
+        }
+        catch(const std::exception& e) {
+            handle->ERROR_MESSAGE("%s", e.what());
+        }
+        catch(...) {
+            handle->ERROR_MESSAGE("Unknown exception thrown");
+        }
+
+        return NULL;
+    }
+
+    Geometry*
     GEOSUnaryUnion_r(GEOSContextHandle_t extHandle, const Geometry* g)
     {
         if(0 == extHandle) {
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
index 0531e38..9a725ea 100644
--- a/tests/unit/Makefile.am
+++ b/tests/unit/Makefile.am
@@ -134,6 +134,7 @@ geos_unit_SOURCES = \
 	util/UniqueCoordinateArrayFilterTest.cpp \
 	capi/GEOSClipByRectTest.cpp \
 	capi/GEOSCoordSeqTest.cpp \
+	capi/GEOSCoverageUnionTest.cpp \
 	capi/GEOSDelaunayTriangulationTest.cpp \
 	capi/GEOSVoronoiDiagramTest.cpp \
 	capi/GEOSGeomFromWKBTest.cpp \
diff --git a/tests/unit/operation/union/CoverageUnionTest.cpp b/tests/unit/operation/union/CoverageUnionTest.cpp
index f1611fb..331f9e6 100644
--- a/tests/unit/operation/union/CoverageUnionTest.cpp
+++ b/tests/unit/operation/union/CoverageUnionTest.cpp
@@ -94,7 +94,7 @@ namespace tut {
     {
         // Adjacent squares
         std::vector<std::string> geoms{
-            "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"
+            "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
             "POLYGON ((1 0, 1 1, 2 1, 2 0, 1 0))"
         };
 

commit 7919420cac16ebd32501c37bed9f4d6bb249ca0a
Author: Daniel Baston <dbaston at gmail.com>
Date:   Tue May 14 13:46:30 2019 -0400

    Define a hash function for LineSegment

diff --git a/include/geos/geom/LineSegment.h b/include/geos/geom/LineSegment.h
index 073bea8..008d48a 100644
--- a/include/geos/geom/LineSegment.h
+++ b/include/geos/geom/LineSegment.h
@@ -27,6 +27,7 @@
 #include <geos/inline.h>
 
 #include <iostream> // for ostream
+#include <functional> // for std::hash
 #include <memory> // for unique_ptr
 
 // Forward declarations
@@ -366,6 +367,14 @@ public:
      */
     std::unique_ptr<LineString> toGeometry(const GeometryFactory& gf) const;
 
+    struct HashCode {
+        size_t operator()(const LineSegment & s) const {
+            size_t h = std::hash<double>{}(s.p0.x);
+            h ^= (std::hash<double>{}(s.p0.y) << 1);
+            h ^= (std::hash<double>{}(s.p1.x) << 1);
+            return h ^ (std::hash<double>{}(s.p1.y) << 1);
+        }
+    };
 };
 
 std::ostream& operator<< (std::ostream& o, const LineSegment& l);
diff --git a/include/geos/operation/union/CoverageUnion.h b/include/geos/operation/union/CoverageUnion.h
index f38b4cc..23e2ca1 100644
--- a/include/geos/operation/union/CoverageUnion.h
+++ b/include/geos/operation/union/CoverageUnion.h
@@ -45,15 +45,7 @@ namespace geounion {
         void extractSegments(const geom::LineString* geom);
 
         std::unique_ptr<geom::Geometry> polygonize(const geom::GeometryFactory* gf);
-
-        struct SegmentHash {
-            size_t operator()(const geom::LineSegment & s) const {
-                // FIXME define a real hash function, and probably move this class elsewhere.
-                return 1;
-            }
-        };
-
-        std::unordered_set<geos::geom::LineSegment, SegmentHash> segments;
+        std::unordered_set<geos::geom::LineSegment, geos::geom::LineSegment::HashCode> segments;
         static constexpr double AREA_PCT_DIFF_TOL = 1e-6;
     };
 

commit ff01eef205592e467821b822aa3992e70f4ef6fc
Author: Daniel Baston <dbaston at gmail.com>
Date:   Mon May 13 10:13:21 2019 -0400

    Fail on overlapping or incorrectly noded inputs

diff --git a/include/geos/operation/polygonize/Polygonizer.h b/include/geos/operation/polygonize/Polygonizer.h
index eddf0a4..f6e412b 100644
--- a/include/geos/operation/polygonize/Polygonizer.h
+++ b/include/geos/operation/polygonize/Polygonizer.h
@@ -209,6 +209,8 @@ public:
      */
     const std::vector<const geom::LineString*>& getDangles();
 
+    bool hasDangles();
+
     /** \brief
      * Get the list of cut edges found during polygonization.
      *
@@ -218,6 +220,8 @@ public:
      */
     const std::vector<const geom::LineString*>& getCutEdges();
 
+    bool hasCutEdges();
+
     /** \brief
      * Get the list of lines forming invalid rings found during
      * polygonization.
@@ -228,6 +232,10 @@ public:
      */
     const std::vector<geom::LineString*>& getInvalidRingLines();
 
+    bool hasInvalidRingLines();
+
+    bool allInputsFormPolygons();
+
 // This seems to be needed by    GCC 2.95.4
     friend class Polygonizer::LineStringAdder;
 };
diff --git a/include/geos/operation/union/CoverageUnion.h b/include/geos/operation/union/CoverageUnion.h
index 59ba2b6..f38b4cc 100644
--- a/include/geos/operation/union/CoverageUnion.h
+++ b/include/geos/operation/union/CoverageUnion.h
@@ -54,6 +54,7 @@ namespace geounion {
         };
 
         std::unordered_set<geos::geom::LineSegment, SegmentHash> segments;
+        static constexpr double AREA_PCT_DIFF_TOL = 1e-6;
     };
 
 }
diff --git a/src/operation/polygonize/Polygonizer.cpp b/src/operation/polygonize/Polygonizer.cpp
index d670c46..bae99f4 100644
--- a/src/operation/polygonize/Polygonizer.cpp
+++ b/src/operation/polygonize/Polygonizer.cpp
@@ -171,6 +171,12 @@ Polygonizer::getDangles()
     return dangles;
 }
 
+bool
+Polygonizer::hasDangles() {
+    polygonize();
+    return !dangles.empty();
+}
+
 /* public */
 const vector<const LineString*>&
 Polygonizer::getCutEdges()
@@ -179,6 +185,13 @@ Polygonizer::getCutEdges()
     return cutEdges;
 }
 
+bool
+Polygonizer::hasCutEdges()
+{
+    polygonize();
+    return !cutEdges.empty();
+}
+
 /* public */
 const vector<LineString*>&
 Polygonizer::getInvalidRingLines()
@@ -187,6 +200,20 @@ Polygonizer::getInvalidRingLines()
     return invalidRingLines;
 }
 
+bool
+Polygonizer::hasInvalidRingLines()
+{
+    polygonize();
+    return !invalidRingLines.empty();
+}
+
+bool
+Polygonizer::allInputsFormPolygons()
+{
+    polygonize();
+    return !hasCutEdges() && !hasDangles() &&!hasInvalidRingLines();
+}
+
 /* public */
 void
 Polygonizer::polygonize()
diff --git a/src/operation/union/CoverageUnion.cpp b/src/operation/union/CoverageUnion.cpp
index 1290596..e7afdb1 100644
--- a/src/operation/union/CoverageUnion.cpp
+++ b/src/operation/union/CoverageUnion.cpp
@@ -20,6 +20,7 @@
 #include <geos/geom/Polygon.h>
 #include <geos/operation/polygonize/Polygonizer.h>
 #include <geos/util/IllegalArgumentException.h>
+#include <geos/util/TopologyException.h>
 
 namespace geos {
 namespace operation {
@@ -91,6 +92,10 @@ std::unique_ptr<Geometry> CoverageUnion::polygonize(const GeometryFactory* gf) {
         segment_geoms->emplace_back(std::move(seg_geom));
     }
 
+    if (!p.allInputsFormPolygons()) {
+        throw geos::util::TopologyException("CoverageUnion cannot process incorrectly noded inputs.");
+    }
+
     auto polygons{p.getPolygons()};
     segment_geoms.reset();
 
@@ -109,7 +114,18 @@ std::unique_ptr<Geometry> CoverageUnion::polygonize(const GeometryFactory* gf) {
 std::unique_ptr<geom::Geometry> CoverageUnion::Union(const geom::Geometry* geom) {
     CoverageUnion cu;
     cu.extractSegments(geom);
-    return cu.polygonize(geom->getFactory());
+
+    double area_in = geom->getArea();
+
+    auto ret = cu.polygonize(geom->getFactory());
+
+    double area_out = ret->getArea();
+
+    if (std::abs((area_out - area_in)/area_in) > AREA_PCT_DIFF_TOL) {
+        throw geos::util::TopologyException("CoverageUnion cannot process overlapping inputs.");
+    }
+
+    return ret;
 }
 
 }
diff --git a/tests/unit/operation/union/CoverageUnionTest.cpp b/tests/unit/operation/union/CoverageUnionTest.cpp
index 32f8399..f1611fb 100644
--- a/tests/unit/operation/union/CoverageUnionTest.cpp
+++ b/tests/unit/operation/union/CoverageUnionTest.cpp
@@ -33,7 +33,7 @@ namespace tut {
 
     group test_coverageuniontest_group("geos::operation::geounion::CoverageUnion");
 
-    void doTest(const std::vector<std::string> & wkt_geoms) {
+    void checkCoverageUnionEquivalentToUnaryUnion(const std::vector<std::string> &wkt_geoms) {
         using geos::io::WKTReader;
         using geos::geom::Geometry;
         using geos::geom::GeometryFactory;
@@ -61,6 +61,32 @@ namespace tut {
         ensure( u1->equals(u2.get()) );
     }
 
+    void checkCoverageUnionFails(const std::vector<std::string> & wkt_geoms) {
+        using geos::io::WKTReader;
+        using geos::geom::Geometry;
+        using geos::geom::GeometryFactory;
+        using geos::operation::geounion::CoverageUnion;
+        auto gfact = GeometryFactory::create();
+
+        WKTReader reader(gfact.get());
+
+        std::vector<Geometry*> geoms;
+
+        for (const auto& wkt : wkt_geoms) {
+            geoms.push_back(reader.read(wkt));
+        }
+
+        std::unique_ptr<Geometry> coll(gfact->createGeometryCollection(geoms));
+        for (auto& g : geoms) {
+            delete g;
+        }
+
+        try {
+            auto u1 = CoverageUnion::Union(coll.get());
+            fail();
+        } catch(const geos::util::TopologyException e) {}
+    }
+
     template<>
     template<>
     void object::test<1>
@@ -72,7 +98,7 @@ namespace tut {
             "POLYGON ((1 0, 1 1, 2 1, 2 0, 1 0))"
         };
 
-        doTest(geoms);
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
     }
 
     template<>
@@ -85,7 +111,7 @@ namespace tut {
                 "POLYGON ((-0.675 0.918, -0.78 0.918, -0.78 1.02, -0.675 1.02, -0.675 0.918))"
         };
 
-        doTest(geoms);
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
     }
 
     template<>
@@ -98,7 +124,7 @@ namespace tut {
                 "POLYGON ((-1.016 1.184, -0.89 1.184, -0.89 1.11, -1.016 1.11, -1.016 1.184))"
         };
 
-        doTest(geoms);
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
     }
 
     template<>
@@ -111,7 +137,7 @@ namespace tut {
                 "POLYGON ((-0.865 1.123, -0.935 1.167, -0.863 1.186, -0.865 1.123))"
         };
 
-        doTest(geoms);
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
     }
 
     template<>
@@ -124,7 +150,7 @@ namespace tut {
               "POLYGON ((20 10, 20 12, 30 12, 29 10, 20 10))"
         };
 
-        doTest(geoms);
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
     }
 
     template<>
@@ -137,7 +163,7 @@ namespace tut {
                "POLYGON ((20 10, 20 12, 30 12, 30 10, 20 10))"
         };
 
-        doTest(geoms);
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
     }
 
     template<>
@@ -155,7 +181,46 @@ namespace tut {
                 "  ((40 40, 40 50, 50 50, 50 40, 40 40)))"
         };
 
-        doTest(geoms);
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<8>()
+    {
+        // Incorrectly noded input
+        std::vector<std::string> geoms {
+            "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
+            "POLYGON ((1 0, 1 0.5, 1 1, 2 1, 2 0, 1 0))"
+        };
+
+        checkCoverageUnionFails(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<9>()
+    {
+        // Adjacent polygons with sliver
+        std::vector<std::string> geoms {
+                "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
+                "POLYGON ((1 0, 1.00000008 0.5, 1 1, 2 1, 2 0, 1 0))"
+        };
+
+        checkCoverageUnionEquivalentToUnaryUnion(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<10>()
+    {
+        // Adjacent polygons with overlap
+        std::vector<std::string> geoms {
+                "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
+                "POLYGON ((1 0, 0.99 0.5, 1 1, 2 1, 2 0, 1 0))"
+        };
+
+        checkCoverageUnionFails(geoms);
     }
 
 } // namespace tut

commit 72db1ce1c418f0451ded95f8d921a33f6827558c
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed May 8 19:49:20 2019 -0400

    Initial implementation of CoverageUnion.
    
    Tests copied from https://github.com/dbaston/CoverageOp and should be reviewed.

diff --git a/include/geos/operation/union/CoverageUnion.h b/include/geos/operation/union/CoverageUnion.h
new file mode 100644
index 0000000..59ba2b6
--- /dev/null
+++ b/include/geos/operation/union/CoverageUnion.h
@@ -0,0 +1,63 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2019 Daniel Baston <dbaston at gmail.com>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#ifndef GEOS_OP_UNION_COVERAGEUNION_H
+#define GEOS_OP_UNION_COVERAGEUNION_H
+
+#include <geos/geom/LineSegment.h>
+#include <geos/geom/Geometry.h>
+
+#include <memory>
+#include <unordered_set>
+
+namespace geos {
+    namespace geom {
+        class Polygon;
+        class LineString;
+        class GeometryFactory;
+    }
+}
+
+namespace geos {
+namespace operation {
+namespace geounion {
+
+    class GEOS_DLL CoverageUnion {
+    public:
+        static std::unique_ptr<geom::Geometry> Union(const geom::Geometry* geom);
+
+    private:
+        CoverageUnion() = default;
+
+        void extractSegments(const geom::Polygon* geom);
+        void extractSegments(const geom::Geometry* geom);
+        void extractSegments(const geom::LineString* geom);
+
+        std::unique_ptr<geom::Geometry> polygonize(const geom::GeometryFactory* gf);
+
+        struct SegmentHash {
+            size_t operator()(const geom::LineSegment & s) const {
+                // FIXME define a real hash function, and probably move this class elsewhere.
+                return 1;
+            }
+        };
+
+        std::unordered_set<geos::geom::LineSegment, SegmentHash> segments;
+    };
+
+}
+}
+}
+
+#endif
\ No newline at end of file
diff --git a/include/geos/operation/union/Makefile.am b/include/geos/operation/union/Makefile.am
index 8b9b9dc..f99e62b 100644
--- a/include/geos/operation/union/Makefile.am
+++ b/include/geos/operation/union/Makefile.am
@@ -1,15 +1,16 @@
 #
-# This file is part of project GEOS (http://trac.osgeo.org/geos/) 
+# This file is part of project GEOS (http://trac.osgeo.org/geos/)
 #
-#SUBDIRS = 
+#SUBDIRS =
 
-#EXTRA_DIST = 
+#EXTRA_DIST =
 
 geosdir = $(includedir)/geos/operation/union
 
 geos_HEADERS = \
     CascadedPolygonUnion.h \
     CascadedUnion.h \
+    CoverageUnion.h \
     GeometryListHolder.h \
     PointGeometryUnion.h \
     UnaryUnionOp.h
diff --git a/src/operation/union/CoverageUnion.cpp b/src/operation/union/CoverageUnion.cpp
new file mode 100644
index 0000000..1290596
--- /dev/null
+++ b/src/operation/union/CoverageUnion.cpp
@@ -0,0 +1,117 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2019 Daniel Baston <dbaston at gmail.com>
+ *
+ * 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 <geos/operation/union/CoverageUnion.h>
+
+#include <geos/geom/GeometryCollection.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/LineString.h>
+#include <geos/geom/Polygon.h>
+#include <geos/operation/polygonize/Polygonizer.h>
+#include <geos/util/IllegalArgumentException.h>
+
+namespace geos {
+namespace operation {
+namespace geounion {
+
+using geos::geom::Geometry;
+using geos::geom::LineSegment;
+using geos::geom::LineString;
+using geos::geom::Polygon;
+using geos::geom::GeometryCollection;
+using geos::geom::GeometryFactory;
+using geos::operation::polygonize::Polygonizer;
+
+void CoverageUnion::extractSegments(const Geometry* geom) {
+    const Polygon* p = dynamic_cast<const Polygon*>(geom);
+    if (p != nullptr) {
+        extractSegments(p);
+    } else {
+        auto gc = dynamic_cast<const GeometryCollection*>(geom);
+        if (gc == nullptr) {
+            throw geos::util::IllegalArgumentException("Unhandled geometry type in CoverageUnion.");
+        }
+
+        for (size_t i = 0; i < gc->getNumGeometries(); i++) {
+            extractSegments(gc->getGeometryN(i));
+        }
+    }
+}
+
+void CoverageUnion::extractSegments(const Polygon* p) {
+    const LineString* ring = p->getExteriorRing();
+
+    extractSegments(ring);
+    for (size_t i = 0; i < p->getNumInteriorRing(); i++) {
+        extractSegments(p->getInteriorRingN(i));
+    }
+
+
+}
+
+void CoverageUnion::extractSegments(const LineString* ls) {
+    auto coords = ls->getCoordinatesRO();
+
+    if (coords->isEmpty())
+        return;
+
+    for (size_t i = 1; i < coords->size(); i++) {
+        LineSegment segment{coords->getAt(i), coords->getAt(i-1)};
+        segment.normalize();
+
+       if (!segments.erase(segment)) {
+           segments.emplace(std::move(segment));
+       }
+    }
+}
+
+std::unique_ptr<Geometry> CoverageUnion::polygonize(const GeometryFactory* gf) {
+    Polygonizer p{true};
+
+    // Create a vector to manage the lifecycle of a geometry corresponding to each line segment.
+    // Polygonizer needs these to stay alive until it does its work.
+    std::unique_ptr<std::vector<std::unique_ptr<Geometry>>> segment_geoms;
+    segment_geoms.reset(new std::vector<std::unique_ptr<Geometry>>());
+    segment_geoms->reserve(segments.size());
+
+    for (const LineSegment& segment : segments) {
+        auto seg_geom = segment.toGeometry(*gf);
+        p.add(static_cast<Geometry*>(seg_geom.get()));
+        segment_geoms->emplace_back(std::move(seg_geom));
+    }
+
+    auto polygons{p.getPolygons()};
+    segment_geoms.reset();
+
+    if (polygons->size() == 1) {
+        return std::unique_ptr<Geometry>((*polygons)[0].release());
+    }
+
+    std::unique_ptr<std::vector<Geometry*>> geoms{new std::vector<Geometry*>{polygons->size()}};
+    for (size_t i = 0; i < polygons->size(); i++) {
+        (*geoms)[i] = (*polygons)[i].release();
+    }
+
+    return std::unique_ptr<Geometry>{gf->createMultiPolygon(geoms.release())};
+}
+
+std::unique_ptr<geom::Geometry> CoverageUnion::Union(const geom::Geometry* geom) {
+    CoverageUnion cu;
+    cu.extractSegments(geom);
+    return cu.polygonize(geom->getFactory());
+}
+
+}
+}
+}
diff --git a/src/operation/union/Makefile.am b/src/operation/union/Makefile.am
index 3ddce7f..0a35e03 100644
--- a/src/operation/union/Makefile.am
+++ b/src/operation/union/Makefile.am
@@ -1,16 +1,17 @@
 #
-# This file is part of project GEOS (http://trac.osgeo.org/geos/) 
+# This file is part of project GEOS (http://trac.osgeo.org/geos/)
 #
-SUBDIRS = 
+SUBDIRS =
 
 noinst_LTLIBRARIES = libopunion.la
 
-AM_CPPFLAGS = -I$(top_srcdir)/include 
+AM_CPPFLAGS = -I$(top_srcdir)/include
 
 libopunion_la_SOURCES = \
     CascadedPolygonUnion.cpp \
     CascadedUnion.cpp \
+    CoverageUnion.cpp \
     PointGeometryUnion.cpp \
-    UnaryUnionOp.cpp 
+    UnaryUnionOp.cpp
 
-libopunion_la_LIBADD = 
+libopunion_la_LIBADD =
diff --git a/tests/unit/operation/union/CoverageUnionTest.cpp b/tests/unit/operation/union/CoverageUnionTest.cpp
new file mode 100644
index 0000000..32f8399
--- /dev/null
+++ b/tests/unit/operation/union/CoverageUnionTest.cpp
@@ -0,0 +1,163 @@
+//
+// Test Suite for geos::operation::geounion::CoverageUnion class.
+
+// tut
+#include <tut/tut.hpp>
+// geos
+#include <geos/operation/union/UnaryUnionOp.h>
+#include <geos/operation/union/CoverageUnion.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/Polygon.h>
+#include <geos/geom/Point.h>
+#include <geos/io/WKTReader.h>
+#include <geos/io/WKTWriter.h>
+// std
+#include <memory>
+#include <string>
+#include <vector>
+#include <iostream>
+#include <geos/geom/GeometryFactory.inl>
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by tests
+    struct test_coverageuniontest_data {
+    };
+
+    typedef test_group<test_coverageuniontest_data> group;
+    typedef group::object object;
+
+    group test_coverageuniontest_group("geos::operation::geounion::CoverageUnion");
+
+    void doTest(const std::vector<std::string> & wkt_geoms) {
+        using geos::io::WKTReader;
+        using geos::geom::Geometry;
+        using geos::geom::GeometryFactory;
+        using geos::operation::geounion::CoverageUnion;
+        using geos::operation::geounion::UnaryUnionOp;
+
+        auto gfact = GeometryFactory::create();
+
+        WKTReader reader(gfact.get());
+
+        std::vector<Geometry*> geoms;
+
+        for (const auto& wkt : wkt_geoms) {
+            geoms.push_back(reader.read(wkt));
+        }
+
+        std::unique_ptr<Geometry> coll(gfact->createGeometryCollection(geoms));
+        for (auto& g : geoms) {
+            delete g;
+        }
+
+        auto u1 = UnaryUnionOp::Union(*coll);
+        auto u2 = CoverageUnion::Union(coll.get());
+
+        ensure( u1->equals(u2.get()) );
+    }
+
+    template<>
+    template<>
+    void object::test<1>
+    ()
+    {
+        // Adjacent squares
+        std::vector<std::string> geoms{
+            "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"
+            "POLYGON ((1 0, 1 1, 2 1, 2 0, 1 0))"
+        };
+
+        doTest(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<2>()
+    {
+        // Nested squares
+        std::vector<std::string> geoms{
+                "POLYGON ((-0.83 1.06, -0.629 1.06, -0.629 0.875, -0.83 0.875, -0.83 1.06), (-0.675 0.918, -0.78 0.918, -0.78 1.02, -0.675 1.02, -0.675 0.918))",
+                "POLYGON ((-0.675 0.918, -0.78 0.918, -0.78 1.02, -0.675 1.02, -0.675 0.918))"
+        };
+
+        doTest(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<3>()
+    {
+        // Disconnected components
+        std::vector<std::string> geoms{
+                "POLYGON ((-0.84 1.18, -0.705 1.18, -0.705 1.121, -0.84 1.121, -0.84 1.18))",
+                "POLYGON ((-1.016 1.184, -0.89 1.184, -0.89 1.11, -1.016 1.11, -1.016 1.184))"
+        };
+
+        doTest(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<4>()
+    {
+        // Bow-tie
+        std::vector<std::string> geoms{
+                "POLYGON ((-0.88 1.04, -0.79 1.07, -0.865 1.123, -0.88 1.04))",
+                "POLYGON ((-0.865 1.123, -0.935 1.167, -0.863 1.186, -0.865 1.123))"
+        };
+
+        doTest(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<5>()
+    {
+        // Polygon inside hole
+        std::vector<std::string> geoms{
+              "POLYGON ((0 0, 0 20, 40 20, 40 0, 0 0), (30 10, 35 10, 35 15, 5 15, 5 5, 30 5, 30 10))",
+              "POLYGON ((20 10, 20 12, 30 12, 29 10, 20 10))"
+        };
+
+        doTest(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<6>()
+    {
+        // Polygon inside hole, touching
+        std::vector<std::string> geoms{
+               "POLYGON ((0 0, 0 20, 40 20, 40 0, 0 0), (30 10, 35 10, 35 15, 5 15, 5 5, 30 5, 30 10))",
+               "POLYGON ((20 10, 20 12, 30 12, 30 10, 20 10))"
+        };
+
+        doTest(geoms);
+    }
+
+    template<>
+    template<>
+    void object::test<7>()
+    {
+        // Multiple nested holes
+        std::vector<std::string> geoms{
+                "MULTIPOLYGON (((0 0, 0 70, 70 70, 70 0, 0 0), "
+                "(20 10, 30 10, 30 20, 40 20, 40 10, 50 10, 50 20, 60 20, 60 30, 50 30, 50 40, 60 40, 60 50, 50 50, 50 60, 40 60, 40 50, 30 50, 30 60, 20 60, 20 50, 10 50, 10 40, 20 40, 20 30, 10 30, 10 20, 20 20, 20 10)),"
+                "  ((20 20, 20 30, 30 30, 30 20, 20 20)),"
+                "  ((40 20, 40 30, 50 30, 50 20, 40 20)),"
+                "  ((30 30, 30 40, 40 40, 40 30, 30 30)),"
+                "  ((20 40, 20 50, 30 50, 30 40, 20 40)),"
+                "  ((40 40, 40 50, 50 50, 50 40, 40 40)))"
+        };
+
+        doTest(geoms);
+    }
+
+} // namespace tut
+
+

-----------------------------------------------------------------------

Summary of changes:
 NEWS                                               |   3 +
 capi/geos_c.cpp                                    |   6 +
 capi/geos_c.h.in                                   |  10 +
 capi/geos_ts_c.cpp                                 |  27 +++
 include/geos/geom/LineSegment.h                    |   9 +
 include/geos/operation/polygonize/EdgeRing.h       | 178 ++++++++--------
 include/geos/operation/polygonize/HoleAssigner.h   |  66 ++++++
 include/geos/operation/polygonize/Makefile.am      |   1 +
 include/geos/operation/polygonize/Polygonizer.h    |  20 +-
 include/geos/operation/union/CoverageUnion.h       |  56 +++++
 include/geos/operation/union/Makefile.am           |   7 +-
 src/operation/polygonize/EdgeRing.cpp              |  84 ++++++--
 src/operation/polygonize/HoleAssigner.cpp          |  83 ++++++++
 src/operation/polygonize/Makefile.am               |   1 +
 src/operation/polygonize/Polygonizer.cpp           |  77 +++----
 src/operation/union/CoverageUnion.cpp              | 131 ++++++++++++
 src/operation/union/Makefile.am                    |  11 +-
 tests/unit/Makefile.am                             |   1 +
 tests/unit/capi/GEOSCoverageUnionTest.cpp          | 107 ++++++++++
 tests/unit/operation/polygonize/PolygonizeTest.cpp |   8 +-
 tests/unit/operation/union/CoverageUnionTest.cpp   | 228 +++++++++++++++++++++
 21 files changed, 953 insertions(+), 161 deletions(-)
 create mode 100644 include/geos/operation/polygonize/HoleAssigner.h
 create mode 100644 include/geos/operation/union/CoverageUnion.h
 create mode 100644 src/operation/polygonize/HoleAssigner.cpp
 create mode 100644 src/operation/union/CoverageUnion.cpp
 create mode 100644 tests/unit/capi/GEOSCoverageUnionTest.cpp
 create mode 100644 tests/unit/operation/union/CoverageUnionTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list