Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions capi/geos_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,12 @@ extern "C" {
return GEOSNode_r(handle, g);
}

Geometry*
GEOSNodeCollection(const Geometry* input, double gridSize)
{
return GEOSNodeCollection_r(handle, input, gridSize);
}

Geometry*
GEOSSplit(const Geometry* g, const Geometry* edge)
{
Expand Down
34 changes: 34 additions & 0 deletions capi/geos_c.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,12 @@ extern GEOSGeometry GEOS_DLL *GEOSNode_r(
GEOSContextHandle_t handle,
const GEOSGeometry* g);

/** \see GEOSNodeCollection */
extern GEOSGeometry GEOS_DLL *GEOSNodeCollection_r(
GEOSContextHandle_t handle,
const GEOSGeometry* input,
double gridSize);

/** \see GEOSSplit */
extern GEOSGeometry GEOS_DLL *GEOSSplit_r(
GEOSContextHandle_t handle,
Expand Down Expand Up @@ -5562,6 +5568,34 @@ extern GEOSGeometry GEOS_DLL * GEOSVoronoiDiagram(
*/
extern GEOSGeometry GEOS_DLL *GEOSNode(const GEOSGeometry* g);

/**
* Nodes a collection of linear geometries against each other,
* returning a collection of the same size where each member has been
* split into a MultiLineString at all interior node points.
*
* Unlike GEOSNode(), which collects all edges into a single flattened
* MultiLineString, this function preserves the per-member structure of
* the input, returning one MultiLineString per input element.
* Linework shared between members is not dissolved.
*
* Input members must be linear (LineString, MultiLineString, or
* GeometryCollection of linear types). Non-linear components are
* ignored; their output slot will be an empty MultiLineString.
*
* \param input A GeometryCollection of linear geometries.
* \param gridSize Snap-rounding grid size for robust noding of inputs
* with near-coincident coordinates, or 0.0 for standard noding.
* \return A GeometryCollection of the same size as the input, with each
* member returned as a noded MultiLineString, or NULL on error.
* Caller is responsible for freeing with GEOSGeom_destroy().
* \see geos::operation::linenode::LineCollectionNoder::node
*
* \since 3.14
*/
extern GEOSGeometry GEOS_DLL *GEOSNodeCollection(
const GEOSGeometry* input,
double gridSize);


/** Split a linear or polygonal input
*
Expand Down
28 changes: 28 additions & 0 deletions capi/geos_ts_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
#include <geos/operation/grid/Grid.h>
#include <geos/operation/grid/GridIntersection.h>
#include <geos/operation/linemerge/LineMerger.h>
#include <geos/operation/linenode/LineCollectionNoder.h>
#include <geos/operation/spanning/SpanningTree.h>
#include <geos/operation/split/GeometrySplitter.h>
#include <geos/operation/intersection/Rectangle.h>
Expand Down Expand Up @@ -2042,6 +2043,33 @@ extern "C" {
});
}

Geometry*
GEOSNodeCollection_r(GEOSContextHandle_t extHandle,
const Geometry* input,
double gridSize)
{
using geos::operation::linenode::LineCollectionNoder;

return execute(extHandle, [&]() -> Geometry* {
const GeometryCollection* col =
dynamic_cast<const GeometryCollection*>(input);
if (!col) return nullptr;

std::vector<const Geometry*> geoms;
geoms.reserve(col->getNumGeometries());
for (std::size_t i = 0; i < col->getNumGeometries(); i++)
geoms.push_back(col->getGeometryN(i));

LineCollectionNoder lcn(geoms);
auto result = lcn.node(gridSize);

const GeometryFactory* gf = input->getFactory();
auto r = gf->createGeometryCollection(std::move(result));
r->setSRID(input->getSRID());
return r.release();
});
}

Geometry*
GEOSSplit_r(GEOSContextHandle_t extHandle, const Geometry* g, const Geometry* edge)
{
Expand Down
31 changes: 31 additions & 0 deletions include/geos/noding/OrientedCoordinateArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <geos/export.h>

#include <cstddef>
#include <set>

// Forward declarations
namespace geos {
Expand Down Expand Up @@ -108,6 +109,36 @@ operator< (const OrientedCoordinateArray& oca1,
return oca1.compareTo(oca2) < 0;
}

/** \brief
* Deduplicates coordinate sequences in an orientation-independent way.
*
* Tracks the set of sequences seen so far (as OrientedCoordinateArrays)
* and reports whether each newly offered sequence is novel. Used by
* noders to drop edges that are geometrically identical regardless of
* direction.
*
* NOTE: like OrientedCoordinateArray, this stores only pointers to the
* sequences offered to add(). Every sequence passed in must outlive the
* EdgeDeduplicator.
*/
class GEOS_DLL EdgeDeduplicator {
public:

/**
* @param seq a coordinate sequence; must outlive this object
* @return true if seq was not equivalent to any previously added
* sequence (and is now recorded), false if it is a duplicate
*/
bool add(const geom::CoordinateSequence& seq) {
return m_seen.insert(OrientedCoordinateArray(seq)).second;
}

private:

std::set<OrientedCoordinateArray> m_seen;

};

} // namespace geos.noding
} // namespace geos

147 changes: 147 additions & 0 deletions include/geos/operation/linenode/LineCollectionNoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**********************************************************************
*
* GEOS - Geometry Engine Open Source
* http://geos.osgeo.org
*
* Copyright (C) 2025 Paul Ramsey <pramsey@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/export.h>
#include <memory>
#include <vector>

// Forward declarations
namespace geos {
namespace geom {
class Geometry;
class GeometryFactory;
}
namespace noding {
class NodedSegmentString;
class SegmentString;
}
}

namespace geos {
namespace operation {
namespace linenode {

/**
* Nodes a collection of linear geometries against each other,
* returning a collection of the same size where each member has
* been split into a MultiLineString at all interior node points.
*
* Unlike geos::noding::GeometryNoder::node(), which collects all
* input edges into a single flattened MultiLineString, this class
* preserves the identity of each input member. Linework that is
* shared (or nearly shared) between members is not dissolved.
*
* Input geometries must be linear (LineString, MultiLineString, or
* a GeometryCollection of linear types). Non-linear components (e.g.
* polygon rings, points) are silently ignored; their output slot
* will contain an empty MultiLineString.
*
* When gridSize == 0.0 (default), standard IteratedNoder is used.
* When gridSize > 0.0, SnapRoundingNoder is used instead, providing
* robust output for inputs with near-coincident coordinates
* (see https://github.com/libgeos/geos/issues/877).
* Values <= 0.0 all use IteratedNoder.
*
* Usage:
* @code
* std::vector<const Geometry*> lines = { ... };
* auto noded = LineCollectionNoder::node(lines); // standard noding
* auto noded = LineCollectionNoder::node(lines, 1.0); // snap-rounding
* @endcode
*
* @author Paul Ramsey
*/
class GEOS_DLL LineCollectionNoder {

using Geometry = geos::geom::Geometry;
using GeometryFactory = geos::geom::GeometryFactory;
using NodedSegmentString = geos::noding::NodedSegmentString;
using SegmentString = geos::noding::SegmentString;

public:

/**
* Creates a new LineCollectionNoder for the given collection of
* linear geometries.
*
* @param collection input geometries; caller retains ownership.
* The vector must outlive this object.
*/
LineCollectionNoder(const std::vector<const Geometry*>& collection);

/**
* Nodes a collection of linear geometries (vector of raw const pointers).
*
* @param collection vector of linear geometries
* @param gridSize snap-rounding grid size, or 0.0 for standard noding
* @return one noded MultiLineString per input element, in the same order
*/
static std::vector<std::unique_ptr<Geometry>> node(
std::vector<const Geometry*>& collection,
double gridSize = 0.0);

/**
* Nodes a collection of linear geometries (vector of unique_ptr).
*
* @param collection vector of owning pointers to linear geometries
* @param gridSize snap-rounding grid size, or 0.0 for standard noding
* @return one noded MultiLineString per input element, in the same order
*/
static std::vector<std::unique_ptr<Geometry>> node(
const std::vector<std::unique_ptr<Geometry>>& collection,
double gridSize = 0.0);

/**
* Computes the noded collection.
*
* @param gridSize snap-rounding grid size, or 0.0 for standard noding
* @return one noded MultiLineString per input element, in the same order
*/
std::vector<std::unique_ptr<Geometry>> node(double gridSize = 0.0);

// Noncopyable
LineCollectionNoder(const LineCollectionNoder&) = delete;
LineCollectionNoder& operator=(const LineCollectionNoder&) = delete;

private:

const std::vector<const Geometry*>& m_input;
const GeometryFactory* m_geomFactory;

/**
* Extracts NodedSegmentStrings from the linear components of g
* (LineString only, not LinearRing/polygon rings), tagging each
* with reinterpret_cast<const void*>(static_cast<uintptr_t>(index)).
*/
static void extractSegments(
const Geometry& g,
std::size_t index,
std::vector<std::unique_ptr<NodedSegmentString>>& segments);

/**
* Builds a MultiLineString from the noded sub-segments that carry
* the given source index tag. Deduplicates via OrientedCoordinateArray
* (same logic as GeometryNoder::toGeometry).
*/
std::unique_ptr<Geometry> buildResult(
const std::vector<std::unique_ptr<SegmentString>>& nodedSegs,
std::size_t index) const;

};

} // geos::operation::linenode
} // geos::operation
} // geos
7 changes: 2 additions & 5 deletions src/noding/GeometryNoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ GeometryNoder::toGeometry(std::vector<std::unique_ptr<PathString>>& nodedEdges)
{
const geom::GeometryFactory* geomFact = argGeom1->getFactory();

std::set< OrientedCoordinateArray > ocas;
EdgeDeduplicator dedup;

std::vector<PathString*> pathsToKeep;

Expand All @@ -327,11 +327,8 @@ GeometryNoder::toGeometry(std::vector<std::unique_ptr<PathString>>& nodedEdges)
continue;
}

const auto& coords = path->getCoordinates();
OrientedCoordinateArray oca1(*coords);

// Check if an equivalent edge is known
if(ocas.insert(oca1).second) {
if(dedup.add(*path->getCoordinates())) {
pathsToKeep.push_back(path.get());
}
}
Expand Down
Loading
Loading