Skip to content

Commit 96f73a4

Browse files
committed
Add GEOSGridIntersectionFractions, GEOSSubdivideByGrid
1 parent f7627b4 commit 96f73a4

31 files changed

+3496
-1
lines changed

benchmarks/operation/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,12 @@ if (benchmark_FOUND)
2727
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>)
2828
target_link_libraries(perf_coverage_union PRIVATE
2929
benchmark::benchmark geos geos_cxx_flags)
30+
31+
add_executable(perf_grid_intersection GridIntersectionPerfTest.cpp)
32+
target_include_directories(perf_grid_intersection PUBLIC
33+
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
34+
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
35+
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>)
36+
target_link_libraries(perf_grid_intersection
37+
benchmark::benchmark geos geos_cxx_flags)
3038
endif()
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**********************************************************************
2+
*
3+
* GEOS - Geometry Engine Open Source
4+
* http://geos.osgeo.org
5+
*
6+
* Copyright (C) 2025 ISciences LLC
7+
*
8+
* This is free software; you can redistribute and/or modify it under
9+
* the terms of the GNU Lesser General Public Licence as published
10+
* by the Free Software Foundation.
11+
* See the COPYING file for more information.
12+
*
13+
**********************************************************************/
14+
15+
#include <benchmark/benchmark.h>
16+
#include <BenchmarkUtils.h>
17+
18+
#include <geos/geom/Envelope.h>
19+
#include <geos/geom/prep/PreparedGeometryFactory.h>
20+
#include <geos/operation/grid/Grid.h>
21+
22+
#include <geos/operation/grid/GridIntersection.h>
23+
#include <geos/operation/intersection/Rectangle.h>
24+
#include <geos/operation/intersection/RectangleIntersection.h>
25+
26+
using geos::geom::CoordinateXY;
27+
using geos::geom::Envelope;
28+
using geos::geom::Geometry;
29+
using Grid = geos::operation::grid::Grid<geos::operation::grid::bounded_extent>;
30+
31+
template<bool AreaOnly>
32+
struct GridIntersection {
33+
static double Intersection(const Envelope& env, int nx, int ny, const Geometry& g) {
34+
Grid grid(env, env.getWidth() / nx, env.getHeight() / ny);
35+
if constexpr (AreaOnly) {
36+
auto result = geos::operation::grid::GridIntersection::grid_intersection(grid, g);
37+
float area = 0;
38+
for (std::size_t i = 0; i < result->rows(); i++) {
39+
for (std::size_t j = 0; j < result->cols(); j++) {
40+
area += (*result)(i, j);
41+
}
42+
}
43+
return static_cast<double>(area);
44+
} else {
45+
auto subdivided = geos::operation::grid::GridIntersection::subdivide_polygon(grid, g, true);
46+
return subdivided->getArea();
47+
}
48+
}
49+
};
50+
51+
using GridIntersectionAreaOnly = GridIntersection<true>;
52+
using GridIntersectionFull = GridIntersection<false>;
53+
54+
template<bool UseRectangleIntersection>
55+
struct SingleIntersection {
56+
57+
static double Intersection(const Envelope& env, int nx, int ny, const Geometry& g) {
58+
double dx = env.getWidth() / nx;
59+
double dy = env.getHeight() / ny;
60+
61+
double x0 = env.getMinX();
62+
double y0 = env.getMinY();
63+
64+
const auto& gfact = *g.getFactory();
65+
auto prepGeom = geos::geom::prep::PreparedGeometryFactory::prepare(&g);
66+
67+
double area = 0;
68+
69+
for (int i = 0; i < nx; i++) {
70+
for (int j = 0; j < ny; j++) {
71+
Envelope subEnv(x0 + i*dx, x0 + (i+1)*dx, y0 + j*dy, y0 + (j+1)*dy);
72+
auto cellGeom = gfact.toGeometry(&subEnv);
73+
if (!prepGeom->intersects(cellGeom.get())) {
74+
continue;
75+
}
76+
77+
std::unique_ptr<Geometry> isect;
78+
if constexpr (UseRectangleIntersection) {
79+
geos::operation::intersection::Rectangle rect(x0 + i*dx, y0 + j*dy, x0 + (i+1)*dx, y0 + (j+1)*dy);
80+
isect = geos::operation::intersection::RectangleIntersection::clip(g, rect);
81+
} else {
82+
isect = g.intersection(cellGeom.get());
83+
}
84+
85+
area += isect->getArea();
86+
}
87+
}
88+
89+
return area;
90+
}
91+
};
92+
93+
using PolygonIntersection = SingleIntersection<false>;
94+
using RectangleIntersection = SingleIntersection<true>;
95+
96+
template<typename Impl>
97+
static void BM_GridIntersection(benchmark::State& state)
98+
{
99+
auto nCells = state.range(0);
100+
101+
auto nx = static_cast<int>(std::ceil(std::sqrt(nCells)));
102+
auto ny = static_cast<int>(std::ceil(std::sqrt(nCells)));
103+
104+
CoordinateXY center;
105+
Envelope env(0, nx, 0, ny);
106+
env.centre(center);
107+
108+
auto geom = geos::benchmark::createSineStar(center, env.getWidth() / 2, 500);
109+
110+
for (auto _ : state) {
111+
Impl::Intersection(env, nx, ny, *geom);
112+
}
113+
114+
}
115+
116+
BENCHMARK_TEMPLATE(BM_GridIntersection, GridIntersectionAreaOnly)->Range(1000, 1000000);
117+
BENCHMARK_TEMPLATE(BM_GridIntersection, GridIntersectionFull)->Range(1000, 1000000);
118+
BENCHMARK_TEMPLATE(BM_GridIntersection, RectangleIntersection)->Range(1000, 1000000);
119+
BENCHMARK_TEMPLATE(BM_GridIntersection, PolygonIntersection)->Range(1000, 1000000);
120+
121+
BENCHMARK_MAIN();

capi/geos_c.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,19 @@ extern "C" {
713713
return GEOSClipByRect_r(handle, g, xmin, ymin, xmax, ymax);
714714
}
715715

716+
Geometry*
717+
GEOSSubdivideByGrid(const Geometry* g, double xmin, double ymin, double xmax, double ymax,
718+
unsigned nx, unsigned ny, int include_exterior)
719+
{
720+
return GEOSSubdivideByGrid_r(handle, g, xmin, ymin, xmax, ymax, nx, ny, include_exterior);
721+
}
722+
723+
int
724+
GEOSGridIntersectionFractions(const Geometry* g, double xmin, double ymin, double xmax, double ymax,
725+
unsigned nx, unsigned ny, float* buf)
726+
{
727+
return GEOSGridIntersectionFractions_r(handle, g, xmin, ymin, xmax, ymax, nx, ny, buf);
728+
}
716729

717730
Geometry*
718731
GEOSGeom_transformXY(const GEOSGeometry* g, GEOSTransformXYCallback callback, void* userdata) {

capi/geos_c.h.in

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,24 @@ extern GEOSGeometry GEOS_DLL *GEOSClipByRect_r(
10351035
double xmin, double ymin,
10361036
double xmax, double ymax);
10371037

1038+
/** \see GEOSSubdivideByGrid */
1039+
extern GEOSGeometry GEOS_DLL *GEOSSubdivideByGrid_r(
1040+
GEOSContextHandle_t handle,
1041+
const GEOSGeometry* g,
1042+
double xmin, double ymin,
1043+
double xmax, double ymax,
1044+
unsigned nx, unsigned ny,
1045+
int include_exterior);
1046+
1047+
/** \see GEOSGridIntersectionFractions */
1048+
extern int GEOS_DLL GEOSGridIntersectionFractions_r(
1049+
GEOSContextHandle_t handle,
1050+
const GEOSGeometry* g,
1051+
double xmin, double ymin,
1052+
double xmax, double ymax,
1053+
unsigned nx, unsigned ny,
1054+
float* buf);
1055+
10381056
/** \see GEOSPolygonize */
10391057
extern GEOSGeometry GEOS_DLL *GEOSPolygonize_r(
10401058
GEOSContextHandle_t handle,
@@ -3788,6 +3806,56 @@ extern GEOSGeometry GEOS_DLL *GEOSClipByRect(
37883806
double xmin, double ymin,
37893807
double xmax, double ymax);
37903808

3809+
/**
3810+
* Compute the intersection of a geometry with each polygon in
3811+
* a rectangular grid.
3812+
* \param g The input geometry to be clipped
3813+
* \param xmin Left bound of grd
3814+
* \param ymin Lower bound of grid
3815+
* \param xmax Right bound of grid
3816+
* \param ymax Upper bound of grid
3817+
* \param nx number of columns in grid
3818+
* \param ny number of rows in grid
3819+
* \param include_exterior whether to include portions of the
3820+
input geometry that do not intersect the grid in
3821+
the returned result.
3822+
* \return A geometry collection whose components represent the
3823+
* intersection with each cell in the grid.
3824+
* Caller is responsible for freeing with GEOSGeom_destroy().
3825+
* \see GEOSGetGridIntersectionFractions
3826+
*
3827+
* \since 3.14.0
3828+
*/
3829+
extern GEOSGeometry GEOS_DLL *GEOSSubdivideByGrid(
3830+
const GEOSGeometry* g,
3831+
double xmin, double ymin,
3832+
double xmax, double ymax,
3833+
unsigned nx, unsigned ny,
3834+
int include_exterior);
3835+
3836+
/**
3837+
* Determine the fraction of each cell in a rectangular grid
3838+
* that is covered by a polygon.
3839+
* \param g The input geometry
3840+
* \param xmin Left bound of grd
3841+
* \param ymin Lower bound of grid
3842+
* \param xmax Right bound of grid
3843+
* \param ymax Upper bound of grid
3844+
* \param nx number of columns in grid
3845+
* \param ny number of rows in grid
3846+
* \param buf a buffer of size nx*ny to which the grid cell
3847+
* coverage fractions will be written in row-major order
3848+
* \return 1 if the operation was successful, 0 on exception
3849+
*
3850+
* \since 3.14.0
3851+
*/
3852+
extern int GEOS_DLL GEOSGridIntersectionFractions(
3853+
const GEOSGeometry* g,
3854+
double xmin, double ymin,
3855+
double xmax, double ymax,
3856+
unsigned nx, unsigned ny,
3857+
float* buf);
3858+
37913859
/**
37923860
* Find paths shared between the two given lineal geometries.
37933861
*

capi/geos_ts_c.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@
8080
#include <geos/operation/cluster/GeometryIntersectsClusterFinder.h>
8181
#include <geos/operation/distance/DistanceOp.h>
8282
#include <geos/operation/distance/IndexedFacetDistance.h>
83+
#include <geos/operation/grid/Grid.h>
84+
#include <geos/operation/grid/GridIntersection.h>
8385
#include <geos/operation/linemerge/LineMerger.h>
8486
#include <geos/operation/intersection/Rectangle.h>
8587
#include <geos/operation/intersection/RectangleIntersection.h>
@@ -1725,6 +1727,41 @@ extern "C" {
17251727
});
17261728
}
17271729

1730+
Geometry*
1731+
GEOSSubdivideByGrid_r(GEOSContextHandle_t extHandle, const Geometry* g, double xmin, double ymin,
1732+
double xmax, double ymax, unsigned nx, unsigned ny, int include_exterior)
1733+
{
1734+
return execute(extHandle, [&]() {
1735+
Envelope env(xmin, xmax, ymin, ymax);
1736+
double dx = env.getWidth() / static_cast<double>(nx);
1737+
double dy = env.getHeight() / static_cast<double>(ny);
1738+
geos::operation::grid::Grid<geos::operation::grid::bounded_extent> grid(env, dx, dy);
1739+
1740+
return geos::operation::grid::GridIntersection::subdivide_polygon(grid, *g, include_exterior).release();
1741+
});
1742+
}
1743+
1744+
int
1745+
GEOSGridIntersectionFractions_r(GEOSContextHandle_t extHandle, const Geometry* g, double xmin, double ymin,
1746+
double xmax, double ymax, unsigned nx, unsigned ny, float* buf)
1747+
{
1748+
return execute(extHandle, 0, [&]() {
1749+
Envelope env(xmin, xmax, ymin, ymax);
1750+
double dx = env.getWidth() / static_cast<double>(nx);
1751+
double dy = env.getHeight() / static_cast<double>(ny);
1752+
geos::operation::grid::Grid<geos::operation::grid::bounded_extent> grid(env, dx, dy);
1753+
1754+
// Matrix wants a shared_ptr, but we don't actually want anything to be freed because
1755+
// buf is externally owned. So we give it an empty deleter.
1756+
std::shared_ptr<float[]> bufPtr(buf, [](float*) {});
1757+
1758+
auto cov = std::make_shared<geos::operation::grid::Matrix<float>>(ny, nx, bufPtr);
1759+
geos::operation::grid::GridIntersection isect(grid, *g, cov);
1760+
1761+
return 1;
1762+
});
1763+
}
1764+
17281765
Geometry*
17291766
GEOSGeom_transformXY_r(GEOSContextHandle_t handle, const GEOSGeometry* g, GEOSTransformXYCallback callback, void* userdata) {
17301767

include/geos/operation/grid/Cell.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**********************************************************************
2+
*
3+
* GEOS - Geometry Engine Open Source
4+
* http://geos.osgeo.org
5+
*
6+
* Copyright (C) 2018-2025 ISciences, LLC
7+
*
8+
* This is free software; you can redistribute and/or modify it under
9+
* the terms of the GNU Lesser General Public Licence as published
10+
* by the Free Software Foundation.
11+
* See the COPYING file for more information.
12+
*
13+
**********************************************************************/
14+
15+
#pragma once
16+
17+
#include <geos/geom/Envelope.h>
18+
#include <geos/geom/Geometry.h>
19+
20+
#include <geos/operation/grid/Side.h>
21+
#include <geos/operation/grid/Traversal.h>
22+
23+
namespace geos::operation::grid {
24+
25+
/**
26+
* @brief The Cell class stores information about the spatial extent of a `Grid` cell and
27+
* any cases where a line crosses that cell (recorded in a `Traversal`).
28+
*/
29+
class Cell
30+
{
31+
32+
public:
33+
Cell(double xmin, double ymin, double xmax, double ymax)
34+
: m_box{ xmin, ymin, xmax, ymax }
35+
{
36+
}
37+
38+
explicit Cell(const geom::Envelope& b)
39+
: m_box{ b }
40+
{
41+
}
42+
43+
const geom::Envelope& box() const { return m_box; }
44+
45+
double width() const;
46+
47+
double height() const;
48+
49+
double area() const;
50+
51+
/// Force the last Coordinate processed (via `take`) to be considered as an
52+
/// exit point, provided that it lies on the boundary of this Cell.
53+
void force_exit();
54+
55+
/// Returns whether the cell can be determined to be wholly or partially
56+
/// covered by a polygon.
57+
bool determined() const;
58+
59+
/// Return the total length of a linear geometry within this Cell
60+
double traversal_length() const;
61+
62+
/// Return the fraction of this Cell that is covered by a polygon
63+
double covered_fraction() const;
64+
65+
/// Return a newly constructed geometry representing the portion of this Cell
66+
/// that is covered by a polygon
67+
std::unique_ptr<geom::Geometry> covered_polygons(const geom::GeometryFactory&) const;
68+
69+
/// Return the last (most recent) traversal to which a coordinate has been
70+
/// added. The traversal may or may not be completed.
71+
Traversal& last_traversal();
72+
73+
/**
74+
* Attempt to take a coordinate and add it to a Traversal in progress, or start a new Traversal
75+
*
76+
* @param c Coordinate to process
77+
* @param prev_original The last *uninterpolated* coordinate preceding `c` in the
78+
* boundary being processed
79+
*
80+
* @return `true` if the Coordinate was inside this cell, `false` otherwise
81+
*/
82+
bool take(const geom::CoordinateXY& c, const geom::CoordinateXY* prev_original = nullptr);
83+
84+
private:
85+
std::vector<const std::vector<geom::CoordinateXY>*> get_coord_lists() const;
86+
87+
enum class Location
88+
{
89+
INSIDE,
90+
OUTSIDE,
91+
BOUNDARY
92+
};
93+
94+
geom::Envelope m_box;
95+
96+
std::vector<Traversal> m_traversals;
97+
98+
Side side(const geom::CoordinateXY& c) const;
99+
100+
Location location(const geom::CoordinateXY& c) const;
101+
102+
/// If no Traversals have been started or the most recent Traversal has been completed,
103+
/// return a new Traversal. Otherwise, return the most recent Traversal.
104+
Traversal& traversal_in_progress();
105+
};
106+
107+
}

0 commit comments

Comments
 (0)