[geos-commits] [SCM] GEOS branch main updated. dd92d34f74fdda129fa0f1c32ee65d323cd471fb

git at osgeo.org git at osgeo.org
Tue Apr 11 10:05:55 PDT 2023


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, main has been updated
       via  dd92d34f74fdda129fa0f1c32ee65d323cd471fb (commit)
       via  007f18eccd1c385426a9a10e6360a940e8ae1a41 (commit)
      from  7f6baf316254c8a78aaa47f301a85a571ded8a90 (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 dd92d34f74fdda129fa0f1c32ee65d323cd471fb
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Tue Apr 11 10:03:09 2023 -0700

    Port Coverage Simplification https://github.com/locationtech/jts/pull/911
    
    Adds the CoverageSimplifier class to the coverage package. This supports simplification of the edges of polygonal coverages while preserving the coverage topology:
    
        The simplified coverage has the same number and types of polygonal geometries as the input
        Coverage node points (inner vertices shared by three or more polygons, or boundary vertices shared by two or more) are not changed
        if the input is a valid coverage, then so is the result
    
    The simplification uses an approach equivalent to Visvalingam-Whyatt simplification. This is based on removing vertices which are the apex of low-area "corners". The amount of simplification is determined by a tolerance value which is the square root of the maximum corner area that can be removed.

diff --git a/include/geos/constants.h b/include/geos/constants.h
index 2ad3be23e..7d58ca3d5 100644
--- a/include/geos/constants.h
+++ b/include/geos/constants.h
@@ -41,5 +41,7 @@ constexpr double DoubleInfinity = (std::numeric_limits<double>::infinity)();
 constexpr double DoubleNegInfinity = (-(std::numeric_limits<double>::infinity)());
 constexpr double DoubleEpsilon = std::numeric_limits<double>::epsilon();
 
+constexpr std::size_t NO_COORD_INDEX = std::numeric_limits<std::size_t>::max();
+
 } // namespace geos
 
diff --git a/include/geos/coverage/Corner.h b/include/geos/coverage/Corner.h
new file mode 100644
index 000000000..5cada2413
--- /dev/null
+++ b/include/geos/coverage/Corner.h
@@ -0,0 +1,137 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2021 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/geom/Envelope.h>
+#include <geos/simplify/LinkedLine.h>
+#include <geos/export.h>
+
+#include <vector>
+#include <memory>
+#include <queue>
+
+namespace geos {
+namespace simplify {
+class LinkedLine;
+}
+namespace geom {
+class Coordinate;
+class LineString;
+}
+}
+
+
+using geos::geom::Coordinate;
+using geos::geom::Envelope;
+using geos::geom::LineString;
+using geos::simplify::LinkedLine;
+
+
+namespace geos {
+namespace coverage { // geos::coverage
+
+
+class Corner
+{
+
+public:
+
+    Corner(const LinkedLine* edge, std::size_t i);
+
+    bool isVertex(std::size_t index) const;
+
+    inline std::size_t getIndex() const {
+        return m_index;
+    }
+
+    inline double getArea() const {
+        return m_area;
+    };
+
+    const Coordinate& prev() const;
+    const Coordinate& next() const;
+
+    Envelope envelope() const;
+
+    bool isVertex(const Coordinate& v) const;
+    bool isBaseline(const Coordinate& p0, const Coordinate& p1) const;
+    bool intersects(const Coordinate& v) const;
+    bool isRemoved() const;
+
+    const Coordinate& getCoordinate() {
+        return m_edge->getCoordinate(m_index);
+    }
+
+    std::unique_ptr<LineString> toLineString() const;
+
+    inline int compareTo(const Corner& rhs) const {
+        double area_lhs = getArea();
+        double area_rhs = rhs.getArea();
+
+        if (area_lhs == area_rhs) {
+            std::size_t index_lhs = getIndex();
+            std::size_t index_rhs = rhs.getIndex();
+            if (index_lhs == index_rhs) return 0;
+            else return index_lhs < index_rhs ? -1 : 1;
+        }
+        else
+            return area_lhs < area_rhs ? -1 : 1;
+    }
+
+    bool operator< (const Corner& rhs) const {
+        return compareTo(rhs) < 0;
+    };
+
+    bool operator> (const Corner& rhs) const {
+        return compareTo(rhs) > 0;
+    };
+
+    bool operator==(const Corner& rhs) const {
+        return compareTo(rhs) == 0;
+    };
+
+    struct Greater {
+        inline bool operator()(const Corner & a, const Corner & b) const {
+            return a.compareTo(b) > 0;
+        }
+    };
+
+    // Order using greater for compatibility with the Java PriorityQueue
+    // implementation, which returns the smallest item off the top of the
+    // queue
+    using PriorityQueue = std::priority_queue<Corner, std::vector<Corner>, Corner::Greater>;
+
+
+private:
+
+    // members
+    const LinkedLine* m_edge;
+    std::size_t m_index;
+    std::size_t m_prev;
+    std::size_t m_next;
+    double m_area;
+
+    // methods
+    static double area(const LinkedLine& edge, std::size_t index);
+
+}; // Corner
+
+
+
+GEOS_DLL std::ostream& operator<< (std::ostream& os, const Corner& c);
+
+
+} // geos::coverage
+} // geos
diff --git a/include/geos/coverage/CoverageBoundarySegmentFinder.h b/include/geos/coverage/CoverageBoundarySegmentFinder.h
new file mode 100644
index 000000000..cc23093ba
--- /dev/null
+++ b/include/geos/coverage/CoverageBoundarySegmentFinder.h
@@ -0,0 +1,83 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2021 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <unordered_set>
+
+#include <geos/geom/CoordinateSequenceFilter.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/LineSegment.h>
+#include <geos/export.h>
+
+namespace geos {
+namespace geom {
+class CoordinateSequence;
+class Geometry;
+}
+}
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::CoordinateSequenceFilter;
+using geos::geom::Geometry;
+using geos::geom::LineSegment;
+
+namespace geos {
+namespace coverage { // geos::coverage
+
+class CoverageBoundarySegmentFinder : public CoordinateSequenceFilter
+{
+
+
+public:
+
+    CoverageBoundarySegmentFinder(LineSegment::UnorderedSet& segs)
+        : m_boundarySegs(segs)
+        {};
+
+    bool isGeometryChanged() const override {
+        return false;
+    }
+
+    bool isDone() const override {
+        return false;
+    }
+
+    void filter_ro(const CoordinateSequence& seq, std::size_t i) override;
+
+
+    static LineSegment::UnorderedSet
+        findBoundarySegments(const std::vector<const Geometry*>& geoms);
+
+    static bool isBoundarySegment(
+        const LineSegment::UnorderedSet& boundarySegs,
+        const CoordinateSequence* seq,
+        std::size_t i);
+
+private:
+
+    static LineSegment
+        createSegment(const CoordinateSequence& seq, std::size_t i);
+
+
+    LineSegment::UnorderedSet& m_boundarySegs;
+
+
+}; // CoverageBoundarySegmentFinder
+
+
+
+} // geos::coverage
+} // geos
diff --git a/include/geos/coverage/CoverageEdge.h b/include/geos/coverage/CoverageEdge.h
new file mode 100644
index 000000000..a75c3ecf8
--- /dev/null
+++ b/include/geos/coverage/CoverageEdge.h
@@ -0,0 +1,176 @@
+
+
+
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/LineSegment.h>
+#include <geos/util.h>
+
+// Forward declarations
+namespace geos {
+namespace geom {
+class Coordinate;
+class LinearRing;
+class LineString;
+class MultiLineString;
+class GeometryFactory;
+}
+}
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::GeometryFactory;
+using geos::geom::LinearRing;
+using geos::geom::LineString;
+using geos::geom::LineSegment;
+using geos::geom::MultiLineString;
+
+namespace geos {      // geos.
+namespace coverage { // geos.coverage
+
+/**
+ * An edge of a polygonal coverage formed from all or a section of a polygon ring.
+ * An edge may be a free ring, which is a ring which has not node points
+ * (i.e. does not touch any other rings in the parent coverage).
+ *
+ * @author mdavis
+ *
+ */
+class GEOS_DLL CoverageEdge {
+
+private:
+
+    // Members
+    std::unique_ptr<CoordinateSequence> m_pts;
+    std::size_t m_ringCount ;
+    bool m_isFreeRing = true;
+
+    // Methods
+
+    static std::unique_ptr<CoordinateSequence>
+    extractEdgePoints(const LinearRing* ring,
+        std::size_t start, std::size_t end);
+
+    static const Coordinate&
+    findDistinctPoint(
+        const CoordinateSequence* pts,
+        std::size_t index,
+        bool isForward,
+        const Coordinate& pt);
+
+
+public:
+
+    CoverageEdge(std::unique_ptr<CoordinateSequence> && pts, bool isFreeRing)
+        : m_pts(pts ? std::move(pts) : detail::make_unique<CoordinateSequence>())
+        , m_ringCount(0)
+        , m_isFreeRing(isFreeRing)
+        {}
+
+    /**
+    * Computes a key segment for a ring.
+    * The key is the segment starting at the lowest vertex,
+    * towards the lowest adjacent distinct vertex.
+    *
+    * @param ring a linear ring
+    * @return a LineSegment representing the key
+    */
+    static LineSegment key(
+        const LinearRing* ring);
+
+    /**
+    * Computes a distinct key for a section of a linear ring.
+    *
+    * @param ring the linear ring
+    * @param start index of the start of the section
+    * @param end end index of the end of the section
+    * @return a LineSegment representing the key
+    */
+    static LineSegment key(
+        const LinearRing* ring,
+        std::size_t start,
+        std::size_t end);
+
+    static std::unique_ptr<CoverageEdge> createEdge(
+        const LinearRing* ring);
+
+    static std::unique_ptr<CoverageEdge> createEdge(
+        const LinearRing* ring,
+        std::size_t start,
+        std::size_t end);
+
+    static std::unique_ptr<MultiLineString> createLines(
+        const std::vector<CoverageEdge*>& edges,
+        const GeometryFactory* geomFactory);
+
+    std::unique_ptr<LineString> toLineString(
+        const GeometryFactory* geomFactory);
+
+    /* public */
+    void incRingCount()
+    {
+        m_ringCount++;
+    }
+
+    /* public */
+    std::size_t getRingCount() const
+    {
+        return m_ringCount;
+    }
+
+    /**
+    * Returns whether this edge is a free ring;
+    * i.e. one with no constrained nodes.
+    *
+    * @return true if this is a free ring
+    */
+    bool isFreeRing() const
+    {
+        return m_isFreeRing;
+    }
+
+    void setCoordinates(const CoordinateSequence* pts)
+    {
+        m_pts = pts->clone();
+    }
+
+    const CoordinateSequence* getCoordinates() const
+    {
+        return m_pts.get();
+    }
+
+    const Coordinate& getEndCoordinate() const
+    {
+        return m_pts->getAt(m_pts->size() - 1);
+    }
+
+    const Coordinate& getStartCoordinate() const
+    {
+        return m_pts->getAt(0);
+    }
+
+
+};
+
+} // namespace geos.coverage
+} // namespace geos
+
+
+
+
+
diff --git a/include/geos/coverage/CoverageRingEdges.h b/include/geos/coverage/CoverageRingEdges.h
new file mode 100644
index 000000000..f57473bcd
--- /dev/null
+++ b/include/geos/coverage/CoverageRingEdges.h
@@ -0,0 +1,184 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2023 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2023 Martin Davis.
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/LineSegment.h>
+
+#include <set>
+#include <map>
+
+// Forward declarations
+namespace geos {
+namespace geom {
+class CoordinateSequence;
+class Geometry;
+class LinearRing;
+class MultiPolygon;
+class Polygon;
+}
+namespace coverage {
+class CoverageEdge;
+}
+}
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::Geometry;
+using geos::geom::LinearRing;
+using geos::geom::LineSegment;
+using geos::geom::MultiPolygon;
+using geos::geom::Polygon;
+
+namespace geos {     // geos
+namespace coverage { // geos.coverage
+
+/**
+ * Models a polygonal coverage as a set of unique {@link CoverageEdge}s,
+ * linked to the parent rings in the coverage polygons.
+ * Each edge has either one or two parent rings, depending on whether
+ * it is an inner or outer edge of the coverage.
+ * The source coverage is represented as a array of polygonal geometries
+ * (either {@link geos::geom::Polygon}s or {@link geos::geom::MultiPolygon}s).
+ *
+ * @author Martin Davis
+ */
+class GEOS_DLL CoverageRingEdges {
+
+private:
+
+    // Members
+    std::vector<const Geometry*>& m_coverage;
+    std::map<const LinearRing*, std::vector<CoverageEdge*>> m_ringEdgesMap;
+    std::vector<CoverageEdge*> m_edges;
+    std::vector<std::unique_ptr<CoverageEdge>> m_edgeStore;
+
+    /* Turn off copy constructors for MSVC */
+    CoverageRingEdges(const CoverageRingEdges&) = delete;
+    CoverageRingEdges& operator=(const CoverageRingEdges&) = delete;
+
+public:
+
+    /**
+    * Create a new instance for a given coverage.
+    *
+    * @param coverage the set of polygonal geometries in the coverage
+    * @return the edges of the coverage
+    */
+    // static std::unique_ptr<CoverageRingEdges> create(
+    //     std::vector<const Geometry*>& coverage);
+
+
+    CoverageRingEdges(std::vector<const Geometry*>& coverage)
+        : m_coverage(coverage)
+    {
+        build();
+    };
+
+
+    std::vector<CoverageEdge*>& getEdges()
+    {
+        return m_edges;
+    };
+
+    /**
+    * Selects the edges with a given ring count (which can be 1 or 2).
+    *
+    * @param ringCount the edge ring count to select (1 or 2)
+    * @return the selected edges
+    */
+    std::vector<CoverageEdge*> selectEdges(
+        std::size_t ringCount) const;
+
+    /**
+    * Recreates the polygon coverage from the current edge values.
+    *
+    * @return an array of polygonal geometries representing the coverage
+    */
+    std::vector<std::unique_ptr<Geometry>> buildCoverage() const;
+
+
+private:
+
+    void build();
+
+    void addRingEdges(
+        const LinearRing* ring,
+        Coordinate::UnorderedSet& nodes,
+        LineSegment::UnorderedSet& boundarySegs,
+        std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap);
+
+    void addBoundaryNodes(
+        const LinearRing* ring,
+        LineSegment::UnorderedSet& boundarySegs,
+        Coordinate::UnorderedSet& nodes);
+
+    std::vector<CoverageEdge*> extractRingEdges(
+        const LinearRing* ring,
+        std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap,
+        Coordinate::UnorderedSet& nodes);
+
+    CoverageEdge* createEdge(
+        const LinearRing* ring,
+        std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap);
+
+    CoverageEdge* createEdge(
+        const LinearRing* ring,
+        std::size_t start, std::size_t end,
+        std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap);
+
+    std::size_t findNextNodeIndex(
+        const LinearRing* ring,
+        std::size_t start,
+        Coordinate::UnorderedSet& nodes) const;
+
+    static std::size_t next(
+        std::size_t index,
+        const LinearRing* ring);
+
+    Coordinate::UnorderedSet findNodes(
+        std::vector<const Geometry*>& coverage);
+
+    Coordinate::UnorderedSet findBoundaryNodes(
+        LineSegment::UnorderedSet& lineSegments);
+
+    std::unique_ptr<Geometry> buildPolygonal(
+        const Geometry* geom) const;
+
+    std::unique_ptr<Geometry> buildMultiPolygon(
+        const MultiPolygon* geom) const;
+
+    std::unique_ptr<Polygon> buildPolygon(
+        const Polygon* polygon) const;
+
+    std::unique_ptr<LinearRing> buildRing(
+        const LinearRing* ring) const;
+
+    bool isEdgeDirForward(
+        const std::vector<CoverageEdge*>& ringEdges,
+        std::size_t index,
+        const Coordinate& prevPt) const;
+
+
+};
+
+} // namespace geos.coverage
+} // namespace geos
+
+
+
+
+
diff --git a/include/geos/coverage/CoverageSimplifier.h b/include/geos/coverage/CoverageSimplifier.h
new file mode 100644
index 000000000..11e8fa3d1
--- /dev/null
+++ b/include/geos/coverage/CoverageSimplifier.h
@@ -0,0 +1,166 @@
+
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2023 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2023 Martin Davis.
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <vector>
+#include <memory>
+#include <geos/export.h>
+
+
+namespace geos {
+namespace geom {
+class Geometry;
+class GeometryFactory;
+class MultiLineString;
+}
+namespace coverage {
+class CoverageEdge;
+}
+}
+
+
+using geos::coverage::CoverageEdge;
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::MultiLineString;
+
+
+namespace geos {
+namespace coverage { // geos::coverage
+
+/**
+ * Simplifies the boundaries of the polygons in a polygonal coverage
+ * while preserving the original coverage topology.
+ * An area-based simplification algorithm
+ * (similar to Visvalingam-Whyatt simplification)
+ * is used to provide high-quality results.
+ * Also supports simplifying just the inner edges in a coverage,
+ * which allows simplifying "patches" without affecting their boundary.
+ *
+ * The amount of simplification is determined by a tolerance value,
+ * which is a non-negative quantity. It equates roughly to the maximum
+ * distance by which a simplified line can change from the original.
+ * (In fact, it is the square root of the area tolerance used
+ * in the Visvalingam-Whyatt algorithm.)
+ *
+ * The simplified result coverage has the following characteristics:
+ *
+ *   * It has the same number and types of polygonal geometries as the input
+ *   * Node points (inner vertices shared by three or more polygons,
+ *     or boundary vertices shared by two or more) are not changed
+ *   * If the input is a valid coverage, then so is the result
+ *
+ * This class also supports inner simplification, which simplifies
+ * only edges of the coverage which are adjacent to two polygons.
+ * This allows partial simplification of a coverage, since a simplified
+ * subset of a coverage still matches the remainder of the coverage.
+ *
+ * The input coverage should be valid according to {@link CoverageValidator}.
+ *
+ * @author Martin Davis
+ */
+class GEOS_DLL CoverageSimplifier {
+
+
+public:
+
+    /**
+    * Create a new coverage simplifier instance.
+    *
+    * @param coverage a set of polygonal geometries forming a coverage
+    */
+    CoverageSimplifier(std::vector<const Geometry*>& coverage);
+
+    /**
+    * Simplifies the boundaries of a set of polygonal geometries forming a coverage,
+    * preserving the coverage topology.
+    *
+    * @param coverage a set of polygonal geometries forming a coverage
+    * @param tolerance the simplification tolerance
+    * @return the simplified polygons
+    */
+    static std::vector<std::unique_ptr<Geometry>> simplify(
+        std::vector<const Geometry*>& coverage,
+        double tolerance);
+
+    static std::vector<std::unique_ptr<Geometry>> simplify(
+        const std::vector<std::unique_ptr<Geometry>>& coverage,
+        double tolerance);
+
+    /**
+    * Simplifies the inner boundaries of a set of polygonal geometries forming a coverage,
+    * preserving the coverage topology.
+    * Edges which form the exterior boundary of the coverage are left unchanged.
+    *
+    * @param coverage a set of polygonal geometries forming a coverage
+    * @param tolerance the simplification tolerance
+    * @return the simplified polygons
+    */
+    static std::vector<std::unique_ptr<Geometry>> simplifyInner(
+        std::vector<const Geometry*>& coverage,
+        double tolerance);
+
+    static std::vector<std::unique_ptr<Geometry>> simplifyInner(
+        const std::vector<std::unique_ptr<Geometry>>& coverage,
+        double tolerance);
+
+    /**
+    * Computes the simplified coverage, preserving the coverage topology.
+    *
+    * @param tolerance the simplification tolerance
+    * @return the simplified polygons
+    */
+    std::vector<std::unique_ptr<Geometry>> simplify(
+        double tolerance);
+
+    /**
+    * Computes the inner-boundary simplified coverage,
+    * preserving the coverage topology,
+    * and leaving outer boundary edges unchanged.
+    *
+    * @param tolerance the simplification tolerance
+    * @return the simplified polygons
+    */
+    std::vector<std::unique_ptr<Geometry>> simplifyInner(
+        double tolerance);
+
+
+private:
+
+    // Members
+    std::vector<const Geometry*>& m_input; // TODO? make this const
+    const GeometryFactory* m_geomFactory;
+
+    // Methods
+    void simplifyEdges(
+        std::vector<CoverageEdge*> edges,
+        const MultiLineString* constraints,
+        double tolerance);
+
+    void setCoordinates(
+        std::vector<CoverageEdge*>& edges,
+        const MultiLineString* lines);
+
+    std::vector<bool> getFreeRings(
+        const std::vector<CoverageEdge*>& edges) const;
+
+
+}; // CoverageSimplifier
+
+
+} // geos::coverage
+} // geos
diff --git a/include/geos/coverage/TPVWSimplifier.h b/include/geos/coverage/TPVWSimplifier.h
new file mode 100644
index 000000000..657616e87
--- /dev/null
+++ b/include/geos/coverage/TPVWSimplifier.h
@@ -0,0 +1,225 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2021 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <queue>
+
+#include <geos/index/strtree/TemplateSTRtree.h>
+#include <geos/index/VertexSequencePackedRtree.h>
+#include <geos/simplify/LinkedLine.h>
+#include <geos/coverage/Corner.h>
+#include <geos/export.h>
+
+
+namespace geos {
+namespace geom {
+class Coordinate;
+class CoordinateSequence;
+class Envelope;
+class Geometry;
+class GeometryFactory;
+class LineString;
+class MultiLineString;
+}
+}
+
+
+using geos::coverage::Corner;
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::Envelope;
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::LineString;
+using geos::geom::MultiLineString;
+using geos::index::VertexSequencePackedRtree;
+using geos::index::strtree::TemplateSTRtree;
+using geos::simplify::LinkedLine;
+
+
+namespace geos {
+namespace coverage { // geos::coverage
+
+
+/**
+ * Computes a Topology-Preserving Visvalingam-Whyatt simplification
+ * of a set of input lines.
+ * The simplified lines will contain no more intersections than are present
+ * in the original input.
+ * Line and ring endpoints are preserved, except for rings
+ * which are flagged as "free".
+ *
+ * The amount of simplification is determined by a tolerance value,
+ * which is a non-zero quantity.
+ * It is the square root of the area tolerance used
+ * in the Visvalingam-Whyatt algorithm.
+ * This equates roughly to the maximum
+ * distance by which a simplified line can change from the original.
+ *
+ * @author mdavis
+ *
+ */
+class GEOS_DLL TPVWSimplifier
+{
+
+public:
+
+    // Prototype
+    class EdgeIndex;
+
+    /* private static */
+    class Edge {
+
+    public:
+
+        // Members
+        double areaTolerance;
+        bool isFreeRing;
+        const Envelope* envelope;
+        std::size_t nbPts;
+        LinkedLine linkedLine;
+        VertexSequencePackedRtree vertexIndex;
+        std::size_t minEdgeSize;
+
+        /**
+         * Creates a new edge.
+         * The endpoints of the edge are preserved during simplification,
+         * unless it is a ring and the isFreeRing flag is set.
+         *
+         * @param p_inputLine the line or ring
+         * @param p_isFreeRing whether a ring endpoint can be removed
+         * @param p_areaTolerance the simplification tolerance
+         */
+        Edge(const LineString* p_inputLine, bool p_isFreeRing, double p_areaTolerance);
+
+        const Coordinate& getCoordinate(std::size_t index) const;
+
+        const Envelope* getEnvelopeInternal() const;
+
+        std::size_t size() const;
+
+        std::unique_ptr<CoordinateSequence> simplify(EdgeIndex& edgeIndex);
+
+        void createQueue(Corner::PriorityQueue& pq);
+
+        void addCorner(std::size_t i, Corner::PriorityQueue& cornerQueue);
+
+        bool isRemovable(Corner& corner, EdgeIndex& edgeIndex) const;
+
+        /**
+         * Tests if any vertices in a line intersect the corner triangle.
+         * Uses the vertex spatial index for efficiency.
+         *
+         * @param corner the corner vertices
+         * @param cornerEnv the envelope of the corner
+         * @param edge the hull to test
+         * @return true if there is an intersecting vertex
+         */
+        bool hasIntersectingVertex(const Corner& corner,
+            const Envelope& cornerEnv,
+            const Edge& edge) const;
+
+        std::vector<std::size_t> query(const Envelope& cornerEnv) const;
+
+        /**
+         * Removes a corner by removing the apex vertex from the ring.
+         * Two new corners are created with apexes
+         * at the other vertices of the corner
+         * (if they are non-convex and thus removable).
+         *
+         * @param corner the corner to remove
+         * @param cornerQueue the corner queue
+         */
+        void removeCorner(
+            Corner& corner,
+            Corner::PriorityQueue& cornerQueue);
+
+    }; // Edge
+
+    class EdgeIndex
+    {
+        public:
+
+            TemplateSTRtree<const Edge*> index;
+
+            void add(std::vector<Edge>& edges);
+
+            std::vector<const Edge*> query(const Envelope& queryEnv);
+
+    }; // EdgeIndex
+
+
+    /**
+    * Simplifies a set of lines, preserving the topology of the lines.
+    *
+    * @param lines the lines to simplify
+    * @param distanceTolerance the simplification tolerance
+    * @return the simplified lines
+    */
+    static std::unique_ptr<MultiLineString> simplify(
+        const MultiLineString* lines,
+        double distanceTolerance);
+
+    /**
+    * Simplifies a set of lines, preserving the topology of the lines between
+    * themselves and a set of linear constraints.
+    * The endpoints of lines are preserved.
+    * The endpoint of rings are preserved as well, unless
+    * the ring is indicated as "free" via a bit flag with the same index.
+    *
+    * @param lines the lines to simplify
+    * @param freeRings flags indicating which ring edges do not have node endpoints
+    * @param constraintLines the linear constraints
+    * @param distanceTolerance the simplification tolerance
+    * @return the simplified lines
+    */
+    static std::unique_ptr<MultiLineString> simplify(
+        const MultiLineString* lines,
+        std::vector<bool>& freeRings,
+        const MultiLineString* constraintLines,
+        double distanceTolerance);
+
+    // Constructor
+    TPVWSimplifier(const MultiLineString* lines,
+        double distanceTolerance);
+
+
+private:
+
+    // Members
+    const MultiLineString* inputLines;
+    std::vector<bool> isFreeRing;
+    double areaTolerance;
+    const GeometryFactory* geomFactory;
+    const MultiLineString* constraintLines;
+
+
+    // Methods
+    void setFreeRingIndices(std::vector<bool>& freeRing);
+
+    void setConstraints(const MultiLineString* constraints);
+
+    std::unique_ptr<MultiLineString> simplify();
+
+    std::vector<Edge> createEdges(
+        const MultiLineString* lines,
+        std::vector<bool>& freeRing);
+
+
+}; // TPVWSimplifier
+
+
+} // geos::coverage
+} // geos
diff --git a/include/geos/coverage/VertexCounter.h b/include/geos/coverage/VertexCounter.h
new file mode 100644
index 000000000..bf10ea3c0
--- /dev/null
+++ b/include/geos/coverage/VertexCounter.h
@@ -0,0 +1,71 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2021 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <map>
+
+#include <geos/geom/CoordinateSequenceFilter.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/export.h>
+
+namespace geos {
+namespace geom {
+class CoordinateSequence;
+class Geometry;
+}
+}
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::CoordinateSequenceFilter;
+using geos::geom::Geometry;
+
+
+namespace geos {
+namespace coverage { // geos::coverage
+
+class VertexCounter : public CoordinateSequenceFilter
+{
+
+public:
+
+    VertexCounter(std::map<Coordinate, std::size_t>& counts)
+        : vertexCounts(counts)
+        {};
+
+    bool isGeometryChanged() const override {
+        return false;
+    }
+
+    bool isDone() const override {
+        return false;
+    }
+
+    void filter_ro(const CoordinateSequence& seq, std::size_t i) override;
+
+    static void count(
+        std::vector<const Geometry*>& geoms,
+        std::map<Coordinate, std::size_t>& counts);
+
+private:
+
+    std::map<Coordinate, std::size_t>& vertexCounts;
+
+}; // VertexCounter
+
+
+
+} // geos::coverage
+} // geos
diff --git a/include/geos/geom/Coordinate.h b/include/geos/geom/Coordinate.h
index 462fcd46a..220dd7cef 100644
--- a/include/geos/geom/Coordinate.h
+++ b/include/geos/geom/Coordinate.h
@@ -17,6 +17,7 @@
 #include <geos/export.h>
 #include <geos/constants.h> // for DoubleNotANumber
 #include <set>
+#include <unordered_set>
 #include <stack>
 #include <vector> // for typedefs
 #include <string>
@@ -183,6 +184,8 @@ public:
         };
     };
 
+    using UnorderedSet = std::unordered_set<Coordinate, HashCode>;
+
     ///  Returns a string of the form <I>(x,y,z)</I> .
     std::string toString() const;
 };
@@ -557,7 +560,6 @@ inline double CoordinateXYZM::get<Ordinate::M>() const
     return m;
 }
 
-
 GEOS_DLL std::ostream& operator<< (std::ostream& os, const CoordinateXY& c);
 GEOS_DLL std::ostream& operator<< (std::ostream& os, const Coordinate& c);
 GEOS_DLL std::ostream& operator<< (std::ostream& os, const CoordinateXYM& c);
diff --git a/include/geos/geom/LineSegment.h b/include/geos/geom/LineSegment.h
index 4b0238c62..025e11ba9 100644
--- a/include/geos/geom/LineSegment.h
+++ b/include/geos/geom/LineSegment.h
@@ -31,6 +31,7 @@
 #include <functional> // for std::hash
 #include <memory> // for unique_ptr
 #include <cassert>
+#include <unordered_set>
 
 // Forward declarations
 namespace geos {
@@ -60,17 +61,10 @@ namespace geom { // geos::geom
 class GEOS_DLL LineSegment {
 public:
 
+
     Coordinate p0; /// Segment start
     Coordinate p1; /// Segment end
 
-    friend std::ostream& operator<< (std::ostream& o, const LineSegment& l);
-
-    /// Checks if two LineSegment are equal (2D only check)
-    friend bool operator==(const LineSegment& a, const LineSegment& b)
-    {
-        return a.p0 == b.p0 && a.p1 == b.p1;
-    };
-
     LineSegment(const Coordinate& c0, const Coordinate& c1)
         : p0(c0)
         , p1(c1)
@@ -406,19 +400,6 @@ public:
     ///
     void closestPoint(const CoordinateXY& p, CoordinateXY& ret) const;
 
-    /** \brief
-     * Compares this object with the specified object for order.
-     *
-     * Uses the standard lexicographic ordering for the points in the LineSegment.
-     *
-     * @param  other  the LineSegment with which this LineSegment
-     *            is being compared
-     * @return a negative integer, zero, or a positive integer as this
-     *         LineSegment is less than, equal to, or greater than the
-     *         specified LineSegment
-     */
-    int compareTo(const LineSegment& other) const;
-
     /** \brief
      *  Returns <code>true</code> if <code>other</code> is
      *  topologically equal to this LineSegment (e.g. irrespective
@@ -484,15 +465,50 @@ public:
      */
     std::unique_ptr<LineString> toGeometry(const GeometryFactory& gf) const;
 
+
+    /** \brief
+     * Compares this object with the specified object for order.
+     *
+     * Uses the standard lexicographic ordering for the points in the LineSegment.
+     *
+     * @param  other  the LineSegment with which this LineSegment
+     *            is being compared
+     * @return a negative integer, zero, or a positive integer as this
+     *         LineSegment is less than, equal to, or greater than the
+     *         specified LineSegment
+     */
+    inline int compareTo(const LineSegment& other) const
+    {
+        int comp0 = p0.compareTo(other.p0);
+        if (comp0 != 0) {
+            return comp0;
+        }
+        return p1.compareTo(other.p1);
+    }
+
+    std::ostream& operator<< (std::ostream& o);
+
+    inline bool operator==(const LineSegment& rhs) const {
+        return compareTo(rhs) == 0;
+    };
+
+    inline bool operator<(const LineSegment& rhs) const {
+        return compareTo(rhs) < 0;
+    };
+
+    inline bool operator>(const LineSegment& rhs) const {
+        return compareTo(rhs) > 0;
+    };
+
     struct HashCode {
-        std::size_t operator()(const LineSegment & s) const {
+        inline std::size_t operator()(const LineSegment & s) const {
             std::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::size_t operator()(const LineSegment * s) const {
+        inline std::size_t operator()(const LineSegment * s) const {
             std::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);
@@ -501,13 +517,16 @@ public:
 
     };
 
+    using UnorderedSet = std::unordered_set<LineSegment, LineSegment::HashCode>;
+
+
 private:
     void project(double factor, CoordinateXY& ret) const;
 
 };
 
-// std::ostream& operator<< (std::ostream& o, const LineSegment& l);
 
+// std::ostream& operator<< (std::ostream& o, const LineSegment& l);
 
 
 } // namespace geos::geom
diff --git a/include/geos/operation/buffer/OffsetCurve.h b/include/geos/operation/buffer/OffsetCurve.h
index b9c3bf942..299cbdd8c 100644
--- a/include/geos/operation/buffer/OffsetCurve.h
+++ b/include/geos/operation/buffer/OffsetCurve.h
@@ -18,6 +18,7 @@
 
 #include <geos/operation/buffer/BufferParameters.h>
 #include <geos/geom/GeometryFactory.h>
+#include <geos/constants.h>
 
 // Forward declarations
 namespace geos {
@@ -188,7 +189,6 @@ public:
 
     // Constants
     static constexpr int MATCH_DISTANCE_FACTOR = 10000;
-    static constexpr std::size_t UNKNOWN_INDEX = std::numeric_limits<std::size_t>::max();
 
     /**
     * Creates a new instance for computing an offset curve for a geometry at a given distance.
diff --git a/include/geos/operation/union/CoverageUnion.h b/include/geos/operation/union/CoverageUnion.h
index b6dad3cad..4b29b8e22 100644
--- a/include/geos/operation/union/CoverageUnion.h
+++ b/include/geos/operation/union/CoverageUnion.h
@@ -18,7 +18,6 @@
 #include <geos/geom/Geometry.h>
 
 #include <memory>
-#include <unordered_set>
 
 namespace geos {
     namespace geom {
@@ -29,26 +28,35 @@ namespace geos {
     }
 }
 
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::Polygon;
+using geos::geom::LineString;
+using geos::geom::LinearRing;
+using geos::geom::LineSegment;
+
+
 namespace geos {
 namespace operation {
 namespace geounion {
 
     class GEOS_DLL CoverageUnion {
     public:
-        static std::unique_ptr<geom::Geometry> Union(const geom::Geometry* geom);
+        static std::unique_ptr<Geometry> Union(const Geometry* geom);
 
     private:
         CoverageUnion() = default;
 
-        void extractRings(const geom::Polygon* geom);
-        void extractRings(const geom::Geometry* geom);
-        void extractSegments(const geom::LineString* geom);
+        void extractRings(const Polygon* geom);
+        void extractRings(const Geometry* geom);
+        void extractSegments(const LineString* geom);
         void sortRings();
 
-        std::unique_ptr<geom::Geometry> polygonize(const geom::GeometryFactory* gf);
+        std::unique_ptr<Geometry> polygonize(const GeometryFactory* gf);
 
-        std::vector<const geom::LinearRing*> rings;
-        std::unordered_set<geos::geom::LineSegment, geos::geom::LineSegment::HashCode> segments;
+        std::vector<const LinearRing*> rings;
+        // std::unordered_set<geos::geom::LineSegment, geos::geom::LineSegment::HashCode> segments;
+        LineSegment::UnorderedSet segments;
         static constexpr double AREA_PCT_DIFF_TOL = 1e-6;
     };
 
diff --git a/include/geos/simplify/LinkedLine.h b/include/geos/simplify/LinkedLine.h
new file mode 100644
index 000000000..7d78d59fe
--- /dev/null
+++ b/include/geos/simplify/LinkedLine.h
@@ -0,0 +1,84 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2021 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+// #include <geos/geom/Coordinate.h>
+
+#include <geos/export.h>
+
+#include <vector>
+#include <memory>
+
+
+namespace geos {
+namespace geom {
+class Coordinate;
+class CoordinateSequence;
+}
+}
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+
+
+namespace geos {
+namespace simplify { // geos::simplify
+
+class LinkedLine
+{
+
+public:
+
+    LinkedLine(const CoordinateSequence& pts);
+
+    bool isRing() const;
+    bool isCorner(std::size_t i) const;
+
+    std::size_t size() const;
+    std::size_t next(std::size_t i) const;
+    std::size_t prev(std::size_t i) const;
+
+    const Coordinate& getCoordinate(std::size_t index) const;
+    const Coordinate& prevCoordinate(std::size_t index) const;
+    const Coordinate& nextCoordinate(std::size_t index) const;
+
+    bool hasCoordinate(std::size_t index) const;
+
+    void remove(std::size_t index);
+
+    std::unique_ptr<CoordinateSequence> getCoordinates() const;
+
+
+private:
+
+    // Members
+    const CoordinateSequence&  m_coord;
+    bool m_isRing;
+    std::size_t m_size;
+    std::vector<std::size_t> m_next;
+    std::vector<std::size_t> m_prev;
+
+    void createNextLinks(std::size_t size);
+
+    void createPrevLinks(std::size_t size);
+
+
+}; // LinkedLine
+
+GEOS_DLL std::ostream& operator<< (std::ostream& os, const LinkedLine& ll);
+
+
+} // geos::simplify
+} // geos
diff --git a/include/geos/simplify/LinkedRing.h b/include/geos/simplify/LinkedRing.h
index eec1854db..5df7eb98c 100644
--- a/include/geos/simplify/LinkedRing.h
+++ b/include/geos/simplify/LinkedRing.h
@@ -18,6 +18,7 @@
 
 #include <geos/geom/Coordinate.h>
 #include <geos/geom/CoordinateSequence.h>
+#include <geos/constants.h>
 
 using geos::geom::Coordinate;
 using geos::geom::CoordinateSequence;
@@ -31,9 +32,6 @@ class LinkedRing
 {
     private:
 
-        static constexpr std::size_t
-            NO_COORD_INDEX = std::numeric_limits<std::size_t>::max();
-
         const CoordinateSequence& m_coord;
         std::size_t m_size;
         std::vector<std::size_t> m_next;
diff --git a/include/geos/simplify/RingHull.h b/include/geos/simplify/RingHull.h
index abee9cd6e..fd8a0443b 100644
--- a/include/geos/simplify/RingHull.h
+++ b/include/geos/simplify/RingHull.h
@@ -97,26 +97,25 @@ private:
             , area(p_area)
             {};
 
-        /**
-        * Orders corners by increasing area
-        * Used in std::priority_queue which
-        * ordinarily returns largest element.
-        * We invert sense of <> here in order to get
-        * smallest element first.
-        */
-        bool operator< (const Corner& rhs) const
-        {
-            return area > rhs.area;
+        inline int compareTo(const Corner& rhs) const {
+            if (area == rhs.getArea()) {
+                if (index == rhs.getIndex())
+                    return 0;
+                else return index < rhs.getIndex() ? -1 : 1;
+            }
+            else return area < rhs.getArea() ? -1 : 1;
+        }
+
+        inline bool operator< (const Corner& rhs) const {
+            return compareTo(rhs) < 0;
         };
 
-        bool operator> (const Corner& rhs) const
-        {
-            return area < rhs.area;
+        inline bool operator> (const Corner& rhs) const {
+            return compareTo(rhs) > 0;
         };
 
-        bool operator==(const Corner& rhs) const
-        {
-            return area == rhs.area;
+        inline bool operator==(const Corner& rhs) const {
+            return compareTo(rhs) == 0;
         };
 
         bool isVertex(std::size_t p_index) const;
@@ -127,6 +126,14 @@ private:
         bool isRemoved(const LinkedRing& ring) const;
         std::unique_ptr<LineString> toLineString(const LinkedRing& ring);
 
+        struct Greater {
+            inline bool operator()(const Corner & a, const Corner & b) const {
+                return a > b;
+            }
+        };
+
+        using PriorityQueue = std::priority_queue<Corner, std::vector<Corner>, Corner::Greater>;
+
     }; // class Corner
 
 
@@ -151,11 +158,11 @@ private:
     */
     std::unique_ptr<VertexSequencePackedRtree> vertexIndex;
 
-    std::priority_queue<Corner> cornerQueue;
+    Corner::PriorityQueue cornerQueue;
 
 
     void init(CoordinateSequence& ring, bool isOuter);
-    void addCorner(std::size_t i, std::priority_queue<Corner>& cornerQueue);
+    void addCorner(std::size_t i, Corner::PriorityQueue& cornerQueue);
     bool isAtTarget(const Corner& corner);
 
     /**
@@ -167,7 +174,7 @@ private:
     * @param corner the corner to remove
     * @param cornerQueue the corner queue
     */
-    void removeCorner(const Corner& corner, std::priority_queue<Corner>& cornerQueue);
+    void removeCorner(const Corner& corner, Corner::PriorityQueue& cornerQueue);
     bool isRemovable(const Corner& corner, const RingHullIndex& hullIndex) const;
 
     /**
diff --git a/include/geos/triangulate/polygon/PolygonEarClipper.h b/include/geos/triangulate/polygon/PolygonEarClipper.h
index 9484c0f1a..561621661 100644
--- a/include/geos/triangulate/polygon/PolygonEarClipper.h
+++ b/include/geos/triangulate/polygon/PolygonEarClipper.h
@@ -17,6 +17,7 @@
 #include <geos/index/VertexSequencePackedRtree.h>
 #include <geos/triangulate/tri/TriList.h>
 #include <geos/triangulate/tri/Tri.h>
+#include <geos/constants.h>
 
 #include <array>
 #include <memory>
@@ -69,8 +70,6 @@ private:
 
     // Members
 
-    static constexpr std::size_t NO_VERTEX_INDEX = std::numeric_limits<std::size_t>::max();
-
     bool isFlatCornersSkipped = false;
 
     /**
@@ -110,7 +109,7 @@ private:
     *
     * @param cornerIndex the index of the corner apex vertex
     * @param corner the corner vertices
-    * @return the index of an intersecting or duplicate vertex, or {@link #NO_VERTEX_INDEX} if none
+    * @return the index of an intersecting or duplicate vertex, or NO_COORD_INDEX if none
     */
     std::size_t findIntersectingVertex(std::size_t cornerIndex, const std::array<Coordinate, 3>& corner) const;
 
diff --git a/include/geos/triangulate/polygon/PolygonHoleJoiner.h b/include/geos/triangulate/polygon/PolygonHoleJoiner.h
index 38f439103..665b16126 100644
--- a/include/geos/triangulate/polygon/PolygonHoleJoiner.h
+++ b/include/geos/triangulate/polygon/PolygonHoleJoiner.h
@@ -20,6 +20,7 @@
 #include <geos/noding/SegmentIntersector.h>
 #include <geos/noding/BasicSegmentString.h>
 #include <geos/noding/SegmentSetMutualIntersector.h>
+#include <geos/constants.h>
 
 #include <set>
 #include <limits>
@@ -71,8 +72,6 @@ private:
 
     // Members
 
-    static constexpr std::size_t NO_INDEX = std::numeric_limits<std::size_t>::max();
-
     const Polygon* inputPolygon;
 
     //-- normalized, sorted and noded polygon rings
diff --git a/src/coverage/Corner.cpp b/src/coverage/Corner.cpp
new file mode 100644
index 000000000..743681e7b
--- /dev/null
+++ b/src/coverage/Corner.cpp
@@ -0,0 +1,164 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2022 Martin Davis.
+ *
+ * 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/coverage/Corner.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/Envelope.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/Triangle.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/simplify/LinkedLine.h>
+#include <geos/constants.h>
+
+using geos::coverage::Corner;
+using geos::geom::Coordinate;
+using geos::geom::Envelope;
+using geos::geom::GeometryFactory;
+using geos::geom::Triangle;
+using geos::io::WKTWriter;
+
+namespace geos {
+namespace coverage { // geos.coverage
+
+
+Corner::Corner(const LinkedLine* edge, std::size_t i)
+    : m_edge(edge)
+    , m_index(i)
+    , m_prev(edge->prev(i))
+    , m_next(edge->next(i))
+    , m_area(area(*edge, i))
+    {}
+
+/* public */
+bool
+Corner::isVertex(std::size_t index) const
+{
+    return index == m_index
+        || index == m_prev
+        || index == m_next;
+}
+
+/* public */
+const Coordinate&
+Corner::prev() const
+{
+    return m_edge->getCoordinate(m_prev);
+}
+
+/* public */
+const Coordinate&
+Corner::next() const
+{
+    return m_edge->getCoordinate(m_next);
+}
+
+/* private static */
+double
+Corner::area(const LinkedLine& edge, std::size_t index)
+{
+    const Coordinate& pp = edge.prevCoordinate(index);
+    const Coordinate& p = edge.getCoordinate(index);
+    const Coordinate& pn = edge.nextCoordinate(index);
+    return Triangle::area(pp, p, pn);
+}
+
+
+/* public */
+Envelope
+Corner::envelope() const
+{
+    const Coordinate& pp = m_edge->getCoordinate(m_prev);
+    const Coordinate& p = m_edge->getCoordinate(m_index);
+    const Coordinate& pn = m_edge->getCoordinate(m_next);
+    Envelope env(pp, pn);
+    env.expandToInclude(p);
+    return env;
+}
+
+/* public */
+bool
+Corner::isVertex(const Coordinate& v) const
+{
+    if (v.equals2D(m_edge->getCoordinate(m_prev))) return true;
+    if (v.equals2D(m_edge->getCoordinate(m_index))) return true;
+    if (v.equals2D(m_edge->getCoordinate(m_next))) return true;
+    return false;
+}
+
+/* public */
+bool
+Corner::isBaseline(const Coordinate& p0, const Coordinate& p1) const
+{
+    const Coordinate& l_prev = prev();
+    const Coordinate& l_next = next();
+    if (l_prev.equals2D( p0 ) && l_next.equals2D( p1 ))
+        return true;
+    if (l_prev.equals2D( p1 ) && l_next.equals2D( p0 ))
+        return true;
+    return false;
+}
+
+/* public */
+bool
+Corner::intersects(const Coordinate& v) const
+{
+    const Coordinate& pp = m_edge->getCoordinate(m_prev);
+    const Coordinate& p = m_edge->getCoordinate(m_index);
+    const Coordinate& pn = m_edge->getCoordinate(m_next);
+    return Triangle::intersects(pp, p, pn, v);
+}
+
+/* public */
+bool
+Corner::isRemoved() const
+{
+    return m_edge->prev(m_index) != m_prev
+        || m_edge->next(m_index) != m_next;
+}
+
+/* public */
+std::unique_ptr<LineString>
+Corner::toLineString() const
+{
+    Coordinate pp = m_edge->getCoordinate(m_prev);
+    Coordinate p = m_edge->getCoordinate(m_index);
+    Coordinate pn = m_edge->getCoordinate(m_next);
+
+    /* safeCoord replacement */
+    if (pp.isNull()) pp.x = pp.y = DoubleNotANumber;
+    if (p.isNull()) p.x = p.y = DoubleNotANumber;
+    if (pn.isNull()) pn.x = pn.y = DoubleNotANumber;
+
+    CoordinateSequence cs;
+    cs.add(pp); cs.add(p); cs.add(pn);
+
+    auto gf = GeometryFactory::create();
+    return gf->createLineString(std::move(cs));
+}
+
+
+std::ostream&
+operator<< (std::ostream& os, const Corner& corner)
+{
+    WKTWriter writer;
+    auto ls = corner.toLineString();
+    os << writer.write(*ls);
+    return os;
+}
+
+
+} // namespace geos.coverage
+} // namespace geos
diff --git a/src/coverage/CoverageBoundarySegmentFinder.cpp b/src/coverage/CoverageBoundarySegmentFinder.cpp
new file mode 100644
index 000000000..582188613
--- /dev/null
+++ b/src/coverage/CoverageBoundarySegmentFinder.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2022 Martin Davis.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
+ * and the Eclipse Distribution License is available at
+ *
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+
+#include <geos/coverage/CoverageBoundarySegmentFinder.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/Geometry.h>
+
+
+using geos::geom::CoordinateSequence;
+using geos::geom::Geometry;
+
+
+namespace geos {     // geos
+namespace coverage { // geos.coverage
+
+// public static
+LineSegment::UnorderedSet
+CoverageBoundarySegmentFinder::findBoundarySegments(
+    const std::vector<const Geometry*>& geoms)
+{
+    LineSegment::UnorderedSet segs;
+    CoverageBoundarySegmentFinder finder(segs);
+    for (const Geometry* geom : geoms) {
+        geom->apply_ro(finder);
+    }
+    return segs;
+}
+
+// private static
+LineSegment
+CoverageBoundarySegmentFinder::createSegment(
+    const CoordinateSequence& seq, std::size_t i)
+{
+    LineSegment seg(seq.getAt(i), seq.getAt(i + 1));
+    seg.normalize();
+    return seg;
+}
+
+
+// public
+void
+CoverageBoundarySegmentFinder::filter_ro(
+    const CoordinateSequence& seq, std::size_t i)
+{
+    //-- final point does not start a segment
+    if (i >= seq.size() - 1)
+        return;
+
+    LineSegment seg = createSegment(seq, i);
+
+
+
+    if (m_boundarySegs.find(seg) != m_boundarySegs.end()) {
+        m_boundarySegs.erase(seg);
+    }
+    else {
+        m_boundarySegs.insert(seg);
+    }
+}
+
+
+/* public static */
+bool
+CoverageBoundarySegmentFinder::isBoundarySegment(
+    const LineSegment::UnorderedSet& boundarySegs,
+    const CoordinateSequence* seq,
+    std::size_t i)
+{
+    LineSegment seg = CoverageBoundarySegmentFinder::createSegment(*seq, i);
+    return boundarySegs.find(seg) != boundarySegs.end();
+}
+
+
+
+
+
+} // namespace geos.coverage
+} // namespace geos
+
+
diff --git a/src/coverage/CoverageEdge.cpp b/src/coverage/CoverageEdge.cpp
new file mode 100644
index 000000000..f98ec5111
--- /dev/null
+++ b/src/coverage/CoverageEdge.cpp
@@ -0,0 +1,177 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2022 Martin Davis.
+ *
+ * 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/coverage/CoverageEdge.h>
+
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/LinearRing.h>
+#include <geos/geom/LineSegment.h>
+#include <geos/util/IllegalStateException.h>
+
+
+using geos::geom::Coordinate;
+using geos::geom::GeometryFactory;
+using geos::geom::LinearRing;
+using geos::geom::LineSegment;
+
+
+namespace geos {     // geos
+namespace coverage { // geos.coverage
+
+/* public static */
+std::unique_ptr<CoverageEdge>
+CoverageEdge::createEdge(const LinearRing* ring)
+{
+    auto pts = extractEdgePoints(ring, 0, ring->getNumPoints() - 1);
+    return detail::make_unique<CoverageEdge>(std::move(pts), true);
+}
+
+/* public static */
+std::unique_ptr<CoverageEdge>
+CoverageEdge::createEdge(const LinearRing* ring,
+    std::size_t start, std::size_t end)
+{
+    auto pts = extractEdgePoints(ring, start, end);
+    return detail::make_unique<CoverageEdge>(std::move(pts), false);
+}
+
+/* public static */
+std::unique_ptr<MultiLineString>
+CoverageEdge::createLines(
+    const std::vector<CoverageEdge*>& edges,
+    const GeometryFactory* geomFactory)
+{
+    std::vector<std::unique_ptr<LineString>> lines;
+    for (const CoverageEdge* edge : edges) {
+        auto cs = edge->getCoordinates()->clone();
+        auto ls = geomFactory->createLineString(std::move(cs));
+        lines.push_back(std::move(ls));
+    }
+    return geomFactory->createMultiLineString(std::move(lines));
+}
+
+/* public */
+std::unique_ptr<LineString>
+CoverageEdge::toLineString(const GeometryFactory* geomFactory)
+{
+    const CoordinateSequence* cs = getCoordinates();
+    return geomFactory->createLineString(cs->clone());
+}
+
+/* private static */
+std::unique_ptr<CoordinateSequence>
+CoverageEdge::extractEdgePoints(const LinearRing* ring,
+    std::size_t start, std::size_t end)
+{
+    auto pts = detail::make_unique<CoordinateSequence>();
+    std::size_t size = start < end
+                  ? end - start + 1
+                  : ring->getNumPoints() - start + end;
+    std::size_t iring = start;
+    auto cs = ring->getCoordinatesRO();
+    for (std::size_t i = 0; i < size; i++) {
+        pts->add(cs->getAt(iring));
+        iring += 1;
+        if (iring >= ring->getNumPoints())
+            iring = 1;
+    }
+    return pts;
+}
+
+
+/* public static */
+LineSegment
+CoverageEdge::key(const LinearRing* ring)
+{
+    const CoordinateSequence* pts = ring->getCoordinatesRO();
+    // find lowest vertex index
+    std::size_t indexLow = 0;
+    for (std::size_t i = 1; i < pts->size() - 1; i++) {
+        if (pts->getAt(indexLow).compareTo(pts->getAt(i)) < 0)
+            indexLow = i;
+    }
+    const Coordinate& key0 = pts->getAt(indexLow);
+    // find distinct adjacent vertices
+    const Coordinate& adj0 = findDistinctPoint(pts, indexLow, true, key0);
+    const Coordinate& adj1 = findDistinctPoint(pts, indexLow, false, key0);
+    const Coordinate& key1 = adj0.compareTo(adj1) < 0 ? adj0 : adj1;
+    return LineSegment(key0, key1);
+}
+
+
+/* public static */
+LineSegment
+CoverageEdge::key(const LinearRing* ring,
+    std::size_t start, std::size_t end)
+{
+    const CoordinateSequence* pts = ring->getCoordinatesRO();
+    //-- endpoints are distinct in a line edge
+    const Coordinate& end0 = pts->getAt(start);
+    const Coordinate& end1 = pts->getAt(end);
+    bool isForward = 0 > end0.compareTo(end1);
+    const Coordinate* key0;
+    const Coordinate* key1;
+    if (isForward) {
+        key0 = &end0;
+        key1 = &findDistinctPoint(pts, start, true, *key0);
+    }
+    else {
+        key0 = &end1;
+        key1 = &findDistinctPoint(pts, end, false, *key0);
+    }
+    return LineSegment(*key0, *key1);
+}
+
+/* private static */
+const Coordinate&
+CoverageEdge::findDistinctPoint(
+    const CoordinateSequence* pts,
+    std::size_t index,
+    bool isForward,
+    const Coordinate& pt)
+{
+    std::size_t i = index;
+    std::size_t endIndex = pts->size()-1;
+    do {
+        if (! pts->getAt(i).equals2D(pt)) {
+            return pts->getAt(i);
+        }
+        // increment index with wrapping
+        if (isForward) {
+            i = (i == endIndex) ? 0 : (i+1);
+        }
+        else {
+            i = (i == 0) ? endIndex : (i-1);
+        }
+
+    } while (i != index);
+    throw util::IllegalStateException("Edge does not contain distinct points");
+}
+
+
+
+// /* public */
+// std::string toString()
+// {
+//     return WKTWriter::toLineString(pts);
+// }
+
+
+
+} // namespace geos.coverage
+} // namespace geos
+
+
diff --git a/src/coverage/CoverageRingEdges.cpp b/src/coverage/CoverageRingEdges.cpp
new file mode 100644
index 000000000..dea6f350e
--- /dev/null
+++ b/src/coverage/CoverageRingEdges.cpp
@@ -0,0 +1,408 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2023 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2023 Martin Davis.
+ *
+ * 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 <map>
+
+#include <geos/coverage/CoverageBoundarySegmentFinder.h>
+#include <geos/coverage/CoverageEdge.h>
+#include <geos/coverage/CoverageRingEdges.h>
+#include <geos/coverage/VertexCounter.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/LineSegment.h>
+#include <geos/geom/LinearRing.h>
+#include <geos/geom/Polygon.h>
+#include <geos/geom/MultiPolygon.h>
+#include <geos/util/IllegalStateException.h>
+#include <geos/constants.h>
+
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::LineSegment;
+using geos::geom::LinearRing;
+using geos::geom::Polygon;
+using geos::geom::MultiPolygon;
+
+
+namespace geos {     // geos
+namespace coverage { // geos.coverage
+
+
+/* public */
+std::vector<CoverageEdge*>
+CoverageRingEdges::selectEdges(std::size_t ringCount) const
+{
+    std::vector<CoverageEdge*> result;
+    for (CoverageEdge* edge : m_edges) {
+        if (edge->getRingCount() == ringCount) {
+            result.push_back(edge);
+        }
+    }
+    return result;
+}
+
+
+/* private */
+void
+CoverageRingEdges::build()
+{
+    Coordinate::UnorderedSet nodes = findNodes(m_coverage);
+    LineSegment::UnorderedSet boundarySegs = CoverageBoundarySegmentFinder::findBoundarySegments(m_coverage);
+    Coordinate::UnorderedSet boundaryNodes = findBoundaryNodes(boundarySegs);
+    nodes.insert(boundaryNodes.begin(), boundaryNodes.end());
+
+    std::map<LineSegment, CoverageEdge*> uniqueEdgeMap;
+    for (const Geometry* geom : m_coverage) {
+        for (std::size_t ipoly = 0; ipoly < geom->getNumGeometries(); ipoly++) {
+            const Polygon* poly = static_cast<const Polygon*>(geom->getGeometryN(ipoly));
+            //-- extract shell
+            const LinearRing* shell = poly->getExteriorRing();
+            addRingEdges(shell, nodes, boundarySegs, uniqueEdgeMap);
+            //-- extract holes
+            for (std::size_t ihole = 0; ihole < poly->getNumInteriorRing(); ihole++) {
+                const LinearRing* hole = poly->getInteriorRingN(ihole);
+                addRingEdges(hole, nodes, boundarySegs, uniqueEdgeMap);
+            }
+        }
+    }
+}
+
+/* private */
+void
+CoverageRingEdges::addRingEdges(
+    const LinearRing* ring,
+    Coordinate::UnorderedSet& nodes,
+    LineSegment::UnorderedSet& boundarySegs,
+    std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap)
+{
+    addBoundaryNodes(ring, boundarySegs, nodes);
+    std::vector<CoverageEdge*> ringEdges = extractRingEdges(ring, uniqueEdgeMap, nodes);
+    m_ringEdgesMap[ring] = ringEdges;
+}
+
+/* private */
+void
+CoverageRingEdges::addBoundaryNodes(
+    const LinearRing* ring,
+    LineSegment::UnorderedSet& boundarySegs,
+    Coordinate::UnorderedSet& nodes)
+{
+    const CoordinateSequence* seq = ring->getCoordinatesRO();
+    bool isBdyLast = CoverageBoundarySegmentFinder::isBoundarySegment(boundarySegs, seq, seq->size() - 2);
+    bool isBdyPrev = isBdyLast;
+    for (std::size_t i = 0; i < seq->size() - 1; i++) {
+        bool isBdy = CoverageBoundarySegmentFinder::isBoundarySegment(boundarySegs, seq, i);
+        if (isBdy != isBdyPrev) {
+            const Coordinate& nodePt = seq->getAt(i);
+            nodes.insert(nodePt);
+        }
+        isBdyPrev = isBdy;
+    }
+}
+
+/* private */
+std::vector<CoverageEdge*>
+CoverageRingEdges::extractRingEdges(
+    const LinearRing* ring,
+    std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap,
+    Coordinate::UnorderedSet& nodes)
+{
+    std::vector<CoverageEdge*> ringEdges;
+    std::size_t first = findNextNodeIndex(ring, NO_COORD_INDEX, nodes);
+    if (first == NO_COORD_INDEX) {
+        //-- ring does not contain a node, so edge is entire ring
+        CoverageEdge* edge = createEdge(ring, uniqueEdgeMap);
+        ringEdges.push_back(edge);
+    }
+    else {
+        std::size_t start = first;
+        std::size_t end = start;
+        do {
+            end = findNextNodeIndex(ring, start, nodes);
+            CoverageEdge* edge = createEdge(ring, start, end, uniqueEdgeMap);
+            ringEdges.push_back(edge);
+            start = end;
+        } while (end != first);
+    }
+    return ringEdges;
+}
+
+/* private */
+CoverageEdge*
+CoverageRingEdges::createEdge(
+    const LinearRing* ring,
+    std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap)
+{
+    CoverageEdge* edge;
+    LineSegment edgeKey = CoverageEdge::key(ring);
+    auto result = uniqueEdgeMap.find(edgeKey);
+    if (result != uniqueEdgeMap.end()) {
+        edge = result->second;
+    }
+    // if (uniqueEdgeMap.containsKey(edgeKey)) {
+    //   edge = uniqueEdgeMap.get(edgeKey);
+    // }
+    else {
+        std::unique_ptr<CoverageEdge> edge_ptr = CoverageEdge::createEdge(ring);
+        edge = edge_ptr.release();
+        m_edgeStore.emplace_back(edge);
+        m_edges.push_back(edge);
+        uniqueEdgeMap[edgeKey] = edge;
+    }
+    edge->incRingCount();
+    return edge;
+}
+
+/* private */
+CoverageEdge*
+CoverageRingEdges::createEdge(
+    const LinearRing* ring,
+    std::size_t start, std::size_t end,
+    std::map<LineSegment, CoverageEdge*>& uniqueEdgeMap)
+{
+    CoverageEdge* edge;
+    LineSegment edgeKey = (end == start) ? CoverageEdge::key(ring) : CoverageEdge::key(ring, start, end);
+    // if (uniqueEdgeMap.containsKey(edgeKey)) {
+    //     edge = uniqueEdgeMap.get(edgeKey);
+    // }
+    auto result = uniqueEdgeMap.find(edgeKey);
+    if (result != uniqueEdgeMap.end()) {
+        edge = result->second;
+    }
+    else {
+        std::unique_ptr<CoverageEdge> edge_ptr = CoverageEdge::createEdge(ring, start, end);
+        edge = edge_ptr.release();
+        m_edgeStore.emplace_back(edge);
+        m_edges.push_back(edge);
+        uniqueEdgeMap[edgeKey] = edge;
+    }
+    edge->incRingCount();
+    return edge;
+}
+
+/* private */
+std::size_t
+CoverageRingEdges::findNextNodeIndex(
+    const LinearRing* ring,
+    std::size_t start,
+    Coordinate::UnorderedSet& nodes) const
+{
+    std::size_t index = start;
+    bool isScanned0 = false;
+    do {
+        index = next(index, ring);
+        if (index == 0) {
+            if (start == NO_COORD_INDEX && isScanned0) {
+                return NO_COORD_INDEX;
+            }
+            isScanned0 = true;
+        }
+        const Coordinate& pt = ring->getCoordinatesRO()->getAt(index);
+        if (nodes.find(pt) != nodes.end()) {
+            return index;
+        }
+    } while (index != start);
+    return NO_COORD_INDEX;
+}
+
+/* private static */
+std::size_t
+CoverageRingEdges::next(std::size_t index, const LinearRing* ring)
+{
+    if (index == NO_COORD_INDEX) return 0;
+    index = index + 1;
+    if (index >= ring->getNumPoints() - 1)
+        index = 0;
+    return index;
+}
+
+
+/* private */
+Coordinate::UnorderedSet
+CoverageRingEdges::findNodes(std::vector<const Geometry*>& coverage)
+{
+    std::map<Coordinate, std::size_t> vertexCount;
+    VertexCounter::count(coverage, vertexCount);
+    Coordinate::UnorderedSet nodes;
+    // for (Coordinate v : vertexCount.keySet()) {
+    //     if (vertexCount.get(v) > 2) {
+    //         nodes.add(v);
+    //     }
+    // }
+    for (const auto &mapPair : vertexCount) {
+        const Coordinate& v = mapPair.first;
+        std::size_t count = mapPair.second;
+        if (count > 2)
+            nodes.insert(v);
+    }
+    return nodes;
+}
+
+
+/* private */
+Coordinate::UnorderedSet
+CoverageRingEdges::findBoundaryNodes(LineSegment::UnorderedSet& lineSegments)
+{
+    std::map<Coordinate, std::size_t> counter;
+    for (const LineSegment& line : lineSegments) {
+        // counter.put(line.p0, counter.getOrDefault(line.p0, 0) + 1);
+        // counter.put(line.p1, counter.getOrDefault(line.p1, 0) + 1);
+        auto search0 = counter.find(line.p0);
+        if (search0 != counter.end()) {
+            counter[line.p0] = search0->second + 1;
+        }
+        else {
+            counter[line.p0] = 0;
+        }
+
+        auto search1 = counter.find(line.p1);
+        if (search1 != counter.end()) {
+            counter[line.p1] = search1->second + 1;
+        }
+        else {
+            counter[line.p1] = 0;
+        }
+    }
+
+    Coordinate::UnorderedSet nodes;
+    for (const auto& c : counter) {
+        const Coordinate& v = c.first;
+        std::size_t count = c.second;
+        if (count > 2)
+            nodes.insert(v);
+    }
+    return nodes;
+
+    // return counter.entrySet().stream()
+    //     .filter(e->e.getValue()>2)
+    //     .map(Map.Entry::getKey).collect(Collectors.toSet());
+}
+
+
+/* public */
+std::vector<std::unique_ptr<Geometry>>
+CoverageRingEdges::buildCoverage() const
+{
+    std::vector<std::unique_ptr<Geometry>> result;
+    for (const Geometry* geom : m_coverage) {
+        result.push_back(buildPolygonal(geom));
+    }
+    return result;
+}
+
+/* private */
+std::unique_ptr<Geometry>
+CoverageRingEdges::buildPolygonal(const Geometry* geom) const
+{
+    if (geom->getGeometryTypeId() == geom::GEOS_MULTIPOLYGON) {
+        return buildMultiPolygon(static_cast<const MultiPolygon*>(geom));
+    }
+    else {
+        return buildPolygon(static_cast<const Polygon*>(geom));
+    }
+}
+
+/* private */
+std::unique_ptr<Geometry>
+CoverageRingEdges::buildMultiPolygon(const MultiPolygon* geom) const
+{
+    // Polygon[] polys = new Polygon[geom.getNumGeometries()];
+    std::vector<std::unique_ptr<Polygon>> polys;
+    for (std::size_t i = 0; i < geom->getNumGeometries(); i++) {
+        const Polygon* poly = static_cast<const Polygon*>(geom->getGeometryN(i));
+        polys.push_back(buildPolygon(poly));
+    }
+    return geom->getFactory()->createMultiPolygon(std::move(polys));
+}
+
+/* private */
+std::unique_ptr<Polygon>
+CoverageRingEdges::buildPolygon(const Polygon* polygon) const
+{
+    std::size_t numRings = polygon->getNumInteriorRing();
+    std::unique_ptr<LinearRing> shell = buildRing(polygon->getExteriorRing());
+    if (numRings == 0) {
+        return polygon->getFactory()->createPolygon(std::move(shell));
+    }
+    std::vector<std::unique_ptr<LinearRing>> holes;
+    for (std::size_t i = 0; i < numRings; i++) {
+        const LinearRing* hole = polygon->getInteriorRingN(i);
+        auto newHole = buildRing(hole);
+        holes.emplace_back(newHole.release());
+    }
+    return polygon->getFactory()->createPolygon(std::move(shell), std::move(holes));
+}
+
+
+/* private */
+std::unique_ptr<LinearRing>
+CoverageRingEdges::buildRing(const LinearRing* ring) const
+{
+    const std::vector<CoverageEdge*>* ringEdges;
+
+    // List<CoverageEdge> ringEdges = m_ringEdgesMap.get(ring);
+    auto result = m_ringEdgesMap.find(ring);
+    if (result == m_ringEdgesMap.end()) {
+        // return nullptr;
+        throw util::IllegalStateException("buildRing");
+    }
+    else {
+        ringEdges = &(result->second);
+    }
+
+    // CoordinateList ptsList = new CoordinateList();
+    std::unique_ptr<CoordinateSequence> pts(new CoordinateSequence());
+    Coordinate nullPt = Coordinate::getNull();
+    for (std::size_t i = 0; i < ringEdges->size(); i++) {
+        Coordinate& lastPt = pts->isEmpty() ? nullPt : pts->back();
+        bool dir = isEdgeDirForward(*ringEdges, i, lastPt);
+        const CoordinateSequence* ringCs = ringEdges->at(i)->getCoordinates();
+        pts->add(*ringCs, false, dir);
+    }
+    return ring->getFactory()->createLinearRing(std::move(pts));
+}
+
+/* private */
+bool
+CoverageRingEdges::isEdgeDirForward(
+    const std::vector<CoverageEdge*>& ringEdges,
+    std::size_t index,
+    const Coordinate& prevPt) const
+{
+    std::size_t size = ringEdges.size();
+    if (size <= 1) return true;
+    if (index == 0) {
+        //-- if only 2 edges, first one can keep orientation
+        if (size == 2)
+            return true;
+        const Coordinate& endPt0 = ringEdges[0]->getEndCoordinate();
+        return endPt0.equals2D(ringEdges[1]->getStartCoordinate())
+            || endPt0.equals2D(ringEdges[1]->getEndCoordinate());
+    }
+    //-- previous point determines required orientation
+    return prevPt.equals2D(ringEdges[index]->getStartCoordinate());
+}
+
+
+
+} // namespace geos.coverage
+} // namespace geos
+
+
diff --git a/src/coverage/CoverageSimplifier.cpp b/src/coverage/CoverageSimplifier.cpp
new file mode 100644
index 000000000..cd4b7d164
--- /dev/null
+++ b/src/coverage/CoverageSimplifier.cpp
@@ -0,0 +1,151 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2023 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2023 Martin Davis.
+ *
+ * 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/coverage/CoverageSimplifier.h>
+#include <geos/coverage/CoverageEdge.h>
+#include <geos/coverage/CoverageRingEdges.h>
+#include <geos/coverage/TPVWSimplifier.h>
+#include <geos/coverage/VertexCounter.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/MultiLineString.h>
+
+
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::MultiLineString;
+
+
+namespace geos {     // geos
+namespace coverage { // geos.coverage
+
+/* public static */
+std::vector<std::unique_ptr<Geometry>>
+CoverageSimplifier::simplify(
+    std::vector<const Geometry*>& coverage,
+    double tolerance)
+{
+    CoverageSimplifier simplifier(coverage);
+    return simplifier.simplify(tolerance);
+}
+
+/* public static */
+std::vector<std::unique_ptr<Geometry>>
+CoverageSimplifier::simplify(
+    const std::vector<std::unique_ptr<Geometry>>& coverage,
+    double tolerance)
+{
+    std::vector<const Geometry*> geoms;
+    for (auto& geom : coverage) {
+        geoms.push_back(geom.get());
+    }
+    return simplify(geoms, tolerance);
+}
+
+
+/* public static */
+std::vector<std::unique_ptr<Geometry>>
+CoverageSimplifier::simplifyInner(
+    std::vector<const Geometry*>& coverage,
+    double tolerance)
+{
+    CoverageSimplifier simplifier(coverage);
+    return simplifier.simplifyInner(tolerance);
+}
+
+
+/* public static */
+std::vector<std::unique_ptr<Geometry>>
+CoverageSimplifier::simplifyInner(
+    const std::vector<std::unique_ptr<Geometry>>& coverage,
+    double tolerance)
+{
+    std::vector<const Geometry*> geoms;
+    for (auto& geom : coverage) {
+        geoms.push_back(geom.get());
+    }
+    return simplifyInner(geoms, tolerance);
+}
+
+
+/* public */
+CoverageSimplifier::CoverageSimplifier(std::vector<const Geometry*>& coverage)
+    : m_input(coverage)
+    , m_geomFactory(coverage.empty() ? nullptr : coverage[0]->getFactory())
+    {}
+
+/* public */
+std::vector<std::unique_ptr<Geometry>>
+CoverageSimplifier::simplify(double tolerance)
+{
+    CoverageRingEdges cov(m_input);
+    simplifyEdges(cov.getEdges(), nullptr, tolerance);
+    return cov.buildCoverage();
+}
+
+/* public */
+std::vector<std::unique_ptr<Geometry>>
+CoverageSimplifier::simplifyInner(double tolerance)
+{
+    CoverageRingEdges cov(m_input);
+    std::vector<CoverageEdge*> innerEdges = cov.selectEdges(2);
+    std::vector<CoverageEdge*> outerEdges = cov.selectEdges(1);
+    std::unique_ptr<MultiLineString> constraintEdges = CoverageEdge::createLines(outerEdges, m_geomFactory);
+
+    simplifyEdges(innerEdges, constraintEdges.get(), tolerance);
+    return cov.buildCoverage();
+}
+
+/* private */
+void
+CoverageSimplifier::simplifyEdges(
+    std::vector<CoverageEdge*> edges,
+    const MultiLineString* constraints,
+    double tolerance)
+{
+    std::unique_ptr<MultiLineString> lines = CoverageEdge::createLines(edges, m_geomFactory);
+    std::vector<bool> freeRings = getFreeRings(edges);
+    std::unique_ptr<MultiLineString> linesSimp = TPVWSimplifier::simplify(lines.get(), freeRings, constraints, tolerance);
+    //Assert: mlsSimp.getNumGeometries = edges.length
+
+    setCoordinates(edges, linesSimp.get());
+}
+
+
+/* private */
+void
+CoverageSimplifier::setCoordinates(std::vector<CoverageEdge*>& edges, const MultiLineString* lines)
+{
+    for (std::size_t i = 0; i < edges.size(); i++) {
+        CoverageEdge* edge = edges[i];
+        edge->setCoordinates(lines->getGeometryN(i)->getCoordinatesRO());
+    }
+}
+
+
+/* private */
+std::vector<bool>
+CoverageSimplifier::getFreeRings(const std::vector<CoverageEdge*>& edges) const
+{
+    std::vector<bool> freeRings;
+    for (auto edge: edges) {
+        freeRings.push_back(edge->isFreeRing());
+    }
+    return freeRings;
+}
+
+
+} // geos.coverage
+} // geos
diff --git a/src/coverage/TPVWSimplifier.cpp b/src/coverage/TPVWSimplifier.cpp
new file mode 100644
index 000000000..7d395cdf3
--- /dev/null
+++ b/src/coverage/TPVWSimplifier.cpp
@@ -0,0 +1,332 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2022 Martin Davis.
+ *
+ * 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/coverage/TPVWSimplifier.h>
+#include <geos/coverage/Corner.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/Envelope.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/LineString.h>
+#include <geos/geom/MultiLineString.h>
+
+#include <geos/simplify/LinkedLine.h>
+
+using geos::geom::Coordinate;
+using geos::geom::Envelope;
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::LineString;
+using geos::geom::MultiLineString;
+using geos::simplify::LinkedLine;
+
+
+namespace geos {
+namespace coverage { // geos.coverage
+
+
+typedef TPVWSimplifier::Edge Edge;
+typedef TPVWSimplifier::EdgeIndex EdgeIndex;
+
+
+/* public static */
+std::unique_ptr<MultiLineString>
+TPVWSimplifier::simplify(
+    const MultiLineString* lines,
+    double distanceTolerance)
+{
+    TPVWSimplifier simp(lines, distanceTolerance);
+    std::unique_ptr<MultiLineString> result = simp.simplify();
+    return result;
+}
+
+
+/* public static */
+std::unique_ptr<MultiLineString>
+TPVWSimplifier::simplify(
+    const MultiLineString* p_lines,
+    std::vector<bool>& p_freeRings,
+    const MultiLineString* p_constraintLines,
+    double distanceTolerance)
+{
+    TPVWSimplifier simp(p_lines, distanceTolerance);
+    simp.setFreeRingIndices(p_freeRings);
+    simp.setConstraints(p_constraintLines);
+    std::unique_ptr<MultiLineString> result = simp.simplify();
+    return result;
+}
+
+
+/* public */
+TPVWSimplifier::TPVWSimplifier(
+    const MultiLineString* lines,
+    double distanceTolerance)
+    : inputLines(lines)
+    , areaTolerance(distanceTolerance*distanceTolerance)
+    , geomFactory(inputLines->getFactory())
+    , constraintLines(nullptr)
+    {}
+
+
+/* private */
+void
+TPVWSimplifier::setConstraints(const MultiLineString* constraints)
+{
+    constraintLines = constraints;
+}
+
+/* public */
+void
+TPVWSimplifier::setFreeRingIndices(std::vector<bool>& freeRing)
+{
+    //XXX Assert: bit set has same size as number of lines.
+    isFreeRing = freeRing;
+}
+
+/* private */
+std::unique_ptr<MultiLineString>
+TPVWSimplifier::simplify()
+{
+    std::vector<bool> emptyList;
+    std::vector<Edge> edges = createEdges(inputLines, isFreeRing);
+    std::vector<Edge> constraintEdges = createEdges(constraintLines, emptyList);
+
+    EdgeIndex edgeIndex;
+    edgeIndex.add(edges);
+    edgeIndex.add(constraintEdges);
+
+    std::vector<std::unique_ptr<LineString>> result;
+    for (auto& edge : edges) {
+        std::unique_ptr<CoordinateSequence> ptsSimp = edge.simplify(edgeIndex);
+        auto ls = geomFactory->createLineString(std::move(ptsSimp));
+        result.emplace_back(ls.release());
+    }
+    return geomFactory->createMultiLineString(std::move(result));
+}
+
+/* private */
+std::vector<Edge>
+TPVWSimplifier::createEdges(
+    const MultiLineString* lines,
+    std::vector<bool>& freeRing)
+{
+    std::vector<Edge> edges;
+
+    if (lines == nullptr)
+        return edges;
+
+    for (std::size_t i = 0; i < lines->getNumGeometries(); i++) {
+        const LineString* line = lines->getGeometryN(i);
+        bool isFree = freeRing.empty() ? false : freeRing[i];
+        edges.emplace_back(line, isFree, areaTolerance);
+    }
+    return edges;
+}
+
+/************************************************************************/
+
+TPVWSimplifier::Edge::Edge(const LineString* p_inputLine, bool p_isFreeRing, double p_areaTolerance)
+    : areaTolerance(p_areaTolerance)
+    , isFreeRing(p_isFreeRing)
+    , envelope(p_inputLine->getEnvelopeInternal())
+    , nbPts(p_inputLine->getNumPoints())
+    , linkedLine(*(p_inputLine->getCoordinatesRO()))
+    , vertexIndex(*(p_inputLine->getCoordinatesRO()))
+    , minEdgeSize(p_inputLine->getCoordinatesRO()->isRing() ? 3 : 2)
+{
+    // linkedLine = new LinkedLine(pts);
+    // minEdgeSize = linkedLine.isRing() ? 3 : 2;
+    // vertexIndex = new VertexSequencePackedRtree(pts);
+    //-- remove ring duplicate final vertex
+    if (linkedLine.isRing()) {
+        vertexIndex.remove(nbPts-1);
+    }
+}
+
+/* private */
+const Coordinate&
+TPVWSimplifier::Edge::getCoordinate(std::size_t index) const
+{
+    return linkedLine.getCoordinate(index);
+}
+
+/* public */
+const Envelope*
+TPVWSimplifier::Edge::getEnvelopeInternal() const
+{
+    return envelope;
+}
+
+/* public */
+std::size_t
+TPVWSimplifier::Edge::size() const
+{
+    return linkedLine.size();
+}
+
+/* private */
+std::unique_ptr<CoordinateSequence>
+TPVWSimplifier::Edge::simplify(EdgeIndex& edgeIndex)
+{
+    Corner::PriorityQueue cornerQueue;
+    createQueue(cornerQueue);
+    while (! cornerQueue.empty() && size() > minEdgeSize) {
+        //Corner corner = cornerQueue.poll();
+        Corner corner = cornerQueue.top();
+        cornerQueue.pop();
+
+        //-- a corner may no longer be valid due to removal of adjacent corners
+        if (corner.isRemoved())
+            continue;
+        //System.out.println(corner.toLineString(edge));
+        //-- done when all small corners are removed
+        if (corner.getArea() > areaTolerance)
+            break;
+        if (isRemovable(corner, edgeIndex) ) {
+            removeCorner(corner, cornerQueue);
+        }
+    }
+    return linkedLine.getCoordinates();
+}
+
+/* private */
+void
+TPVWSimplifier::Edge::createQueue(Corner::PriorityQueue& cornerQueue)
+{
+    std::size_t minIndex = (linkedLine.isRing() && isFreeRing) ? 0 : 1;
+    std::size_t maxIndex = nbPts - 1;
+    for (std::size_t i = minIndex; i < maxIndex; i++) {
+        addCorner(i, cornerQueue);
+    }
+    return;
+}
+
+/* private */
+void
+TPVWSimplifier::Edge::addCorner(
+    std::size_t i,
+    Corner::PriorityQueue& cornerQueue)
+{
+    if (isFreeRing || (i != 0 && i != nbPts-1)) {
+        Corner corner(&linkedLine, i);
+        if (corner.getArea() <= areaTolerance) {
+            cornerQueue.push(corner);
+        }
+    }
+}
+
+/* private */
+bool
+TPVWSimplifier::Edge::isRemovable(
+    Corner& corner,
+    EdgeIndex& edgeIndex) const
+{
+    Envelope cornerEnv = corner.envelope();
+    //-- check nearby lines for violating intersections
+    //-- the query also returns this line for checking
+    std::vector<const Edge*> edgeHits = edgeIndex.query(cornerEnv);
+    for (const Edge* edge : edgeHits) {
+        if (hasIntersectingVertex(corner, cornerEnv, *edge))
+            return false;
+    //-- check if corner base equals line (2-pts)
+    //-- if so, don't remove corner, since that would collapse to the line
+        if (edge != this && edge->size() == 2) {
+            // TODO xxxxxx make linkedLine coordinates local
+            // to linkedline and return a reference, update
+            // simplify to clone reference at final step
+            auto linePts = edge->linkedLine.getCoordinates();
+            if (corner.isBaseline(linePts->getAt(0), linePts->getAt(1)))
+                return false;
+        }
+    }
+    return true;
+}
+
+
+/* private */
+bool
+TPVWSimplifier::Edge::hasIntersectingVertex(
+    const Corner& corner,
+    const Envelope& cornerEnv,
+    const Edge& edge) const
+{
+    std::vector<std::size_t> result = edge.query(cornerEnv);
+    for (std::size_t index : result) {
+
+        const Coordinate& v = edge.getCoordinate(index);
+        // ok if corner touches another line - should only happen at endpoints
+        if (corner.isVertex(v))
+            continue;
+
+        //--- does corner triangle contain vertex?
+        if (corner.intersects(v))
+            return true;
+    }
+    return false;
+}
+
+/* private */
+std::vector<std::size_t>
+TPVWSimplifier::Edge::query(const Envelope& cornerEnv) const
+{
+    std::vector<std::size_t> result;
+    vertexIndex.query(cornerEnv, result);
+    return result;
+}
+
+
+/* private */
+void
+TPVWSimplifier::Edge::removeCorner(
+    Corner& corner,
+    Corner::PriorityQueue& cornerQueue)
+{
+    std::size_t index = corner.getIndex();
+    std::size_t prev = linkedLine.prev(index);
+    std::size_t next = linkedLine.next(index);
+    linkedLine.remove(index);
+    vertexIndex.remove(index);
+
+    //-- potentially add the new corners created
+    addCorner(prev, cornerQueue);
+    addCorner(next, cornerQueue);
+}
+
+/************************************************************************/
+
+
+/* public */
+void
+TPVWSimplifier::EdgeIndex::add(std::vector<Edge>& edges)
+{
+    for (Edge& edge : edges) {
+        index.insert(&edge);
+    }
+}
+
+/* public */
+std::vector<const Edge*>
+TPVWSimplifier::EdgeIndex::query(const Envelope& queryEnv)
+{
+    std::vector<const Edge*> hits;
+    index.query(queryEnv, hits);
+    return hits;
+}
+
+
+
+} // geos.coverage
+} // geos
diff --git a/src/coverage/VertexCounter.cpp b/src/coverage/VertexCounter.cpp
new file mode 100644
index 000000000..6415b9138
--- /dev/null
+++ b/src/coverage/VertexCounter.cpp
@@ -0,0 +1,66 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2022 Martin Davis.
+ *
+ * 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/coverage/VertexCounter.h>
+
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/CoordinateSequenceFilter.h>
+#include <geos/geom/Geometry.h>
+
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::CoordinateSequenceFilter;
+using geos::geom::Geometry;
+
+
+namespace geos {
+namespace coverage { // geos.coverage
+
+/* public static  */
+void
+VertexCounter::count(
+    std::vector<const Geometry*>& geoms,
+    std::map<Coordinate, std::size_t>& counts)
+{
+    VertexCounter vertextCounter(counts);
+    for (const Geometry* geom : geoms) {
+        geom->apply_ro(vertextCounter);
+    }
+}
+
+
+/* public */
+void
+VertexCounter::filter_ro(const CoordinateSequence& seq, std::size_t i)
+{
+    //-- for rings don't double-count duplicate endpoint
+    if (seq.isRing() && i == 0)
+        return;
+
+    const Coordinate& v = seq.getAt(i);
+    auto search = vertexCounts.find(v);
+    std::size_t count = 0;
+    if (search != vertexCounts.end()) {
+        count = search->second;
+    }
+    count++;
+    vertexCounts[v] = count;
+}
+
+
+} // geos.coverage
+} // geos
diff --git a/src/geom/CoordinateSequence.cpp b/src/geom/CoordinateSequence.cpp
index 5219339e7..237a17145 100644
--- a/src/geom/CoordinateSequence.cpp
+++ b/src/geom/CoordinateSequence.cpp
@@ -14,6 +14,7 @@
  *
  **********************************************************************/
 
+#include <geos/constants.h>
 #include <geos/profiler.h>
 #include <geos/geom/CoordinateFilter.h>
 #include <geos/geom/CoordinateSequence.h>
@@ -368,7 +369,7 @@ CoordinateSequence::indexOf(const CoordinateXY* coordinate,
             return i;
         }
     }
-    return std::numeric_limits<std::size_t>::max();
+    return NO_COORD_INDEX;
 }
 
 void
diff --git a/src/geom/LineSegment.cpp b/src/geom/LineSegment.cpp
index e0dade148..54b85c9ee 100644
--- a/src/geom/LineSegment.cpp
+++ b/src/geom/LineSegment.cpp
@@ -159,17 +159,6 @@ LineSegment::closestPoint(const CoordinateXY& p, CoordinateXY& ret) const
     ret = p1;
 }
 
-/*public*/
-int
-LineSegment::compareTo(const LineSegment& other) const
-{
-    int comp0 = p0.compareTo(other.p0);
-    if(comp0 != 0) {
-        return comp0;
-    }
-    return p1.compareTo(other.p1);
-}
-
 /*public*/
 bool
 LineSegment::equalsTopo(const LineSegment& other) const
@@ -327,11 +316,17 @@ LineSegment::toGeometry(const GeometryFactory& gf) const
     return gf.createLineString(std::move(cl));
 }
 
+/* public */
 std::ostream&
-operator<< (std::ostream& o, const LineSegment& l)
+LineSegment::operator<< (std::ostream& os)
 {
-    return o << "LINESEGMENT(" << l.p0.x << " " << l.p0.y << "," << l.p1.x << " " << l.p1.y << ")";
+    return os << "LINESEGMENT("
+        << p0.x << " " << p0.y << ","
+        << p1.x << " " << p1.y << ")";
 }
 
+
+
+
 } // namespace geos::geom
 } // namespace geos
diff --git a/src/operation/buffer/OffsetCurve.cpp b/src/operation/buffer/OffsetCurve.cpp
index c517a0719..d68c0545f 100644
--- a/src/operation/buffer/OffsetCurve.cpp
+++ b/src/operation/buffer/OffsetCurve.cpp
@@ -258,20 +258,20 @@ OffsetCurve::computeCurveSections(
     std::vector<double> rawPosition(bufferRingPts->size()-1, NOT_IN_CURVE);
 
     SegmentMCIndex bufferSegIndex(bufferRingPts);
-    std::size_t bufferFirstIndex = UNKNOWN_INDEX;
+    std::size_t bufferFirstIndex = NO_COORD_INDEX;
     double minRawPosition = -1;
     for (std::size_t i = 0; i < rawCurve.size() - 1; i++) {
         std::size_t minBufferIndexForSeg = matchSegments(rawCurve[i], rawCurve[i+1], i, bufferSegIndex, bufferRingPts, rawPosition);
-        if (minBufferIndexForSeg != UNKNOWN_INDEX) {
+        if (minBufferIndexForSeg != NO_COORD_INDEX) {
             double pos = rawPosition[minBufferIndexForSeg];
-            if (bufferFirstIndex == UNKNOWN_INDEX || pos < minRawPosition) {
+            if (bufferFirstIndex == NO_COORD_INDEX || pos < minRawPosition) {
                 minRawPosition = pos;
                 bufferFirstIndex = minBufferIndexForSeg;
             }
         }
     }
     //-- no matching sections found in this buffer ring
-    if (bufferFirstIndex == UNKNOWN_INDEX)
+    if (bufferFirstIndex == NO_COORD_INDEX)
         return;
 
     extractSections(bufferRingPts, rawPosition, bufferFirstIndex, sections);
@@ -324,7 +324,7 @@ OffsetCurve::matchSegments(
             , bufferRingPts(p_bufferRingPts)
             , rawCurveLoc(p_rawCurveLoc)
             , minRawLocation(-1.0)
-            , bufferRingMinIndex(UNKNOWN_INDEX)
+            , bufferRingMinIndex(NO_COORD_INDEX)
             {};
 
         std::size_t getBufferMinIndex() {
diff --git a/src/operation/polygonize/BuildArea.cpp b/src/operation/polygonize/BuildArea.cpp
index e44009d5b..3956c735d 100644
--- a/src/operation/polygonize/BuildArea.cpp
+++ b/src/operation/polygonize/BuildArea.cpp
@@ -94,7 +94,7 @@ static bool ringsEqualAnyDirection(const LinearRing* r1, const LinearRing* r2)
 
     const Coordinate& firstPoint = cs1->getAt(0);
     size_t offset = CoordinateSequence::indexOf(&firstPoint, cs2);
-    if ( offset == std::numeric_limits<std::size_t>::max() ) return false;
+    if ( offset == NO_COORD_INDEX ) return false;
 
     bool equal = true;
 
diff --git a/src/simplify/LinkedLine.cpp b/src/simplify/LinkedLine.cpp
new file mode 100644
index 000000000..798769b5a
--- /dev/null
+++ b/src/simplify/LinkedLine.cpp
@@ -0,0 +1,183 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (c) 2022 Martin Davis.
+ *
+ * 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/simplify/LinkedLine.h>
+
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/constants.h>
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::io::WKTWriter;
+
+
+namespace geos {
+namespace simplify { // geos.simplify
+
+
+LinkedLine::LinkedLine(const CoordinateSequence& pts)
+    : m_coord(pts)
+    , m_isRing(pts.isRing())
+    , m_size(pts.isRing() ? pts.size() - 1 : pts.size())
+{
+    createNextLinks(m_size);
+    createPrevLinks(m_size);
+}
+
+
+/* public */
+bool
+LinkedLine::isRing() const
+{
+    return m_isRing;
+}
+
+/* public */
+bool
+LinkedLine::isCorner(std::size_t i) const
+{
+    if (! isRing()
+        && (i == 0 || i == m_coord.size() - 1))
+    {
+        return false;
+    }
+    return true;
+}
+
+/* private */
+void
+LinkedLine::createNextLinks(std::size_t size)
+{
+    m_next.resize(size);
+    for (std::size_t i = 0; i < size; i++) {
+      m_next[i] = i + 1;
+    }
+    m_next[size - 1] = m_isRing ? 0 : NO_COORD_INDEX;
+    return;
+}
+
+/* private */
+void
+LinkedLine::createPrevLinks(std::size_t size)
+{
+    m_prev.resize(size);
+    for (std::size_t i = 1; i < size; i++) {
+      m_prev[i] = i - 1;
+    }
+    m_prev[0] = m_isRing ? size - 1 : NO_COORD_INDEX;
+    return;
+}
+
+/* public */
+std::size_t
+LinkedLine::size() const
+{
+    return m_size;
+}
+
+/* public */
+std::size_t
+LinkedLine::next(std::size_t i) const
+{
+    return m_next[i];
+}
+
+/* public */
+std::size_t
+LinkedLine::prev(std::size_t i) const
+{
+    return m_prev[i];
+}
+
+/* public */
+const Coordinate&
+LinkedLine::getCoordinate(std::size_t index) const
+{
+    return m_coord.getAt(index);
+}
+
+/* public */
+const Coordinate&
+LinkedLine::prevCoordinate(std::size_t index) const
+{
+    return m_coord.getAt(prev(index));
+}
+
+/* public */
+const Coordinate&
+LinkedLine::nextCoordinate(std::size_t index) const
+{
+    return m_coord.getAt(next(index));
+}
+
+/* public */
+bool
+LinkedLine::hasCoordinate(std::size_t index) const
+{
+    //-- if not a ring, endpoints are alway present
+    if (! m_isRing && (index == 0 || index == m_coord.size() - 1))
+        return true;
+
+    return index != NO_COORD_INDEX
+        && index < m_prev.size()
+        && m_prev[index] != NO_COORD_INDEX;
+}
+
+/* public */
+void
+LinkedLine::remove(std::size_t index)
+{
+    std::size_t iprev = m_prev[index];
+    std::size_t inext = m_next[index];
+    if (iprev != NO_COORD_INDEX)
+        m_next[iprev] = inext;
+    if (inext != NO_COORD_INDEX)
+        m_prev[inext] = iprev;
+    m_prev[index] = NO_COORD_INDEX;
+    m_next[index] = NO_COORD_INDEX;
+    m_size = m_size > 0 ? m_size - 1 : m_size;
+}
+
+/* public */
+std::unique_ptr<CoordinateSequence>
+LinkedLine::getCoordinates() const
+{
+    std::unique_ptr<CoordinateSequence> coords(new CoordinateSequence());
+    std::size_t len = m_isRing ? m_coord.size() - 1 : m_coord.size();
+    for (std::size_t i = 0; i < len; i++) {
+        if (hasCoordinate(i)) {
+            coords->add(m_coord.getAt(i), false);
+        }
+    }
+    if (m_isRing) {
+        coords->closeRing();
+    }
+    return coords;
+}
+
+std::ostream&
+operator<< (std::ostream& os, const LinkedLine& ll)
+{
+    auto cs = ll.getCoordinates();
+    os << WKTWriter::toLineString(*cs);
+    return os;
+}
+
+
+
+} // namespace geos.simplify
+} // namespace geos
diff --git a/src/simplify/RingHull.cpp b/src/simplify/RingHull.cpp
index 98f7f7916..0fbd73d54 100644
--- a/src/simplify/RingHull.cpp
+++ b/src/simplify/RingHull.cpp
@@ -118,7 +118,7 @@ RingHull::init(CoordinateSequence& ring, bool isOuter)
 
 /* private */
 void
-RingHull::addCorner(std::size_t i, std::priority_queue<Corner>& queue)
+RingHull::addCorner(std::size_t i, Corner::PriorityQueue& queue)
 {
     //-- convex corners are left untouched
     if (isConvex(*vertexRing, i))
@@ -204,7 +204,7 @@ RingHull::isAtTarget(const Corner& corner)
 */
 /* private */
 void
-RingHull::removeCorner(const Corner& corner, std::priority_queue<Corner>& queue)
+RingHull::removeCorner(const Corner& corner, Corner::PriorityQueue& queue)
 {
     std::size_t index = corner.getIndex();
     std::size_t prev = vertexRing->prev(index);
diff --git a/src/triangulate/polygon/PolygonEarClipper.cpp b/src/triangulate/polygon/PolygonEarClipper.cpp
index 535c0f5ba..3bb54069c 100644
--- a/src/triangulate/polygon/PolygonEarClipper.cpp
+++ b/src/triangulate/polygon/PolygonEarClipper.cpp
@@ -142,7 +142,7 @@ PolygonEarClipper::isValidEar(std::size_t cornerIdx, const std::array<Coordinate
 {
     std::size_t intApexIndex = findIntersectingVertex(cornerIdx, corner);
     //--- no intersections found
-    if (intApexIndex == NO_VERTEX_INDEX)
+    if (intApexIndex == NO_COORD_INDEX)
         return true;
     //--- check for duplicate corner apex vertex
     if ( vertex[intApexIndex].equals2D(corner[1]) ) {
@@ -161,7 +161,7 @@ PolygonEarClipper::findIntersectingVertex(std::size_t cornerIdx, const std::arra
     std::vector<std::size_t> result;
     vertexCoordIndex.query(cornerEnv, result);
 
-    std::size_t dupApexIndex = NO_VERTEX_INDEX;
+    std::size_t dupApexIndex = NO_COORD_INDEX;
     //--- check for duplicate vertices
     for (std::size_t i = 0; i < result.size(); i++) {
         std::size_t vertIndex = result[i];
@@ -191,10 +191,10 @@ PolygonEarClipper::findIntersectingVertex(std::size_t cornerIdx, const std::arra
         else if (geom::Triangle::intersects(corner[0], corner[1], corner[2], v))
             return vertIndex;
     }
-    if (dupApexIndex != NO_VERTEX_INDEX) {
+    if (dupApexIndex != NO_COORD_INDEX) {
         return dupApexIndex;
     }
-    return NO_VERTEX_INDEX;
+    return NO_COORD_INDEX;
 }
 
 
@@ -260,7 +260,7 @@ PolygonEarClipper::removeCorner()
     }
     vertexNext[cornerIndex[0]] = vertexNext[cornerApexIndex];
     vertexCoordIndex.remove(cornerApexIndex);
-    vertexNext[cornerApexIndex] = NO_VERTEX_INDEX;
+    vertexNext[cornerApexIndex] = NO_COORD_INDEX;
     vertexSize--;
     //-- adjust following corner indexes
     cornerIndex[1] = nextIndex(cornerIndex[0]);
@@ -272,7 +272,7 @@ PolygonEarClipper::removeCorner()
 bool
 PolygonEarClipper::isRemoved(std::size_t vertexIndex) const
 {
-    return NO_VERTEX_INDEX == vertexNext[vertexIndex];
+    return NO_COORD_INDEX == vertexNext[vertexIndex];
 }
 
 
diff --git a/src/triangulate/polygon/PolygonHoleJoiner.cpp b/src/triangulate/polygon/PolygonHoleJoiner.cpp
index 753a93ff9..8b4c0e908 100644
--- a/src/triangulate/polygon/PolygonHoleJoiner.cpp
+++ b/src/triangulate/polygon/PolygonHoleJoiner.cpp
@@ -205,7 +205,7 @@ PolygonHoleJoiner::joinTouchingHole(const CoordinateSequence& holeCoords)
     std::size_t holeTouchIndex = findHoleTouchIndex(holeCoords);
 
     //-- hole does not touch
-    if (holeTouchIndex == NO_INDEX)
+    if (holeTouchIndex == NO_COORD_INDEX)
         return false;
 
     /**
@@ -232,7 +232,7 @@ PolygonHoleJoiner::findHoleTouchIndex(const CoordinateSequence& holeCoords)
         }
         i++;
     }
-    return NO_INDEX;
+    return NO_COORD_INDEX;
 }
 
 
@@ -391,7 +391,7 @@ PolygonHoleJoiner::findLowestLeftVertexIndex(const CoordinateSequence& holeCoord
 {
     Coordinate lowestLeftCoord;
     lowestLeftCoord.setNull();
-    std::size_t lowestLeftIndex = NO_INDEX;
+    std::size_t lowestLeftIndex = NO_COORD_INDEX;
     for (std::size_t i = 0; i < holeCoords.size() - 1; i++) {
         if (lowestLeftCoord.isNull() || holeCoords.getAt(i).compareTo(lowestLeftCoord) < 0) {
             lowestLeftCoord = holeCoords.getAt(i);
diff --git a/tests/unit/coverage/CoverageRingEdgesTest.cpp b/tests/unit/coverage/CoverageRingEdgesTest.cpp
new file mode 100644
index 000000000..9c8b17648
--- /dev/null
+++ b/tests/unit/coverage/CoverageRingEdgesTest.cpp
@@ -0,0 +1,141 @@
+//
+// Test Suite for geos::coverage::CoverageRingEdges class.
+
+#include <tut/tut.hpp>
+#include <utility.h>
+
+// geos
+#include <geos/coverage/CoverageRingEdges.h>
+#include <geos/coverage/CoverageEdge.h>
+
+using geos::coverage::CoverageRingEdges;
+using geos::coverage::CoverageEdge;
+using geos::geom::MultiLineString;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by all tests
+struct test_coverageringedges_data
+ {
+
+    WKTReader r;
+    WKTWriter w;
+
+    test_coverageringedges_data() {
+        w.setTrim(true);
+    }
+
+    void
+    checkEdges(const std::string& wkt, const std::string& wktExpected)
+    {
+        std::unique_ptr<Geometry> geom = r.read(wkt);
+        std::vector<const Geometry*> polygons = toArray(*geom);
+        CoverageRingEdges cov(polygons);
+        std::vector<CoverageEdge*>& edges = cov.getEdges();
+        std::unique_ptr<Geometry> edgeLines = toArray(edges, geom->getFactory());
+        std::unique_ptr<Geometry> expected = r.read(wktExpected);
+        ensure_equals_geometry(edgeLines.get(), expected.get());
+    }
+
+    void
+    checkEdgesSelected(const std::string& wkt, std::size_t ringCount, const std::string& wktExpected)
+    {
+        std::unique_ptr<Geometry> geom = r.read(wkt);
+        auto polygons = toArray(*geom);
+        CoverageRingEdges covEdges(polygons);
+        auto edges = covEdges.selectEdges(ringCount);
+        auto edgeLines = toArray(edges, geom->getFactory());
+        std::unique_ptr<Geometry> expected = r.read(wktExpected);
+        ensure_equals_geometry(edgeLines.get(), expected.get());
+    }
+
+    std::unique_ptr<Geometry>
+    toArray(std::vector<CoverageEdge*>& edges, const GeometryFactory* geomFactory)
+    {
+        std::vector<std::unique_ptr<LineString>> lines;
+        for (auto* edge : edges) {
+            lines.push_back(edge->toLineString(geomFactory));
+        }
+        return geomFactory->createMultiLineString(std::move(lines));
+    }
+
+    std::vector<const Geometry*>
+    toArray(const Geometry& geom)
+    {
+        std::vector<const Geometry*> geoms;
+        for (std::size_t i = 0; i < geom.getNumGeometries(); i++) {
+            geoms.push_back(geom.getGeometryN(i));
+        }
+        return geoms;
+    }
+};
+
+
+typedef test_group<test_coverageringedges_data> group;
+typedef group::object object;
+
+group test_coverageringedges_data("geos::coverage::CoverageRingEdges");
+
+
+// testTwoAdjacent
+template<>
+template<>
+void object::test<1> ()
+{
+    checkEdges(
+        "GEOMETRYCOLLECTION (POLYGON ((1 1, 1 6, 6 5, 9 6, 9 1, 1 1)), POLYGON ((1 9, 6 9, 6 5, 1 6, 1 9)))",
+        "MULTILINESTRING ((1 6, 1 1, 9 1, 9 6, 6 5), (1 6, 1 9, 6 9, 6 5), (1 6, 6 5))"
+    );
+}
+
+// testTwoAdjacentWithFilledHole
+template<>
+template<>
+void object::test<2> ()
+{
+    checkEdges(
+        "GEOMETRYCOLLECTION (POLYGON ((1 1, 1 6, 6 5, 9 6, 9 1, 1 1), (2 4, 4 4, 4 2, 2 2, 2 4)), POLYGON ((1 9, 6 9, 6 5, 1 6, 1 9)), POLYGON ((4 2, 2 2, 2 4, 4 4, 4 2)))",
+        "MULTILINESTRING ((1 6, 1 1, 9 1, 9 6, 6 5), (1 6, 1 9, 6 9, 6 5), (1 6, 6 5), (2 4, 2 2, 4 2, 4 4, 2 4))"
+    );
+}
+
+// testHolesAndFillWithDifferentEndpoints
+template<>
+template<>
+void object::test<3> ()
+{
+    checkEdges(
+        "GEOMETRYCOLLECTION (POLYGON ((0 10, 10 10, 10 0, 0 0, 0 10), (1 9, 4 8, 9 9, 9 1, 1 1, 1 9)), POLYGON ((9 9, 1 1, 1 9, 4 8, 9 9)), POLYGON ((1 1, 9 9, 9 1, 1 1)))",
+        "MULTILINESTRING ((0 10, 0 0, 10 0, 10 10, 0 10), (1 1, 1 9, 4 8, 9 9), (1 1, 9 1, 9 9), (1 1, 9 9))"
+    );
+}
+
+// testTouchingSquares
+template<>
+template<>
+void object::test<4> ()
+{
+    std::string wkt = "MULTIPOLYGON (((2 7, 2 8, 3 8, 3 7, 2 7)), ((1 6, 1 7, 2 7, 2 6, 1 6)), ((0 7, 0 8, 1 8, 1 7, 0 7)), ((0 5, 0 6, 1 6, 1 5, 0 5)), ((2 5, 2 6, 3 6, 3 5, 2 5)))";
+    checkEdgesSelected(wkt, 1,
+        "MULTILINESTRING ((1 6, 0 6, 0 5, 1 5, 1 6), (1 6, 1 7), (1 6, 2 6), (1 7, 0 7, 0 8, 1 8, 1 7), (1 7, 2 7), (2 6, 2 5, 3 5, 3 6, 2 6), (2 6, 2 7), (2 7, 2 8, 3 8, 3 7, 2 7))");
+    checkEdgesSelected(wkt, 2,
+        "MULTILINESTRING EMPTY");
+}
+
+// testAdjacentSquares
+template<>
+template<>
+void object::test<5> ()
+{
+    std::string wkt = "GEOMETRYCOLLECTION (POLYGON ((1 3, 2 3, 2 2, 1 2, 1 3)), POLYGON ((3 3, 3 2, 2 2, 2 3, 3 3)), POLYGON ((3 1, 2 1, 2 2, 3 2, 3 1)), POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1)))";
+    checkEdgesSelected(wkt, 1,
+        "MULTILINESTRING ((1 2, 1 1, 2 1), (1 2, 1 3, 2 3), (2 1, 3 1, 3 2), (2 3, 3 3, 3 2))");
+    checkEdgesSelected(wkt, 2,
+        "MULTILINESTRING ((1 2, 2 2), (2 1, 2 2), (2 2, 2 3), (2 2, 3 2))");
+}
+
+
+} // namespace tut
diff --git a/tests/unit/coverage/CoverageSimplifierTest.cpp b/tests/unit/coverage/CoverageSimplifierTest.cpp
new file mode 100644
index 000000000..3d4c6f92f
--- /dev/null
+++ b/tests/unit/coverage/CoverageSimplifierTest.cpp
@@ -0,0 +1,373 @@
+//
+// Test Suite for geos::coverage::CoverageSimplifierTest class.
+
+#include <tut/tut.hpp>
+#include <utility.h>
+
+// geos
+#include <geos/coverage/CoverageSimplifier.h>
+
+using geos::coverage::CoverageSimplifier;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by all tests
+struct test_coveragesimplifier_data {
+
+    WKTReader r;
+    WKTWriter w;
+    std::vector<std::unique_ptr<Geometry>> geomArray;
+
+    test_coveragesimplifier_data() {
+        w.setTrim(true);
+    }
+
+    void checkNoop(
+        const std::vector<std::unique_ptr<Geometry>>& input)
+    {
+        std::vector<std::unique_ptr<Geometry>> actual = CoverageSimplifier::simplify(input, 0);
+
+        // std::cout << w.write(*input[0]) << std::endl;
+        // std::cout << w.write(*input[1]) << std::endl;
+        // std::cout << "--" << std::endl;
+        // std::cout << w.write(*actual[0]) << std::endl;
+        // std::cout << w.write(*actual[1]) << std::endl;
+
+        checkArrayEqual(input, actual);
+    }
+
+    void checkResult(
+        const std::vector<std::unique_ptr<Geometry>>& input,
+        double tolerance,
+        const std::vector<std::unique_ptr<Geometry>>& expected)
+    {
+        std::vector<std::unique_ptr<Geometry>> actual = CoverageSimplifier::simplify(input, tolerance);
+        checkArrayEqual(expected, actual);
+    }
+
+    void checkResultInner(
+        const std::vector<std::unique_ptr<Geometry>>& input,
+        double tolerance,
+        const std::vector<std::unique_ptr<Geometry>>& expected)
+    {
+        std::vector<std::unique_ptr<Geometry>> actual = CoverageSimplifier::simplifyInner(input, tolerance);
+        checkArrayEqual(expected, actual);
+    }
+
+    std::vector<std::unique_ptr<Geometry>>
+    readArray(std::vector<std::string> wkts)
+    {
+        std::vector<std::unique_ptr<Geometry>> geometries;
+        for (std::string& wkt : wkts) {
+            auto geom = r.read(wkt);
+            if (geom != nullptr) {
+                geometries.push_back(std::move(geom));
+            }
+        }
+        return geometries;
+    }
+
+    void
+    checkArrayEqual(
+        const std::vector<std::unique_ptr<Geometry>>& input,
+        const std::vector<std::unique_ptr<Geometry>>& expected)
+    {
+        ensure("arrays same size", input.size() == expected.size());
+        for (std::size_t i = 0; i < input.size(); i++) {
+            ensure_equals_geometry(input[i].get(), expected[i].get());
+        }
+    }
+
+};
+
+
+typedef test_group<test_coveragesimplifier_data> group;
+typedef group::object object;
+
+group test_coveragesimplifier_data("geos::coverage::CoverageSimplifier");
+
+
+// testNoopSimple2
+template<>
+template<>
+void object::test<1> ()
+{
+    checkNoop(readArray({
+        "POLYGON ((100 100, 200 200, 300 100, 200 101, 100 100))",
+        "POLYGON ((150 0, 100 100, 200 101, 300 100, 250 0, 150 0))" })
+    );
+}
+
+// testNoopSimple3
+template<>
+template<>
+void object::test<2> ()
+{
+    checkNoop(readArray({
+        "POLYGON ((100 300, 200 200, 100 200, 100 300))",
+        "POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200))",
+        "POLYGON ((100 100, 200 100, 150 50, 100 100))" })
+    );
+}
+
+// testNoopHole
+template<>
+template<>
+void object::test<3> ()
+{
+    checkNoop(readArray({
+        "POLYGON ((10 90, 90 90, 90 10, 10 10, 10 90), (20 80, 80 80, 80 20, 20 20, 20 80))",
+        "POLYGON ((80 20, 20 20, 20 80, 80 80, 80 20))" })
+    );
+}
+
+// testNoopMulti
+template<>
+template<>
+void object::test<4> ()
+{
+    checkNoop(readArray({
+        "MULTIPOLYGON (((10 10, 10 50, 50 50, 50 10, 10 10)), ((90 90, 90 50, 50 50, 50 90, 90 90)))",
+        "MULTIPOLYGON (((10 90, 50 90, 50 50, 10 50, 10 90)), ((90 10, 50 10, 50 50, 90 50, 90 10)))" })
+    );
+}
+
+//---------------------------------------------
+
+// testSimple2
+template<>
+template<>
+void object::test<5> ()
+{
+    checkResult(readArray({
+        "POLYGON ((100 100, 200 200, 300 100, 200 101, 100 100))",
+        "POLYGON ((150 0, 100 100, 200 101, 300 100, 250 0, 150 0))" }),
+        10,
+        readArray({
+            "POLYGON ((100 100, 200 200, 300 100, 100 100))",
+            "POLYGON ((150 0, 100 100, 300 100, 250 0, 150 0))" })
+    );
+}
+
+// testSingleRingNoCollapse
+template<>
+template<>
+void object::test<6> ()
+{
+    checkResult(readArray({
+        "POLYGON ((10 50, 60 90, 70 50, 60 10, 10 50))" }),
+        100000,
+        readArray({
+            "POLYGON ((10 50, 60 90, 60 10, 10 50))" })
+    );
+}
+
+/**
+* Checks that a polygon on the edge of the coverage does not collapse
+* under maximal simplification
+*/
+// testMultiEdgeRingNoCollapse
+template<>
+template<>
+void object::test<7> ()
+{
+    checkResult(readArray({
+        "POLYGON ((50 250, 200 200, 180 170, 200 150, 50 50, 50 250))",
+        "POLYGON ((200 200, 180 170, 200 150, 200 200))" }),
+        40,
+        readArray({
+            "POLYGON ((50 250, 200 200, 180 170, 200 150, 50 50, 50 250))",
+            "POLYGON ((200 200, 180 170, 200 150, 200 200))" })
+    );
+}
+
+// testFilledHole
+template<>
+template<>
+void object::test<8> ()
+{
+    checkResult(readArray({
+            "POLYGON ((20 30, 20 80, 60 50, 80 20, 50 20, 20 30))",
+            "POLYGON ((10 90, 90 90, 90 10, 10 10, 10 90), (50 20, 20 30, 20 80, 60 50, 80 20, 50 20))"
+        }),
+        28,
+        readArray({
+            "POLYGON ((20 30, 20 80, 80 20, 20 30))",
+            "POLYGON ((10 10, 10 90, 90 90, 90 10, 10 10), (20 30, 80 20, 20 80, 20 30))"
+        })
+    );
+}
+
+// testTouchingHoles
+template<>
+template<>
+void object::test<9> ()
+{
+    checkResult(readArray({
+            "POLYGON (( 0 0, 0 11, 19 11, 19 0, 0 0 ), ( 4 5, 12 5, 12 6, 10 6, 10 8, 9 8, 9 9, 7 9, 7 8, 6 8, 6 6, 4 6, 4 5 ), ( 12 6, 14 6, 14 9, 13 9, 13 7, 12 7, 12 6 ))",
+            "POLYGON (( 12 6, 12 5, 4 5, 4 6, 6 6, 6 8, 7 8, 7 9, 9 9, 9 8, 10 8, 10 6, 12 6 ))",
+            "POLYGON (( 12 6, 12 7, 13 7, 13 9, 14 9, 14 6, 12 6 ))" }),
+        1.0,
+        readArray({
+            "POLYGON ((0 0, 0 11, 19 11, 19 0, 0 0), (4 5, 12 5, 12 6, 10 6, 9 9, 6 8, 6 6, 4 5), (12 6, 14 6, 14 9, 12 6))",
+            "POLYGON ((4 5, 6 6, 6 8, 9 9, 10 6, 12 6, 12 5, 4 5))",
+            "POLYGON ((12 6, 14 9, 14 6, 12 6))"
+        })
+    );
+}
+
+// testHoleTouchingShell
+template<>
+template<>
+void object::test<10> ()
+{
+    checkResultInner(readArray({
+            "POLYGON ((200 300, 300 300, 300 100, 100 100, 100 300, 200 300), (170 220, 170 160, 200 140, 200 250, 170 220), (170 250, 200 250, 200 300, 170 250))",
+            "POLYGON ((170 220, 200 250, 200 140, 170 160, 170 220))",
+            "POLYGON ((170 250, 200 300, 200 250, 170 250))" }),
+        100.0,
+        readArray({
+            "POLYGON ((100 100, 100 300, 200 300, 300 300, 300 100, 100 100), (170 160, 200 140, 200 250, 170 160), (170 250, 200 250, 200 300, 170 250))",
+            "POLYGON ((170 160, 200 250, 200 140, 170 160))",
+            "POLYGON ((200 250, 200 300, 170 250, 200 250))" })
+    );
+}
+
+// testHolesTouchingHolesAndShellInner
+template<>
+template<>
+void object::test<11> ()
+{
+    checkResultInner(readArray({
+            "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))" }),
+        4.0,
+        readArray({
+            "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))" })
+    );
+}
+
+// testHolesTouchingHolesAndShell
+template<>
+template<>
+void object::test<12> ()
+{
+    checkResult(readArray({
+            "POLYGON (( 8 5, 9 4, 9 2, 1 2, 1 4, 2 4, 2 5, 1 5, 1 8, 9 8, 9 6, 8 5 ), ( 8 5, 7 6, 6 6, 6 4, 7 4, 8 5 ), ( 7 6, 8 6, 7 7, 7 6 ), ( 6 6, 6 7, 5 6, 6 6 ), ( 6 4, 5 4, 6 3, 6 4 ), ( 7 4, 7 3, 8 4, 7 4 ))" }),
+        4.0,
+        readArray({
+            "POLYGON (( 1 2, 1 8, 9 8, 8 5, 9 2, 1 2 ), ( 5 4, 6 3, 6 4, 5 4 ), ( 5 6, 6 6, 6 7, 5 6 ), ( 6 4, 7 4, 8 5, 7 6, 6 6, 6 4 ), ( 7 3, 8 4, 7 4, 7 3 ), ( 7 6, 8 6, 7 7, 7 6 ))" })
+    );
+}
+
+// testMultiPolygonWithTouchingShellsInner
+template<>
+template<>
+void object::test<13> ()
+{
+    checkResultInner(
+        readArray({
+            "MULTIPOLYGON ((( 2 7, 2 8, 3 8, 3 7, 2 7 )), (( 1 6, 1 7, 2 7, 2 6, 1 6 )), (( 0 7, 0 8, 1 8, 1 7, 0 7 )), (( 0 5, 0 6, 1 6, 1 5, 0 5 )), (( 2 5, 2 6, 3 6, 3 5, 2 5 )))"
+        }),
+        1.0,
+        readArray({
+            "MULTIPOLYGON ((( 2 7, 2 8, 3 8, 3 7, 2 7 )), (( 1 6, 1 7, 2 7, 2 6, 1 6 )), (( 0 7, 0 8, 1 8, 1 7, 0 7 )), (( 0 5, 0 6, 1 6, 1 5, 0 5 )), (( 2 5, 2 6, 3 6, 3 5, 2 5 )))"
+        })
+    );
+}
+
+// testMultiPolygonWithTouchingShells
+template<>
+template<>
+void object::test<14> ()
+{
+    checkResult(
+        readArray({
+            "MULTIPOLYGON ((( 2 7, 2 8, 3 8, 3 7, 2 7 )), (( 1 6, 1 7, 2 7, 2 6, 1 6 )), (( 0 7, 0 8, 1 8, 1 7, 0 7 )), (( 0 5, 0 6, 1 6, 1 5, 0 5 )), (( 2 5, 2 6, 3 6, 3 5, 2 5 )))" }),
+        1.0,
+        readArray({
+            "MULTIPOLYGON ((( 2 7, 3 8, 3 7, 2 7 )), (( 1 6, 1 7, 2 7, 2 6, 1 6 )), (( 1 7, 0 8, 1 8, 1 7 )), (( 1 6, 0 5, 0 6, 1 6 )), (( 2 6, 3 5, 2 5, 2 6 )))" })
+    );
+}
+
+// testTouchingShellsInner
+template<>
+template<>
+void object::test<15> ()
+{
+    checkResultInner(readArray({
+            "POLYGON ((0 0, 0 5, 5 6, 10 5, 10 0, 0 0))",
+            "POLYGON ((0 10, 5 6, 10 10, 0 10))" }),
+        4.0,
+        readArray({
+            "POLYGON ((0 0, 0 5, 5 6, 10 5, 10 0, 0 0))",
+            "POLYGON ((0 10, 5 6, 10 10, 0 10))" })
+    );
+}
+
+// testShellSimplificationAtStartingNode
+template<>
+template<>
+void object::test<16> ()
+{
+    checkResult(readArray({
+            "POLYGON (( 1 5, 1 7, 5 7, 5 3, 2 3, 1 5 ))" }),
+        1.5,
+        readArray({
+            "POLYGON ((1 7, 5 7, 5 3, 2 3, 1 7))" })
+    );
+}
+
+// testSimplifyInnerAtStartingNode
+template<>
+template<>
+void object::test<17> ()
+{
+    checkResultInner(readArray({
+        "POLYGON (( 0 5, 0 9, 6 9, 6 2, 1 2, 0 5 ), ( 1 5, 2 3, 5 3, 5 7, 1 7, 1 5 ))",
+            "POLYGON (( 1 5, 1 7, 5 7, 5 3, 2 3, 1 5 ))" }),
+        1.5,
+        readArray({
+            "POLYGON ((0 5, 0 9, 6 9, 6 2, 1 2, 0 5), (1 7, 2 3, 5 3, 5 7, 1 7))",
+            "POLYGON ((1 7, 5 7, 5 3, 2 3, 1 7))" })
+    );
+}
+
+// testSimplifyAllAtStartingNode
+template<>
+template<>
+void object::test<18> ()
+{
+    checkResult(readArray({
+            "POLYGON (( 0 5, 0 9, 6 9, 6 2, 1 2, 0 5 ), ( 1 5, 2 3, 5 3, 5 7, 1 7, 1 5 ))",
+            "POLYGON (( 1 5, 1 7, 5 7, 5 3, 2 3, 1 5 ))" }),
+        1.5,
+        readArray({
+            "POLYGON ((0 9, 6 9, 6 2, 1 2, 0 9), (1 7, 2 3, 5 3, 5 7, 1 7))",
+            "POLYGON ((1 7, 5 7, 5 3, 2 3, 1 7))" })
+    );
+}
+
+//---------------------------------
+// testInnerSimple
+template<>
+template<>
+void object::test<19> ()
+{
+    checkResultInner(readArray({
+        "POLYGON ((50 50, 50 150, 100 190, 100 200, 200 200, 160 150, 120 120, 90 80, 50 50))",
+        "POLYGON ((100 0, 50 50, 90 80, 120 120, 160 150, 200 200, 250 100, 170 50, 100 0))" }),
+        100,
+        readArray({
+            "POLYGON ((50 50, 50 150, 100 190, 100 200, 200 200, 50 50))",
+            "POLYGON ((200 200, 50 50, 100 0, 170 50, 250 100, 200 200))" })
+    );
+
+}
+
+
+
+
+} // namespace tut
diff --git a/tests/unit/coverage/TPVWSimplifierTest.cpp b/tests/unit/coverage/TPVWSimplifierTest.cpp
new file mode 100644
index 000000000..dbd2569ee
--- /dev/null
+++ b/tests/unit/coverage/TPVWSimplifierTest.cpp
@@ -0,0 +1,164 @@
+//
+// Test Suite for geos::coverage::TPVWSimplifier class.
+
+#include <tut/tut.hpp>
+#include <utility.h>
+
+// geos
+#include <geos/coverage/TPVWSimplifier.h>
+
+using geos::coverage::TPVWSimplifier;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by all tests
+struct test_tpvwsimplifier_data {
+
+    WKTReader r;
+    WKTWriter w;
+
+    test_tpvwsimplifier_data() {
+        w.setTrim(true);
+    }
+
+    void
+    checkNoop(
+        const std::string& wkt,
+        double tolerance)
+    {
+        std::unique_ptr<Geometry> geom = r.read(wkt);
+        const MultiLineString* mls = static_cast<const MultiLineString*>(geom.get());
+        std::unique_ptr<Geometry> actual = TPVWSimplifier::simplify(mls, tolerance);
+        ensure_equals_geometry(actual.get(), geom.get());
+    }
+
+    void
+    checkSimplify(
+        const std::string& wkt,
+        double tolerance,
+        const std::string& wktExpected)
+    {
+        std::unique_ptr<Geometry> geom = r.read(wkt);
+        const MultiLineString* mls = static_cast<const MultiLineString*>(geom.get());
+        std::unique_ptr<Geometry> actual = TPVWSimplifier::simplify(mls, tolerance);
+        std::unique_ptr<Geometry> expected = r.read(wktExpected);
+        ensure_equals_geometry(actual.get(), expected.get());
+    }
+
+    void
+    checkSimplify(
+        const std::string& wkt,
+        const std::vector<std::size_t> freeRingIndex,
+        double tolerance,
+        const std::string& wktExpected)
+    {
+        checkSimplify(wkt, freeRingIndex, "", tolerance, wktExpected);
+    }
+
+    void
+    checkSimplify(
+        const std::string& wkt,
+        const std::vector<std::size_t> freeRingIndex,
+        const std::string& wktConstraints,
+        double tolerance,
+        const std::string& wktExpected)
+    {
+        auto geom = r.read(wkt);
+        const MultiLineString* lines = static_cast<const MultiLineString*>(geom.get());
+
+        std::vector<bool> freeRings(lines->getNumGeometries(), false);
+        for (std::size_t index : freeRingIndex) {
+            freeRings[index] = true;
+        }
+        std::unique_ptr<Geometry> constraintsPtr(nullptr);
+        if (wktConstraints.length() > 0) {
+            constraintsPtr = r.read(wktConstraints);
+        }
+        const MultiLineString* constraints = static_cast<const MultiLineString*>(constraintsPtr.get());
+        std::unique_ptr<Geometry> actual = TPVWSimplifier::simplify(lines, freeRings, constraints, tolerance);
+        std::unique_ptr<Geometry> expected = r.read(wktExpected);
+
+        // std::cout << "-- actual" << std::endl;
+        // std::cout << w.write(*actual) << std::endl;
+        // std::cout << "-- expected" << std::endl;
+        // std::cout << w.write(*expected) << std::endl;
+
+        ensure_equals_geometry(actual.get(), expected.get());
+    }
+
+};
+
+
+typedef test_group<test_tpvwsimplifier_data> group;
+typedef group::object object;
+
+group test_tpvwsimplifier_data("geos::coverage::TPVWSimplifier");
+
+
+// testSimpleNoop
+template<>
+template<>
+void object::test<1> ()
+{
+    checkNoop(
+        "MULTILINESTRING ((9 9, 3 9, 1 4, 4 1, 9 1), (9 1, 2 4, 9 9))",
+        2);
+}
+
+// testSimple
+template<>
+template<>
+void object::test<2> ()
+{
+    checkSimplify(
+        "MULTILINESTRING ((9 9, 3 9, 1 4, 4 1, 9 1), (9 1, 6 3, 2 4, 5 7, 9 9))",
+        2,
+        "MULTILINESTRING ((9 9, 3 9, 1 4, 4 1, 9 1), (9 1, 2 4, 9 9))"
+    );
+}
+
+// testFreeRing
+template<>
+template<>
+void object::test<3> ()
+{
+    checkSimplify(
+        "MULTILINESTRING ((1 9, 9 9, 9 1), (1 9, 1 1, 9 1), (7 5, 8 8, 2 8, 2 2, 8 2, 7 5))",
+        { 2 },
+        2,
+        "MULTILINESTRING ((1 9, 1 1, 9 1), (1 9, 9 9, 9 1), (8 8, 2 8, 2 2, 8 2, 8 8))"
+    );
+}
+
+// testNoFreeRing
+template<>
+template<>
+void object::test<4> ()
+{
+    checkSimplify(
+        "MULTILINESTRING ((1 9, 9 9, 9 1), (1 9, 1 1, 9 1), (5 5, 4 8, 2 8, 2 2, 4 2, 5 5), (5 5, 6 8, 8 8, 8 2, 6 2, 5 5))",
+        {},
+        2,
+        "MULTILINESTRING ((1 9, 1 1, 9 1), (1 9, 9 9, 9 1), (5 5, 2 2, 2 8, 5 5), (5 5, 8 2, 8 8, 5 5))"
+    );
+}
+
+// testConstraint
+template<>
+template<>
+void object::test<5> ()
+{
+    checkSimplify(
+        "MULTILINESTRING ((6 8, 2 8, 2.1 5, 2 2, 6 2, 5.9 5, 6 8))",
+        {},
+        "MULTILINESTRING ((1 9, 9 9, 6 5, 9 1), (1 9, 1 1, 9 1))",
+        1,
+        "MULTILINESTRING ((6 8, 2 8, 2 2, 6 2, 5.9 5, 6 8))"
+    );
+}
+
+
+} // namespace tut
diff --git a/tests/unit/geom/CoordinateSequenceTest.cpp b/tests/unit/geom/CoordinateSequenceTest.cpp
index 3e7532ec3..3edce6068 100644
--- a/tests/unit/geom/CoordinateSequenceTest.cpp
+++ b/tests/unit/geom/CoordinateSequenceTest.cpp
@@ -7,6 +7,7 @@
 #include <geos/geom/CoordinateFilter.h>
 #include <geos/geom/CoordinateSequence.h>
 #include <geos/util.h>
+#include <geos/constants.h>
 #include <utility.h>
 // std
 #include <string>
@@ -925,7 +926,7 @@ void object::test<31>
     ensure_equals("z dimension ignored", CoordinateSequence::indexOf(&c1, &seq), 1u);
 
     CoordinateXY c2{9, 9};
-    ensure_equals("maxint when not found", CoordinateSequence::indexOf(&c2, &seq), std::numeric_limits<std::size_t>::max());
+    ensure_equals("maxint when not found", CoordinateSequence::indexOf(&c2, &seq), geos::NO_COORD_INDEX);
 }
 
 // Test add 4D to empty sequence with unspecified dimensionality
diff --git a/tests/unit/geom/CoordinateTest.cpp b/tests/unit/geom/CoordinateTest.cpp
index 996294ef5..1531fe845 100644
--- a/tests/unit/geom/CoordinateTest.cpp
+++ b/tests/unit/geom/CoordinateTest.cpp
@@ -270,7 +270,7 @@ template<>
 void object::test<10>
 ()
 {
-    std::unordered_set<Coordinate, Coordinate::HashCode> coords;
+    Coordinate::UnorderedSet coords;
 
     coords.emplace(1, 2);
     ensure_equals(coords.size(), 1ul);
diff --git a/tests/unit/utility.h b/tests/unit/utility.h
index 802142e13..51c8a9bbf 100644
--- a/tests/unit/utility.h
+++ b/tests/unit/utility.h
@@ -182,6 +182,7 @@ ensure_equals_geometry(T const* lhs_in, T const* rhs_in, double tolerance = 0.0)
 
     using geos::geom::Polygon;
     using geos::geom::GeometryCollection;
+    using geos::io::WKTWriter;
 
     // Take clones so we can normalize them
     std::unique_ptr<geos::geom::Geometry> lhs = lhs_in->clone();
@@ -213,17 +214,20 @@ ensure_equals_geometry(T const* lhs_in, T const* rhs_in, double tolerance = 0.0)
     ensure_equals("boundary dimension do not match",
                   lhs->getBoundaryDimension(), rhs->getBoundaryDimension());
 
-    // NOTE - mloskot: Intentionally disabled, so simplified geometry
-    // can be compared to its original
-    ensure_equals("number of points do not match",
-                  lhs->getNumPoints(), rhs->getNumPoints());
+    bool areaNumPointsEqual = lhs->getNumPoints() == rhs->getNumPoints();
+    bool areCoordsEqual = lhs->equalsExact(rhs.get(), tolerance);
 
-    bool areEqual = lhs->equalsExact(rhs.get(), tolerance);
-    if(!areEqual) {
-        std::cout << std::endl << rhs->toText() << std::endl << lhs->toText() << std::endl;
+    if(! (areCoordsEqual && areaNumPointsEqual)) {
+        WKTWriter writer;
+        writer.setTrim(true);
+        std::cout << std::endl
+            << writer.write(*rhs) << std::endl
+            << writer.write(*lhs) << std::endl;
     }
 
-    ensure("coordinates do not match", areEqual);
+    ensure("number of points do not match", areaNumPointsEqual);
+
+    ensure("coordinates do not match", areCoordsEqual);
     // Dispatch to run more specific testes
     // if(isInstanceOf<Polygon>(lhs)
     //         && isInstanceOf<Polygon>(rhs)) {

commit 007f18eccd1c385426a9a10e6360a940e8ae1a41
Author: Martin Davis <mtnclimb at gmail.com>
Date:   Tue Apr 11 07:45:50 2023 -0700

    Add doc bindings entry

diff --git a/web/content/usage/bindings.md b/web/content/usage/bindings.md
index 30990a756..e2ef32f18 100644
--- a/web/content/usage/bindings.md
+++ b/web/content/usage/bindings.md
@@ -53,6 +53,7 @@ Geospatial applications using GEOS include:
  * [MonetDB](http://monetdb.cwi.nl/)
  * [SpatiaLite](http://www.gaia-gis.it/spatialite/)
  * [CockroachDB](https://github.com/cockroachdb/cockroach)
+ * [DuckDB](https://github.com/duckdblabs/duckdb_spatial)
 
 ## Applications
 

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

Summary of changes:
 include/geos/constants.h                           |   2 +
 include/geos/coverage/Corner.h                     | 137 +++++++
 .../geos/coverage/CoverageBoundarySegmentFinder.h  |  83 +++++
 include/geos/coverage/CoverageEdge.h               | 176 +++++++++
 include/geos/coverage/CoverageRingEdges.h          | 184 ++++++++++
 include/geos/coverage/CoverageSimplifier.h         | 166 +++++++++
 include/geos/coverage/TPVWSimplifier.h             | 225 ++++++++++++
 include/geos/coverage/VertexCounter.h              |  71 ++++
 include/geos/geom/Coordinate.h                     |   4 +-
 include/geos/geom/LineSegment.h                    |  67 ++--
 include/geos/operation/buffer/OffsetCurve.h        |   2 +-
 include/geos/operation/union/CoverageUnion.h       |  24 +-
 include/geos/simplify/LinkedLine.h                 |  84 +++++
 include/geos/simplify/LinkedRing.h                 |   4 +-
 include/geos/simplify/RingHull.h                   |  45 ++-
 .../geos/triangulate/polygon/PolygonEarClipper.h   |   5 +-
 .../geos/triangulate/polygon/PolygonHoleJoiner.h   |   3 +-
 src/coverage/Corner.cpp                            | 164 +++++++++
 src/coverage/CoverageBoundarySegmentFinder.cpp     |  89 +++++
 src/coverage/CoverageEdge.cpp                      | 177 +++++++++
 src/coverage/CoverageRingEdges.cpp                 | 408 +++++++++++++++++++++
 src/coverage/CoverageSimplifier.cpp                | 151 ++++++++
 src/coverage/TPVWSimplifier.cpp                    | 332 +++++++++++++++++
 src/coverage/VertexCounter.cpp                     |  66 ++++
 src/geom/CoordinateSequence.cpp                    |   3 +-
 src/geom/LineSegment.cpp                           |  21 +-
 src/operation/buffer/OffsetCurve.cpp               |  10 +-
 src/operation/polygonize/BuildArea.cpp             |   2 +-
 src/simplify/LinkedLine.cpp                        | 183 +++++++++
 src/simplify/RingHull.cpp                          |   4 +-
 src/triangulate/polygon/PolygonEarClipper.cpp      |  12 +-
 src/triangulate/polygon/PolygonHoleJoiner.cpp      |   6 +-
 tests/unit/coverage/CoverageRingEdgesTest.cpp      | 141 +++++++
 tests/unit/coverage/CoverageSimplifierTest.cpp     | 373 +++++++++++++++++++
 tests/unit/coverage/TPVWSimplifierTest.cpp         | 164 +++++++++
 tests/unit/geom/CoordinateSequenceTest.cpp         |   3 +-
 tests/unit/geom/CoordinateTest.cpp                 |   2 +-
 tests/unit/utility.h                               |  20 +-
 web/content/usage/bindings.md                      |   1 +
 39 files changed, 3512 insertions(+), 102 deletions(-)
 create mode 100644 include/geos/coverage/Corner.h
 create mode 100644 include/geos/coverage/CoverageBoundarySegmentFinder.h
 create mode 100644 include/geos/coverage/CoverageEdge.h
 create mode 100644 include/geos/coverage/CoverageRingEdges.h
 create mode 100644 include/geos/coverage/CoverageSimplifier.h
 create mode 100644 include/geos/coverage/TPVWSimplifier.h
 create mode 100644 include/geos/coverage/VertexCounter.h
 create mode 100644 include/geos/simplify/LinkedLine.h
 create mode 100644 src/coverage/Corner.cpp
 create mode 100644 src/coverage/CoverageBoundarySegmentFinder.cpp
 create mode 100644 src/coverage/CoverageEdge.cpp
 create mode 100644 src/coverage/CoverageRingEdges.cpp
 create mode 100644 src/coverage/CoverageSimplifier.cpp
 create mode 100644 src/coverage/TPVWSimplifier.cpp
 create mode 100644 src/coverage/VertexCounter.cpp
 create mode 100644 src/simplify/LinkedLine.cpp
 create mode 100644 tests/unit/coverage/CoverageRingEdgesTest.cpp
 create mode 100644 tests/unit/coverage/CoverageSimplifierTest.cpp
 create mode 100644 tests/unit/coverage/TPVWSimplifierTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list