[geos-commits] [SCM] GEOS branch main updated. 7445d96c1d10df18418b73f85a1b6c4d05381498

git at osgeo.org git at osgeo.org
Tue Apr 28 06:49:59 PDT 2026


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  7445d96c1d10df18418b73f85a1b6c4d05381498 (commit)
       via  9c6791c064feb003742fd0cc278deee1f7ca02ec (commit)
       via  1b37ecd1f94563a4b043a1f5c00eaf0e181556c7 (commit)
       via  ab5581b312b28f5677bc08da46988758e41565ff (commit)
       via  9dc15f5ac81cacacb7319b8a8dd6013d09e99cca (commit)
       via  6f3ca8b02589e4d4aedc12e2ff6262af7c274f7e (commit)
       via  37d194e042b90981ab98449e64de3b6848104c17 (commit)
      from  32ccf683317a2f649fea96b8a930f6515823a451 (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 7445d96c1d10df18418b73f85a1b6c4d05381498
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Apr 22 11:38:30 2026 -0400

    C API: Add GEOSSplit

diff --git a/capi/geos_c.cpp b/capi/geos_c.cpp
index 1341f6365..477fd0fcf 100644
--- a/capi/geos_c.cpp
+++ b/capi/geos_c.cpp
@@ -720,6 +720,12 @@ extern "C" {
         return GEOSNode_r(handle, g);
     }
 
+    Geometry*
+    GEOSSplit(const Geometry* g, const Geometry* edge)
+    {
+        return GEOSSplit_r(handle, g, edge);
+    }
+
     Geometry*
     GEOSUnionCascaded(const Geometry* g)
     {
diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index c941d50dd..c7fb1d389 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -1168,6 +1168,12 @@ extern GEOSGeometry GEOS_DLL *GEOSNode_r(
     GEOSContextHandle_t handle,
     const GEOSGeometry* g);
 
+/** \see GEOSSplit */
+extern GEOSGeometry GEOS_DLL *GEOSSplit_r(
+    GEOSContextHandle_t handle,
+    const GEOSGeometry* g,
+    const GEOSGeometry* edge);
+
 /** \see GEOSClipByRect */
 extern GEOSGeometry GEOS_DLL *GEOSClipByRect_r(
     GEOSContextHandle_t handle,
@@ -5259,6 +5265,24 @@ extern GEOSGeometry GEOS_DLL * GEOSVoronoiDiagram(
 */
 extern GEOSGeometry GEOS_DLL *GEOSNode(const GEOSGeometry* g);
 
+
+/** Split a linear or polygonal input 
+* 
+* Linear inputs can be split with points, lines, and/or polygons.
+* Polygons can be split with lines and/or polygons.
+*
+* The returned geometry will be a GeometryCollection containing the
+* split elements.
+*
+* \param g The geometry to split
+* \param edge the geometry containing splitting edges/points
+* \return the split geometry or NULL on exception.
+*  Caller is responsible for freeing with GEOSGeom_destroy().
+*
+* \since 3.15
+*/
+extern GEOSGeometry GEOS_DLL *GEOSSplit(const GEOSGeometry* g, const GEOSGeometry* edge);
+
 /**
 * Polygonizes a set of Geometries which contain linework that
 * represents the edges of a planar graph.
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index fe58078a5..5568b7731 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -86,6 +86,7 @@
 #include <geos/operation/grid/GridIntersection.h>
 #include <geos/operation/linemerge/LineMerger.h>
 #include <geos/operation/spanning/SpanningTree.h>
+#include <geos/operation/split/GeometrySplitter.h>
 #include <geos/operation/intersection/Rectangle.h>
 #include <geos/operation/intersection/RectangleIntersection.h>
 #include <geos/operation/overlay/snap/GeometrySnapper.h>
@@ -1818,6 +1819,16 @@ extern "C" {
         });
     }
 
+    Geometry*
+    GEOSSplit_r(GEOSContextHandle_t extHandle, const Geometry* g, const Geometry* edge)
+    {
+        return execute(extHandle, [&]() {
+            auto g3 = geos::operation::split::GeometrySplitter::split(*g, *edge);
+            g3->setSRID(g->getSRID());
+            return g3.release();
+        });
+    }
+
     Geometry*
     GEOSUnionCascaded_r(GEOSContextHandle_t extHandle, const Geometry* g1)
     {
diff --git a/tests/unit/capi/GEOSSplitTest.cpp b/tests/unit/capi/GEOSSplitTest.cpp
new file mode 100644
index 000000000..c6c795088
--- /dev/null
+++ b/tests/unit/capi/GEOSSplitTest.cpp
@@ -0,0 +1,53 @@
+#include <tut/tut.hpp>
+// geos
+#include <geos_c.h>
+// std
+
+#include "capi_test_utils.h"
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used in test cases.
+struct test_capigeossplit_data : public capitest::utility {
+};
+
+typedef test_group<test_capigeossplit_data> group;
+typedef group::object object;
+
+group test_capigeossplit_group("capi::GEOSSplit");
+
+//
+// Test Cases
+//
+
+template<>
+template<>
+void object::test<1>()
+{
+    geom1_ = fromWKT("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))");
+    geom2_ = fromWKT("LINESTRING (-5 5, 5 5, 5 -5)");
+
+    result_ = GEOSSplit(geom1_, geom2_);
+    ensure(result_);
+
+    expected_ = fromWKT("GEOMETRYCOLLECTION (POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)), POLYGON ((0 5, 5 5, 5 0, 10 0, 10 10, 0 10, 0 5)))");
+
+    ensure_geometry_equals(result_, expected_);
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    geom1_ = fromWKT("POINT (3 7)");
+    geom2_ = fromWKT("LINESTRING (3 7, 3 8)");
+
+    result_ = GEOSSplit(geom1_, geom2_); // cannot split a point geometry
+    ensure(!result_);
+}
+
+} // namespace tut
+

commit 9c6791c064feb003742fd0cc278deee1f7ca02ec
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Apr 22 11:36:49 2026 -0400

    GeometrySplitter: raise error when splitting Polygon with point

diff --git a/src/operation/split/GeometrySplitter.cpp b/src/operation/split/GeometrySplitter.cpp
index 5305ed3df..41cac82ce 100644
--- a/src/operation/split/GeometrySplitter.cpp
+++ b/src/operation/split/GeometrySplitter.cpp
@@ -37,12 +37,14 @@
 using geos::geom::CircularString;
 using geos::geom::CoordinateXY;
 using geos::geom::Curve;
+using geos::geom::CurvePolygon;
 using geos::geom::Geometry;
 using geos::geom::GeometryCollection;
 using geos::geom::Point;
 using geos::geom::LineString;
 using geos::geom::MultiLineString;
 using geos::geom::MultiPoint;
+using geos::geom::Polygon;
 using geos::geom::prep::PreparedGeometryFactory;
 using geos::geom::util::GeometryCombiner;
 using geos::geom::util::GeometryTransformer;
@@ -79,6 +81,18 @@ public:
         return transformGeometryCollection(mls, parent);
     }
 
+    std::unique_ptr<Geometry>
+    transformPolygon(const Polygon*, const Geometry*) override
+    {
+        throw geos::util::IllegalArgumentException("Splitting a Polygon with a point is not supported.");
+    }
+
+    std::unique_ptr<Geometry>
+    transformCurvePolygon(const CurvePolygon*, const Geometry*) override
+    {
+        throw geos::util::IllegalArgumentException("Splitting a CurvePolygon with a point is not supported.");
+    }
+
     std::unique_ptr<Geometry>
     transformGeometryCollection(const GeometryCollection* inputGC, const Geometry* /*parent*/) override
     {
@@ -248,7 +262,7 @@ std::unique_ptr<Geometry>
 GeometrySplitter::splitLinealWithEdge(const Geometry &geom, const Geometry &edge)
 {
     if (!geom.isDimensionStrict(geom::Dimension::L)) {
-        throw util::IllegalArgumentException("Input geometry must be linear.");
+        throw util::IllegalArgumentException("Input geometry must be linear or polygonal.");
     }
 
     if (geom.isEmpty()) {
diff --git a/tests/unit/operation/split/GeometrySplitterTest.cpp b/tests/unit/operation/split/GeometrySplitterTest.cpp
index a4a210bf4..15a53d964 100644
--- a/tests/unit/operation/split/GeometrySplitterTest.cpp
+++ b/tests/unit/operation/split/GeometrySplitterTest.cpp
@@ -747,4 +747,28 @@ void object::test<60>()
               "GEOMETRYCOLLECTION (LINESTRING (0 1, 1 0), LINESTRING (0 2, 1 1), LINESTRING (1 1, 2 0))");
 }
 
+template<>
+template<>
+void object::test<61>()
+{
+    set_test_name("cannot split Polygon with point");
+
+    auto geom = reader_.read("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))");
+    auto splitGeom = reader_.read("POINT (5 0)");
+
+    ensure_THROW(GeometrySplitter::split(*geom, *splitGeom), geos::util::IllegalArgumentException);
+}
+
+template<>
+template<>
+void object::test<62>()
+{
+    set_test_name("cannot split CurvePolygon with point");
+
+    auto geom = reader_.read("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-5 0, 0 5, 5 0), (5 0, -5 0)))");
+    auto splitGeom = reader_.read("POINT (0 0)");
+
+    ensure_THROW(GeometrySplitter::split(*geom, *splitGeom), geos::util::IllegalArgumentException);
+}
+
 }

commit 1b37ecd1f94563a4b043a1f5c00eaf0e181556c7
Author: Daniel Baston <dbaston at gmail.com>
Date:   Wed Apr 22 11:23:18 2026 -0400

    geosop: add split command

diff --git a/util/geosop/GeometryOp.cpp b/util/geosop/GeometryOp.cpp
index 376d444b6..f10e22e84 100644
--- a/util/geosop/GeometryOp.cpp
+++ b/util/geosop/GeometryOp.cpp
@@ -44,6 +44,7 @@
 #include <geos/operation/buffer/OffsetCurve.h>
 #include <geos/operation/cluster/GeometryDistanceClusterFinder.h>
 #include <geos/operation/cluster/GeometryIntersectsClusterFinder.h>
+#include <geos/operation/split/GeometrySplitter.h>
 #include <geos/coverage/CoverageCleaner.h>
 #include <geos/coverage/CoverageSimplifier.h>
 #include <geos/coverage/CoverageValidator.h>
@@ -919,6 +920,13 @@ std::vector<GeometryOpCreator> opRegistry {
         return new Result( geos::noding::GeometryNoder::node( geom ) );
 });
 }},
+{"split", [](std::string name) { return GeometryOp::create(name,
+    catOverlay,
+    "split a geometry using another geometry",
+    [](const Geometry& geomA, const Geometry& geomB) {
+        return new Result( operation::split::GeometrySplitter::split(geomA, geomB));
+});
+}},
 {"clipRect", [](std::string name) { return GeometryOp::create(name,
     catOverlay,
     "clip geometry A to envelope of B",

commit ab5581b312b28f5677bc08da46988758e41565ff
Author: Daniel Baston <dbaston at gmail.com>
Date:   Fri Apr 17 09:58:00 2026 -0400

    GeometrySplitter: support splitting CurvePolygon with line

diff --git a/src/operation/split/GeometrySplitter.cpp b/src/operation/split/GeometrySplitter.cpp
index 75bcb21d2..5305ed3df 100644
--- a/src/operation/split/GeometrySplitter.cpp
+++ b/src/operation/split/GeometrySplitter.cpp
@@ -32,6 +32,7 @@
 #include <geos/operation/distance/GeometryLocation.h>
 #include <geos/operation/polygonize/Polygonizer.h>
 #include <geos/operation/split/SplitGeometryAtVertex.h>
+#include <geos/shape/random/RandomPointsBuilder.h>
 
 using geos::geom::CircularString;
 using geos::geom::CoordinateXY;
@@ -41,6 +42,7 @@ using geos::geom::GeometryCollection;
 using geos::geom::Point;
 using geos::geom::LineString;
 using geos::geom::MultiLineString;
+using geos::geom::MultiPoint;
 using geos::geom::prep::PreparedGeometryFactory;
 using geos::geom::util::GeometryCombiner;
 using geos::geom::util::GeometryTransformer;
@@ -51,6 +53,7 @@ using geos::noding::GeometryNoder;
 using geos::operation::polygonize::Polygonizer;
 using geos::operation::distance::DistanceOp;
 using geos::operation::distance::GeometryLocation;
+using geos::shape::random::RandomPointsBuilder;
 
 namespace geos::operation::split {
 
@@ -264,6 +267,23 @@ GeometrySplitter::splitLinealWithEdge(const Geometry &geom, const Geometry &edge
     return nodedGC;
 }
 
+static std::unique_ptr<Point>
+getInteriorPoint(const geom::Surface& surface)
+{
+    if (!surface.hasCurvedComponents()) {
+        return surface.getInteriorPoint();
+    }
+
+    RandomPointsBuilder rpb(surface.getFactory());
+    rpb.setNumPoints(1);
+    rpb.setExtent(surface);
+    auto mp = rpb.getGeometry();
+
+    auto geoms = detail::down_cast<MultiPoint*>(mp.get())->releaseGeometries();
+
+    return std::unique_ptr<Point>(detail::down_cast<Point*>(geoms[0].release()));
+}
+
 std::unique_ptr<Geometry>
 GeometrySplitter::splitPolygonalWithEdge(const Geometry &geom, const Geometry &edge)
 {
@@ -294,7 +314,7 @@ GeometrySplitter::splitPolygonalWithEdge(const Geometry &geom, const Geometry &e
     } else {
         for (auto& surface : surfaces)
         {
-            const auto testPoint = surface->getInteriorPoint();
+            const auto testPoint = getInteriorPoint(*surface);
             if (geom.intersects(testPoint.get())) {
                 keep.push_back(std::move(surface));
             }
diff --git a/tests/unit/operation/split/GeometrySplitterTest.cpp b/tests/unit/operation/split/GeometrySplitterTest.cpp
index 269577223..a4a210bf4 100644
--- a/tests/unit/operation/split/GeometrySplitterTest.cpp
+++ b/tests/unit/operation/split/GeometrySplitterTest.cpp
@@ -592,13 +592,11 @@ void object::test<48>()
               "GEOMETRYCOLLECTION (CIRCULARSTRING (-5 0, -2.6286555605956674 4.2532540417602, 2.23606797749979 4.47213595499958), CIRCULARSTRING (2.23606797749979 4.47213595499958, 3.2037371962288375 3.8387586505926348, 4 3))");
 }
 
-#if 0
 template<>
 template<>
 void object::test<49>()
 {
     set_test_name("split CurvePolygon with line");
-    // fails because getInteriorPoint does not support curves
 
     testSplit("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-5 0, -4 3, 4 3), (4 3, -5 0)))",
               "LINESTRING (0 0, 3 6)",
@@ -608,6 +606,7 @@ void object::test<49>()
              );
 }
 
+#if 0
 template<>
 template<>
 void object::test<50>()

commit 9dc15f5ac81cacacb7319b8a8dd6013d09e99cca
Author: Daniel Baston <dbaston at gmail.com>
Date:   Fri Apr 17 09:43:57 2026 -0400

    Add RandomPointsBuilder

diff --git a/include/geos/shape/GeometricShapeBuilder.h b/include/geos/shape/GeometricShapeBuilder.h
new file mode 100644
index 000000000..22708b26f
--- /dev/null
+++ b/include/geos/shape/GeometricShapeBuilder.h
@@ -0,0 +1,83 @@
+/**********************************************************************
+*
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2016 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.
+ *
+ **********************************************************************
+ *
+ * Last port: shape/GeometricShapeBuilder.java c2e8e1d069
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/export.h>
+
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/Envelope.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/LineSegment.h>
+
+#include <cstddef>
+#include <memory>
+
+namespace geos {
+namespace shape {
+
+class GEOS_DLL GeometricShapeBuilder {
+public:
+    GeometricShapeBuilder();
+    explicit GeometricShapeBuilder(const geom::GeometryFactory* gf);
+
+    virtual ~GeometricShapeBuilder() = default;
+
+    /**
+     * Sets the extent as an envelope.
+     *
+     * @param env the extent envelope
+     */
+    void setExtent(const geom::Envelope& env);
+
+    /**
+     * Gets the extent envelope.
+     *
+     * @return the extent envelope
+     */
+    const geom::Envelope& getExtent() const;
+
+    geom::CoordinateXY getCentre() const;
+
+    double getDiameter() const;
+
+    double getRadius() const;
+
+    geom::LineSegment getSquareBaseLine() const;
+
+    geom::Envelope getSquareExtent() const;
+
+    /**
+     * Sets the total number of points in the created {@link Geometry}.
+     * The created geometry will have no more than this number of points,
+     * unless more are needed to create a valid geometry.
+     */
+    void setNumPoints(std::size_t n);
+
+    virtual std::unique_ptr<geom::Geometry> getGeometry() = 0;
+
+protected:
+    geom::CoordinateXY createCoord(double x, double y) const;
+
+    const geom::GeometryFactory* geometryFactory;
+    geom::Envelope extent;
+    std::size_t numPts;
+};
+
+} // namespace shape
+} // namespace geos
diff --git a/include/geos/shape/random/RandomPointsBuilder.h b/include/geos/shape/random/RandomPointsBuilder.h
new file mode 100644
index 000000000..762db53ed
--- /dev/null
+++ b/include/geos/shape/random/RandomPointsBuilder.h
@@ -0,0 +1,65 @@
+/**********************************************************************
+*
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2016 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.
+ *
+ **********************************************************************
+ *
+ * Last port: shape/random/RandomPointsBuilder.java e92970e3c0
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/export.h>
+
+#include <geos/algorithm/locate/PointOnGeometryLocator.h>
+#include <geos/geom/Envelope.h>
+#include <geos/geom/Geometry.h>
+#include <geos/shape/GeometricShapeBuilder.h>
+
+#include <memory>
+#include <random>
+
+namespace geos::shape::random {
+
+ /**
+  * Creates random point sets contained in a
+  * region defined by either a rectangular or a polygonal extent.
+  *
+  * @author mbdavis
+  *
+  */
+class GEOS_DLL RandomPointsBuilder : public GeometricShapeBuilder {
+public:
+
+    explicit RandomPointsBuilder(const geom::GeometryFactory* gf);
+
+    using GeometricShapeBuilder::setExtent;
+
+    void setExtent(const geom::Geometry& mask);
+
+    std::unique_ptr<geom::Geometry> getGeometry() override;
+
+protected:
+
+    bool isInExtent(const geom::CoordinateXY& p) const;
+
+    geom::CoordinateXY createRandomCoord(const geom::Envelope& env);
+
+    std::unique_ptr<geom::Geometry> maskPoly;
+private:
+    std::unique_ptr<algorithm::locate::PointOnGeometryLocator> extentLocator;
+    std::random_device rd;
+    std::mt19937 rng;
+    std::uniform_real_distribution<double> dist{0, 1};
+};
+
+}
diff --git a/src/shape/GeometricShapeBuilder.cpp b/src/shape/GeometricShapeBuilder.cpp
new file mode 100644
index 000000000..eb1b9e9c5
--- /dev/null
+++ b/src/shape/GeometricShapeBuilder.cpp
@@ -0,0 +1,101 @@
+/**********************************************************************
+*
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2016 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.
+ *
+ **********************************************************************
+ *
+ * Last port: shape/GeometricShapeBuilder.java c2e8e1d069
+ *
+ **********************************************************************/
+
+#include <geos/shape/GeometricShapeBuilder.h>
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::Envelope;
+using geos::geom::LineSegment;
+using geos::geom::GeometryFactory;
+
+namespace geos::shape {
+
+GeometricShapeBuilder::GeometricShapeBuilder(const GeometryFactory *gf)
+    : geometryFactory(gf)
+{
+}
+
+void
+GeometricShapeBuilder::setExtent(const Envelope &env)
+{
+    extent = env;
+}
+
+const Envelope&
+GeometricShapeBuilder::getExtent() const
+{
+    return extent;
+}
+
+CoordinateXY
+GeometricShapeBuilder::getCentre() const
+{
+    CoordinateXY centre;
+    extent.centre(centre);
+    return centre;
+}
+
+double
+GeometricShapeBuilder::getDiameter() const
+{
+    return std::min(extent.getHeight(), extent.getWidth());
+}
+
+double
+GeometricShapeBuilder::getRadius() const
+{
+    return getDiameter() / 2;
+}
+
+LineSegment
+GeometricShapeBuilder::getSquareBaseLine() const
+{
+    const double radius = getRadius();
+    const CoordinateXY centre = getCentre();
+
+    const Coordinate p0(centre.x - radius, centre.y - radius);
+    const Coordinate p1(centre.x + radius, centre.y - radius);
+
+    return LineSegment(p0, p1);
+}
+
+Envelope
+GeometricShapeBuilder::getSquareExtent() const
+{
+    const double radius = getRadius();
+    const CoordinateXY centre = getCentre();
+
+    return Envelope(centre.x - radius, centre.x + radius, centre.y - radius, centre.y + radius);
+}
+
+void
+GeometricShapeBuilder::setNumPoints(std::size_t n)
+{
+    numPts = n;
+}
+
+CoordinateXY
+GeometricShapeBuilder::createCoord(double x, double y) const
+{
+    CoordinateXY pt(x, y);
+    geometryFactory->getPrecisionModel()->makePrecise(pt);
+    return pt;
+}
+
+}
diff --git a/src/shape/random/RandomPointsBuilder.cpp b/src/shape/random/RandomPointsBuilder.cpp
new file mode 100644
index 000000000..3b520cfaf
--- /dev/null
+++ b/src/shape/random/RandomPointsBuilder.cpp
@@ -0,0 +1,96 @@
+/**********************************************************************
+*
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2016 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.
+ *
+ **********************************************************************
+ *
+ * Last port: shape/random/RandomPointsBuilder.java e92970e3c0
+ *
+ **********************************************************************/
+
+#include <geos/shape/random/RandomPointsBuilder.h>
+
+#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
+#include <geos/algorithm/locate/SimplePointInAreaLocator.h>
+#include <geos/util/IllegalArgumentException.h>
+
+#include <algorithm>
+#include <random>
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::Envelope;
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::Point;
+
+namespace geos::shape::random {
+
+RandomPointsBuilder::RandomPointsBuilder(const GeometryFactory* gf)
+    : GeometricShapeBuilder(gf)
+    , rd{}
+    , rng{rd()}
+{
+}
+
+void
+RandomPointsBuilder::setExtent(const Geometry& mask)
+{
+    if (!mask.isDimensionStrict(geom::Dimension::A)) {
+        throw geos::util::IllegalArgumentException(
+            "RandomPointsBuilder: Only polygonal extents are supported");
+    }
+
+    maskPoly = mask.clone();
+    GeometricShapeBuilder::setExtent(*mask.getEnvelopeInternal());
+
+    if (maskPoly->hasCurvedComponents()) {
+        extentLocator = std::make_unique<algorithm::locate::SimplePointInAreaLocator>(maskPoly.get());
+    } else {
+        extentLocator = std::make_unique<algorithm::locate::IndexedPointInAreaLocator>(*maskPoly);
+    }
+}
+
+std::unique_ptr<Geometry>
+RandomPointsBuilder::getGeometry()
+{
+    std::vector<std::unique_ptr<Point>> pts(numPts);
+    std::size_t i = 0;
+    while (i < numPts) {
+        const CoordinateXY p = createRandomCoord(getExtent());
+        if (extentLocator && !isInExtent(p)) {
+            continue;
+        }
+        pts[i++] = geometryFactory->createPoint(p);
+    }
+
+    return geometryFactory->createMultiPoint(std::move(pts));
+}
+
+bool
+RandomPointsBuilder::isInExtent(const CoordinateXY &p) const
+{
+    if (extentLocator) {
+        return extentLocator->locate(&p) != geom::Location::EXTERIOR;
+    }
+    return getExtent().contains(p);
+}
+
+CoordinateXY
+RandomPointsBuilder::createRandomCoord(const Envelope& env)
+{
+    const double x = env.getMinX() + env.getWidth() * dist(rng);
+    const double y = env.getMinY() + env.getHeight() * dist(rng);
+
+    return createCoord(x, y);
+}
+
+}
diff --git a/tests/unit/shape/random/RandomPointsBuilderTest.cpp b/tests/unit/shape/random/RandomPointsBuilderTest.cpp
new file mode 100644
index 000000000..9eb5e137b
--- /dev/null
+++ b/tests/unit/shape/random/RandomPointsBuilderTest.cpp
@@ -0,0 +1,94 @@
+// tut
+#include <tut/tut.hpp>
+// geos
+#include <geos/shape/random/RandomPointsBuilder.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/io/WKTReader.h>
+// std
+
+using geos::shape::random::RandomPointsBuilder;
+using geos::geom::Coordinate;
+using geos::geom::Envelope;
+using geos::geom::GeometryFactory;
+using geos::io::WKTReader;
+
+namespace tut {
+
+// Common data used by tests
+struct test_randompointsbuilder_data {
+    GeometryFactory::Ptr factory_;
+    WKTReader reader_;
+
+    test_randompointsbuilder_data() :
+        factory_(GeometryFactory::create()),
+        reader_(factory_.get())
+    {}
+
+    void
+    checkPointsInGeometry(const std::string& wkt, std::size_t nPts) const
+    {
+        auto g = reader_.read(wkt);
+
+        RandomPointsBuilder rpb(factory_.get());
+        rpb.setNumPoints(nPts);
+        rpb.setExtent(*g);
+
+        auto mp = rpb.getGeometry();
+
+        ensure_equals(mp->getGeometryTypeId(), geos::geom::GEOS_MULTIPOINT);
+        ensure_equals(mp->getNumGeometries(), nPts);
+
+        for (std::size_t i = 0; i < mp->getNumGeometries(); i++) {
+            const auto* pt = mp->getGeometryN(i);
+            ensure(g->intersects(pt));
+        }
+    }
+};
+
+typedef test_group<test_randompointsbuilder_data> group;
+typedef group::object object;
+
+group test_randompointsbuilder_group("geos::shape::random::RandomPointsBuilder");
+
+template<>
+template<>
+void object::test<1>()
+{
+    set_test_name("points within Envelope");
+
+    Envelope e(-20, -10, 8, 9);
+
+    RandomPointsBuilder rpb(factory_.get());
+    rpb.setNumPoints(9);
+    rpb.setExtent(e);
+
+    auto mp = rpb.getGeometry();
+
+    ensure_equals(mp->getGeometryTypeId(), geos::geom::GEOS_MULTIPOINT);
+    ensure_equals(mp->getNumGeometries(), 9u);
+
+    for (std::size_t i = 0; i < mp->getNumGeometries(); i++) {
+        const auto* pt = mp->getGeometryN(i);
+        ensure(e.contains(*pt->getCoordinate()));
+    }
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    set_test_name("points within linear Geometry");
+
+    checkPointsInGeometry("POLYGON ((0 20, 20 20, 20 0, 18 0, 18 18, 0 18, 0 20))", 9);
+}
+
+template<>
+template<>
+void object::test<3>()
+{
+    set_test_name("points within curved Geometry");
+
+    checkPointsInGeometry("CURVEPOLYGON (COMPOUNDCURVE ((0 18, 0 20, 20 20, 20 0, 18 0), CIRCULARSTRING (18 0, 12.5 12.5, 0 18)))", 7);
+}
+
+} // namespace tut

commit 6f3ca8b02589e4d4aedc12e2ff6262af7c274f7e
Author: Daniel Baston <dbaston at gmail.com>
Date:   Thu Apr 9 13:16:37 2026 -0400

    Add GeometrySplitter

diff --git a/include/geos/operation/split/GeometrySplitter.h b/include/geos/operation/split/GeometrySplitter.h
new file mode 100644
index 000000000..7511940a8
--- /dev/null
+++ b/include/geos/operation/split/GeometrySplitter.h
@@ -0,0 +1,62 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2026 ISciences LLC
+ *
+ * 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/export.h>
+#include <memory>
+
+namespace geos::geom {
+class Geometry;
+class LineString;
+class Point;
+}
+
+namespace geos::operation::split {
+
+class GEOS_DLL GeometrySplitter {
+
+public:
+    /** Split a geometry.
+     *
+     * The geometry to be split must be a (Multi)LineString or (Multi)Polygon.
+     *
+     * The split geometry may contain any number of (Multi)Point, (Multi)LineString, or
+     * (Multi)Polygon elements.
+     *
+     * @param geom geometry to be split
+     * @param splitGeom "blade" and/or points defining where the geometry will be split
+     * @return a GeometryCollection containing the split parts
+     */
+    static std::unique_ptr<geom::Geometry>
+    split(const geom::Geometry& geom, const geom::Geometry& splitGeom);
+
+private:
+
+    static std::unique_ptr<geom::Geometry>
+    splitLinealWithEdge(const geom::Geometry& geom, const geom::Geometry& edge);
+
+    static std::unique_ptr<geom::Geometry>
+    splitPolygonalWithEdge(const geom::Geometry& geom, const geom::Geometry& edge);
+
+    static std::unique_ptr<geom::Geometry>
+    splitLineWithPoint(const geom::LineString& g, const geom::Point& point);
+
+    static std::unique_ptr<geom::Geometry>
+    splitAtPoints(const geom::Geometry& geom, const geom::Geometry& splitPoints);
+
+    class SplitWithPointTransformer;
+};
+
+}
diff --git a/include/geos/operation/split/SplitGeometryAtVertex.h b/include/geos/operation/split/SplitGeometryAtVertex.h
index cfee8f817..37c331cb9 100644
--- a/include/geos/operation/split/SplitGeometryAtVertex.h
+++ b/include/geos/operation/split/SplitGeometryAtVertex.h
@@ -21,6 +21,7 @@
 // Forward declarations
 namespace geos::geom {
 class CircularString;
+class CoordinateXY;
 class LineString;
 class SimpleCurve;
 }
@@ -38,6 +39,10 @@ public:
 
     static std::pair<std::unique_ptr<geom::CircularString>, std::unique_ptr<geom::CircularString>>
             splitCircularStringAtVertex(const geom::CircularString& cs, std::size_t i);
+
+    static std::pair<std::unique_ptr<geom::LineString>, std::unique_ptr<geom::LineString>>
+            splitLineStringAtPoint(const geom::LineString& ls, std::size_t i, const geom::CoordinateXY& pt);
+
 };
 
 }
diff --git a/src/operation/split/GeometrySplitter.cpp b/src/operation/split/GeometrySplitter.cpp
new file mode 100644
index 000000000..75bcb21d2
--- /dev/null
+++ b/src/operation/split/GeometrySplitter.cpp
@@ -0,0 +1,311 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2026 ISciences LLC
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include <geos/operation/split/GeometrySplitter.h>
+
+#include <geos/geom/CircularString.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryCollection.h>
+#include <geos/geom/GeometryFactory.h>
+#include <geos/geom/Point.h>
+#include <geos/geom/prep/PreparedGeometry.h>
+#include <geos/geom/prep/PreparedGeometryFactory.h>
+#include <geos/geom/util/GeometryCombiner.h>
+#include <geos/geom/util/GeometryTransformer.h>
+#include <geos/geom/util/LinealExtracter.h>
+#include <geos/geom/util/LinearComponentExtracter.h>
+#include <geos/geom/util/PointExtracter.h>
+#include <geos/geom/util/PolygonalExtracter.h>
+#include <geos/noding/GeometryNoder.h>
+#include <geos/operation/distance/DistanceOp.h>
+#include <geos/operation/distance/GeometryLocation.h>
+#include <geos/operation/polygonize/Polygonizer.h>
+#include <geos/operation/split/SplitGeometryAtVertex.h>
+
+using geos::geom::CircularString;
+using geos::geom::CoordinateXY;
+using geos::geom::Curve;
+using geos::geom::Geometry;
+using geos::geom::GeometryCollection;
+using geos::geom::Point;
+using geos::geom::LineString;
+using geos::geom::MultiLineString;
+using geos::geom::prep::PreparedGeometryFactory;
+using geos::geom::util::GeometryCombiner;
+using geos::geom::util::GeometryTransformer;
+using geos::geom::util::LinealExtracter;
+using geos::geom::util::PointExtracter;
+using geos::geom::util::PolygonalExtracter;
+using geos::noding::GeometryNoder;
+using geos::operation::polygonize::Polygonizer;
+using geos::operation::distance::DistanceOp;
+using geos::operation::distance::GeometryLocation;
+
+namespace geos::operation::split {
+
+class GeometrySplitter::SplitWithPointTransformer : public geom::util::GeometryTransformer {
+public:
+    SplitWithPointTransformer(const Point& pt) : m_pt(pt) {}
+
+    std::unique_ptr<Geometry>
+    transformCircularString(const CircularString*, const Geometry* /*parent*/) override
+    {
+        throw geos::util::UnsupportedOperationException("Splitting a CircularString with a point is not supported.");
+    }
+
+    std::unique_ptr<Geometry>
+    transformLineString(const LineString* geom, const Geometry* /*parent*/) override
+    {
+        return GeometrySplitter::splitLineWithPoint(*geom, m_pt);
+    }
+
+    std::unique_ptr<Geometry>
+    transformMultiLineString(const MultiLineString* mls, const Geometry* parent) override
+    {
+        return transformGeometryCollection(mls, parent);
+    }
+
+    std::unique_ptr<Geometry>
+    transformGeometryCollection(const GeometryCollection* inputGC, const Geometry* /*parent*/) override
+    {
+        std::vector<std::unique_ptr<Geometry>> geoms;
+
+        for (const auto& input : *inputGC) {
+            auto transformed = transform(input.get());
+            GeometryCollection* transformedGC = detail::down_cast<GeometryCollection*>(transformed.get());
+
+            for (auto& g : transformedGC->releaseGeometries()) {
+                if (!g->isEmpty()) {
+                    geoms.push_back(std::move(g));
+                }
+            }
+        }
+
+        return inputGC->getFactory()->createGeometryCollection(std::move(geoms));
+    }
+
+private:
+    const Point& m_pt;
+};
+
+class RemoveCoordinateZM : public geom::CoordinateSequenceFilter {
+
+public:
+
+    void filter_rw(geom::CoordinateSequence& seq, std::size_t) override {
+        seq.setZM(false, false);
+        m_done = true;
+    }
+
+    bool isDone() const override {
+        return m_done;
+    }
+
+    bool isGeometryChanged() const override {
+        // We didn't change the XY coords; no need to update the envelope.
+        return false;
+    }
+
+private:
+    bool m_done{false};
+};
+
+std::unique_ptr<Geometry>
+GeometrySplitter::splitAtPoints(const Geometry& geom, const Geometry& splitPoints)
+{
+    const Geometry* toSplit = &geom;
+
+    std::vector<const Point*> points;
+    PointExtracter::getPoints(splitPoints, points);
+
+    std::unique_ptr<Geometry> result;
+
+    for (const auto& point : points) {
+        SplitWithPointTransformer xform(*point);
+        result = xform.transform(toSplit);
+        toSplit = result.get();
+    }
+
+    return result;
+}
+
+std::unique_ptr<Geometry>
+GeometrySplitter::split(const Geometry &geom, const Geometry &splitGeom)
+{
+    if (geom.isEmpty() || splitGeom.isEmpty()) {
+        std::vector<std::unique_ptr<Geometry>> geoms;
+        geoms.push_back(geom.clone());
+        return geom.getFactory()->createGeometryCollection(std::move(geoms));
+    }
+
+    if (splitGeom.hasZ() || splitGeom.hasM()) {
+        RemoveCoordinateZM xform;
+        auto splitGeom2D = splitGeom.clone();
+        splitGeom2D->apply_rw(xform);
+        assert(!splitGeom2D->hasZ());
+        assert(!splitGeom2D->hasM());
+        return split(geom, *splitGeom2D);
+    }
+
+    const Geometry* toSplit = &geom;
+
+    std::unique_ptr<Geometry> splitByPointsResult;
+    if (splitGeom.hasDimension(geom::Dimension::P)) {
+        splitByPointsResult = splitAtPoints(geom, splitGeom);
+
+        if (splitGeom.isDimensionStrict(geom::Dimension::P)) {
+            return splitByPointsResult;
+        }
+
+        toSplit = splitByPointsResult.get();
+    }
+
+    // If our input has mixed areas and lines, split them separately, then recombine.
+    if (toSplit->hasDimension(geom::Dimension::A) && toSplit->hasDimension(geom::Dimension::L)) {
+        std::vector<const Geometry*> lines;
+        LinealExtracter::getLineals(toSplit, lines);
+        auto linearInput = geom.getFactory()->createGeometryCollection(lines);
+        auto linearOutput = splitLinealWithEdge(*linearInput, splitGeom);
+
+        std::vector<const Geometry*> polys;
+        PolygonalExtracter::getPolygonals(*toSplit, polys);
+        auto polygonalInput = geom.getFactory()->createGeometryCollection(polys);
+        auto polygonalOutput = splitPolygonalWithEdge(*polygonalInput, splitGeom);
+
+        return GeometryCombiner::combine(std::move(linearOutput), std::move(polygonalOutput));
+    }
+
+    if (toSplit->hasDimension(geom::Dimension::A)) {
+        return splitPolygonalWithEdge(*toSplit, splitGeom);
+    }
+
+    return splitLinealWithEdge(*toSplit, splitGeom);
+}
+
+std::unique_ptr<Geometry>
+GeometrySplitter::splitLineWithPoint(const geom::LineString& g, const Point& point)
+{
+    constexpr double tolerance = 1e-10;
+
+    if (g.isEmpty()) {
+        std::vector<std::unique_ptr<Geometry>> geoms;
+        geoms.push_back(g.clone());
+        return g.getFactory()->createGeometryCollection(std::move(geoms));
+    }
+
+    DistanceOp distance(g, point);
+
+    if (distance.distance() > tolerance) {
+        std::vector<std::unique_ptr<Geometry>> geoms;
+        geoms.push_back(g.clone());
+        return g.getFactory()->createGeometryCollection(std::move(geoms));
+    }
+
+    const auto& nearestLoc = distance.nearestLocations()[0];
+
+    const auto* seq = detail::down_cast<const LineString*>(nearestLoc.getGeometryComponent())->getCoordinatesRO();
+
+    const CoordinateXY& p0 = seq->getAt<CoordinateXY>(nearestLoc.getSegmentIndex());
+    const CoordinateXY& p1 = seq->getAt<CoordinateXY>(nearestLoc.getSegmentIndex() + 1);
+
+
+    std::pair<std::unique_ptr<LineString>, std::unique_ptr<LineString>> split;
+    if (nearestLoc.getCoordinate().equals2D(p0))  {
+        // no need to add a new point
+        split = SplitGeometryAtVertex::splitLineStringAtVertex(static_cast<const LineString&>(g), nearestLoc.getSegmentIndex());
+    } else if (nearestLoc.getCoordinate().equals2D(p1)) {
+        split = SplitGeometryAtVertex::splitLineStringAtVertex(static_cast<const LineString&>(g), nearestLoc.getSegmentIndex() + 1);
+    } else {
+        split = SplitGeometryAtVertex::splitLineStringAtPoint(static_cast<const LineString&>(g), nearestLoc.getSegmentIndex(), nearestLoc.getCoordinate());
+    }
+
+    std::vector<std::unique_ptr<Geometry>> geoms;
+    if (!split.first->isEmpty()) {
+        geoms.push_back(std::move(split.first));
+    }
+    if (!split.second->isEmpty()) {
+        geoms.push_back(std::move(split.second));
+    }
+
+    return g.getFactory()->createGeometryCollection(std::move(geoms));
+}
+
+std::unique_ptr<Geometry>
+GeometrySplitter::splitLinealWithEdge(const Geometry &geom, const Geometry &edge)
+{
+    if (!geom.isDimensionStrict(geom::Dimension::L)) {
+        throw util::IllegalArgumentException("Input geometry must be linear.");
+    }
+
+    if (geom.isEmpty()) {
+        std::vector<std::unique_ptr<Geometry>> geoms;
+        geoms.push_back(geom.clone());
+        return geom.getFactory()->createGeometryCollection(std::move(geoms));
+    }
+
+    GeometryNoder noder(geom, edge);
+    noder.setOnlyFirstGeomEdges(true);
+
+    auto nodedMLS = noder.getNoded();
+
+    auto nodedGC = geom.getFactory()->createGeometryCollection(detail::down_cast<GeometryCollection*>(nodedMLS.get())->releaseGeometries());
+
+    return nodedGC;
+}
+
+std::unique_ptr<Geometry>
+GeometrySplitter::splitPolygonalWithEdge(const Geometry &geom, const Geometry &edge)
+{
+    if (!geom.isDimensionStrict(geom::Dimension::A)) {
+        throw util::IllegalArgumentException("Input geometry must be polygonal.");
+    }
+
+    auto noded = GeometryNoder::node(geom, edge);
+
+    Polygonizer polygonizer;
+    polygonizer.add(noded.get());
+
+    auto surfaces = polygonizer.getSurfaces();
+
+    const bool usePrepared = !geom.hasCurvedComponents() && !edge.hasCurvedComponents();
+    std::vector<std::unique_ptr<Geometry>> keep;
+
+    if (usePrepared) {
+        auto prepGeom = PreparedGeometryFactory::prepare(&geom);
+
+        for (auto& surface : surfaces)
+        {
+            const auto testPoint = surface->getInteriorPoint();
+            if (prepGeom->intersects(testPoint.get())) {
+                keep.push_back(std::move(surface));
+            }
+        }
+    } else {
+        for (auto& surface : surfaces)
+        {
+            const auto testPoint = surface->getInteriorPoint();
+            if (geom.intersects(testPoint.get())) {
+                keep.push_back(std::move(surface));
+            }
+        }
+    }
+
+    if (keep.empty()) {
+        return geom.getFactory()->createEmptyGeometry(geom::GEOS_GEOMETRYCOLLECTION, geom.hasZ(), geom.hasM());
+    }
+
+    return geom.getFactory()->createGeometryCollection(std::move(keep));
+}
+
+}
diff --git a/src/operation/split/SplitGeometryAtVertex.cpp b/src/operation/split/SplitGeometryAtVertex.cpp
index eba4beeef..512e6f009 100644
--- a/src/operation/split/SplitGeometryAtVertex.cpp
+++ b/src/operation/split/SplitGeometryAtVertex.cpp
@@ -14,20 +14,59 @@
 
 #include <geos/operation/split/SplitGeometryAtVertex.h>
 
+#include <geos/algorithm/Interpolate.h>
 #include <geos/geom/LineString.h>
 #include <geos/geom/CircularString.h>
 #include <geos/geom/GeometryFactory.h>
 #include <geos/util/UnsupportedOperationException.h>
 
 using geos::geom::CoordinateSequence;
+using geos::geom::CoordinateXY;
+using geos::geom::CoordinateXYZM;
 using geos::geom::CircularString;
 using geos::geom::LineString;
 using geos::geom::SimpleCurve;
 using geos::geom::GeometryTypeId;
 
 namespace geos::operation::split {
+
 std::pair<std::unique_ptr<LineString>, std::unique_ptr<LineString>>
-        SplitGeometryAtVertex::splitLineStringAtVertex(const LineString& ls, std::size_t i)
+SplitGeometryAtVertex::splitLineStringAtPoint(const LineString& ls, std::size_t i, const CoordinateXY& pt)
+{
+    const auto& gf = *ls.getFactory();
+    const CoordinateSequence& pts = *ls.getCoordinatesRO();
+
+    if (i + 1 >= pts.size()) {
+        throw util::IllegalArgumentException("Cannot split LineString at point beyond end");
+    }
+
+    auto pts1 = std::make_shared<CoordinateSequence>(0, pts.hasZ(), pts.hasM());
+    auto pts2 = std::make_shared<CoordinateSequence>(0, pts.hasZ(), pts.hasM());
+
+    CoordinateXYZM ptZM(pt);
+    if (pts.hasZ() || pts.hasM()) {
+        CoordinateXYZM prev;
+        CoordinateXYZM next;
+        pts.getAt(i, prev);
+        pts.getAt(i + 1, next);
+
+        ptZM.z = algorithm::Interpolate::zGetOrInterpolate(pt, prev, next);
+        ptZM.m = algorithm::Interpolate::mGetOrInterpolate(pt, prev, next);
+    }
+
+    pts1->add(pts, 0, i);
+    pts1->add(ptZM);
+
+    if (i < pts.size() - 1) {
+        pts2->add(ptZM);
+        pts2->add(pts, i + 1, pts.size() - 1);
+    }
+
+    return { gf.createLineString(pts1), gf.createLineString(pts2) };
+}
+
+std::pair<std::unique_ptr<LineString>, std::unique_ptr<LineString>>
+SplitGeometryAtVertex::splitLineStringAtVertex(const LineString& ls, std::size_t i)
 {
     const auto& gf = *ls.getFactory();
     const CoordinateSequence& pts = *ls.getCoordinatesRO();
diff --git a/tests/unit/operation/split/GeometrySplitterTest.cpp b/tests/unit/operation/split/GeometrySplitterTest.cpp
new file mode 100644
index 000000000..269577223
--- /dev/null
+++ b/tests/unit/operation/split/GeometrySplitterTest.cpp
@@ -0,0 +1,751 @@
+#include <tut/tut.hpp>
+#include <tut/tut_macros.hpp>
+#include <utility.h>
+
+#include <geos/operation/split/GeometrySplitter.h>
+#include <geos/io/WKBReader.h>
+
+using geos::geom::CircularString;
+using geos::geom::LineString;
+using geos::operation::split::GeometrySplitter;
+
+namespace tut {
+
+struct test_geometrysplitter_data {
+    const geos::io::WKTReader reader_;
+
+    void testSplit(const std::string& wktGeom, const std::string& wktEdge, const std::string& wktExpected) const
+    {
+        auto geom = reader_.read(wktGeom);
+        auto edge = reader_.read(wktEdge);
+        auto split = GeometrySplitter::split(*geom, *edge);
+        auto expected = reader_.read(wktExpected);
+
+        WKTWriter writer;
+        writer.setRoundingPrecision(-1);
+        //std::cout << writer.write(split.get()) << std::endl;
+
+        ensure_equals_geometry_xyzm(split.get(), expected.get());
+    }
+};
+
+typedef test_group<test_geometrysplitter_data, 255> group;
+typedef group::object object;
+
+group test_geometrysplitter_group("geos::operation::split::GeometrySplitter");
+
+template<>
+template<>
+void object::test<1>()
+{
+    set_test_name("split square in two parts; PostGIS test #20");
+
+    testSplit("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))",
+              "LINESTRING (5 0, 5 10)",
+              "GEOMETRYCOLLECTION (POLYGON ((0 0, 5 0, 5 10, 0 10, 0 0)), POLYGON ((5 0, 10 0, 10 10, 5 10, 5 0)))");
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    set_test_name("split multipart geometry");
+
+    auto geom = reader_.read("MULTIPOINT (0 0, 5 0, 2.5 2.5)")->buffer(1, 1);
+    auto edge = reader_.read("LINESTRING (-5 0.5, 15 0.5)");
+
+    auto split = GeometrySplitter::split(*geom, *edge);
+    ensure_equals(split->getNumGeometries(), 5u);
+
+    // SELECT ST_AsText(ST_Split(ST_Buffer('MULTIPOINT (0 0, 5 0, 2.5 2.5)', 1, 1), 'LINESTRING (-5 .5, 15 0.5)'));
+    auto expected = reader_.read(
+                        "GEOMETRYCOLLECTION (POLYGON ((6 0, 5 -1, 4 0, 4.5 0.5, 5.5 0.5, 6 0)),"
+                        "POLYGON ((4.5 0.5, 5 1, 5.5 0.5, 4.5 0.5)),"
+                        "POLYGON ((3.5 2.5, 2.5 1.5, 1.5 2.5, 2.5 3.5, 3.5 2.5)), "
+                        "POLYGON ((1 0, 0 -1, -1 0, -0.5 0.5, 0.5 0.5, 1 0)), "
+                        "POLYGON ((-0.5 0.5, 0 1, 0.5 0.5, -0.5 0.5)))");
+
+    ensure_equals_geometry(split.get(), expected.get());
+}
+
+template<>
+template<>
+void object::test<3>()
+{
+    set_test_name("split edge ends inside polygon");
+
+    // SELECT ST_AsText(ST_Split('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))', 'LINESTRING (-5 5, 5 5)'))
+    // returned geometry is a GeometryCollection even though it only has a single component
+    // node is added where split line intersects input
+    testSplit("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))",
+              "LINESTRING (5 -5, 5 5)",
+              "GEOMETRYCOLLECTION( POLYGON ((0 0, 5 0, 10 0, 10 10, 0 10, 0 0)))");
+}
+
+template<>
+template<>
+void object::test<4>()
+{
+    set_test_name("split line is collinear with polygon edge");
+
+    // SELECT ST_AsText(ST_Split('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))', 'LINESTRING (-5 5, 0 5, 0 8, 15 8)'));
+    testSplit("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))",
+              "LINESTRING (-5 5, 0 5, 0 8, 15 8)",
+              "GEOMETRYCOLLECTION(POLYGON((10 8,10 0,0 0,0 5,0 8,10 8)),POLYGON((0 8,0 10,10 10,10 8,0 8)))");
+}
+
+template<>
+template<>
+void object::test<5>()
+{
+    set_test_name("geometry to be split contains points");
+
+    auto edge = reader_.read("LINESTRING (3 7, 8 2)");
+
+    for (std::string wkt: {
+                "POINT (3 7)",
+                "GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 0)), POINT (3 7))"
+            }) {
+        auto geom = reader_.read(wkt);
+        ensure_THROW(GeometrySplitter::split(*geom, *edge), geos::util::IllegalArgumentException);
+    }
+}
+
+template<>
+template<>
+void object::test<6>()
+{
+    set_test_name("splitting edge does not touch geometry; PostGIS ticket #745");
+
+    testSplit("POLYGON Z((-72 42 1,-70 43 1,-71 41 1,-72 42 1))",
+              "LINESTRING(-10 40 1,-9 41 1)",
+              "GEOMETRYCOLLECTION(POLYGON Z((-72 42 1,-70 43 1,-71 41 1,-72 42 1)))");
+}
+
+template<>
+template<>
+void object::test<7>()
+{
+    set_test_name("Split single-hole Polygon by line crossing both exterior and hole; PostGIS test #21");
+
+    testSplit("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(2 2, 8 2, 8 8, 2 8, 2 2))",
+              "LINESTRING(5 -5, 5 15)",
+              "GEOMETRYCOLLECTION(POLYGON((5 0,5 2,8 2,8 8,5 8,5 10,10 10,10 0,5 0)),POLYGON((0 0,0 10,5 10,5 8,2 8,2 2,5 2,5 0,0 0)))");
+}
+
+template<>
+template<>
+void object::test<8>()
+{
+    set_test_name("Split single-hole Polygon by line crossing only exterior; PostGIS test #22");
+
+    testSplit("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(5 2, 8 2, 8 8, 5 8, 5 2))",
+              "LINESTRING(2 -5, 2 15)",
+              "GEOMETRYCOLLECTION(POLYGON((2 0,2 10,10 10,10 0,2 0),(5 2,8 2,8 8,5 8,5 2)),POLYGON((0 0,0 10,2 10,2 0,0 0)))");
+}
+
+template<>
+template<>
+void object::test<9>()
+{
+    set_test_name("Split double-hole Polygon by line crossing exterior and both holes; PostGIS test #23");
+
+    testSplit("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(2 2, 8 2, 8 4, 2 4, 2 2),(2 6,8 6,8 8,2 8,2 6))",
+              "LINESTRING(5 -5, 5 15)",
+              "GEOMETRYCOLLECTION(POLYGON((5 0,5 2,8 2,8 4,5 4,5 6,8 6,8 8,5 8,5 10,10 10,10 0,5 0)),POLYGON((0 0,0 10,5 10,5 8,2 8,2 6,5 6,5 4,2 4,2 2,5 2,5 0,0 0)))");
+}
+
+template<>
+template<>
+void object::test<10>()
+{
+    set_test_name("Split MultiPolygon by line; PostGIS test #50");
+
+    testSplit("MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0),(2 2, 8 2, 8 4, 2 4, 2 2),(2 6,8 6,8 8,2 8,2 6)),((20 0,20 10, 30 10, 30 0, 20 0),(25 5, 28 5, 25 8, 25 5)))",
+              "LINESTRING(5 -5, 5 15)",
+              "GEOMETRYCOLLECTION(POLYGON((5 0,5 2,8 2,8 4,5 4,5 6,8 6,8 8,5 8,5 10,10 10,10 0,5 0)),POLYGON((0 0,0 10,5 10,5 8,2 8,2 6,5 6,5 4,2 4,2 2,5 2,5 0,0 0)),POLYGON((20 0,20 10,30 10,30 0,20 0),(25 5,28 5,25 8,25 5)))");
+}
+
+template<>
+template<>
+void object::test<11>()
+{
+    set_test_name("Split mixed poly/line GeometryCollection by line; PostGIS test #60");
+
+    testSplit("GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0),(2 2, 8 2, 8 4, 2 4, 2 2),(2 6,8 6,8 8,2 8,2 6)),((20 0,20 10, 30 10, 30 0, 20 0),(25 5, 28 5, 25 8, 25 5))),MULTILINESTRING((0 0, 10 0),(0 5, 10 5)))",
+              "LINESTRING(5 -5, 5 15)",
+              "GEOMETRYCOLLECTION(POLYGON((5 0,5 2,8 2,8 4,5 4,5 6,8 6,8 8,5 8,5 10,10 10,10 0,5 0)),POLYGON((0 0,0 10,5 10,5 8,2 8,2 6,5 6,5 4,2 4,2 2,5 2,5 0,0 0)),POLYGON((20 0,20 10,30 10,30 0,20 0),(25 5,28 5,25 8,25 5)),LINESTRING(5 5,10 5),LINESTRING(5 0,10 0),LINESTRING(0 5,5 5),LINESTRING(0 0,5 0))");
+}
+
+template<>
+template<>
+void object::test<12>()
+{
+    set_test_name("dimension preserved on empty geometry");
+
+    // PostGIS returns GEOMETRYCOLLECTION Z EMPTY here
+    // That's not currently possible in GEOS, where GeometryCollections don't have their own
+    // dimension; see https://github.com/libgeos/geos/issues/888.
+    testSplit("POLYGON ZM EMPTY",
+              "LINESTRING EMPTY",
+              "GEOMETRYCOLLECTION (POLYGON ZM EMPTY)");
+}
+
+template<>
+template<>
+void object::test<14>()
+{
+    set_test_name("split MultiLineString with Point (liblwgeom)");
+
+    testSplit("MULTILINESTRING((-5 -2,0 0),(0 0,10 10))",
+              "POINT(0 0)",
+              "GEOMETRYCOLLECTION(LINESTRING(-5 -2,0 0),LINESTRING(0 0,10 10))");
+}
+
+template<>
+template<>
+void object::test<15>()
+{
+    set_test_name("split LineString with Point (liblwgeom)");
+
+    testSplit("LINESTRING(0 0,10 0,20 4,0 3)",
+              "POINT(10 0)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,10 0),LINESTRING(10 0,20 4,0 3))" );
+}
+
+template<>
+template<>
+void object::test<16>()
+{
+    set_test_name("split LineString with LineString (liblwgeom)");
+
+    testSplit("LINESTRING(0 1,10 1)",
+              "LINESTRING(7 0,7 3)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 1,7 1),LINESTRING(7 1,10 1))");
+}
+
+template<>
+template<>
+void object::test<17>()
+{
+    set_test_name("split LineString with MultiLineString (liblwgeom)");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "MULTILINESTRING((1 1,1 -1),(2 1,2 -1,3 -1,3 1))",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,1 0),LINESTRING(1 0,2 0),LINESTRING(2 0,3 0),LINESTRING(3 0,10 0))");
+}
+
+template<>
+template<>
+void object::test<18>()
+{
+    set_test_name("split LineString with Polygon (liblwgeom)");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "POLYGON((1 -2,1 1,2 1,2 -1,3 -1,3 1,11 1,11 -2,1 -2))",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,1 0),LINESTRING(1 0,2 0),LINESTRING(2 0,3 0),LINESTRING(3 0,10 0))");
+}
+
+template<>
+template<>
+void object::test<19>()
+{
+    set_test_name("split LineString with empty Polygon (liblwgeom)");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "POLYGON EMPTY",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0, 10 0))");
+}
+
+template<>
+template<>
+void object::test<20>()
+{
+    set_test_name("split LineString with MultiPolygon (liblwgeom)");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "MULTIPOLYGON(((1 -1,1 1,2 1,2 -1,1 -1)),((3 -1,3 1,11 1,11 -1,3 -1)))",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,1 0),LINESTRING(1 0,2 0),LINESTRING(2 0,3 0),LINESTRING(3 0,10 0))");
+}
+
+template<>
+template<>
+void object::test<21>()
+{
+    set_test_name("split LineString with MultiPoint (liblwgeom)");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "MULTIPOINT(2 0,8 0,4 0)",
+              "GEOMETRYCOLLECTION(LINESTRING(8 0,10 0),LINESTRING(0 0,2 0),LINESTRING(4 0,8 0),LINESTRING(2 0,4 0))");
+
+}
+
+template<>
+template<>
+void object::test<22>()
+{
+    set_test_name("split LineString with Point (PostGIS ticket #3401)");
+
+    testSplit("LINESTRING(-180 0,0 0)",
+              "POINT(-20 0)",
+              "GEOMETRYCOLLECTION(LINESTRING (-180 0, -20 0), LINESTRING (-20 0, 0 0))");
+}
+
+template<>
+template<>
+void object::test<23>()
+{
+    set_test_name("split LineString with Point (PostGIS ticket #5698)");
+
+    testSplit("LINESTRING(15.796760167740288 69.05714853429149,15.796760167739626 69.05714853429157,15.795906966300288 69.05725770093837)",
+              "POINT (15.796760167739626 69.05714853429157)",
+              "GEOMETRYCOLLECTION (LINESTRING(15.796760167740288 69.05714853429149,15.796760167739626 69.05714853429157), LINESTRING(15.796760167739626 69.05714853429157,15.795906966300288 69.05725770093837))");
+}
+
+template<>
+template<>
+void object::test<24>()
+{
+    set_test_name("Split line by point on the line boundary; PostGIS test 2");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "POINT(10 0)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,10 0))");
+}
+
+template<>
+template<>
+void object::test<25>()
+{
+    set_test_name("split line by point on the line exterior; PostGIS test 3");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "POINT(5 1)", 
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,10 0))");
+}
+
+template<>
+template<>
+void object::test<26>()
+{
+    set_test_name("split line by disjoint line; PostGIS test 4");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "LINESTRING (20 0, 20 20)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,10 0))");
+}
+
+template<>
+template<>
+void object::test<27>()
+{
+    set_test_name("split line by touching line; PostGIS test 5");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "LINESTRING (10 -5, 10 5)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 0,10 0))");
+}
+
+template<>
+template<>
+void object::test<28>()
+{
+    set_test_name("Split line by crossing line; PostGIS test 6");
+
+    testSplit("LINESTRING(0 0, 10 0)",
+              "LINESTRING(5 -5, 5 5)",
+              "GEOMETRYCOLLECTION(LINESTRING(5 0,10 0),LINESTRING(0 0,5 0))");
+}
+
+template<>
+template<>
+void object::test<29>()
+{
+    set_test_name("Split line by multiply-crossing line; PostGIS test 7");
+
+    testSplit("LINESTRING(0 0, 10 0, 10 10, 0 10, 0 20, 10 20)",
+              "LINESTRING(5 -5, 5 25)",
+              "GEOMETRYCOLLECTION(LINESTRING(5 10,0 10,0 20,5 20),LINESTRING(5 0,10 0,10 10,5 10),LINESTRING(5 20,10 20),LINESTRING(0 0,5 0))");
+}
+
+template<>
+template<>
+void object::test<30>()
+{
+    set_test_name("Split line by overlapping line (1); PostGIS test 8.1");
+
+    // Note: PostGIS produces an error here instead of a result
+    testSplit("LINESTRING(0 0, 10 0)",
+              "LINESTRING(5 0, 20 0)",
+              "GEOMETRYCOLLECTION (LINESTRING (0 0, 5 0), LINESTRING (5 0, 10 0))");
+}
+
+template<>
+template<>
+void object::test<31>()
+{
+    set_test_name("Split line by overlapping line (1); PostGIS test 8.2");
+
+    // Note: PostGIS produces an error here instead of a result
+    testSplit("LINESTRING(0 0, 10 0)",
+              "LINESTRING(5 0, 8 0)",
+              "GEOMETRYCOLLECTION (LINESTRING (0 0, 5 0), LINESTRING (5 0, 8 0), LINESTRING (8 0, 10 0))");
+}
+
+template<>
+template<>
+void object::test<32>()
+{
+    set_test_name("Split multiline by line crossing both; PostGIS test 30");
+
+    testSplit("MULTILINESTRING((0 0, 10 0),(0 5, 10 5))",
+              "LINESTRING(5 -5, 5 10)",
+              "GEOMETRYCOLLECTION(LINESTRING(5 5,10 5),LINESTRING(5 0,10 0),LINESTRING(0 5,5 5),LINESTRING(0 0,5 0))");
+}
+
+template<>
+template<>
+void object::test<33>()
+{
+    set_test_name("Split multiline by line crossing only one of them; PostGIS test 31");
+
+    testSplit("MULTILINESTRING((0 0, 10 0),(0 5, 10 5))",
+              "LINESTRING(5 -5, 5 2)",
+              "GEOMETRYCOLLECTION(LINESTRING(5 0,10 0),LINESTRING(0 5,10 5),LINESTRING(0 0,5 0))");
+}
+
+template<>
+template<>
+void object::test<34>()
+{
+    set_test_name("Split multiline by disjoint line; PostGIS test 32");
+
+    testSplit("MULTILINESTRING((0 0, 10 0),(0 5, 10 5))",
+              "LINESTRING(5 10, 5 20)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 5,10 5),LINESTRING(0 0,10 0))");
+
+}
+
+template<>
+template<>
+void object::test<35>()
+{
+    set_test_name("Split multiline by point on one of them; PostGIS test 40");
+
+    testSplit("MULTILINESTRING((0 0, 10 0),(0 5, 10 5))",
+              "POINT(5 0)",
+              "GEOMETRYCOLLECTION(LINESTRING(5 0,10 0),LINESTRING(0 5,10 5),LINESTRING(0 0,5 0))");
+}
+
+template<>
+template<>
+void object::test<36>()
+{
+    set_test_name("Split geometrycollection by line; PostGIS test 60");
+
+    testSplit("GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0),(2 2, 8 2, 8 4, 2 4, 2 2),(2 6,8 6,8 8,2 8,2 6)),((20 0,20 10, 30 10, 30 0, 20 0),(25 5, 28 5, 25 8, 25 5))),MULTILINESTRING((0 0, 10 0),(0 5, 10 5)))",
+              "LINESTRING(5 -5, 5 15)",
+              "GEOMETRYCOLLECTION(POLYGON((5 0,5 2,8 2,8 4,5 4,5 6,8 6,8 8,5 8,5 10,10 10,10 0,5 0)),POLYGON((0 0,0 10,5 10,5 8,2 8,2 6,5 6,5 4,2 4,2 2,5 2,5 0,0 0)),POLYGON((20 0,20 10,30 10,30 0,20 0),(25 5,28 5,25 8,25 5)),LINESTRING(5 5,10 5),LINESTRING(5 0,10 0),LINESTRING(0 5,5 5),LINESTRING(0 0,5 0))");
+}
+
+template<>
+template<>
+void object::test<37>()
+{
+    set_test_name("Split 3d line by 2d line; PostGIS test 70");
+
+    testSplit("LINESTRING(1691983.26 4874594.81 312.24, 1691984.86 4874593.69 312.24, 1691979.54 4874586.09 312.24, 1691978.03 4874587.16 298.36)",
+              "LINESTRING(1691978.0 4874589.0,1691982.0 4874588.53, 1691982.0 4874591.0)",
+              "GEOMETRYCOLLECTION(LINESTRING(1691982 4874589.604285714 312.24,1691984.86 4874593.69 312.24,1691983.26 4874594.81 312.24),LINESTRING(1691978.03 4874587.16 298.36,1691979.54 4874586.09 312.24,1691981.3051513054 4874588.611644722 312.24),LINESTRING(1691981.3051513054 4874588.611644722 312.24,1691982 4874589.604285714 312.24))");
+
+}
+
+template<>
+template<>
+void object::test<38>()
+{
+    set_test_name("Split collapsed line by point; PostGIS test 80");
+
+    testSplit("LINESTRING(0 1, 0 1, 0 1)",
+              "POINT(0 1)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 1,0 1,0 1))");
+
+}
+
+template<>
+template<>
+void object::test<39>()
+{
+    set_test_name("Split collapsed line by point; PostGIS test 81");
+    
+    testSplit("LINESTRING(0 1, 0 1)",
+              "POINT(0 1)",
+              "GEOMETRYCOLLECTION(LINESTRING(0 1,0 1))");
+}
+
+template<>
+template<>
+void object::test<40>()
+{
+    set_test_name("Split long line by vertex point; PostGIS test 82");
+
+    geos::io::WKBReader wkbReader;
+    auto input = wkbReader.readHEX("01020000001000000034030F8FB15866C0F2311FFD3B9A53C0571C87CF1BB65BC0182DB847DB9052C0EBD57BDEEBF658C05CA18B9FA81B52C074384E71C20552C05AD308B7C38351C0A4B3920AA7914CC0ACD200FB29784FC0F8892AEE70E14040C0C8143E325651C0234604DC104E5440EF10F2807BF850C08FEE52B6CAE15F4002BF1C6676B450C0051A57A65BB061405B9E445AEC9F50C05AF3E1D5815665405E3A4A2BB6CF51C0591DE7ECD21F66400D33BFE91C7E53C0000000E0FF7F6640000000C04E9353C0000000000080664000000000008056C000000000008066C000000000008056C000000000008066C0000000E04D9353C034030F8FB15866C0F2311FFD3B9A53C0");
+
+    auto splitPoint = geos::detail::down_cast<const LineString*>(input.get())->getPointN(14);
+
+    auto split = GeometrySplitter::split(*input, *splitPoint);
+
+    ensure(split->Union()->equals(input.get()));
+}
+
+template<>
+template<>
+void object::test<41>()
+{
+    set_test_name("Split line by multiline; PostGIS test 83");
+
+    testSplit("LINESTRING(1 -1,1 1)",
+              "MULTILINESTRING((10 0, 10 4),(-4 0, 4 0))",
+              "GEOMETRYCOLLECTION(LINESTRING(1 -1,1 0),LINESTRING(1 0,1 1))");
+
+}
+
+template<>
+template<>
+void object::test<42>()
+{
+    set_test_name("Split line by polygon (boundary); PostGIS test 84");
+
+    testSplit("LINESTRING(1 -1,1 1)",
+              "POLYGON((-10 -10,-10 10,10 10,10 -10,-10 -10),(-4 2,-4 0,4 0,4 2,-4 2))",
+              "GEOMETRYCOLLECTION(LINESTRING(1 -1,1 0),LINESTRING(1 0,1 1))");
+
+}
+
+template<>
+template<>
+void object::test<43>()
+{
+    set_test_name("Split line by multipolygon (boundary); PostGIS test 85");
+
+    testSplit("LINESTRING(1 -2,1 1,4 1)",
+              "MULTIPOLYGON(((0 -1,0 -3,2 -3,2 -1,0 -1)),((3 0,3 2,5 2,5 0,3 0)))",
+              "GEOMETRYCOLLECTION(LINESTRING(1 -2,1 -1),LINESTRING(1 -1,1 1,3 1),LINESTRING(3 1,4 1))");
+}
+
+template<>
+template<>
+void object::test<44>()
+{
+    set_test_name("Split multiline by multipoint; PostGIS test 86");
+
+    testSplit("MULTILINESTRING((0 0,10 0),(5 -5, 5 5),(0 20,10 20))",
+              "MULTIPOINT(2 6,5 0,5 20,2 20,8 20,8 0,5 -2,0 0, 5 -5, 10 20)",
+              "GEOMETRYCOLLECTION(LINESTRING(8 0,10 0),LINESTRING(0 0,5 0),LINESTRING(5 0,8 0),LINESTRING(5 0,5 5),LINESTRING(5 -2,5 0),LINESTRING(5 -5,5 -2),LINESTRING(8 20,10 20),LINESTRING(2 20,5 20),LINESTRING(0 20,2 20),LINESTRING(5 20,8 20))");
+}
+
+template<>
+template<>
+void object::test<45>()
+{
+    set_test_name("Split empty line by point; PostGIS test 87");
+
+    testSplit("LINESTRING EMPTY",
+              "POINT(0 1)",
+              "GEOMETRYCOLLECTION(LINESTRING EMPTY)");
+}
+
+template<>
+template<>
+void object::test<46>()
+{
+    set_test_name("LineStringZ with NaN values; PostGIS ticket #5635");
+
+    // PostGIS throws error
+    testSplit("LINESTRING Z (1 2 NaN,3 4 10,5 6 NaN)",
+              "MULTIPOINT( EMPTY, (2 1) , (2 4) , (4 5))",
+              "GEOMETRYCOLLECTION( LINESTRING Z (1 2 NaN, 3 4 10, 4 5 10), LINESTRING Z (4 5 10, 5 6 NaN))");
+}
+
+template<>
+template<>
+void object::test<47>()
+{
+    set_test_name("LineStringZ split by edge with NaN values; PostGIS ticket #5635");
+
+    // PostGIS throws error
+    // Instead, we ignore the invalid point
+    testSplit("LINESTRING Z (1 2 1,3 4 10,5 6 3)",
+              "MULTIPOINT(1 NaN,2 1,2 4, 4 5)",
+              "GEOMETRYCOLLECTION( LINESTRING Z (1 2 1, 3 4 10, 4 5 6.5), LINESTRING Z (4 5 6.5, 5 6 3))");
+}
+
+template<>
+template<>
+void object::test<48>()
+{
+    set_test_name("split CircularString with line");
+
+    testSplit("CIRCULARSTRING (-5 0, -4 3, 4 3)",
+              "LINESTRING (0 0, 3 6)",
+              "GEOMETRYCOLLECTION (CIRCULARSTRING (-5 0, -2.6286555605956674 4.2532540417602, 2.23606797749979 4.47213595499958), CIRCULARSTRING (2.23606797749979 4.47213595499958, 3.2037371962288375 3.8387586505926348, 4 3))");
+}
+
+#if 0
+template<>
+template<>
+void object::test<49>()
+{
+    set_test_name("split CurvePolygon with line");
+    // fails because getInteriorPoint does not support curves
+
+    testSplit("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-5 0, -4 3, 4 3), (4 3, -5 0)))",
+              "LINESTRING (0 0, 3 6)",
+              "GEOMETRYCOLLECTION ("
+              "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING (-5 0, -2.6286555605956674 4.2532540417602, 2.23606797749979 4.47213595499958), (2.23606797749979 4.47213595499958, 1 2, -5 0))),"
+              "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING (2.23606797749979 4.47213595499958, 3.2037371962288375 3.8387586505926348, 4 3), (4 3, 1 2, 2.23606797749979 4.47213595499958))))"
+             );
+}
+
+template<>
+template<>
+void object::test<50>()
+{
+    set_test_name("split CircularString with point");
+    // not implemented yet: need curve support in DistanceOp
+
+    testSplit("CIRCULARSTRING (-5 0, -4 3, 4 3)",
+              "POINT (0 5)",
+              "GEOMETRYCOLLECTION (CIRCULARSTRING (-5 0, -3.5355339059327373 3.5355339059327378, 0 5), CIRCULARSTRING (0 5, 2.2360679774997902 4.47213595499958, 4 3))");
+}
+
+template<>
+template<>
+void object::test<51>()
+{
+    set_test_name("split CompoundCurve with point on curve");
+    // not implemented yet: need curve support in DistanceOp
+
+    testSplit("COMPOUNDCURVE(CIRCULARSTRING (-5 0, -4 3, 4 3), (4 3, 0 0))",
+              "POINT (0 5)",
+              "GEOMETRYCOLLECTION (CIRCULARSTRING (-5 0, -3.5355339059327373 3.5355339059327378, 0 5), CIRCULARSTRING (0 5, 2.2360679774997902 4.47213595499958, 4 3))");
+}
+#endif
+
+#if 0
+template<>
+template<>
+void object::test<52>()
+{
+    set_test_name("nearly-collapsed Polygon (QGIS test #1)");
+    // void TestQgsGeometry::splitGeometry()
+
+    // QGIS and PostGIS gives "GEOMETRYCOLLECTION EMPTY" here.
+    // Currently, GEOS is producing a result that is smaller than the original polygon.
+    // Neither seems ideal?
+    testSplit("POLYGON ((492980.38648063864093274 7082334.45244149677455425, 493082.65415841294452548 7082319.87918917648494244, 492980.38648063858272508 7082334.45244149677455425, 492980.38648063864093274 7082334.45244149677455425))",
+              "LINESTRING (493825.46541286131832749 7082214.02779923938214779, 492955.04876351181883365 7082338.06309300474822521)",
+              "GEOMETRYCOLLECTION EMPTY");
+}
+#endif
+
+template<>
+template<>
+void object::test<53>()
+{
+    set_test_name("Z values of split edge are not used in interpolation; QGIS test #2");
+    // See https://github.com/qgis/QGIS/issues/33489
+
+    testSplit("COMPOUNDCURVE Z ((2749546.20 1262904.45 100, 2749557.82 1262920.06 200))",
+              "LINESTRING Z (2749544.19 1262914.79 0, 2749557.64 1262897.30 0 )",
+              "GEOMETRYCOLLECTION Z (LINESTRING Z (2749546.2 1262904.45 100, 2749549.122464944 1262908.3759619428 125.15030072306642), LINESTRING Z (2749549.122464944 1262908.3759619428 125.15030072306642, 2749557.82 1262920.06 200))");
+}
+
+template<>
+template<>
+void object::test<54>()
+{
+    set_test_name("split CompoundCurve at an existing vertex; QGIS test #3");
+
+    testSplit("CompoundCurve ((1 1, 2 2, 3 3))",
+              "POINT (2 2)",
+              "GEOMETRYCOLLECTION(LINESTRING (1 1, 2 2), LINESTRING (2 2, 3 3))");
+}
+
+template<>
+template<>
+void object::test<55>()
+{
+    set_test_name("Split self-intersecting LineString at points; adaptation of QGIS test #4");
+
+    // Do not split on self-intersections - https://github.com/qgis/QGIS/issues/14070
+    testSplit("LINESTRING (0 0, 10 0, 10 2, 6 2, 6 -2, 3 -2, 3 2, 0 2, 0 0)",
+              "MULTIPOINT (0 1, 3 -1, 3 1, 6 -1, 6 1, 10 1)",
+              "GEOMETRYCOLLECTION (LINESTRING (0 0, 10 0, 10 1), LINESTRING (10 1, 10 2, 6 2, 6 1), LINESTRING (6 1, 6 -1), LINESTRING (6 -1, 6 -2, 3 -2, 3 -1), LINESTRING (3 -1, 3 1), LINESTRING (3 1, 3 2, 0 2, 0 1), LINESTRING (0 1, 0 0))");
+}
+
+#if 0
+template<>
+template<>
+void object::test<56>()
+{
+    set_test_name("do not split on self-intersections; QGIS test #4");
+
+    // Need to either
+    // a) do as QGIS does, and use Geometry::intersection to get intersection points, then node on those
+    // b) update GeometryNoder / SegmentIntersector classes with an ignoreSelfIntersections flag that would
+    //    ignore PathStrings with the same context.
+
+    // Do not split on self-intersections - https://github.com/qgis/QGIS/issues/14070
+    testSplit("LINESTRING (0 0, 10 0, 10 2, 6 2, 6 -2, 3 -2, 3 2, 0 2, 0 0)",
+              "LINESTRING (0 1, 11 1, 11 -1, 0 -1)",
+              "GEOMETRYCOLLECTION (LINESTRING (0 0, 10 0, 10 1), LINESTRING (10 1, 10 2, 6 2, 6 1), LINESTRING (6 1, 6 -1), LINESTRING (6 -1, 6 -2, 3 -2, 3 -1), LINESTRING (3 -1, 3 1), LINESTRING (3 1, 3 2, 0 2, 0 1), LINESTRING (0 1, 0 0))");
+}
+#endif
+
+template<>
+template<>
+void object::test<57>()
+{
+    set_test_name("do not split on self-intersections; QGIS test #5");
+
+    // Do not split on self-intersections - https://github.com/qgis/QGIS/issues/14070
+    testSplit("LINESTRING (0 0, 10 0, 10 2, 6 2, 6 -2, 3 -2, 3 2, 0 2, 0 0)",
+              "POINT (6 2)",
+              "GEOMETRYCOLLECTION (LINESTRING (0 0, 10 0, 10 2, 6 2), LINESTRING (6 2, 6 -2, 3 -2, 3 2, 0 2, 0 0))");
+}
+
+template<>
+template<>
+void object::test<58>()
+{
+    set_test_name("split LineString Z on existing vertex; QGIS test #6");
+
+    // https://github.com/qgis/QGIS/issues/49403
+    testSplit("LINESTRING Z (0 0 0, 1 1 1, 2 2 2)",
+              "POINT (1 1)",
+              "GEOMETRYCOLLECTION(LINESTRING Z (0 0 0, 1 1 1), LINESTRING Z (1 1 1, 2 2 2))");
+}
+
+template<>
+template<>
+void object::test<59>()
+{
+    // Should not crash - https://github.com/qgis/QGIS/issues/50948
+    testSplit("LINESTRING ( -63294.10966012725839391 -79156.27234554117603693, -63290.25259721937618451 -79162.78533450335089583, -63290.25259721936890855 -79162.78533450335089583)",
+              "LINESTRING (-63290.25259721936890855  -79165.28533450335089583, -63290.25259721936890855 -79160.28533450335089583)",
+              "GEOMETRYCOLLECTION(LINESTRING ( -63294.10966012725839391 -79156.27234554117603693, -63290.25259721937618451 -79162.78533450335089583, -63290.25259721936890855 -79162.78533450335089583))");
+}
+
+template<>
+template<>
+void object::test<60>()
+{
+    // Should not split the first part - https://github.com/qgis/QGIS/issues/54155
+    testSplit("MULTILINESTRING((0 1, 1 0), (0 2, 2 0))",
+              "LINESTRING (0.8 0.8, 1.2 1.2)",
+              "GEOMETRYCOLLECTION (LINESTRING (0 1, 1 0), LINESTRING (0 2, 1 1), LINESTRING (1 1, 2 0))");
+}
+
+}
diff --git a/tests/unit/operation/split/SplitGeometryAtVertexTest.cpp b/tests/unit/operation/split/SplitGeometryAtVertexTest.cpp
index da1a96449..59af695d2 100644
--- a/tests/unit/operation/split/SplitGeometryAtVertexTest.cpp
+++ b/tests/unit/operation/split/SplitGeometryAtVertexTest.cpp
@@ -7,6 +7,7 @@
 #include <geos/operation/split/SplitGeometryAtVertex.h>
 #include <geos/io/WKTReader.h>
 
+using geos::geom::CoordinateXY;
 using geos::geom::CircularString;
 using geos::geom::LineString;
 using geos::operation::split::SplitGeometryAtVertex;
@@ -26,7 +27,7 @@ template<>
 template<>
 void object::test<1>()
 {
-    set_test_name("LineString");
+    set_test_name("Split LineString ZM at vertex");
 
     auto input = reader_.read<LineString>("LINESTRING ZM (0 3 2 3, 5 8 3 4, 2 2 4 5, 6 1 5 6)");
 
@@ -63,7 +64,7 @@ template<>
 template<>
 void object::test<2>()
 {
-    set_test_name("CircularString");
+    set_test_name("Split CircularString ZM at vertex");
 
     auto input = reader_.read<CircularString>("CIRCULARSTRING ZM (-5 0 1 2, 0 5 2 3, 5 0 3 4, 10 -5 4 5, 15 0 5 6)");
 
@@ -98,4 +99,41 @@ void object::test<2>()
     ensure_THROW(SplitGeometryAtVertex::splitSimpleCurveAtVertex(*input, 1), geos::util::IllegalArgumentException);
 }
 
+template<>
+template<>
+void object::test<3>()
+{
+    set_test_name("Split LineString ZM at new point");
+
+    auto input = reader_.read<LineString>("LINESTRING ZM (0 3 2 3, 5 8 3 4, 2 2 4 5, 6 1 5 6)");
+
+    CoordinateXY pt{2, 3};
+
+    ensure_THROW(SplitGeometryAtVertex::splitLineStringAtPoint(*input, 3, pt), geos::util::IllegalArgumentException);
+
+    // Split first segment
+    {
+        auto [first, second] = SplitGeometryAtVertex::splitLineStringAtPoint(*input, 0, pt);
+
+        auto expectedFirst = reader_.read("LINESTRING ZM (0 3 2 3, 2 3 2.282842712474619 3.282842712474619)");
+        auto expectedSecond = reader_.read("LINESTRING ZM (2 3 2.282842712474619 3.282842712474619, 5 8 3 4, 2 2 4 5, 6 1 5 6)");
+
+        ensure_equals_exact_geometry_xyzm(first.get(), expectedFirst.get(), 0.0);
+        ensure_equals_exact_geometry_xyzm(second.get(), expectedSecond.get(), 0.0);
+    }
+
+    // Split second segment
+    {
+        auto [first, second] = SplitGeometryAtVertex::splitLineStringAtPoint(*input, 1, pt);
+
+        auto expectedFirst = reader_.read("LINESTRING ZM (0 3 2 3, 5 8 3 4, 2 3 3.8692269873603533 4.869226987360353)");
+        auto expectedSecond = reader_.read("LINESTRING ZM (2 3 3.8692269873603533 4.869226987360353, 2 2 4 5, 6 1 5 6)");
+
+        ensure_equals_exact_geometry_xyzm(first.get(), expectedFirst.get(), 0.0);
+        ensure_equals_exact_geometry_xyzm(second.get(), expectedSecond.get(), 0.0);
+    }
+
+
+}
+
 }

commit 37d194e042b90981ab98449e64de3b6848104c17
Author: Daniel Baston <dbaston at gmail.com>
Date:   Thu Apr 9 12:10:27 2026 -0400

    GeometryNoder: Handle two geometries as input

diff --git a/include/geos/noding/GeometryNoder.h b/include/geos/noding/GeometryNoder.h
index 007010c35..90fed94de 100644
--- a/include/geos/noding/GeometryNoder.h
+++ b/include/geos/noding/GeometryNoder.h
@@ -45,20 +45,28 @@ public:
 
     static std::unique_ptr<geom::Geometry> node(const geom::Geometry& geom);
 
+    static std::unique_ptr<geom::Geometry> node(const geom::Geometry& geom1, const geom::Geometry& geom2);
+
     GeometryNoder(const geom::Geometry& g);
 
+    GeometryNoder(const geom::Geometry& g1, const geom::Geometry& g2);
+
     ~GeometryNoder();
 
     std::unique_ptr<geom::Geometry> getNoded();
 
+    void setOnlyFirstGeomEdges(bool onlyFirstGeomEdges);
+
     // Declare type as noncopyable
     GeometryNoder(GeometryNoder const&) = delete;
     GeometryNoder& operator=(GeometryNoder const&) = delete;
 
 private:
 
-    const geom::Geometry& argGeom;
+    const geom::Geometry* argGeom1;
+    const geom::Geometry* argGeom2;
     const bool argGeomHasCurves;
+    bool onlyFirstGeomEdges;
 
     std::unique_ptr<Noder> noder;
     std::unique_ptr<algorithm::CircularArcIntersector> m_cai;
diff --git a/src/noding/GeometryNoder.cpp b/src/noding/GeometryNoder.cpp
index 6c990cbae..83fea2fa7 100644
--- a/src/noding/GeometryNoder.cpp
+++ b/src/noding/GeometryNoder.cpp
@@ -58,10 +58,12 @@ class PathStringExtractor: public geom::GeometryComponentFilter {
 public:
     PathStringExtractor(std::vector<std::unique_ptr<PathString>> & to,
                            bool constructZ,
-                           bool constructM)
+                           bool constructM,
+                           const void* context)
         : _to(to)
         , _constructZ(constructZ)
         , _constructM(constructM)
+        , _context(context)
     {}
 
     void
@@ -73,13 +75,13 @@ public:
 
         if(const auto* ls = dynamic_cast<const geom::LineString*>(g)) {
             auto coord = ls->getSharedCoordinates();
-            auto ss = std::make_unique<NodedSegmentString>(coord, _constructZ, _constructM, nullptr);
+            auto ss = std::make_unique<NodedSegmentString>(coord, _constructZ, _constructM, _context);
             _to.push_back(std::move(ss));
         } else if (const auto* cs = dynamic_cast<const geom::CircularString*>(g)) {
             const auto& coords = cs->getSharedCoordinates();
             auto arcs = cs->getArcs();
 
-            auto as = std::make_unique<NodableArcString>(std::move(arcs), coords, _constructZ, _constructM, nullptr);
+            auto as = std::make_unique<NodableArcString>(std::move(arcs), coords, _constructZ, _constructM, _context);
             _to.push_back(std::move(as));
         } else if (const auto* cc = dynamic_cast<const geom::CompoundCurve*>(g)) {
             for (std::size_t i = 0; i < cc->getNumCurves(); i++) {
@@ -91,6 +93,7 @@ private:
     std::vector<std::unique_ptr<PathString>>& _to;
     bool _constructZ;
     bool _constructM;
+    const void* _context;
 
     PathStringExtractor(PathStringExtractor const&); /*= delete*/
     PathStringExtractor& operator=(PathStringExtractor const&); /*= delete*/
@@ -107,11 +110,28 @@ GeometryNoder::node(const geom::Geometry& geom)
     return noder.getNoded();
 }
 
+std::unique_ptr<geom::Geometry>
+GeometryNoder::node(const geom::Geometry& geom1, const geom::Geometry& geom2)
+{
+    GeometryNoder noder(geom1, geom2);
+    return noder.getNoded();
+}
+
 /* public */
 GeometryNoder::GeometryNoder(const geom::Geometry& g)
     :
-    argGeom(g),
-    argGeomHasCurves(g.hasCurvedComponents())
+    argGeom1(&g),
+    argGeom2(nullptr),
+    argGeomHasCurves(g.hasCurvedComponents()),
+    onlyFirstGeomEdges(false)
+{}
+
+GeometryNoder::GeometryNoder(const geom::Geometry& g1, const geom::Geometry& g2)
+    :
+    argGeom1(&g1),
+    argGeom2(&g2),
+    argGeomHasCurves(g1.hasCurvedComponents() || g2.hasCurvedComponents()),
+    onlyFirstGeomEdges(false)
 {}
 
 GeometryNoder::~GeometryNoder() = default;
@@ -120,7 +140,7 @@ GeometryNoder::~GeometryNoder() = default;
 std::unique_ptr<geom::Geometry>
 GeometryNoder::toGeometry(std::vector<std::unique_ptr<PathString>>& nodedEdges) const
 {
-    const geom::GeometryFactory* geomFact = argGeom.getFactory();
+    const geom::GeometryFactory* geomFact = argGeom1->getFactory();
 
     std::set< OrientedCoordinateArray > ocas;
 
@@ -130,6 +150,10 @@ GeometryNoder::toGeometry(std::vector<std::unique_ptr<PathString>>& nodedEdges)
 
     bool resultArcs = false;
     for(auto& path :  nodedEdges) {
+        if (onlyFirstGeomEdges && path->getData() != argGeom1) {
+            continue;
+        }
+
         const auto& coords = path->getCoordinates();
 
         bool isLinear = dynamic_cast<SegmentString*>(path.get());
@@ -159,11 +183,15 @@ GeometryNoder::toGeometry(std::vector<std::unique_ptr<PathString>>& nodedEdges)
 std::unique_ptr<geom::Geometry>
 GeometryNoder::getNoded()
 {
-    if (argGeom.isEmpty())
-        return argGeom.clone();
+    if (argGeom1->isEmpty() && (argGeom2 == nullptr || argGeom2->isEmpty()))
+        return argGeom1->clone();
 
     std::vector<std::unique_ptr<PathString>> lineList;
-    extractPathStrings(argGeom, lineList);
+
+    extractPathStrings(*argGeom1, lineList);
+    if (argGeom2 != nullptr) {
+        extractPathStrings(*argGeom2, lineList);
+    }
 
     Noder& p_noder = getNoder();
     p_noder.computePathNodes(PathString::toRawPointerVector(lineList));
@@ -179,7 +207,7 @@ void
 GeometryNoder::extractPathStrings(const geom::Geometry& g,
                                   std::vector<std::unique_ptr<PathString>>& to)
 {
-    PathStringExtractor ex(to, g.hasZ(), g.hasM());
+    PathStringExtractor ex(to, g.hasZ(), g.hasM(), &g);
     g.apply_ro(&ex);
 }
 
@@ -188,11 +216,11 @@ Noder&
 GeometryNoder::getNoder()
 {
     if(!noder) {
-        const geom::PrecisionModel* pm = argGeom.getFactory()->getPrecisionModel();
+        const geom::PrecisionModel* pm = argGeom1->getFactory()->getPrecisionModel();
         if (argGeomHasCurves) {
             noder = std::make_unique<SimpleNoder>();
 
-            m_cai = std::make_unique<algorithm::CircularArcIntersector>(argGeom.getPrecisionModel());
+            m_cai = std::make_unique<algorithm::CircularArcIntersector>(argGeom1->getPrecisionModel());
             m_aia = std::make_unique<ArcIntersectionAdder>(*m_cai);
             detail::down_cast<SimpleNoder*>(noder.get())->setArcIntersector(*m_aia);
         } else {
@@ -202,6 +230,11 @@ GeometryNoder::getNoder()
     return *noder;
 }
 
+void
+GeometryNoder::setOnlyFirstGeomEdges(bool p_onlyFirstGeomEdges)
+{
+    onlyFirstGeomEdges = p_onlyFirstGeomEdges;
+}
 
 } // namespace geos.noding
 } // namespace geos
diff --git a/tests/unit/noding/GeometryNoderTest.cpp b/tests/unit/noding/GeometryNoderTest.cpp
new file mode 100644
index 000000000..bf694b56f
--- /dev/null
+++ b/tests/unit/noding/GeometryNoderTest.cpp
@@ -0,0 +1,75 @@
+#include <tut/tut.hpp>
+
+#include <geos/geom/Geometry.h>
+#include <geos/noding/GeometryNoder.h>
+
+#include "utility.h"
+
+using geos::geom::Geometry;
+using geos::noding::GeometryNoder;
+
+namespace tut {
+
+struct test_geometrynoder_data {
+    geos::io::WKTReader reader_;
+};
+
+typedef test_group<test_geometrynoder_data> group;
+typedef group::object object;
+
+group test_geometrynoder_group("geos::noding::GeometryNoder");
+
+template<>
+template<>
+void object::test<1>()
+{
+    set_test_name("single input") ;
+
+    auto input = reader_.read("MULTILINESTRING ((0 0, 10 10, 10 0, 0 10))");
+
+    auto result = GeometryNoder::node(*input);
+    ensure(result != nullptr);
+
+    auto expected = reader_.read("MULTILINESTRING ((0 0, 5 5), (5 5, 10 10, 10 0, 5 5), (5 5, 0 10))");
+
+    ensure_equals_exact_geometry_xyzm( result.get(), expected.get(), 0);
+}
+
+template<>
+template<>
+void object::test<2>()
+{
+    set_test_name("two inputs, output all edges") ;
+
+    auto input1 = reader_.read("LINESTRING (0 0, 10 10)");
+    auto input2 = reader_.read("LINESTRING (0 10, 10 0)");
+
+    auto result = GeometryNoder::node(*input1, *input2);
+    ensure(result != nullptr);
+
+    auto expected = reader_.read("MULTILINESTRING ((0 0, 5 5), (5 5, 10 10), (0 10, 5 5), (5 5, 10 0))");
+
+    ensure_equals_exact_geometry_xyzm(result.get(), expected.get(), 0);
+}
+
+template<>
+template<>
+void object::test<3>()
+{
+    set_test_name("two inputs, only output edges from first") ;
+
+    auto input1 = reader_.read("LINESTRING (0 0, 10 10)");
+    auto input2 = reader_.read("LINESTRING (0 10, 10 0)");
+
+    GeometryNoder noder(*input1, *input2);
+    noder.setOnlyFirstGeomEdges(true);
+
+    auto result = noder.getNoded();
+    ensure(result != nullptr);
+
+    auto expected = reader_.read("MULTILINESTRING ((0 0, 5 5), (5 5, 10 10))");
+
+    ensure_equals_exact_geometry_xyzm(result.get(), expected.get(), 0);
+}
+
+} // namespace tut

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

Summary of changes:
 capi/geos_c.cpp                                    |   6 +
 capi/geos_c.h.in                                   |  24 +
 capi/geos_ts_c.cpp                                 |  11 +
 include/geos/noding/GeometryNoder.h                |  10 +-
 include/geos/operation/split/GeometrySplitter.h    |  62 ++
 .../geos/operation/split/SplitGeometryAtVertex.h   |   5 +
 include/geos/shape/GeometricShapeBuilder.h         |  83 +++
 include/geos/shape/random/RandomPointsBuilder.h    |  65 ++
 src/noding/GeometryNoder.cpp                       |  57 +-
 src/operation/split/GeometrySplitter.cpp           | 345 +++++++++
 src/operation/split/SplitGeometryAtVertex.cpp      |  41 +-
 src/shape/GeometricShapeBuilder.cpp                | 101 +++
 src/shape/random/RandomPointsBuilder.cpp           |  96 +++
 tests/unit/capi/GEOSSplitTest.cpp                  |  53 ++
 tests/unit/noding/GeometryNoderTest.cpp            |  75 ++
 .../unit/operation/split/GeometrySplitterTest.cpp  | 774 +++++++++++++++++++++
 .../operation/split/SplitGeometryAtVertexTest.cpp  |  42 +-
 .../unit/shape/random/RandomPointsBuilderTest.cpp  |  94 +++
 util/geosop/GeometryOp.cpp                         |   8 +
 19 files changed, 1936 insertions(+), 16 deletions(-)
 create mode 100644 include/geos/operation/split/GeometrySplitter.h
 create mode 100644 include/geos/shape/GeometricShapeBuilder.h
 create mode 100644 include/geos/shape/random/RandomPointsBuilder.h
 create mode 100644 src/operation/split/GeometrySplitter.cpp
 create mode 100644 src/shape/GeometricShapeBuilder.cpp
 create mode 100644 src/shape/random/RandomPointsBuilder.cpp
 create mode 100644 tests/unit/capi/GEOSSplitTest.cpp
 create mode 100644 tests/unit/noding/GeometryNoderTest.cpp
 create mode 100644 tests/unit/operation/split/GeometrySplitterTest.cpp
 create mode 100644 tests/unit/shape/random/RandomPointsBuilderTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list