Skip to content

Commit 6a007a8

Browse files
committed
GridIntersection:: Add extra nodes to rectangular Polygon outputs so that coverage is valid
1 parent d48797a commit 6a007a8

File tree

6 files changed

+94
-21
lines changed

6 files changed

+94
-21
lines changed

include/geos/operation/grid/Cell.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class Cell
4040
{
4141
}
4242

43+
/// Get all points that fall on the specified side of this cell.
44+
void getEdgePoints(Side s, std::vector<geom::CoordinateXY>& points) const;
45+
4346
const geom::Envelope& box() const { return m_box; }
4447

4548
double getWidth() const;

include/geos/operation/grid/Traversal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ class GEOS_DLL Traversal
6161

6262
Side getExitSide() const { return m_exit; }
6363

64+
const geom::CoordinateXY& getFirstCoordinate() const;
65+
6466
const geom::CoordinateXY& getLastCoordinate() const;
6567

6668
const geom::CoordinateXY& getExitCoordinate() const;

src/operation/grid/Cell.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,20 @@ Cell::getCoveredPolygons(const GeometryFactory& gfact) const
291291
return TraversalAreas::getLeftHandRings(gfact, m_box, coord_lists);
292292
}
293293

294+
void Cell::getEdgePoints(Side s, std::vector<CoordinateXY> &edgePoints) const
295+
{
296+
for (const Traversal& t : m_traversals) {
297+
const auto& coords = t.getCoordinates();
298+
299+
for (const auto& c : coords) {
300+
if ((s == Side::LEFT && c.x == m_box.getMinX()) ||
301+
(s == Side::RIGHT && c.x == m_box.getMaxX()) ||
302+
(s == Side::BOTTOM && c.y == m_box.getMinY()) ||
303+
(s == Side::TOP && c.y == m_box.getMaxY())) {
304+
edgePoints.push_back(c);
305+
}
306+
}
307+
}
308+
}
309+
294310
}

src/operation/grid/GridIntersection.cpp

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <geos/operation/grid/Cell.h>
2323
#include <geos/operation/grid/FloodFill.h>
2424
#include <geos/operation/grid/GridIntersection.h>
25+
#include <geos/operation/grid/PerimeterDistance.h>
2526
#include <geos/operation/overlayng/CoverageUnion.h>
2627
#include <geos/util.h>
2728

@@ -504,6 +505,58 @@ traverse_polygons(Matrix<std::unique_ptr<Cell>>& cells, const Grid<infinite_exte
504505
}
505506
}
506507

508+
// Create a rectangular polygon from a set of points lying on the boundary
509+
// of a specified envelope. The provided points do not need to include the
510+
// corners of the envelope itself.
511+
static std::unique_ptr<Geometry>
512+
createRectangle(const geom::GeometryFactory& gfact, const Envelope& env, std::vector<CoordinateXY>& points)
513+
{
514+
if (points.empty()) {
515+
return gfact.toGeometry(&env);
516+
} else {
517+
auto perimeterDistanceCmp = [&env](const CoordinateXY& a, const CoordinateXY& b) {
518+
return PerimeterDistance::getPerimeterDistance(env, a) <
519+
PerimeterDistance::getPerimeterDistance(env, b);
520+
};
521+
522+
points.emplace_back(env.getMinX(), env.getMinY());
523+
points.emplace_back(env.getMinX(), env.getMaxY());
524+
points.emplace_back(env.getMaxX(), env.getMinY());
525+
points.emplace_back(env.getMaxX(), env.getMaxY());
526+
527+
std::sort(points.begin(), points.end(), perimeterDistanceCmp);
528+
points.push_back(points.front());
529+
auto seq = std::make_unique<geom::CoordinateSequence>(0, false, false);
530+
seq->reserve(points.size());
531+
for (const auto& c : points) {
532+
seq->add(c, false);
533+
}
534+
535+
auto ring = gfact.createLinearRing(std::move(seq));
536+
return gfact.createPolygon(std::move(ring));
537+
}
538+
}
539+
540+
// Get any points from adjacent cells that are also on the boundary of this cell.
541+
static std::vector<CoordinateXY>
542+
getExtraNodes(const Matrix<std::unique_ptr<Cell> > &cells, size_t i, size_t j) {
543+
std::vector<CoordinateXY> points;
544+
545+
if (const Cell *above = cells(i - 1, j).get()) {
546+
above->getEdgePoints(Side::BOTTOM, points);
547+
}
548+
if (const Cell *below = cells(i + 1, j).get()) {
549+
below->getEdgePoints(Side::TOP, points);
550+
}
551+
if (const Cell *left = cells(i, j - 1).get()) {
552+
left->getEdgePoints(Side::RIGHT, points);
553+
}
554+
if (const Cell *right = cells(i, j + 1).get()) {
555+
right->getEdgePoints(Side::LEFT, points);
556+
}
557+
558+
return points;
559+
}
507560

508561
std::unique_ptr<Geometry>
509562
GridIntersection::subdividePolygon(const Grid<bounded_extent>& p_grid, const Geometry& g, bool includeExterior)
@@ -547,9 +600,11 @@ GridIntersection::subdividePolygon(const Grid<bounded_extent>& p_grid, const Geo
547600
}
548601
} else if (!edge && areas(i - 1, j - 1) == fill_values<float>::INTERIOR) {
549602
// Cell is entirely covered by polygon
550-
// TODO: Add nodes from adjacent polygons?
603+
// In order to have the outputs forms a properly noded coverage,
604+
// we need to add nodes from adjacent polygons.
551605
Envelope env = cell_grid.getCellEnvelope(i, j);
552-
geoms.push_back(gfact.toGeometry(&env));
606+
std::vector<CoordinateXY> points = getExtraNodes(cells, i, j);
607+
geoms.push_back(createRectangle(gfact, env, points));
553608
}
554609
}
555610
}

src/operation/grid/Traversal.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,16 @@ Traversal::isTraversed() const
9696
return isEntered() && isExited();
9797
}
9898

99+
const CoordinateXY&
100+
Traversal::getFirstCoordinate() const
101+
{
102+
return m_coords.front();
103+
}
104+
99105
const CoordinateXY&
100106
Traversal::getLastCoordinate() const
101107
{
102-
return m_coords.at(m_coords.size() - 1);
108+
return m_coords.back();
103109
}
104110

105111
const CoordinateXY&

tests/unit/operation/grid/GridIntersectionTest.cpp

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ void object::test<37>()
696696

697697
auto subdivided = GridIntersection::subdividePolygon(ext, *g, true);
698698

699-
ensure_equals("", subdivided->getArea(), g->getArea(), 1e-8);
699+
check_subdivided_polygon(*g, *subdivided);
700700
}
701701

702702
template<>
@@ -710,9 +710,8 @@ void object::test<38>()
710710

711711
auto g = wkt_reader_.read("POINT (5 5)")->buffer(20);
712712

713-
auto subdivided = GridIntersection::subdividePolygon(ext, *g, true);
714-
715-
ensure_equals("", subdivided->getArea(), g->getArea(), 1e-8);
713+
auto subd = GridIntersection::subdividePolygon(ext, *g, true);
714+
check_subdivided_polygon(*g, *subd);
716715
}
717716

718717
template<>
@@ -726,9 +725,8 @@ void object::test<39>()
726725

727726
auto g = GeometryFactory::getDefaultInstance()->toGeometry(&e);
728727

729-
auto subdivided = GridIntersection::subdividePolygon(ext, *g, false);
730-
731-
ensure_equals("", subdivided->getArea(), e.getArea(), 1e-8);
728+
auto subd = GridIntersection::subdividePolygon(ext, *g, false);
729+
check_subdivided_polygon(*g, *subd);
732730
}
733731

734732
template<>
@@ -742,9 +740,8 @@ void object::test<40>()
742740

743741
auto g = wkt_reader_.read("MULTIPOLYGON (((1 1, 15 1, 15 25, 1 25, 1 1), (12 12, 12 14, 14 14, 14 12, 12 12)), ((16 1, 25 1, 25 25, 16 25, 16 1)))");
744742

745-
auto subdivided = GridIntersection::subdividePolygon(ext, *g, false);
746-
747-
ensure_equals("", subdivided->getArea(), g->getArea(), 1e-8);
743+
auto subd = GridIntersection::subdividePolygon(ext, *g, false);
744+
check_subdivided_polygon(*g, *subd);
748745
}
749746

750747
template<>
@@ -827,10 +824,7 @@ void object::test<45>()
827824
check_area(*rci, ext, *g);
828825

829826
auto subd = GridIntersection::subdividePolygon(ext, *g, false);
830-
// TODO: Currently fails because polygons do not form a valid coverage.
831-
// When we create a rectangle polygon for a fully interior cell, we do not add interior nodes
832-
// along its edges if they are present in the input geometry.
833-
//check_subdivided_polygon(*g, *subd);
827+
check_subdivided_polygon(*g, *subd);
834828
}
835829

836830
template<>
@@ -849,10 +843,7 @@ void object::test<46>()
849843

850844
auto subd = GridIntersection::subdividePolygon(ext, *g, false);
851845

852-
// TODO: Currently fails because polygons do not form a valid coverage.
853-
// When we create a rectangle polygon for a fully interior cell, we do not add interior nodes
854-
// along its edges if they are present in the input geometry.
855-
//check_subdivided_polygon(*g, *subd);
846+
check_subdivided_polygon(*g, *subd);
856847
}
857848

858849
template<>

0 commit comments

Comments
 (0)