[geos-commits] [SCM] GEOS branch main updated. 8ef887079e3d8f18fef4bb7f7e9ab52866b437fa
git at osgeo.org
git at osgeo.org
Wed Feb 4 12:19:52 PST 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 8ef887079e3d8f18fef4bb7f7e9ab52866b437fa (commit)
via b2041c1034533ddb5f4e3cdccee96309c815ffdc (commit)
via bf8e9ba34a6e9081d4d8a7a4cf2f468989143667 (commit)
from c7ca3dd29bf379c43ae6f97fbc1bdfa10dcf630e (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 8ef887079e3d8f18fef4bb7f7e9ab52866b437fa
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Wed Feb 4 11:55:51 2026 -0800
Add news entry for bf8e9ba
diff --git a/NEWS.md b/NEWS.md
index 43926615a..187b4781c 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -8,6 +8,10 @@
- Breaking Changes:
- BufferOp returns POLYGON EMPTY when fed Inf/Nan coords (GH-1332)
+- Fixes/Improvements:
+ - Buffer of Linestring includes spurious hole (GH-1217, Moritz Kirmse)
+
+
## Changes in 3.14.0
2025-08-21
commit b2041c1034533ddb5f4e3cdccee96309c815ffdc
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Wed Feb 4 11:53:53 2026 -0800
Remove duplicate tests
diff --git a/tests/unit/operation/buffer/BufferOpTest.cpp b/tests/unit/operation/buffer/BufferOpTest.cpp
index 8adc77438..8971402ed 100644
--- a/tests/unit/operation/buffer/BufferOpTest.cpp
+++ b/tests/unit/operation/buffer/BufferOpTest.cpp
@@ -717,38 +717,4 @@ void object::test<34> ()
-// testInvalidCoordPoint
-template<>
-template<>
-void object::test<31> ()
-{
- // works for Inf ordinates as well
- auto geom = wktreader.read("POINT (NaN NaN)");
- checkBufferPolygonEmpty(*geom, 1, true);
-}
-
-// testInvalidCoordsLine
-template<>
-template<>
-void object::test<32> ()
-{
- // works for Inf ordinates as well
- auto geom = wktreader.read("LINESTRING (NaN NaN, NaN NaN)");
- checkBufferPolygonEmpty(*geom, 1, true);
-}
-
-// testInvalidCoordShell
-template<>
-template<>
-void object::test<33> ()
-{
- // using Inf ordinates creates a valid ring with equal endpoints
- // this would be simpler if JTS WKT supported Inf
- auto geom = wktreader.read("POLYGON ((Inf Inf, Inf Inf, Inf Inf, Inf Inf, Inf Inf))");
- checkBufferPolygonEmpty(*geom, 1, true);
-}
-
-
-
-
} // namespace tut
commit bf8e9ba34a6e9081d4d8a7a4cf2f468989143667
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Wed Feb 4 11:31:37 2026 -0800
Buffer of Linestring results in polygon with spurious hole.
Author: Moritz Kirmse <moritz.kirmse at camptocamp.com>
Closes GH-1217
diff --git a/include/geos/operation/buffer/OffsetSegmentGenerator.h b/include/geos/operation/buffer/OffsetSegmentGenerator.h
index 5408f8787..05711d959 100644
--- a/include/geos/operation/buffer/OffsetSegmentGenerator.h
+++ b/include/geos/operation/buffer/OffsetSegmentGenerator.h
@@ -144,7 +144,8 @@ public:
/// Add an end cap around point p1, terminating a line segment
/// coming from p0
void addLineEndCap(const geom::Coordinate& p0,
- const geom::Coordinate& p1);
+ const geom::Coordinate& p1,
+ const geom::Coordinate& p2);
void
addSegments(const geom::CoordinateSequence& pts, bool isForward)
diff --git a/src/operation/buffer/OffsetCurveBuilder.cpp b/src/operation/buffer/OffsetCurveBuilder.cpp
index ed2ae6c22..c57e66f2e 100644
--- a/src/operation/buffer/OffsetCurveBuilder.cpp
+++ b/src/operation/buffer/OffsetCurveBuilder.cpp
@@ -377,8 +377,6 @@ OffsetCurveBuilder::computeLineBufferCurve(const CoordinateSequence& inputPts,
segGen.addNextSegment(simp1[i], true);
}
segGen.addLastSegment();
- // add line cap for end of line
- segGen.addLineEndCap(simp1[n1 - 1], simp1[n1]);
//---------- compute points for right side of line
// Simplify the appropriate side of the line before generating
@@ -387,13 +385,17 @@ OffsetCurveBuilder::computeLineBufferCurve(const CoordinateSequence& inputPts,
const CoordinateSequence& simp2 = *simp2_;
auto n2 = simp2.size() - 1;
+
+ // add line cap for return of line
+ segGen.addLineEndCap(simp1[n1 - 1], simp1[n1], simp2[n2 - 1]);
+
segGen.initSideSegments(simp2[n2], simp2[n2 - 1], Position::LEFT);
for(std::size_t i = n2 - 1; i > 0; --i) {
segGen.addNextSegment(simp2[i - 1], true);
}
segGen.addLastSegment();
// add line cap for start of line
- segGen.addLineEndCap(simp2[1], simp2[0]);
+ segGen.addLineEndCap(simp2[1], simp2[0], simp1[1]);
segGen.closeRing();
}
diff --git a/src/operation/buffer/OffsetSegmentGenerator.cpp b/src/operation/buffer/OffsetSegmentGenerator.cpp
index 6c7c2c3b0..435d4770a 100644
--- a/src/operation/buffer/OffsetSegmentGenerator.cpp
+++ b/src/operation/buffer/OffsetSegmentGenerator.cpp
@@ -187,25 +187,21 @@ OffsetSegmentGenerator::computeOffsetSegment(const LineSegment& seg, int p_side,
/*public*/
void
-OffsetSegmentGenerator::addLineEndCap(const Coordinate& p0, const Coordinate& p1)
+OffsetSegmentGenerator::addLineEndCap(const Coordinate& p0, const Coordinate& p1, const Coordinate& p2)
{
- LineSegment seg(p0, p1);
+ LineSegment segL(p0, p1);
+ LineSegment segR(p2, p1);
LineSegment offsetL;
- computeOffsetSegment(seg, Position::LEFT, distance, offsetL);
+ computeOffsetSegment(segL, Position::LEFT, distance, offsetL);
LineSegment offsetR;
- computeOffsetSegment(seg, Position::RIGHT, distance, offsetR);
-
- double dx = p1.x - p0.x;
- double dy = p1.y - p0.y;
- double angle = atan2(dy, dx);
+ computeOffsetSegment(segR, Position::RIGHT, distance, offsetR);
switch(bufParams.getEndCapStyle()) {
case BufferParameters::CAP_ROUND:
// add offset seg points with a fillet between them
segList.addPt(offsetL.p1);
- addDirectedFillet(p1, angle + Angle::PI_OVER_2, angle - Angle::PI_OVER_2,
- Orientation::CLOCKWISE, distance);
+ addDirectedFillet(p1, offsetL.p1, offsetR.p1, Orientation::CLOCKWISE, distance);
segList.addPt(offsetR.p1);
break;
case BufferParameters::CAP_FLAT:
@@ -216,7 +212,12 @@ OffsetSegmentGenerator::addLineEndCap(const Coordinate& p0, const Coordinate& p1
case BufferParameters::CAP_SQUARE:
// add a square defined by extensions of the offset
// segment endpoints
+
Coordinate squareCapSideOffset;
+ // take average in case angles of left and right sides differ
+ double dx = p1.x - p0.x/2 - p2.x/2;
+ double dy = p1.y - p0.y/2 - p2.y/2;
+ double angle = atan2(dy, dx);
double sinangle, cosangle;
Angle::sinCosSnap(angle, sinangle, cosangle);
squareCapSideOffset.x = fabs(distance) * cosangle;
diff --git a/tests/unit/operation/buffer/BufferOpTest.cpp b/tests/unit/operation/buffer/BufferOpTest.cpp
index 3e5bc5d92..8adc77438 100644
--- a/tests/unit/operation/buffer/BufferOpTest.cpp
+++ b/tests/unit/operation/buffer/BufferOpTest.cpp
@@ -629,10 +629,25 @@ void object::test<28>
"MULTIPOLYGON (((24 95.239, 24 96, 24 99, 24.816 99, 24 95.239)), ((3 90, 3 93, 3 96, 3 99, 21 99, 21 96, 21 93, 21 90, 3 90)))");
}
+// testEndCap
+// test end cap for a line where left and right side are simplified differently, so there is an angle at the end
+// See https://github.com/libgeos/geos/issues/1217
template<>
template<>
void object::test<29>
()
+{
+ std::string wkt("LINESTRING (0.7 4.7, 146.3 137.1, 146.3 137, 146.6 136.7)");
+ std::unique_ptr<Geometry> result13 = buffer(wkt, 11);
+ checkValidPolygon(*result13);
+ checkNumHoles(*result13, 0);
+}
+
+
+template<>
+template<>
+void object::test<30>
+()
{
set_test_name("GH-1321");
// Ensure positive buffer of LineString produces a single-part Polygon
@@ -648,7 +663,7 @@ void object::test<29>
template<>
template<>
-void object::test<30>
+void object::test<31>
()
{
set_test_name("sf-2552");
@@ -669,6 +684,39 @@ void object::test<30>
}
+// testInvalidCoordPoint
+template<>
+template<>
+void object::test<32> ()
+{
+ // works for Inf ordinates as well
+ auto geom = wktreader.read("POINT (NaN NaN)");
+ checkBufferPolygonEmpty(*geom, 1, true);
+}
+
+// testInvalidCoordsLine
+template<>
+template<>
+void object::test<33> ()
+{
+ // works for Inf ordinates as well
+ auto geom = wktreader.read("LINESTRING (NaN NaN, NaN NaN)");
+ checkBufferPolygonEmpty(*geom, 1, true);
+}
+
+// testInvalidCoordShell
+template<>
+template<>
+void object::test<34> ()
+{
+ // using Inf ordinates creates a valid ring with equal endpoints
+ // this would be simpler if JTS WKT supported Inf
+ auto geom = wktreader.read("POLYGON ((Inf Inf, Inf Inf, Inf Inf, Inf Inf, Inf Inf))");
+ checkBufferPolygonEmpty(*geom, 1, true);
+}
+
+
+
// testInvalidCoordPoint
template<>
template<>
-----------------------------------------------------------------------
Summary of changes:
NEWS.md | 4 ++++
.../geos/operation/buffer/OffsetSegmentGenerator.h | 3 ++-
src/operation/buffer/OffsetCurveBuilder.cpp | 8 +++++---
src/operation/buffer/OffsetSegmentGenerator.cpp | 21 ++++++++++---------
tests/unit/operation/buffer/BufferOpTest.cpp | 24 +++++++++++++++++-----
5 files changed, 41 insertions(+), 19 deletions(-)
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list