/src/geos/src/operation/grid/GridIntersection.cpp
Line | Count | Source |
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 | | #include <stdexcept> |
16 | | |
17 | | #include <geos/algorithm/Area.h> |
18 | | #include <geos/algorithm/Length.h> |
19 | | #include <geos/algorithm/Orientation.h> |
20 | | #include <geos/geom/GeometryFactory.h> |
21 | | #include <geos/geom/Polygon.h> |
22 | | #include <geos/operation/grid/Cell.h> |
23 | | #include <geos/operation/grid/FloodFill.h> |
24 | | #include <geos/operation/grid/GridIntersection.h> |
25 | | #include <geos/operation/grid/PerimeterDistance.h> |
26 | | #include <geos/operation/overlayng/CoverageUnion.h> |
27 | | #include <geos/util.h> |
28 | | |
29 | | using geos::geom::Geometry; |
30 | | using geos::geom::LineString; |
31 | | using geos::geom::Polygon; |
32 | | using geos::geom::Envelope; |
33 | | using geos::geom::CoordinateXY; |
34 | | |
35 | | namespace geos::operation::grid { |
36 | | |
37 | | std::shared_ptr<Matrix<float>> |
38 | | GridIntersection::getIntersectionFractions(const Grid<bounded_extent>& raster_grid, const Geometry& g) |
39 | 0 | { |
40 | 0 | GridIntersection rci(raster_grid, g); |
41 | 0 | return rci.getResults(); |
42 | 0 | } |
43 | | |
44 | | std::shared_ptr<Matrix<float>> |
45 | | GridIntersection::getIntersectionFractions(const Grid<bounded_extent>& raster_grid, const Envelope& box) |
46 | 0 | { |
47 | 0 | GridIntersection rci(raster_grid, box); |
48 | 0 | return rci.getResults(); |
49 | 0 | } |
50 | | |
51 | | static Cell* |
52 | | get_cell(Matrix<std::unique_ptr<Cell>>& cells, const Grid<infinite_extent>& ex, size_t row, size_t col) |
53 | 0 | { |
54 | 0 | assert(row < cells.getNumRows()); |
55 | 0 | assert(col < cells.getNumCols()); |
56 | 0 | if (cells(row, col) == nullptr) { |
57 | 0 | cells(row, col) = std::make_unique<Cell>(ex.getCellEnvelope(row, col)); |
58 | 0 | } |
59 | |
|
60 | 0 | return cells(row, col).get(); |
61 | 0 | } |
62 | | |
63 | | Envelope |
64 | | GridIntersection::processingRegion(const Envelope& raster_extent, const Geometry& g) |
65 | 0 | { |
66 | 0 | Envelope ret; |
67 | |
|
68 | 0 | auto ngeoms = g.getNumGeometries(); |
69 | 0 | for (size_t i = 0; i < ngeoms; i++) { |
70 | 0 | if (ret == raster_extent) { |
71 | | // No more expansion is possible |
72 | 0 | return ret; |
73 | 0 | } |
74 | | |
75 | 0 | const Envelope& box = *g.getGeometryN(i)->getEnvelopeInternal(); |
76 | |
|
77 | 0 | Envelope isect = raster_extent.intersection(box); |
78 | 0 | if (ret.isNull()) { |
79 | 0 | ret = isect; |
80 | 0 | } else if (!ret.contains(isect)) { |
81 | 0 | ret.expandToInclude(isect); |
82 | 0 | } |
83 | 0 | } |
84 | | |
85 | 0 | return ret; |
86 | 0 | } |
87 | | |
88 | | GridIntersection::GridIntersection(const Grid<bounded_extent>& raster_grid, const Geometry& g, const std::shared_ptr<Matrix<float>>& cov) |
89 | 0 | : m_geometry_grid{ make_infinite(raster_grid, *g.getEnvelopeInternal()) } |
90 | 0 | , m_results{ cov ? cov : std::make_shared<Matrix<float>>(raster_grid.getNumRows(), raster_grid.getNumCols()) } |
91 | 0 | , m_first_geom{ true } |
92 | 0 | , m_areal{ false } |
93 | 0 | { |
94 | 0 | if (g.getDimension() == 0) { |
95 | 0 | throw std::invalid_argument("Unsupported geometry type."); |
96 | 0 | } |
97 | | |
98 | 0 | if (!m_geometry_grid.isEmpty()) { |
99 | 0 | process(g); |
100 | 0 | } |
101 | 0 | } |
102 | | |
103 | | GridIntersection::GridIntersection(const Grid<bounded_extent>& raster_grid, const Envelope& box, const std::shared_ptr<Matrix<float>>& cov) |
104 | 0 | : m_geometry_grid{ make_infinite(raster_grid, box) } |
105 | 0 | , m_results{ cov ? cov : std::make_shared<Matrix<float>>(raster_grid.getNumRows(), raster_grid.getNumCols()) } |
106 | 0 | , m_first_geom { true } |
107 | 0 | , m_areal{ false } |
108 | 0 | { |
109 | 0 | if (!m_geometry_grid.isEmpty()) { |
110 | 0 | processRectangularRing(box, true); |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | void |
115 | | GridIntersection::process(const Geometry& g) |
116 | 0 | { |
117 | 0 | auto type = g.getGeometryTypeId(); |
118 | |
|
119 | 0 | if (type == geom::GEOS_POLYGON) { |
120 | 0 | setAreal(true); |
121 | |
|
122 | 0 | const Polygon& p = static_cast<const Polygon&>(g); |
123 | |
|
124 | 0 | processLine(*p.getExteriorRing(), true); |
125 | 0 | auto nrings = p.getNumInteriorRing(); |
126 | 0 | for (std::size_t i = 0; i < nrings; i++) { |
127 | 0 | processLine(*p.getInteriorRingN(i), false); |
128 | 0 | } |
129 | 0 | } else if (type == geom::GEOS_LINESTRING || type == geom::GEOS_LINEARRING) { |
130 | 0 | setAreal(false); |
131 | |
|
132 | 0 | processLine(static_cast<const LineString&>(g), true); |
133 | 0 | } else if (type == geom::GEOS_GEOMETRYCOLLECTION || type == geom::GEOS_MULTILINESTRING || type == geom::GEOS_MULTIPOLYGON) { |
134 | 0 | auto ngeoms = g.getNumGeometries(); |
135 | 0 | for (std::size_t i = 0; i < ngeoms; i++) { |
136 | 0 | process(*g.getGeometryN(i)); |
137 | 0 | } |
138 | 0 | } else { |
139 | 0 | throw std::invalid_argument("Unsupported geometry type."); |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | static Grid<infinite_extent> |
144 | | get_box_grid(const Envelope& box, const Grid<infinite_extent>& geometry_grid) |
145 | 0 | { |
146 | 0 | Envelope cropped_ring_extent = geometry_grid.getExtent().intersection(box); |
147 | 0 | return geometry_grid.shrinkToFit(cropped_ring_extent); |
148 | 0 | } |
149 | | |
150 | | static Grid<infinite_extent> |
151 | | get_ring_grid(const Geometry& ls, const Grid<infinite_extent>& geometry_grid) |
152 | 0 | { |
153 | 0 | return get_box_grid(*ls.getEnvelopeInternal(), geometry_grid); |
154 | 0 | } |
155 | | |
156 | | void |
157 | | GridIntersection::processRectangularRing(const Envelope& box, bool exterior_ring) |
158 | 0 | { |
159 | 0 | if (!box.intersects(m_geometry_grid.getExtent())) { |
160 | 0 | return; |
161 | 0 | } |
162 | | |
163 | 0 | auto ring_grid = get_box_grid(box, m_geometry_grid); |
164 | |
|
165 | 0 | auto row_min = ring_grid.getRow(box.getMaxY()); |
166 | 0 | auto row_max = ring_grid.getRow(box.getMinY()); |
167 | 0 | auto col_min = ring_grid.getColumn(box.getMinX()); |
168 | 0 | auto col_max = ring_grid.getColumn(box.getMaxX()); |
169 | |
|
170 | 0 | Matrix<float> areas(ring_grid.getNumRows() - 2, ring_grid.getNumCols() - 2); |
171 | | |
172 | | // upper-left |
173 | 0 | if (row_min > 0 && col_min > 0) { |
174 | 0 | auto ul = ring_grid.getCellEnvelope(row_min, col_min); |
175 | 0 | areas(row_min - 1, col_min - 1) = static_cast<float>(ul.intersection(box).getArea() / ul.getArea()); |
176 | 0 | } |
177 | | |
178 | | // upper-right |
179 | 0 | if (row_min > 0 && col_max < ring_grid.getNumCols() - 1) { |
180 | 0 | auto ur = ring_grid.getCellEnvelope( row_min, col_max); |
181 | 0 | auto frac = ur.intersection(box).getArea() / ur.getArea(); |
182 | 0 | areas(row_min - 1, col_max - 1) = static_cast<float>(frac); |
183 | 0 | } |
184 | | |
185 | | // lower-left |
186 | 0 | if (row_max < ring_grid.getNumRows() - 1 && col_min > 0) { |
187 | 0 | auto ll = ring_grid.getCellEnvelope(row_max, col_min); |
188 | 0 | areas(row_max - 1, col_min - 1) = static_cast<float>(ll.intersection(box).getArea() / ll.getArea()); |
189 | 0 | } |
190 | | |
191 | | // lower-right |
192 | 0 | if (row_max < ring_grid.getNumRows() - 1 && col_max < ring_grid.getNumCols() - 1) { |
193 | 0 | auto lr = ring_grid.getCellEnvelope(row_max, col_max); |
194 | 0 | areas(row_max - 1, col_max - 1) = static_cast<float>(lr.intersection(box).getArea() / lr.getArea()); |
195 | 0 | } |
196 | | |
197 | | // left |
198 | 0 | if (col_min > 0) { |
199 | 0 | auto left = ring_grid.getCellEnvelope(row_min + 1, col_min); |
200 | 0 | auto frac = left.intersection(box).getArea() / left.getArea(); |
201 | 0 | for (size_t row = row_min + 1; row < row_max; row++) { |
202 | 0 | areas(row - 1, col_min - 1) = static_cast<float>(frac); |
203 | 0 | } |
204 | 0 | } |
205 | | |
206 | | // right |
207 | 0 | if (col_max < ring_grid.getNumCols() - 1) { |
208 | 0 | auto right = ring_grid.getCellEnvelope(row_min + 1, col_max); |
209 | 0 | auto frac = right.intersection(box).getArea() / right.getArea(); |
210 | 0 | for (size_t row = row_min + 1; row < row_max; row++) { |
211 | 0 | areas(row - 1, col_max - 1) = static_cast<float>(frac); |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | // top |
216 | 0 | if (row_min > 0) { |
217 | 0 | auto top = ring_grid.getCellEnvelope(row_min, col_min + 1); |
218 | 0 | auto frac = top.intersection(box).getArea() / top.getArea(); |
219 | 0 | for (size_t col = col_min + 1; col < col_max; col++) { |
220 | 0 | areas(row_min - 1, col - 1) = static_cast<float>(frac); |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | // bottom |
225 | 0 | if (row_max < ring_grid.getNumRows() - 1) { |
226 | 0 | auto bottom = ring_grid.getCellEnvelope(row_max, col_min + 1); |
227 | 0 | auto frac = bottom.intersection(box).getArea() / bottom.getArea(); |
228 | 0 | for (size_t col = col_min + 1; col < col_max; col++) { |
229 | 0 | areas(row_max - 1, col - 1) = static_cast<float>(frac); |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | | // interior |
234 | 0 | for (size_t row = row_min + 1; row < row_max; row++) { |
235 | 0 | for (size_t col = col_min + 1; col < col_max; col++) { |
236 | 0 | areas(row - 1, col - 1) = 1.0f; |
237 | 0 | } |
238 | 0 | } |
239 | | |
240 | | // Transfer these areas to our global set |
241 | 0 | size_t i0 = ring_grid.getRowOffset(m_geometry_grid); |
242 | 0 | size_t j0 = ring_grid.getColOffset(m_geometry_grid); |
243 | |
|
244 | 0 | addRingResults(i0, j0, areas, exterior_ring); |
245 | 0 | } |
246 | | |
247 | | void |
248 | | GridIntersection::setAreal(bool areal) |
249 | 0 | { |
250 | 0 | if (m_first_geom) { |
251 | 0 | m_first_geom = false; |
252 | 0 | m_areal = areal; |
253 | 0 | } else { |
254 | 0 | if (m_areal != areal) { |
255 | 0 | throw util::GEOSException("Mixed-type geometries not supported."); |
256 | 0 | } |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | | Matrix<float> |
261 | | GridIntersection::collectAreas(const Matrix<std::unique_ptr<Cell>>& cells, |
262 | | const Grid<bounded_extent>& finite_ring_grid, |
263 | | const Geometry& g) |
264 | 0 | { |
265 | | |
266 | | // Create a matrix of areas using the FILLABLE placeholder value, which means that |
267 | | // a known state (interior/exterior) can be propated from an adjacent cell. |
268 | 0 | Matrix<float> areas(cells.getNumRows() - 2, |
269 | 0 | cells.getNumCols() - 2, |
270 | 0 | fill_values<float>::FILLABLE); |
271 | |
|
272 | 0 | FloodFill ff(g, finite_ring_grid); |
273 | | |
274 | | // Mark all cells that have been traversed as being either INTERIOR or EXTERIOR. |
275 | 0 | for (size_t i = 1; i <= areas.getNumRows(); i++) { |
276 | 0 | for (size_t j = 1; j <= areas.getNumCols(); j++) { |
277 | 0 | if (cells(i, j) != nullptr) { |
278 | | // When we encounter a cell that has been processed (ie, it is not nullptr) |
279 | | // but whose traversals contains no linear segments, we have no way to know |
280 | | // if it is inside of the polygon. So we perform point-in-polygon test and set |
281 | | // the covered fraction to 1.0 if needed. |
282 | |
|
283 | 0 | if (!cells(i, j)->isDetermined()) { |
284 | 0 | areas(i - 1, j - 1) = ff.cellIsInside(i - 1, j - 1) ? fill_values<float>::INTERIOR : fill_values<float>::EXTERIOR; |
285 | 0 | } else { |
286 | 0 | areas(i - 1, j - 1) = static_cast<float>(cells(i, j)->getCoveredFraction()); |
287 | 0 | } |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | | // Propagate INTERIOR and EXTERIOR values to all remaining FILLABLE cells |
293 | 0 | ff.flood(areas); |
294 | |
|
295 | 0 | return areas; |
296 | 0 | } |
297 | | |
298 | | Matrix<float> |
299 | | collect_lengths(const Matrix<std::unique_ptr<Cell>>& cells) |
300 | 0 | { |
301 | 0 | Matrix<float> lengths(cells.getNumRows() - 2, |
302 | 0 | cells.getNumCols() - 2, |
303 | 0 | fill_values<float>::EXTERIOR); |
304 | |
|
305 | 0 | for (size_t i = 1; i <= lengths.getNumRows(); i++) { |
306 | 0 | for (size_t j = 1; j <= lengths.getNumCols(); j++) { |
307 | 0 | if (cells(i, j) != nullptr) { |
308 | 0 | lengths(i - 1, j - 1) = static_cast<float>(cells(i, j)->getTraversalLength()); |
309 | 0 | } |
310 | 0 | } |
311 | 0 | } |
312 | |
|
313 | 0 | return lengths; |
314 | 0 | } |
315 | | |
316 | | static void |
317 | | traverseCells(Matrix<std::unique_ptr<Cell>>& cells, std::vector<CoordinateXY>& coords, const Grid<infinite_extent>& ring_grid, bool areal, bool exitOnBoundary, const void* parentage) |
318 | 0 | { |
319 | 0 | size_t pos = 0; |
320 | 0 | size_t row = ring_grid.getRow(coords.front().y); |
321 | 0 | size_t col = ring_grid.getColumn(coords.front().x); |
322 | 0 | const CoordinateXY* last_exit = nullptr; |
323 | |
|
324 | 0 | while (pos < coords.size()) { |
325 | 0 | Cell& cell = *get_cell(cells, ring_grid, row, col); |
326 | |
|
327 | 0 | while (pos < coords.size()) { |
328 | 0 | const CoordinateXY* next_coord = last_exit ? last_exit : &coords[pos]; |
329 | 0 | const CoordinateXY* prev_coord = pos > 0 ? &coords[pos - 1] : nullptr; |
330 | |
|
331 | 0 | cell.take(*next_coord, prev_coord, exitOnBoundary, parentage); |
332 | |
|
333 | 0 | if (cell.getLastTraversal().isExited()) { |
334 | | // Only push our exit coordinate if it's not same as the |
335 | | // coordinate we just took. This covers the case where |
336 | | // the next coordinate in the stack falls exactly on |
337 | | // the cell boundary. |
338 | 0 | const CoordinateXY& exc = cell.getLastTraversal().getExitCoordinate(); |
339 | 0 | if (exc != *next_coord) { |
340 | 0 | last_exit = &exc; |
341 | 0 | } |
342 | 0 | break; |
343 | 0 | } else { |
344 | 0 | if (last_exit) { |
345 | 0 | last_exit = nullptr; |
346 | 0 | } else { |
347 | 0 | pos++; |
348 | 0 | } |
349 | 0 | } |
350 | 0 | } |
351 | |
|
352 | 0 | cell.forceExit(); |
353 | |
|
354 | 0 | if (cell.getLastTraversal().isExited()) { |
355 | | // When we start in the middle of a cell, for a polygon (areal) calculation, |
356 | | // we need to save the coordinates from our incomplete traversal and reprocess |
357 | | // them at the end of the line. The effect is just to restructure the polygon |
358 | | // so that the start/end coordinate falls on a cell boundary. |
359 | 0 | if (areal && !cell.getLastTraversal().isTraversed()) { |
360 | 0 | for (const auto& coord : cell.getLastTraversal().getCoordinates()) { |
361 | 0 | coords.push_back(coord); |
362 | 0 | } |
363 | 0 | } |
364 | |
|
365 | 0 | switch (cell.getLastTraversal().getExitSide()) { |
366 | 0 | case Side::TOP: |
367 | 0 | row--; |
368 | 0 | break; |
369 | 0 | case Side::BOTTOM: |
370 | 0 | row++; |
371 | 0 | break; |
372 | 0 | case Side::LEFT: |
373 | 0 | col--; |
374 | 0 | break; |
375 | 0 | case Side::RIGHT: |
376 | 0 | col++; |
377 | 0 | break; |
378 | 0 | default: |
379 | 0 | throw util::GEOSException("Invalid traversal"); |
380 | 0 | } |
381 | 0 | } |
382 | 0 | } |
383 | 0 | } |
384 | | |
385 | | void |
386 | | GridIntersection::processLine(const LineString& ls, bool exterior_ring) |
387 | 0 | { |
388 | 0 | const Envelope& geom_box = *ls.getEnvelopeInternal(); |
389 | |
|
390 | 0 | const Envelope intersection = geom_box.intersection(m_geometry_grid.getExtent()); |
391 | 0 | if (intersection.getArea() == 0) { |
392 | 0 | return; |
393 | 0 | } |
394 | | |
395 | 0 | const geom::CoordinateSequence& coords = *ls.getCoordinatesRO(); |
396 | |
|
397 | 0 | if (m_areal) { |
398 | 0 | if (coords.size() < 4) { |
399 | | // Component cannot have any area, just skip it. |
400 | 0 | return; |
401 | 0 | } |
402 | | |
403 | 0 | if (coords.size() == 5 && algorithm::Area::ofRing(&coords) == geom_box.getArea()) { |
404 | 0 | processRectangularRing(geom_box, exterior_ring); |
405 | 0 | return; |
406 | 0 | } |
407 | 0 | } |
408 | | |
409 | 0 | Grid<infinite_extent> ring_grid = get_ring_grid(ls, m_geometry_grid); |
410 | |
|
411 | 0 | size_t rows = ring_grid.getNumRows(); |
412 | 0 | size_t cols = ring_grid.getNumCols(); |
413 | | |
414 | | // Short circuit for small geometries that are entirely contained within a single grid cell. |
415 | 0 | if (rows == (1 + 2 * infinite_extent::padding) && |
416 | 0 | cols == (1 + 2 * infinite_extent::padding) && |
417 | 0 | ring_grid.getCellEnvelope(1, 1).contains(geom_box)) { |
418 | |
|
419 | 0 | size_t i0 = ring_grid.getRowOffset(m_geometry_grid); |
420 | 0 | size_t j0 = ring_grid.getColOffset(m_geometry_grid); |
421 | |
|
422 | 0 | if (m_areal) { |
423 | 0 | auto ring_area = static_cast<float>(algorithm::Area::ofRing(&coords) / ring_grid.getCellEnvelope(1, 1).getArea()); |
424 | |
|
425 | 0 | if (exterior_ring) { |
426 | 0 | m_results->increment(i0, j0, ring_area); |
427 | 0 | } else { |
428 | 0 | m_results->increment(i0, j0, -1 * ring_area); |
429 | 0 | } |
430 | 0 | } else { |
431 | 0 | m_results->increment(i0, j0, static_cast<float>(algorithm::Length::ofLine(&coords))); |
432 | 0 | } |
433 | |
|
434 | 0 | return; |
435 | 0 | } |
436 | | |
437 | 0 | std::vector<CoordinateXY> coordsVec; |
438 | 0 | coords.toVector(coordsVec); |
439 | |
|
440 | 0 | if (m_areal && !algorithm::Orientation::isCCW(&coords)) { |
441 | 0 | std::reverse(coordsVec.begin(), coordsVec.end()); |
442 | 0 | } |
443 | |
|
444 | 0 | Matrix<std::unique_ptr<Cell>> cells(ring_grid.getNumRows(), ring_grid.getNumCols()); |
445 | 0 | traverseCells(cells, coordsVec, ring_grid, m_areal, false, &ls); |
446 | | |
447 | | // Compute the fraction covered for all cells and assign it to |
448 | | // the area matrix |
449 | | // TODO avoid copying matrix when geometry has only one polygon, and polygon has only one ring |
450 | 0 | Matrix<float> areas = m_areal ? collectAreas(cells, make_finite(ring_grid), ls) : collect_lengths(cells); |
451 | | |
452 | | // Transfer these areas to our global set |
453 | 0 | size_t i0 = ring_grid.getRowOffset(m_geometry_grid); |
454 | 0 | size_t j0 = ring_grid.getColOffset(m_geometry_grid); |
455 | |
|
456 | 0 | addRingResults(i0, j0, areas, exterior_ring || !m_areal); |
457 | 0 | } |
458 | | |
459 | | void |
460 | | GridIntersection::addRingResults(size_t i0, size_t j0, const Matrix<float>& areas, bool exterior_ring) |
461 | 0 | { |
462 | 0 | float factor = exterior_ring ? 1.0f : -1.0f; |
463 | |
|
464 | 0 | for (size_t i = 0; i < areas.getNumRows(); i++) { |
465 | 0 | for (size_t j = 0; j < areas.getNumCols(); j++) { |
466 | 0 | m_results->increment(i0 + i, j0 + j, factor * areas(i, j)); |
467 | 0 | } |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | | static void |
472 | | traverseRing(Matrix<std::unique_ptr<Cell>>& cells, const Grid<infinite_extent>& grid, const LineString& g, bool want_ccw, bool exitOnBoundary) |
473 | 0 | { |
474 | 0 | const geom::CoordinateSequence& seq = *g.getCoordinatesRO(); |
475 | |
|
476 | 0 | std::vector<CoordinateXY> coords; |
477 | 0 | seq.toVector(coords); |
478 | |
|
479 | 0 | if (want_ccw != algorithm::Orientation::isCCW(&seq)) { |
480 | 0 | std::reverse(coords.begin(), coords.end()); |
481 | 0 | } |
482 | 0 | traverseCells(cells, coords, grid, true, exitOnBoundary, &g); |
483 | 0 | } |
484 | | |
485 | | static void |
486 | | traversePolygons(Matrix<std::unique_ptr<Cell>>& cells, const Grid<infinite_extent>& grid, const Geometry& g, bool exitOnBoundary) |
487 | 0 | { |
488 | 0 | using geom::GeometryTypeId; |
489 | |
|
490 | 0 | std::size_t ngeoms = g.getNumGeometries(); |
491 | 0 | for (std::size_t i = 0; i < ngeoms; i++) { |
492 | 0 | const Geometry& gi = *g.getGeometryN(i); |
493 | 0 | auto typ = gi.getGeometryTypeId(); |
494 | |
|
495 | 0 | if (typ == GeometryTypeId::GEOS_POLYGON) { |
496 | 0 | const Polygon& poly = static_cast<const Polygon&>(gi); |
497 | 0 | const LineString& shell = *poly.getExteriorRing(); |
498 | 0 | traverseRing(cells, grid, shell, true, exitOnBoundary); |
499 | |
|
500 | 0 | std::size_t nrings = poly.getNumInteriorRing(); |
501 | 0 | for (std::size_t j = 0; j < nrings; j++) { |
502 | 0 | traverseRing(cells, grid, *poly.getInteriorRingN(j), false, exitOnBoundary); |
503 | 0 | } |
504 | 0 | } else if (typ == GeometryTypeId::GEOS_MULTIPOLYGON || typ == GeometryTypeId::GEOS_GEOMETRYCOLLECTION) { |
505 | 0 | traversePolygons(cells, grid, gi, exitOnBoundary); |
506 | 0 | } else { |
507 | 0 | throw util::GEOSException("Unsupported geometry type"); |
508 | 0 | } |
509 | 0 | } |
510 | 0 | } |
511 | | |
512 | | // Create a rectangular polygon from a set of points lying on the boundary |
513 | | // of a specified envelope. The provided points do not need to include the |
514 | | // corners of the envelope itself. |
515 | | static std::unique_ptr<Geometry> |
516 | | createRectangle(const geom::GeometryFactory& gfact, const Envelope& env, std::vector<CoordinateXY>& points) |
517 | 0 | { |
518 | 0 | if (points.empty()) { |
519 | 0 | return gfact.toGeometry(&env); |
520 | 0 | } else { |
521 | 0 | auto perimeterDistanceCmp = [&env](const CoordinateXY& a, const CoordinateXY& b) { |
522 | 0 | return PerimeterDistance::isLessThan(env, a, b); |
523 | 0 | }; |
524 | |
|
525 | 0 | points.emplace_back(env.getMinX(), env.getMinY()); |
526 | 0 | points.emplace_back(env.getMinX(), env.getMaxY()); |
527 | 0 | points.emplace_back(env.getMaxX(), env.getMinY()); |
528 | 0 | points.emplace_back(env.getMaxX(), env.getMaxY()); |
529 | |
|
530 | 0 | std::sort(points.begin(), points.end(), perimeterDistanceCmp); |
531 | 0 | points.push_back(points.front()); |
532 | 0 | auto seq = std::make_unique<geom::CoordinateSequence>(0, false, false); |
533 | 0 | seq->reserve(points.size()); |
534 | 0 | for (const auto& c : points) { |
535 | 0 | seq->add(c, false); |
536 | 0 | } |
537 | |
|
538 | 0 | auto ring = gfact.createLinearRing(std::move(seq)); |
539 | 0 | return gfact.createPolygon(std::move(ring)); |
540 | 0 | } |
541 | 0 | } |
542 | | |
543 | | // Get any points from adjacent cells that are also on the boundary of this cell. |
544 | | static std::vector<CoordinateXY> |
545 | 0 | getExtraNodes(const Matrix<std::unique_ptr<Cell> > &cells, size_t i, size_t j) { |
546 | 0 | std::vector<CoordinateXY> points; |
547 | |
|
548 | 0 | if (const Cell *above = cells(i - 1, j).get()) { |
549 | 0 | above->getEdgePoints(Side::BOTTOM, points); |
550 | 0 | } |
551 | 0 | if (const Cell *below = cells(i + 1, j).get()) { |
552 | 0 | below->getEdgePoints(Side::TOP, points); |
553 | 0 | } |
554 | 0 | if (const Cell *left = cells(i, j - 1).get()) { |
555 | 0 | left->getEdgePoints(Side::RIGHT, points); |
556 | 0 | } |
557 | 0 | if (const Cell *right = cells(i, j + 1).get()) { |
558 | 0 | right->getEdgePoints(Side::LEFT, points); |
559 | 0 | } |
560 | |
|
561 | 0 | return points; |
562 | 0 | } |
563 | | |
564 | | std::unique_ptr<Geometry> |
565 | | GridIntersection::subdividePolygon(const Grid<bounded_extent>& p_grid, const Geometry& g, bool includeExterior) |
566 | 0 | { |
567 | 0 | util::ensureNoCurvedComponents(g); |
568 | |
|
569 | 0 | const geom::GeometryFactory& gfact = *g.getFactory(); |
570 | |
|
571 | 0 | const auto cropped_grid = p_grid.shrinkToFit(p_grid.getExtent().intersection(*g.getEnvelopeInternal()), false); |
572 | |
|
573 | 0 | geom::Envelope gridExtent = *g.getEnvelopeInternal(); |
574 | 0 | gridExtent.expandBy(1); |
575 | |
|
576 | 0 | const Grid<infinite_extent> cell_grid = make_infinite(cropped_grid, gridExtent); |
577 | 0 | Matrix<std::unique_ptr<Cell>> cells(cell_grid.getNumRows(), cell_grid.getNumCols()); |
578 | |
|
579 | 0 | traversePolygons(cells, cell_grid, g, true); |
580 | |
|
581 | 0 | const auto areas = collectAreas(cells, cropped_grid, g); |
582 | |
|
583 | 0 | std::vector<std::unique_ptr<Geometry>> geoms; |
584 | 0 | std::vector<std::unique_ptr<Geometry>> edge_geoms; |
585 | |
|
586 | 0 | for (std::size_t i = 0; i < cells.getNumRows(); i++) { |
587 | 0 | const bool row_edge = i == 0 || i == (cells.getNumRows() - 1); |
588 | |
|
589 | 0 | for (std::size_t j = 0; j < cells.getNumCols(); j++) { |
590 | 0 | const bool col_edge = j == 0 || j == cells.getNumCols() - 1; |
591 | 0 | const bool edge = row_edge || col_edge; |
592 | |
|
593 | 0 | const Cell* cell = cells(i, j).get(); |
594 | |
|
595 | 0 | if (cell != nullptr && cell->isDetermined()) { |
596 | | // It is possible for the area to equal the cell area when the polygon is not the same as the |
597 | | // cell area. (See GridIntersectionTest test #46). So, we only compare the area to |
598 | | // fill_values<float>::INTERIOR for cells that have not been traversed. |
599 | 0 | if (edge) { |
600 | 0 | if (includeExterior) { |
601 | 0 | edge_geoms.push_back(cell->getCoveredPolygons(gfact)); |
602 | 0 | } |
603 | 0 | } else if (areas(i - 1, j - 1) != fill_values<float>::EXTERIOR) { |
604 | | // Cell is partly covered by polygon |
605 | 0 | geoms.push_back(cell->getCoveredPolygons(gfact)); |
606 | 0 | } |
607 | 0 | } else if (!edge && areas(i - 1, j - 1) == fill_values<float>::INTERIOR) { |
608 | | // Cell is entirely covered by polygon |
609 | | // In order to have the outputs forms a properly noded coverage, |
610 | | // we need to add nodes from adjacent polygons. |
611 | 0 | Envelope env = cell_grid.getCellEnvelope(i, j); |
612 | 0 | std::vector<CoordinateXY> points = getExtraNodes(cells, i, j); |
613 | 0 | geoms.push_back(createRectangle(gfact, env, points)); |
614 | 0 | } |
615 | 0 | } |
616 | 0 | } |
617 | |
|
618 | 0 | if (!edge_geoms.empty()) { |
619 | 0 | auto edge_coll = gfact.createGeometryCollection(std::move(edge_geoms)); |
620 | 0 | geoms.push_back(overlayng::CoverageUnion::geomunion(edge_coll.get())); |
621 | | // Polygon generated here may have extra nodes that do not match adjacent polygons not processed through |
622 | | // subdividePolygon. |
623 | 0 | } |
624 | |
|
625 | 0 | return gfact.createGeometryCollection(std::move(geoms)); |
626 | 0 | } |
627 | | |
628 | | } |