Coverage Report

Created: 2025-12-18 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}