[geos-commits] [SCM] GEOS branch 3.15 updated. 5e34781c76282e949dde1498fc89a3f8f42d2e20
git at osgeo.org
git at osgeo.org
Fri Nov 6 10:09:10 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, 3.15 has been updated
via 5e34781c76282e949dde1498fc89a3f8f42d2e20 (commit)
via a38377342a9c1786c425b9a33270eec64900866a (commit)
via a6f1f8227fd45b5e16cb21f0f03a26b6cf61f54d (commit)
from 3ee1c785707005880df0899d9ba153093b4e8a85 (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 5e34781c76282e949dde1498fc89a3f8f42d2e20
Author: Daniel Baston <dbaston at gmail.com>
Date: Fri Nov 6 11:32:00 2026 -0500
NEWS updates
diff --git a/NEWS.md b/NEWS.md
index 05d6eecaf..e836ba19d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -6,6 +6,7 @@
- Arc noding: handle degenerate arcs with repeated points (GH-1584, Alejandro Pacheco and Dan Baston)
- Make GeometrySplitter able to split MultiSurface inputs (GH-1518, Dan Baston)
- GEOSGridIntersectionFractions: Set cells outside geometry bounding box to zero (GH-1532, Dan Baston)
+ - GEOSGridIntersectionFractions: Improve robustness (GH-1589, Dan Baston, Alejandro Pacheco)
- Skip testing FE_INVALID from unit-geom-Envelope for some Clang builds (GH-1555, Mike Taves)
- Polygonize / LineMerge: Copy Z/M values at shared endpoints of mixed-dimension inputs (GH-1545, Dan Baston)
- CircularArcIntersector: Use Orient2D to check for arc control points on line (GH-1546, Dan Baston)
@@ -20,6 +21,7 @@
- Envelope: improve robustness of distanceSquared (GH-1576, Jeroen Bloemscheer)
- Distance: avoid erroneously large distance between arc and segment (GH-1577, Dan Baston and Jeroen Bloemscheer)
- GeometryFactory: fix return type from buildGeometry with collection inputs (GH-1580, Alejandro Pacheco)
+ - RelateNG: fix crash with on GeometryCollection with empty Polygon (GH-1590, Dan Baston)
## Changes in 3.15.0
2026-09-01
commit a38377342a9c1786c425b9a33270eec64900866a
Author: Daniel Baston <dbaston at gmail.com>
Date: Thu Nov 5 09:10:33 2026 -0500
AdjacentEdgeLocator: Avoid checking empty rings (#1590)
Resolves https://github.com/libgeos/geos/issues/1585
diff --git a/src/operation/relateng/AdjacentEdgeLocator.cpp b/src/operation/relateng/AdjacentEdgeLocator.cpp
index aeb5f8084..fa63f4fc9 100644
--- a/src/operation/relateng/AdjacentEdgeLocator.cpp
+++ b/src/operation/relateng/AdjacentEdgeLocator.cpp
@@ -133,6 +133,10 @@ AdjacentEdgeLocator::addRings(const Geometry* geom)
void
AdjacentEdgeLocator::addRing(const LinearRing* ring, bool requireCW)
{
+ if (ring->isEmpty()) {
+ return;
+ }
+
//TODO: remove repeated points?
const CoordinateSequence* pts = ring->getCoordinatesRO();
bool isFlipped = requireCW == Orientation::isCCW(pts);
diff --git a/tests/unit/operation/relateng/RelateNGTest.cpp b/tests/unit/operation/relateng/RelateNGTest.cpp
index ee8d3a841..8c7860577 100644
--- a/tests/unit/operation/relateng/RelateNGTest.cpp
+++ b/tests/unit/operation/relateng/RelateNGTest.cpp
@@ -925,5 +925,17 @@ void object::test<64> ()
checkRelate(b, a, "102F01FF2");
}
+template<>
+template<>
+void object::test<65>()
+{
+ set_test_name("GeometryCollection with empty polygon");
-} // namespace tut
+ std::string a = "POINT (1 0.5)";
+ std::string b = "GEOMETRYCOLLECTION (POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0)), POLYGON ((2 0, 2 1, 1 1, 1 0, 2 0)), POLYGON EMPTY)";
+
+ checkIntersectsDisjoint(a, b, true);
+}
+
+
+} // namespace tut
\ No newline at end of file
commit a6f1f8227fd45b5e16cb21f0f03a26b6cf61f54d
Author: Daniel Baston <dbaston at gmail.com>
Date: Thu Nov 5 09:10:08 2026 -0500
GridIntersection: robustness fixes (#1589)
Co-authored-by: Alejandro Pacheco <ale at alepacheco.dev>
diff --git a/include/geos/operation/grid/Grid.h b/include/geos/operation/grid/Grid.h
index d94d80916..e56d7dd84 100644
--- a/include/geos/operation/grid/Grid.h
+++ b/include/geos/operation/grid/Grid.h
@@ -19,6 +19,7 @@
#include <vector>
#include <geos/geom/Envelope.h>
+#include <geos/util/IllegalArgumentException.h>
namespace geos::operation::grid {
struct infinite_extent
@@ -69,6 +70,12 @@ class GEOS_DLL Grid
, m_num_cols{ 2 * extent_tag::padding + (extent.getMaxX() > extent.getMinX() ? static_cast<size_t>(std::round(extent.getWidth() / dx)) : 0) }
{
static_assert(std::is_same_v<extent_tag, infinite_extent>);
+ if (!isEmpty()) {
+ if (m_domain.getMinX() >= m_extent.getMinX() || m_domain.getMaxX() <= m_extent.getMaxX() ||
+ m_domain.getMinY() >= m_extent.getMinY() || m_domain.getMaxY() <= m_extent.getMaxY()) {
+ throw geos::util::IllegalArgumentException("Domain of infinite grid must be larger than extent");
+ }
+ }
}
diff --git a/src/operation/grid/Grid.cpp b/src/operation/grid/Grid.cpp
index d4db6659b..bd63dff71 100644
--- a/src/operation/grid/Grid.cpp
+++ b/src/operation/grid/Grid.cpp
@@ -41,11 +41,9 @@ Grid<infinite_extent>::getCellEnvelope(size_t row, size_t col) const
{
double cell_xmin, cell_xmax, cell_ymin, cell_ymax;
- constexpr double PADDED_CELL_SIZE = 1.0;
-
if (col == 0) {
// Left-side padding
- cell_xmin = std::min(xmin() - PADDED_CELL_SIZE, m_domain.getMinX());
+ cell_xmin = m_domain.getMinX();
} else if (col == getNumCols() - 1) {
// Right-side padding
cell_xmin = xmax(); // because rightmost col of regular may have different width from others
@@ -57,7 +55,7 @@ Grid<infinite_extent>::getCellEnvelope(size_t row, size_t col) const
switch (getNumCols() - col) {
case 1:
// Right-side padding
- cell_xmax = std::max(xmax() + PADDED_CELL_SIZE, m_domain.getMaxX());
+ cell_xmax = m_domain.getMaxX();
break;
case 2:
// Rightmost internal cell
@@ -69,7 +67,7 @@ Grid<infinite_extent>::getCellEnvelope(size_t row, size_t col) const
if (row == 0) {
// Top row padding
- cell_ymax = std::max(ymax() + PADDED_CELL_SIZE, m_domain.getMaxY());
+ cell_ymax = m_domain.getMaxY();
} else if (row == getNumRows() - 1) {
// Bottom row padding
cell_ymax = ymin(); // because bottom row of regular may have different height from others
@@ -80,7 +78,7 @@ Grid<infinite_extent>::getCellEnvelope(size_t row, size_t col) const
switch (getNumRows() - row) {
case 1:
// Bottom row padding
- cell_ymin = std::min(xmin() - PADDED_CELL_SIZE, m_domain.getMinY());
+ cell_ymin = m_domain.getMinY();
break;
case 2:
// Bottom-most internal row
diff --git a/src/operation/grid/GridIntersection.cpp b/src/operation/grid/GridIntersection.cpp
index c60c3aaec..ef94ce5ad 100644
--- a/src/operation/grid/GridIntersection.cpp
+++ b/src/operation/grid/GridIntersection.cpp
@@ -34,6 +34,24 @@ using geos::geom::CoordinateXY;
namespace geos::operation::grid {
+static Envelope
+getDomain(const Envelope& gridExtent, const Envelope& geomExtent)
+{
+ constexpr double PADDED_CELL_SIZE = 1.0;
+ constexpr double PADDED_CELL_RELATIVE_SIZE = 1e-12;
+
+ Envelope padded(gridExtent);
+ padded.expandToInclude(geomExtent);
+
+ const double xmag = std::max(std::abs(padded.getMinX()), std::abs(padded.getMaxX()));
+ const double ymag = std::max(std::abs(padded.getMinY()), std::abs(padded.getMaxY()));
+
+ padded.expandBy(std::max(PADDED_CELL_SIZE, PADDED_CELL_RELATIVE_SIZE * xmag),
+ std::max(PADDED_CELL_SIZE, PADDED_CELL_RELATIVE_SIZE * ymag));
+
+ return padded;
+}
+
std::shared_ptr<Matrix<float>>
GridIntersection::getIntersectionFractions(const Grid<bounded_extent>& raster_grid, const Geometry& g)
{
@@ -111,7 +129,7 @@ GridIntersection::processingRegion(const Envelope& raster_extent, const Geometry
}
GridIntersection::GridIntersection(const Grid<bounded_extent>& raster_grid, const Geometry& g, const std::shared_ptr<Matrix<float>>& cov)
- : m_geometry_grid{ make_infinite(raster_grid, *g.getEnvelopeInternal()) }
+ : m_geometry_grid{ make_infinite(raster_grid, getDomain(raster_grid.getExtent(), *g.getEnvelopeInternal())) }
, m_results{ cov ? cov : std::make_shared<Matrix<float>>(raster_grid.getNumRows(), raster_grid.getNumCols()) }
, m_first_geom{ true }
, m_areal{ false }
@@ -130,7 +148,7 @@ GridIntersection::GridIntersection(const Grid<bounded_extent>& raster_grid, cons
}
GridIntersection::GridIntersection(const Grid<bounded_extent>& raster_grid, const Envelope& box, const std::shared_ptr<Matrix<float>>& cov)
- : m_geometry_grid{ make_infinite(raster_grid, box) }
+ : m_geometry_grid{ make_infinite(raster_grid, getDomain(raster_grid.getExtent(), box)) }
, m_results{ cov ? cov : std::make_shared<Matrix<float>>(raster_grid.getNumRows(), raster_grid.getNumCols()) }
, m_first_geom { true }
, m_areal{ false }
@@ -182,6 +200,10 @@ GridIntersection::processRectangularRing(const Envelope& box, bool exterior_ring
auto ring_grid = getSubgrid(box, m_geometry_grid);
+ if (ring_grid.isEmpty()) {
+ return;
+ }
+
auto row_min = ring_grid.getRow(box.getMaxY());
auto row_max = ring_grid.getRow(box.getMinY());
auto col_min = ring_grid.getColumn(box.getMinX());
@@ -590,8 +612,7 @@ GridIntersection::subdividePolygon(const Grid<bounded_extent>& p_grid, const Geo
const auto cropped_grid = p_grid.shrinkToFit(p_grid.getExtent().intersection(*g.getEnvelopeInternal()), false);
- geom::Envelope gridExtent = *g.getEnvelopeInternal();
- gridExtent.expandBy(1);
+ geom::Envelope gridExtent = getDomain(cropped_grid.getExtent(), *g.getEnvelopeInternal());
const Grid<infinite_extent> cell_grid = make_infinite(cropped_grid, gridExtent);
Matrix<std::unique_ptr<Cell>> cells(cell_grid.getNumRows(), cell_grid.getNumCols());
diff --git a/tests/unit/operation/grid/GridIntersectionTest.cpp b/tests/unit/operation/grid/GridIntersectionTest.cpp
index 2c5cb8a01..940d54f3a 100644
--- a/tests/unit/operation/grid/GridIntersectionTest.cpp
+++ b/tests/unit/operation/grid/GridIntersectionTest.cpp
@@ -998,4 +998,98 @@ void object::test<53>() {
ensure(subd->getGeometryN(1)->equals(outside.get()));
}
+template<>
+template<>
+void object::test<54>()
+{
+ Envelope e(0, 10, 0, 10);
+ Grid<bounded_extent> ext(e, 0.1, 0.1);
+
+ auto g = wkt_reader_.read("POLYGON ((0.3 0.3, 4.7 0.3, 2.5 6.2, 0.3 0.3))");
+
+ auto fracs = GridIntersection::getIntersectionFractions(ext, *g);
+ auto tot = std::accumulate(fracs->begin(), fracs->end(), 0.0);
+
+ ensure_equals("check area", tot/100, g->getArea(), 1e-6);
+}
+
+template<>
+template<>
+void object::test<55>()
+{
+ set_test_name("getIntersectionFractions: traversal stays in grid when a vertex is the top corner of the geometry envelope");
+
+ // The vertex at (-259.05369950585896 811.4225290921494) is the upper-left corner of the polygon envelope
+ // and lies on a cell boundary, to within rounding.
+ Envelope e(-300.91149168825694, -175.33811514106293, 225.41343853857734, 602.13356818015927);
+
+ Grid<bounded_extent> ext(e, e.getWidth() / 6, e.getHeight() / 18);
+
+ auto g = wkt_reader_.read("POLYGON ((-70.69363468506793 685.8491525449554, -133.48032295866494 748.6358408185523, -175.33811514106293 790.4936330009504, -259.05369950585896 811.4225290921494, -238.12480341465994 706.7780486361544, -259.05369950585896 664.9202564537563, -259.05369950585896 581.2046720889604, -196.26701123226195 643.9913603625573, -112.55142686746595 623.0624642713583, -70.69363468506793 685.8491525449554))");
+
+ auto fracs = GridIntersection::getIntersectionFractions(ext, *g);
+
+ auto inside = g->intersection(g->getFactory()->toGeometry(&e).get());
+ check_area(*fracs, ext, *inside);
+}
+
+template<>
+template<>
+void object::test<56>()
+{
+ set_test_name("getIntersectionFractions: traversal stays in grid when a vertex is the bottom corner of the geometry envelope");
+
+ // The vertex at (68.44482903503193 -90.56515438144179) is the lower-right corner of the polygon envelope
+ // and lies on a cell boundary, to within rounding.
+ Envelope e(-10.004484448704343, 152.12409675101728, 35.385086543444757, 73.717768564062396);
+ Grid<bounded_extent> ext(e, e.getWidth() / 31, e.getHeight() / 14);
+
+ auto g = wkt_reader_.read("POLYGON ((173.04391368001362 60.02752498527039, 78.9047374995301 109.31240186892164, 89.36464596402827 257.1670325198754, -25.694347145451598 213.35825306774097, -93.68375216468971 161.3353274683313, -104.14366062918788 90.14606085861283, -119.83352332593513 24.432891680411146, -51.84411830669703 5.266550670102323, -20.464392913202513 -52.23247236082415, 68.44482903503193 -90.56515438144179, 173.04391368001362 -54.97052107658256, 173.04391368001362 60.02752498527039))");
+
+ auto fracs = GridIntersection::getIntersectionFractions(ext, *g);
+
+ auto inside = g->intersection(g->getFactory()->toGeometry(&e).get());
+ check_area(*fracs, ext, *inside);
+}
+
+template<>
+template<>
+void object::test<57>()
+{
+ set_test_name("subdividePolygon: traversal stays in grid when padding is lost to rounding at large coordinates");
+
+ // At 1e22, adding a fixed padding of 1 to the geometry envelope has no effect,
+ // so the vertex at (1e22 0.9) would lie on the outer edge of a padding cell.
+ Envelope e(-0.375, 2.1875, -0.375, 2.1875);
+ Grid<bounded_extent> ext(e, e.getWidth() / 64, e.getHeight() / 2);
+
+ auto g = wkt_reader_.read("POLYGON ((0.1 0.1, 0.1 0.2, 0.95 0.95, 1e22 0.9, 0.1 0.1))");
+
+ auto subd = GridIntersection::subdividePolygon(ext, *g, false);
+
+ auto inside = g->intersection(g->getFactory()->toGeometry(&e).get());
+ check_subdivided_polygon(*inside, *subd);
+}
+
+template<>
+template<>
+void object::test<58>()
+{
+ set_test_name("getIntersectionFractions: box sharing an edge with the grid");
+
+ Envelope e(0, 10, 0, 10);
+ Grid<bounded_extent> ext(e, 1, 1);
+
+ for (const auto& box : {
+ Envelope(10, 20, 0, 10),
+ Envelope(0, 10, 10, 20),
+ Envelope(-10, 0, -10, 0) })
+ {
+ auto fracs = GridIntersection::getIntersectionFractions(ext, box);
+ auto tot = std::accumulate(fracs->begin(), fracs->end(), 0.0);
+
+ ensure_equals(tot, 0.0);
+ }
+}
+
}
diff --git a/tests/unit/operation/grid/GridTest.cpp b/tests/unit/operation/grid/GridTest.cpp
index 0df5ea8a7..c142c4911 100644
--- a/tests/unit/operation/grid/GridTest.cpp
+++ b/tests/unit/operation/grid/GridTest.cpp
@@ -314,8 +314,10 @@ void object::test<17>()
set_test_name("Crop infinite grid while calculating cell boundaries relative to parent");
double res = 1.0/3601;
+ Envelope domain = global;
+ domain.expandBy(1);
- Grid<infinite_extent> grid{global, res, res, global};
+ Grid<infinite_extent> grid{global, res, res, domain};
const auto env0 = grid.getCellEnvelope(30, 30);
-----------------------------------------------------------------------
Summary of changes:
NEWS.md | 2 +
include/geos/operation/grid/Grid.h | 7 ++
src/operation/grid/Grid.cpp | 10 +--
src/operation/grid/GridIntersection.cpp | 29 ++++++-
src/operation/relateng/AdjacentEdgeLocator.cpp | 4 +
tests/unit/operation/grid/GridIntersectionTest.cpp | 94 ++++++++++++++++++++++
tests/unit/operation/grid/GridTest.cpp | 4 +-
tests/unit/operation/relateng/RelateNGTest.cpp | 14 +++-
8 files changed, 152 insertions(+), 12 deletions(-)
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list