GEOS 3.15.0dev
Grid.h
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 <numeric>
18#include <stdexcept>
19#include <vector>
20
21#include <geos/geom/Envelope.h>
22
23namespace geos::operation::grid {
24struct infinite_extent
25{
26 static constexpr size_t padding = 1;
27};
28
29struct bounded_extent
30{
31 static constexpr size_t padding = 0;
32};
33
41template<typename extent_tag>
42class GEOS_DLL Grid
43{
44
45 public:
47 Grid(const geom::Envelope& extent, double dx, double dy)
48 : m_extent{ extent }
49 , m_domain{}
50 , m_dx{ dx }
51 , m_dy{ dy }
52 , m_xOrig { extent.getMinX() }
53 , m_yOrig { extent.getMaxY() }
54 , m_num_rows{ 2 * extent_tag::padding + (extent.getMaxY() > extent.getMinY() ? static_cast<size_t>(std::round(extent.getHeight() / dy)) : 0) }
55 , m_num_cols{ 2 * extent_tag::padding + (extent.getMaxX() > extent.getMinX() ? static_cast<size_t>(std::round(extent.getWidth() / dx)) : 0) } {
56 static_assert(std::is_same_v<extent_tag, bounded_extent>);
57 }
58
61 Grid(const geom::Envelope& extent, double dx, double dy, const geom::Envelope& domain)
62 : m_extent{ extent }
63 , m_domain{ domain }
64 , m_dx{ dx }
65 , m_dy{ dy }
66 , m_xOrig { extent.getMinX() }
67 , m_yOrig { extent.getMaxY() }
68 , m_num_rows{ 2 * extent_tag::padding + (extent.getMaxY() > extent.getMinY() ? static_cast<size_t>(std::round(extent.getHeight() / dy)) : 0) }
69 , m_num_cols{ 2 * extent_tag::padding + (extent.getMaxX() > extent.getMinX() ? static_cast<size_t>(std::round(extent.getWidth() / dx)) : 0) }
70 {
71 static_assert(std::is_same_v<extent_tag, infinite_extent>);
72 }
73
74
75 static Grid makeEmpty()
76 {
77 if constexpr (std::is_same_v<extent_tag, bounded_extent>) {
78 return Grid({ 0, 0, 0, 0 }, 0, 0);
79 } else {
80 return Grid({ 0, 0, 0, 0 }, 0, 0, geom::Envelope());
81 }
82 }
83
85 size_t getColumn(double x) const
86 {
87 if (extent_tag::padding) {
88 if (x < m_extent.getMinX())
89 return 0;
90 if (x > m_extent.getMaxX())
91 return m_num_cols - 1;
92 if (x == m_extent.getMaxX()) // special case, returning the cell for which xmax is the right
93 return m_num_cols - 2;
94 } else {
95 if (x < m_extent.getMinX() || x > m_extent.getMaxX())
96 throw std::out_of_range("x");
97
98 if (x == m_extent.getMaxX())
99 return m_num_cols - 1;
100 }
101
102 // Since we've already range-checked our x value, make sure that
103 // the computed column index is no greater than the column index
104 // associated with xmax.
105 return std::min(
106 extent_tag::padding + static_cast<size_t>(std::floor((x - m_extent.getMinX()) / m_dx)),
107 getColumn(m_extent.getMaxX()));
108 }
109
111 size_t getRow(double y) const
112 {
113 if (extent_tag::padding) {
114 if (y > m_extent.getMaxY())
115 return 0;
116 if (y < m_extent.getMinY())
117 return m_num_rows - 1;
118 if (y == m_extent.getMinY()) // special case, returning the cell for which ymin is the bottom
119 return m_num_rows - 2;
120 } else {
121 if (y < m_extent.getMinY() || y > m_extent.getMaxY())
122 throw std::out_of_range("y");
123
124 if (y == m_extent.getMinY())
125 return m_num_rows - 1;
126 }
127
128 // Since we've already range-checked our y value, make sure that
129 // the computed row index is no greater than the column index
130 // associated with ymin.
131 return std::min(
132 extent_tag::padding + static_cast<size_t>(std::floor((m_extent.getMaxY() - y) / m_dy)),
133 getRow(m_extent.getMinY()));
134 }
135
138 std::size_t getCell(double x, double y) const
139 {
140 return getRow(y) * getNumCols() + getColumn(x);
141 }
142
143 bool isEmpty() const { return m_num_rows <= 2 * extent_tag::padding && m_num_cols <= 2 * extent_tag::padding; }
144
145 size_t getNumRows() const { return m_num_rows; }
146
147 size_t getNumCols() const { return m_num_cols; }
148
149 size_t getSize() const { return getNumRows() * getNumCols(); }
150
151 double xmin() const { return m_extent.getMinX(); }
152
153 double xmax() const { return m_extent.getMaxX(); }
154
155 double ymin() const { return m_extent.getMinY(); }
156
157 double ymax() const { return m_extent.getMaxY(); }
158
159 double dx() const { return m_dx; }
160
161 double dy() const { return m_dy; }
162
163 const geom::Envelope& getExtent() const { return m_extent; }
164
168 size_t getRowOffset(const Grid& other) const { return static_cast<size_t>(std::round(std::abs(other.m_extent.getMaxY() - m_extent.getMaxY()) / m_dy)); }
169
173 size_t getColOffset(const Grid& other) const { return static_cast<size_t>(std::round(std::abs(m_extent.getMinX() - other.m_extent.getMinX()) / m_dx)); }
174
176 double getColX(size_t col) const { return m_extent.getMinX() + (static_cast<double>(col - extent_tag::padding) + 0.5) * m_dx; }
177
179 double getRowY(size_t row) const { return m_extent.getMaxY() - (static_cast<double>(row - extent_tag::padding) + 0.5) * m_dy; }
180
181 Grid crop(const geom::Envelope& e) const
182 {
183 if (m_extent.intersects(e)) {
184 return shrinkToFit(e.intersection(m_extent));
185 } else {
186 return makeEmpty();
187 }
188 }
189
194 Grid<extent_tag> shrinkToFit(const geom::Envelope& b, bool calcExtentFromNewGrid=true) const
195 {
196 if (b.getArea() == 0) {
197 return makeEmpty();
198 }
199
200 if (b.getMinX() < m_extent.getMinX() || b.getMinY() < m_extent.getMinY() || b.getMaxX() > m_extent.getMaxX() || b.getMaxY() > m_extent.getMaxY()) {
201 throw std::range_error("Cannot shrink extent to bounds larger than original.");
202 }
203
204 size_t col0 = getColumn(b.getMinX());
205 size_t row1 = getRow(b.getMaxY());
206
207 // Shrink xmin and ymax to fit the upper-left corner of the supplied extent
208 double snapped_xmin = m_extent.getMinX() + static_cast<double>(col0 - extent_tag::padding) * m_dx;
209 double snapped_ymax = m_extent.getMaxY() - static_cast<double>(row1 - extent_tag::padding) * m_dy;
210
211 // Make sure snapped_xmin and snapped_ymax are within the reduced extent.
212 // Because of floating point round-off errors, this is not always the case.
213 if (b.getMinX() < snapped_xmin) {
214 snapped_xmin -= m_dx;
215 col0--;
216 }
217
218 if (b.getMaxY() > snapped_ymax) {
219 snapped_ymax += m_dy;
220 row1--;
221 }
222
223 size_t col1 = getColumn(b.getMaxX());
224 size_t row0 = getRow(b.getMinY());
225
226 size_t num_rows = 1 + (row0 - row1);
227 size_t num_cols = 1 + (col1 - col0);
228
229 // If xmax or ymin falls cleanly on a cell boundary, we don't
230 // need as many rows or columns as we otherwise would, because
231 // we assume that the rightmost cell of the grid is a closed
232 // interval in x, and the lowermost cell of the grid is a
233 // closed interval in y.
234 if (num_rows > 2 && (snapped_ymax - static_cast<double>(num_rows - 1) * m_dy <= b.getMinY())) {
235 num_rows--;
236 }
237 if (num_cols > 2 && (snapped_xmin + static_cast<double>(num_cols - 1) * m_dx >= b.getMaxX())) {
238 num_cols--;
239 }
240
241 const double snapped_xmax = m_extent.getMinX() + static_cast<double>(col0 + num_cols - extent_tag::padding) * m_dx;
242 const double snapped_ymin = m_extent.getMaxY() - static_cast<double>(row1 + num_rows - extent_tag::padding) * m_dy;
243
244 // Perform offsets relative to the new xmin, ymax origin
245 // points. If this is not done, then floating point roundoff
246 // error can cause progressive shrink() calls with the same
247 // inputs to produce different results.
248 geom::Envelope reduced_box = {
249 snapped_xmin,
250 calcExtentFromNewGrid ? std::max(snapped_xmin + static_cast<double>(num_cols) * m_dx, b.getMaxX()) : snapped_xmax,
251 calcExtentFromNewGrid ? std::min(snapped_ymax - static_cast<double>(num_rows) * m_dy, b.getMinY()) : snapped_ymin,
252 snapped_ymax
253 };
254
255 // Fudge computed xmax and ymin, if needed, to prevent extent
256 // from growing during a shrink operation.
257 if (reduced_box.getMaxX() > m_extent.getMaxX()) {
258 if (std::round(reduced_box.getWidth() / m_dx) ==
259 std::round((m_extent.getMaxX() - reduced_box.getMinX()) / m_dx)) {
260 reduced_box = geom::Envelope(reduced_box.getMinX(), m_extent.getMaxX(), reduced_box.getMinY(), reduced_box.getMaxY());
261 } else {
262 throw std::runtime_error("Shrink operation failed.");
263 }
264 }
265 if (reduced_box.getMinY() < m_extent.getMinY()) {
266 if (std::round(reduced_box.getHeight() / m_dy) ==
267 std::round((reduced_box.getMaxY() - m_extent.getMinY()) / m_dy)) {
268 reduced_box = geom::Envelope(reduced_box.getMinX(), reduced_box.getMaxX(), m_extent.getMinY(), reduced_box.getMaxY());
269 } else {
270 throw std::runtime_error("Shrink operation failed.");
271 }
272 }
273
274 if constexpr (std::is_same_v<extent_tag, bounded_extent>) {
275 Grid<extent_tag> reduced{reduced_box, m_dx, m_dy};
276
277 if (!calcExtentFromNewGrid) {
278 reduced.m_xOrig = m_xOrig;
279 reduced.m_yOrig = m_yOrig;
280 reduced.m_skipRows = reduced.getRowOffset(*this);
281 reduced.m_skipCols = reduced.getColOffset(*this);
282 } else if (!reduced.getExtent().contains(b)) {
283 throw std::runtime_error("Shrink operation failed.");
284 }
285 return reduced;
286 } else {
287 Grid<extent_tag> reduced{reduced_box, m_dx, m_dy, m_domain};
288
289 if (!calcExtentFromNewGrid) {
290 reduced.m_xOrig = m_xOrig;
291 reduced.m_yOrig = m_yOrig;
292 reduced.m_skipRows = reduced.getRowOffset(*this);
293 reduced.m_skipCols = reduced.getColOffset(*this);
294 }else if (!reduced.getExtent().contains(b)) {
295 throw std::runtime_error("Shrink operation failed.");
296 }
297 return reduced;
298 }
299 }
300
301 bool operator==(const Grid<extent_tag>& b) const
302 {
303 return m_extent == b.m_extent &&
304 m_dx == b.m_dx &&
305 m_dy == b.m_dy;
306 }
307
308 bool operator!=(const Grid<extent_tag>& b) const
309 {
310 return !(*this == b);
311 }
312
313 geom::Envelope getCellEnvelope(size_t row, size_t col) const;
314
315 private:
316 geom::Envelope m_extent;
317 geom::Envelope m_domain;
318
319 double m_dx;
320 double m_dy;
321
322 double m_xOrig; // origin point that is distinct from xmin of m_extent. Used to allow a subgrid to calculate
323 // sub-envelopes that exactly match those of the parent grid
324 double m_yOrig;
325
326 size_t m_skipRows{0}; // number of rows to skip when computing a cell envelope using m_yOrig
327 size_t m_skipCols{0}; // number of cols to skip when computing a cell envelope using m_xOrig
328
329 size_t m_num_rows;
330 size_t m_num_cols;
331
332 friend Grid<infinite_extent> make_infinite(const Grid<bounded_extent>&, const geom::Envelope&);
333 friend Grid<bounded_extent> make_finite(const Grid<infinite_extent>&);
334};
335
336Grid<infinite_extent>
337make_infinite(const Grid<bounded_extent>& grid, const geom::Envelope& domain);
338Grid<bounded_extent>
339make_finite(const Grid<infinite_extent>& grid);
340
341}
An Envelope defines a rectangulare region of the 2D coordinate plane.
Definition Envelope.h:59
double getMinX() const
Returns the Envelope minimum x-value. Null envelopes do not have maximum values.
Definition Envelope.h:350
bool intersection(const Envelope &env, Envelope &result) const
Computes the intersection of two Envelopes.
double getArea() const
Gets the area of this envelope.
Definition Envelope.h:290
double getHeight() const
Returns the difference between the maximum and minimum y values.
Definition Envelope.h:275
double getWidth() const
Returns the difference between the maximum and minimum x values.
Definition Envelope.h:262
double getMaxX() const
Returns the Envelope maximum x-value. Null envelopes do not have maximum values.
Definition Envelope.h:330
double getMaxY() const
Returns the Envelope maximum y-value. Null envelopes do not have maximum values.
Definition Envelope.h:320
double getMinY() const
Returns the Envelope minimum y-value. Null envelopes do not have maximum values.
Definition Envelope.h:340
The Grid class represents a grid of constant-size rectangular cells that covers a specified envelope....
Definition Grid.h:43
Grid< extent_tag > shrinkToFit(const geom::Envelope &b, bool calcExtentFromNewGrid=true) const
Definition Grid.h:194
double getColX(size_t col) const
Get the x coordinate at the center of the specified column.
Definition Grid.h:176
Grid(const geom::Envelope &extent, double dx, double dy, const geom::Envelope &domain)
Definition Grid.h:61
Grid(const geom::Envelope &extent, double dx, double dy)
Construct a bounded grid covering a specified extent.
Definition Grid.h:47
size_t getColumn(double x) const
Get the column in which the specified x coordinate would fall.
Definition Grid.h:85
size_t getColOffset(const Grid &other) const
Definition Grid.h:173
size_t getRow(double y) const
Get the row in which the specified y coordinate would fall.
Definition Grid.h:111
double getRowY(size_t row) const
Get the y coordinate at the center of the specified row.
Definition Grid.h:179
size_t getRowOffset(const Grid &other) const
Definition Grid.h:168
std::size_t getCell(double x, double y) const
Definition Grid.h:138