[geos-commits] [SCM] GEOS branch main updated. 31399d845873fa03caee5141f7c355cbd0660c83

git at osgeo.org git at osgeo.org
Fri May 9 13:33:15 PDT 2025


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  31399d845873fa03caee5141f7c355cbd0660c83 (commit)
      from  a31dd3557f0c1b9b7365de8e665d00492833a232 (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 31399d845873fa03caee5141f7c355cbd0660c83
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Fri May 9 13:32:24 2025 -0700

    Port LineDissolver from JTS

diff --git a/include/geos/dissolve/DissolveEdgeGraph.h b/include/geos/dissolve/DissolveEdgeGraph.h
new file mode 100644
index 000000000..0283c970f
--- /dev/null
+++ b/include/geos/dissolve/DissolveEdgeGraph.h
@@ -0,0 +1,68 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2025 Martin Davis
+ * Copyright (C) 2025 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/edgegraph/EdgeGraph.h>
+#include <geos/export.h>
+#include <geos/dissolve/DissolveHalfEdge.h>
+
+#include <deque>
+
+// Forward declarations
+namespace geos {
+namespace geom {
+    class CoordinateXYZM;
+}
+namespace edgegraph {
+    class HalfEdge;
+}
+namespace dissolve {
+    // class DissolveHalfEdge;
+}
+}
+
+
+namespace geos {      // geos.
+namespace dissolve {  // geos.dissolve
+
+
+class GEOS_DLL DissolveEdgeGraph : public edgegraph::EdgeGraph {
+
+
+private:
+
+    std::deque<DissolveHalfEdge> dhEdges;
+
+public:
+
+    edgegraph::HalfEdge* createEdge(const geom::CoordinateXYZM& p0) override;
+
+    DissolveEdgeGraph() {};
+    ~DissolveEdgeGraph() {};
+
+    /**
+     * Disable copy construction and assignment. Needed to make this
+     * class compile under MSVC, because it has a vector<unique_ptr>
+     * as a member. (See https://stackoverflow.com/q/29565299)
+     */
+    DissolveEdgeGraph(const DissolveEdgeGraph&) = delete;
+    DissolveEdgeGraph& operator=(const DissolveEdgeGraph&) = delete;
+
+};
+
+} // namespace geos.dissolve
+} // namespace geos
+
diff --git a/include/geos/dissolve/DissolveHalfEdge.h b/include/geos/dissolve/DissolveHalfEdge.h
new file mode 100644
index 000000000..0168c199f
--- /dev/null
+++ b/include/geos/dissolve/DissolveHalfEdge.h
@@ -0,0 +1,69 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2025 Martin Davis
+ * Copyright (C) 2025 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/edgegraph/MarkHalfEdge.h>
+#include <geos/export.h>
+
+
+
+// Forward declarations
+namespace geos {
+namespace geom {
+    class CoordinateXYZM;
+}
+namespace edgegraph {
+}
+}
+
+
+namespace geos {      // geos.
+namespace dissolve {  // geos.dissolve
+
+
+class GEOS_DLL DissolveHalfEdge : public edgegraph::MarkHalfEdge {
+
+
+private:
+
+    bool m_isStart = false;
+
+
+public:
+
+    DissolveHalfEdge(const geom::CoordinateXYZM& orig)
+        : edgegraph::MarkHalfEdge(orig)
+        , m_isStart(false)
+    {}
+
+    /**
+     * Tests whether this edge is the starting segment
+     * in a LineString being dissolved.
+     *
+     * @return true if this edge is a start segment
+     */
+    bool isStart();
+
+    /**
+     * Sets this edge to be the start segment of an input LineString.
+     */
+    void setStart();
+
+};
+
+} // namespace geos.dissolve
+} // namespace geos
+
diff --git a/include/geos/dissolve/LineDissolver.h b/include/geos/dissolve/LineDissolver.h
new file mode 100644
index 000000000..cfae61f80
--- /dev/null
+++ b/include/geos/dissolve/LineDissolver.h
@@ -0,0 +1,195 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2025 Martin Davis
+ * Copyright (C) 2025 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/dissolve/DissolveEdgeGraph.h>
+// #include <memory>
+// #include <vector>
+// #include <stack>
+#include <geos/geom/LineString.h>
+#include <geos/export.h>
+
+
+// Forward declarations
+namespace geos {
+namespace dissolve {
+    class DissolveHalfEdge;
+}
+namespace edgegraph {
+    class HalfEdge;
+}
+namespace geom {
+    class CoordinateSequence;
+    class Geometry;
+    class GeometryFactory;
+    // class LineString;
+}
+}
+
+
+namespace geos {      // geos.
+namespace dissolve {  // geos.dissolve
+
+
+/**
+ * Dissolves the linear components
+ * from a collection of Geometry
+ * into a set of maximal-length LineString
+ * in which every unique segment appears once only.
+ * The output linestrings run between node vertices
+ * of the input, which are vertices which have
+ * either degree 1, or degree 3 or greater.
+ *
+ * Use cases for dissolving linear components
+ * include generalization
+ * (in particular, simplifying polygonal coverages),
+ * and visualization
+ * (in particular, avoiding symbology conflicts when
+ * depicting shared polygon boundaries).
+ *
+ * This class does **not** node the input lines.
+ * If there are line segments crossing in the input,
+ * they will still cross in the output.
+ *
+ * @author Martin Davis
+ *
+ */
+class GEOS_DLL LineDissolver {
+
+    using CoordinateSequence = geos::geom::CoordinateSequence;
+    using Geometry = geos::geom::Geometry;
+    using GeometryFactory = geos::geom::GeometryFactory;
+    using LineString = geos::geom::LineString;
+    using HalfEdge = geos::edgegraph::HalfEdge;
+
+private:
+
+    std::unique_ptr<Geometry> result;
+    const GeometryFactory* factory = nullptr;
+    DissolveEdgeGraph graph;
+    std::vector<std::unique_ptr<LineString>> lines;
+    std::stack<HalfEdge*> nodeEdgeStack;
+    DissolveHalfEdge* ringStartEdge = nullptr;
+
+
+    void computeResult();
+
+    void process(HalfEdge* e);
+
+    /**
+     * Adds edges around this node to the stack.
+     *
+     * @param node
+     */
+    void stackEdges(HalfEdge* node);
+
+    /**
+     * For each edge in stack
+     * (which must originate at a node)
+     * extracts the line it initiates.
+     */
+    void buildLines();
+
+    /**
+     * Updates the tracked ringStartEdge
+     * if the given edge has a lower origin
+     * (using the standard Coordinate ordering).
+     *
+     * Identifying the lowest starting node meets two goals:
+     *
+     *  * It ensures that isolated input rings are created using the original node and orientation
+     *  * For isolated rings formed from multiple input linestrings,
+     *    it provides a canonical node and orientation for the output
+     *    (rather than essentially random, and thus hard to test).
+     *
+     * @param e
+     */
+    void updateRingStartEdge(DissolveHalfEdge* e);
+
+    /**
+     * Builds a line starting from the given edge.
+     * The start edge origin is a node (valence = 1 or >= 3),
+     * unless it is part of a pure ring.
+     * A pure ring has no other incident lines.
+     * In this case the start edge may occur anywhere on the ring.
+     *
+     * The line is built up to the next node encountered,
+     * or until the start edge is re-encountered
+     * (which happens if the edges form a ring).
+     *
+     * @param eStart
+     */
+    void buildLine(HalfEdge* eStart);
+
+    void buildRing(HalfEdge* eStartRing);
+
+    void addLine(std::unique_ptr<CoordinateSequence>& cs);
+
+
+public:
+
+    LineDissolver() : result(nullptr) {};
+
+    /**
+     * Dissolves the linear components in a geometry.
+     *
+     * @param g the geometry to dissolve
+     * @return the dissolved lines
+     */
+    static std::unique_ptr<Geometry> dissolve(const Geometry* g);
+
+    /**
+     * Adds a Geometry to be dissolved.
+     * Any number of geometries may be added by calling this method multiple times.
+     * Any type of Geometry may be added.  The constituent linework will be
+     * extracted to be dissolved.
+     *
+     * @param geometry geometry to be line-merged
+     */
+    void add(const Geometry* geometry);
+
+    /**
+     * Adds a collection of Geometries to be processed. May be called multiple times.
+     * Any dimension of Geometry may be added; the constituent linework will be
+     * extracted.
+     *
+     * @param geometries the geometries to be line-merged
+     */
+    void add(std::vector<const Geometry*> geometries);
+
+    void add(const LineString* lineString);
+
+    /**
+     * Gets the dissolved result as a MultiLineString.
+     *
+     * @return the dissolved lines
+     */
+    std::unique_ptr<Geometry> getResult();
+
+    /**
+     * Disable copy construction and assignment. Needed to make this
+     * class compile under MSVC, because it has a vector<unique_ptr>
+     * as a member. (See https://stackoverflow.com/q/29565299)
+     */
+    LineDissolver(const LineDissolver&) = delete;
+    LineDissolver& operator=(const LineDissolver&) = delete;
+
+
+};
+
+} // namespace geos.dissolve
+} // namespace geos
+
diff --git a/include/geos/edgegraph/EdgeGraph.h b/include/geos/edgegraph/EdgeGraph.h
index 23422a646..c6705bd15 100644
--- a/include/geos/edgegraph/EdgeGraph.h
+++ b/include/geos/edgegraph/EdgeGraph.h
@@ -67,7 +67,7 @@ protected:
     * @param orig the origin location
     * @return a new HalfEdge with the given origin
     */
-    HalfEdge* createEdge(const geom::CoordinateXYZM& orig);
+    virtual HalfEdge* createEdge(const geom::CoordinateXYZM& orig);
 
     /**
     * Inserts an edge not already present into the graph.
@@ -86,6 +86,7 @@ public:
     * Initialized
     */
     EdgeGraph() {};
+    virtual ~EdgeGraph() = default;
 
     /**
     * Adds an edge between the coordinates orig and dest
diff --git a/src/dissolve/DissolveEdgeGraph.cpp b/src/dissolve/DissolveEdgeGraph.cpp
new file mode 100644
index 000000000..0789e8dce
--- /dev/null
+++ b/src/dissolve/DissolveEdgeGraph.cpp
@@ -0,0 +1,41 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2025 Martin Davis
+ * Copyright (C) 2025 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.
+ *
+ **********************************************************************/
+
+#include <geos/dissolve/DissolveEdgeGraph.h>
+
+#include <geos/edgegraph/HalfEdge.h>
+#include <geos/geom/Coordinate.h>
+
+
+using geos::edgegraph::HalfEdge;
+using geos::geom::CoordinateXYZM;
+
+
+namespace geos {      // geos
+namespace dissolve {  // geos.dissolve
+
+
+HalfEdge*
+DissolveEdgeGraph::createEdge(const CoordinateXYZM& p0)
+{
+    dhEdges.emplace_back(p0);
+    return &(dhEdges.back());
+}
+
+
+} // namespace geos.dissolve
+} // namespace geos
+
+
diff --git a/src/dissolve/DissolveHalfEdge.cpp b/src/dissolve/DissolveHalfEdge.cpp
new file mode 100644
index 000000000..0b48b5391
--- /dev/null
+++ b/src/dissolve/DissolveHalfEdge.cpp
@@ -0,0 +1,46 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2025 Martin Davis
+ * Copyright (C) 2025 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.
+ *
+ **********************************************************************/
+
+#include <geos/dissolve/DissolveHalfEdge.h>
+
+namespace geos {      // geos
+namespace dissolve {  // geos.dissolve
+
+/**
+ * Tests whether this edge is the starting segment
+ * in a LineString being dissolved.
+ *
+ * @return true if this edge is a start segment
+ */
+bool
+DissolveHalfEdge::isStart()
+{
+    return m_isStart;
+}
+
+/**
+ * Sets this edge to be the start segment of an input LineString.
+ */
+void
+DissolveHalfEdge::setStart()
+{
+    m_isStart = true;
+}
+
+
+} // namespace geos.dissolve
+} // namespace geos
+
+
diff --git a/src/dissolve/LineDissolver.cpp b/src/dissolve/LineDissolver.cpp
new file mode 100644
index 000000000..2c036cc0d
--- /dev/null
+++ b/src/dissolve/LineDissolver.cpp
@@ -0,0 +1,275 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2025 Martin Davis
+ * Copyright (C) 2025 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.
+ *
+ **********************************************************************/
+
+#include <geos/dissolve/LineDissolver.h>
+
+#include <geos/dissolve/DissolveHalfEdge.h>
+#include <geos/edgegraph/HalfEdge.h>
+#include <geos/edgegraph/MarkHalfEdge.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateList.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryComponentFilter.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/LineString.h>
+
+
+using namespace geos::geom;
+using geos::edgegraph::HalfEdge;
+using geos::edgegraph::MarkHalfEdge;
+
+
+namespace geos {      // geos
+namespace dissolve {  // geos.dissolve
+
+
+/* public */
+std::unique_ptr<Geometry>
+LineDissolver::dissolve(const Geometry* g)
+{
+    LineDissolver d;
+    d.add(g);
+    return d.getResult();
+}
+
+
+/* public */
+void
+LineDissolver::add(const Geometry* geom)
+{
+    struct LineStringFilter : public GeometryComponentFilter {
+
+        LineDissolver *m_ld;
+
+        LineStringFilter(LineDissolver* ld) : m_ld(ld) {};
+
+        void filter_ro(const Geometry* g) override {
+            GeometryTypeId type = g->getGeometryTypeId();
+            if (type == GEOS_LINEARRING || type == GEOS_LINESTRING) {
+                m_ld->add(static_cast<const LineString*>(g));
+            }
+        }
+    };
+
+    LineStringFilter filter(this);
+    geom->apply_ro(&filter);
+}
+
+
+/* public */
+void
+LineDissolver::add(std::vector<const Geometry*> geometries)
+{
+    for (const Geometry* geom : geometries) {
+        add(geom);
+    }
+}
+
+
+/* private */
+void
+LineDissolver::add(const LineString* lineString)
+{
+    if (factory == nullptr) {
+        factory = lineString->getFactory();
+    }
+    const CoordinateSequence* seq = lineString->getCoordinatesRO();
+    bool doneStart = false;
+    for (std::size_t i = 1; i < seq->size(); i++) {
+        CoordinateXYZM orig, dest;
+        seq->getAt(i-1, orig);
+        seq->getAt(i,   dest);
+        DissolveHalfEdge* e = static_cast<DissolveHalfEdge*>(graph.addEdge(orig, dest));
+        // skip zero-length edges
+        if (e == nullptr) continue;
+        /**
+         * Record source initial segments, so that they can be reflected in output when needed
+         * (i.e. during formation of isolated rings)
+         */
+        if (! doneStart) {
+            e->setStart();
+            doneStart = true;
+        }
+    }
+}
+
+
+/* public */
+std::unique_ptr<Geometry>
+LineDissolver::getResult()
+{
+    if (result == nullptr)
+        computeResult();
+    return std::move(result);
+}
+
+
+/* private */
+void
+LineDissolver::computeResult()
+{
+    std::vector<const HalfEdge*> edges;
+    graph.getVertexEdges(edges);
+
+    for (const HalfEdge* ce : edges) {
+        HalfEdge* e = const_cast<HalfEdge*>(ce);
+        if (MarkHalfEdge::isMarked(e)) continue;
+        process(e);
+    }
+    result = factory->buildGeometry(std::move(lines));
+}
+
+
+/* private */
+void
+LineDissolver::process(HalfEdge* e)
+{
+    HalfEdge* eNode = e->prevNode();
+    // if edge is in a ring, just process this edge
+    if (eNode == nullptr)
+        eNode = e;
+    stackEdges(eNode);
+    // extract lines from node edges in stack
+    buildLines();
+}
+
+
+/* private */
+void
+LineDissolver::stackEdges(HalfEdge* node)
+{
+    HalfEdge* e = node;
+    do {
+        if (! MarkHalfEdge::isMarked(e))
+            nodeEdgeStack.push(e);
+        e = e->oNext();
+    } while (e != node);
+}
+
+
+/* private */
+void
+LineDissolver::buildLines()
+{
+    while (! nodeEdgeStack.empty()) {
+        HalfEdge* e = nodeEdgeStack.top();
+        nodeEdgeStack.pop();
+        if (MarkHalfEdge::isMarked(e))
+            continue;
+        buildLine(e);
+    }
+}
+
+
+/* private */
+void
+LineDissolver::updateRingStartEdge(DissolveHalfEdge* e)
+{
+    if (! e->isStart()) {
+        e = static_cast<DissolveHalfEdge*>(e->sym());
+        if (! e->isStart()) return;
+    }
+    // here e is known to be a start edge
+    if (ringStartEdge == nullptr) {
+        ringStartEdge = e;
+        return;
+    }
+    if (e->orig().compareTo(ringStartEdge->orig()) < 0) {
+        ringStartEdge = e;
+    }
+}
+
+
+/* private */
+void
+LineDissolver::buildLine(HalfEdge* eStart)
+{
+    std::unique_ptr<CoordinateSequence> line(new CoordinateSequence(0, 4));
+    DissolveHalfEdge* e = static_cast<DissolveHalfEdge*>(eStart);
+    ringStartEdge = nullptr;
+
+    MarkHalfEdge::markBoth(e);
+    line->add(e->orig(), false);
+
+    // scan along the path until a node is found (if one exists)
+    while (e->sym()->degree() == 2) {
+        updateRingStartEdge(e);
+        DissolveHalfEdge* eNext = static_cast<DissolveHalfEdge*>(e->next());
+        // check if edges form a ring - if so, we're done
+        if (eNext == eStart)  {
+            buildRing(ringStartEdge);
+            return;
+        }
+
+        // add point to line, and move to next edge
+        line->add(eNext->orig(), false);
+
+        e = eNext;
+        MarkHalfEdge::markBoth(e);
+    }
+
+    // add final node
+    line->add(e->dest(), false);
+
+    // queue up the final node edges
+    stackEdges(e->sym());
+    // store the scanned line
+    addLine(line);
+}
+
+
+/* private */
+void
+LineDissolver::buildRing(HalfEdge* eStartRing)
+{
+    std::unique_ptr<CoordinateSequence> line(new CoordinateSequence(0, 4));
+    HalfEdge* e = eStartRing;
+
+    // add first node
+    line->add(e->orig(), false);
+
+    // scan along the path until a node is found (if one exists)
+    while (e->sym()->degree() == 2) {
+        HalfEdge* eNext = e->next();
+        // check if edges form a ring - if so, we're done
+        if (eNext == eStartRing)
+            break;
+
+        // add point to line, and move to next edge
+        line->add(eNext->orig(), false);
+        e = eNext;
+    }
+    // add final node
+    line->add(e->dest(), false);
+
+    // store the scanned line
+    addLine(line);
+}
+
+
+/* private */
+void
+LineDissolver::addLine(std::unique_ptr<CoordinateSequence>& cs)
+{
+    auto ls = factory->createLineString(std::move(cs));
+    lines.emplace_back(ls.release());
+}
+
+
+} // namespace geos.dissolve
+} // namespace geos
+
+
diff --git a/src/operation/GeometryGraphOperation.cpp b/src/operation/GeometryGraphOperation.cpp
index 65b5c298c..41249ca0a 100644
--- a/src/operation/GeometryGraphOperation.cpp
+++ b/src/operation/GeometryGraphOperation.cpp
@@ -54,9 +54,9 @@ GeometryGraphOperation::GeometryGraphOperation(const Geometry* g0,
         setComputationPrecision(pm1);
     }
 
-    arg[0] = std::make_unique<GeometryGraph>(0, g0,
+    arg[0] = std::make_unique<GeometryGraph>((uint8_t)0, g0,
                                algorithm::BoundaryNodeRule::getBoundaryOGCSFS());
-    arg[1] = std::make_unique<GeometryGraph>(1, g1,
+    arg[1] = std::make_unique<GeometryGraph>((uint8_t)1, g1,
                                algorithm::BoundaryNodeRule::getBoundaryOGCSFS());
 }
 
diff --git a/tests/unit/dissolve/LineDissolverTest.cpp b/tests/unit/dissolve/LineDissolverTest.cpp
new file mode 100644
index 000000000..7723aad08
--- /dev/null
+++ b/tests/unit/dissolve/LineDissolverTest.cpp
@@ -0,0 +1,214 @@
+//
+// Test Suite for geos::coverage::LineDissolver class.
+
+#include <tut/tut.hpp>
+#include <utility.h>
+#include <string>
+#include <memory>
+
+// geos
+#include <geos/dissolve/LineDissolver.h>
+
+using geos::dissolve::LineDissolver;
+
+namespace tut {
+
+// Common data used by all tests
+struct test_linedissolver_data {
+
+    WKTReader r;
+    WKTWriter w;
+
+    test_linedissolver_data() {}
+
+    void checkDissolve(const std::string& wkt, const std::string& wkt_expected)
+    {
+        std::vector<std::string> wkt_vec;
+        wkt_vec.push_back(wkt);
+        checkDissolve(wkt_vec, wkt_expected);
+    }
+
+    void checkDissolve(std::vector<std::string>& wkt_vec, const std::string& wkt_expected)
+    {
+        std::vector<std::unique_ptr<Geometry>> geom_store;
+        std::vector<const Geometry*> geoms;
+        for (const std::string& wkt : wkt_vec) {
+            std::unique_ptr<Geometry> geom = r.read(wkt);
+            geoms.push_back(geom.get());
+            geom_store.emplace_back(geom.release());
+        }
+        std::unique_ptr<Geometry> geom_expected = r.read(wkt_expected);
+        checkDissolve(geoms, geom_expected.get());
+    }
+
+    void checkDissolve(
+        std::vector<const Geometry*>& geoms,
+        const Geometry* geom_expected)
+    {
+        LineDissolver d;
+        d.add(geoms);
+        std::unique_ptr<Geometry> result = d.getResult();
+        ensure_equals_geometry(geom_expected, result.get());
+    }
+};
+
+
+typedef test_group<test_linedissolver_data> group;
+typedef group::object object;
+
+group test_linedissolver_data("geos::dissolve::LineDissolver");
+
+
+// testSingleSegmentLine
+template<>
+template<>
+void object::test<1> ()
+{
+    checkDissolve(
+        "LINESTRING (0 0, 1 1)",
+        "LINESTRING (0 0, 1 1)");
+}
+
+// testTwoSegmentLine
+template<>
+template<>
+void object::test<2>()
+{
+    checkDissolve(
+        "LINESTRING (0 0, 1 1, 2 2)",
+        "LINESTRING (0 0, 1 1, 2 2)");
+}
+
+// testOverlappingTwoSegmentLines
+template<>
+template<>
+void object::test<3>()
+{
+    std::vector<std::string> wkts = {
+        "LINESTRING (0 0, 1 1, 2 2)",
+        "LINESTRING (1 1, 2 2, 3 3)"};
+
+    checkDissolve(
+        wkts,
+        "LINESTRING (0 0, 1 1, 2 2, 3 3)");
+}
+
+// testOverlappingLines3
+template<>
+template<>
+void object::test<4>()
+{
+    std::vector<std::string> wkts = {
+        "LINESTRING (0 0, 1 1, 2 2)",
+        "LINESTRING (1 1, 2 2, 3 3)",
+        "LINESTRING (1 1, 2 2, 2 0)" };
+
+    checkDissolve(wkts,
+        "MULTILINESTRING ((0 0, 1 1, 2 2), (2 0, 2 2), (2 2, 3 3))");
+}
+
+// testDivergingLines
+template<>
+template<>
+void object::test<5>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 1 0, 2 1), (0 0, 1 0, 2 0), (1 0, 2 1, 2 0, 3 0))",
+        "MULTILINESTRING ((0 0, 1 0), (1 0, 2 0), (1 0, 2 1, 2 0), (2 0, 3 0))");
+}
+
+// testLollipop
+template<>
+template<>
+void object::test<6>()
+{
+    checkDissolve(
+        "LINESTRING (0 0, 1 0, 2 0, 2 1, 1 0, 0 0)",
+        "MULTILINESTRING ((0 0, 1 0), (1 0, 2 0, 2 1, 1 0))");
+}
+
+// testDisjointLines
+template<>
+template<>
+void object::test<7>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 1 0, 2 1), (10 0, 11 0, 12 0))",
+        "MULTILINESTRING ((0 0, 1 0, 2 1), (10 0, 11 0, 12 0))");
+}
+
+// testSingleLine
+template<>
+template<>
+void object::test<8>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 1 0, 2 1))",
+        "LINESTRING (0 0, 1 0, 2 1)");
+}
+
+// testOneSegmentY
+template<>
+template<>
+void object::test<9>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 1 1, 2 2), (1 1, 1 2))",
+        "MULTILINESTRING ((0 0, 1 1), (1 1, 2 2), (1 1, 1 2))");
+}
+
+// testTwoSegmentY
+template<>
+template<>
+void object::test<10>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 9 9, 10 10, 11 11, 20 20), (10 10, 10 20))",
+        "MULTILINESTRING ((10 20, 10 10), (10 10, 9 9, 0 0), (10 10, 11 11, 20 20))");
+}
+
+// testIsolatedRing
+template<>
+template<>
+void object::test<11>()
+{
+    checkDissolve(
+        "LINESTRING (0 0, 1 1, 1 0, 0 0)",
+        "LINESTRING (0 0, 1 1, 1 0, 0 0)");
+}
+
+// testIsolateRingFromMultipleLineStrings
+template<>
+template<>
+void object::test<12>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 1 0, 1 1), (0 0, 0 1, 1 1))",
+        "LINESTRING (0 0, 0 1, 1 1, 1 0, 0 0)");
+}
+
+/**
+* Shows that rings with incident lines are created with the correct node point.
+*/
+// testRingWithTail
+template<>
+template<>
+void object::test<13>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 1 0, 1 1), (0 0, 0 1, 1 1), (1 0, 2 0))",
+        "MULTILINESTRING ((1 0, 0 0, 0 1, 1 1, 1 0), (1 0, 2 0))");
+}
+
+// testZeroLengthStartSegment
+template<>
+template<>
+void object::test<14>()
+{
+    checkDissolve(
+        "MULTILINESTRING ((0 0, 0 0, 2 1))",
+        "LINESTRING (0 0, 2 1)");
+}
+
+
+} // namespace tut

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

Summary of changes:
 include/geos/dissolve/DissolveEdgeGraph.h          |  68 +++++
 include/geos/dissolve/DissolveHalfEdge.h           |  69 ++++++
 include/geos/dissolve/LineDissolver.h              | 195 +++++++++++++++
 include/geos/edgegraph/EdgeGraph.h                 |   3 +-
 .../EdgeKey.cpp => dissolve/DissolveEdgeGraph.cpp} |  27 +-
 src/dissolve/DissolveHalfEdge.cpp                  |  46 ++++
 src/dissolve/LineDissolver.cpp                     | 275 +++++++++++++++++++++
 src/operation/GeometryGraphOperation.cpp           |   4 +-
 tests/unit/dissolve/LineDissolverTest.cpp          | 214 ++++++++++++++++
 9 files changed, 892 insertions(+), 9 deletions(-)
 create mode 100644 include/geos/dissolve/DissolveEdgeGraph.h
 create mode 100644 include/geos/dissolve/DissolveHalfEdge.h
 create mode 100644 include/geos/dissolve/LineDissolver.h
 copy src/{operation/overlayng/EdgeKey.cpp => dissolve/DissolveEdgeGraph.cpp} (50%)
 create mode 100644 src/dissolve/DissolveHalfEdge.cpp
 create mode 100644 src/dissolve/LineDissolver.cpp
 create mode 100644 tests/unit/dissolve/LineDissolverTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list