From aff9d860e8f7521f21d36e11d44ccbc4ee831553 Mon Sep 17 00:00:00 2001 From: Dan Baston Date: Mon, 9 Mar 2026 10:12:46 -0400 Subject: [PATCH] GEOSSnap: Preserve Z values from input --- capi/geos_c.h.in | 3 +++ .../operation/overlay/snap/GeometrySnapper.h | 2 +- .../overlay/snap/GeometrySnapper.cpp | 15 ++++++++----- .../overlay/snap/LineStringSnapper.cpp | 6 ++++-- tests/unit/capi/GEOSSnapTest.cpp | 21 +++++++++++++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in index 21a85501f9..1c9400d71b 100644 --- a/capi/geos_c.h.in +++ b/capi/geos_c.h.in @@ -5505,6 +5505,9 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXYZ( * Snaps the vertices and segments of the first geometry to vertices of the * second geometry within the given tolerance. * +* Z values from the input will be preserved. +* M values from the input will be discarded. +* * Where possible, this operation tries to avoid creating invalid geometries; * however, it does not guarantee that output geometries will be valid. It is * the responsibility of the caller to check for and handle invalid geometries. diff --git a/include/geos/operation/overlay/snap/GeometrySnapper.h b/include/geos/operation/overlay/snap/GeometrySnapper.h index 63f092f026..aeb807f3e0 100644 --- a/include/geos/operation/overlay/snap/GeometrySnapper.h +++ b/include/geos/operation/overlay/snap/GeometrySnapper.h @@ -138,7 +138,7 @@ class GEOS_DLL GeometrySnapper { const geom::Geometry& srcGeom; /// Extract target (unique) coordinates - std::unique_ptr extractTargetCoordinates( + static std::unique_ptr extractTargetCoordinates( const geom::Geometry& g); // Declare type as noncopyable diff --git a/src/operation/overlay/snap/GeometrySnapper.cpp b/src/operation/overlay/snap/GeometrySnapper.cpp index 7a13005f5f..55925fb3b3 100644 --- a/src/operation/overlay/snap/GeometrySnapper.cpp +++ b/src/operation/overlay/snap/GeometrySnapper.cpp @@ -51,6 +51,8 @@ class SnapTransformer: public geos::geom::util::GeometryTransformer { double snapTol; const Coordinate::ConstVect& snapPts; + bool constructZ; + bool constructM; CoordinateSequence::Ptr snapLine( @@ -59,7 +61,7 @@ class SnapTransformer: public geos::geom::util::GeometryTransformer { using std::unique_ptr; assert(srcPts); - auto coords = detail::make_unique(); + auto coords = detail::make_unique(0, constructZ, constructM); coords->add(*srcPts); LineStringSnapper snapper(*coords, snapTol); return snapper.snapTo(snapPts); @@ -68,10 +70,13 @@ class SnapTransformer: public geos::geom::util::GeometryTransformer { public: SnapTransformer(double nSnapTol, - const Coordinate::ConstVect& nSnapPts) + const Coordinate::ConstVect& nSnapPts, + bool p_constructZ, bool p_constructM) : snapTol(nSnapTol), - snapPts(nSnapPts) + snapPts(nSnapPts), + constructZ(p_constructZ), + constructM(p_constructM) { } @@ -112,7 +117,7 @@ GeometrySnapper::snapTo(const geom::Geometry& g, double snapTolerance) // Apply a SnapTransformer to source geometry // (we need a pointer for dynamic polymorphism) - std::unique_ptr snapTrans(new SnapTransformer(snapTolerance, *snapPts)); + std::unique_ptr snapTrans(new SnapTransformer(snapTolerance, *snapPts, srcGeom.hasZ(), false)); return snapTrans->transform(&srcGeom); } @@ -129,7 +134,7 @@ GeometrySnapper::snapToSelf(double snapTolerance, bool cleanResult) // Apply a SnapTransformer to source geometry // (we need a pointer for dynamic polymorphism) - std::unique_ptr snapTrans(new SnapTransformer(snapTolerance, *snapPts)); + std::unique_ptr snapTrans(new SnapTransformer(snapTolerance, *snapPts, srcGeom.hasZ(), false)); std::unique_ptr result = snapTrans->transform(&srcGeom); diff --git a/src/operation/overlay/snap/LineStringSnapper.cpp b/src/operation/overlay/snap/LineStringSnapper.cpp index d196414a71..e8d5a1e9e7 100644 --- a/src/operation/overlay/snap/LineStringSnapper.cpp +++ b/src/operation/overlay/snap/LineStringSnapper.cpp @@ -153,7 +153,8 @@ LineStringSnapper::snapVertices(geom::CoordinateList& srcCoords, #if GEOS_DEBUG std::cerr << " Vertex to be snapped found, snapping" << std::endl; #endif - *vertpos = snapPt; + vertpos->x = snapPt.x; + vertpos->y = snapPt.y; // keep final closing point in synch (rings only) if(vertpos == srcCoords.begin() && isClosed) { @@ -162,7 +163,8 @@ LineStringSnapper::snapVertices(geom::CoordinateList& srcCoords, #if GEOS_DEBUG std::cerr << " Snapped vertex was first in a closed line, also snapping last" << std::endl; #endif - *vertpos = snapPt; + vertpos->x = snapPt.x; + vertpos->y = snapPt.y; } #if GEOS_DEBUG diff --git a/tests/unit/capi/GEOSSnapTest.cpp b/tests/unit/capi/GEOSSnapTest.cpp index 7fedee2e63..54aba097c0 100644 --- a/tests/unit/capi/GEOSSnapTest.cpp +++ b/tests/unit/capi/GEOSSnapTest.cpp @@ -220,5 +220,26 @@ void object::test<11>() ensure("curved geometry not supported", result_ == nullptr); } +template<> +template<> +void object::test<12> +() +{ + set_test_name("POLYGON ZM snapped to Point"); + + geom1_ = fromWKT("POLYGON ZM ((0 0 4 3, 10 0 2 8, 10 10 6 1, 0 1 -3 4, 0 0 4 3))"); + geom2_ = fromWKT("POINT (0.5 0)"); + ensure(geom1_); + ensure(geom2_); + + result_ = GEOSSnap(geom1_, geom2_, 1); + ensure(result_); + + expected_ = fromWKT("POLYGON Z ((0.5 0 4, 10 0 2, 10 10 6, 0 1 -3, 0.5 0 4))"); + ensure(expected_); + + ensure_geometry_equals_identical(result_, expected_); +} + } // namespace tut