mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 08:25:40 +02:00
Drop a hole that no ring can parent instead of failing the run (#401)
* wagyu: drop a hole no remaining ring can parent instead of throwing correct_tree() throws "Could not properly place hole to a parent" when topology correction leaves a hole whose parent ring was removed (degenerate input such as stacked duplicate rings from coalesced tiny-polygon placeholders). That aborts the entire tiling run over one unrepresentable sliver. Remove the ring and its points instead, matching how other unresolvable degeneracies are handled. * Add a regression test for dropping an unplaceable hole A fuzzer-minimized pair of mutually reversed self-intersecting rings that makes wagyu's correct_tree fail to find a parent for a hole — the same failure reported in mapbox/tippecanoe#761. Before the topology_correction change, running this test exits with EXIT_IMPOSSIBLE via the polygon cleaning error handler; with it, the clean returns.
This commit is contained in:
@@ -1296,7 +1296,10 @@ void correct_tree(ring_manager<T>& manager) {
|
|||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
if ((*itr)->is_hole()) {
|
if ((*itr)->is_hole()) {
|
||||||
throw std::runtime_error("Could not properly place hole to a parent.");
|
// No remaining ring contains this hole (degenerate input, e.g. stacked
|
||||||
|
// duplicate rings from coalesced tiny-polygon placeholders); drop it
|
||||||
|
// rather than fail the entire tiling run.
|
||||||
|
remove_ring_and_points(*itr, manager, false);
|
||||||
} else {
|
} else {
|
||||||
// Assign to base of tree by passing nullptr
|
// Assign to base of tree by passing nullptr
|
||||||
reassign_as_child(*itr, static_cast<ring_ptr<T>>(nullptr), manager);
|
reassign_as_child(*itr, static_cast<ring_ptr<T>>(nullptr), manager);
|
||||||
|
|||||||
@@ -136,3 +136,36 @@ TEST_CASE("line_is_too_small") {
|
|||||||
dv.emplace_back(VT_LINETO, -51864809, 2683873977);
|
dv.emplace_back(VT_LINETO, -51864809, 2683873977);
|
||||||
REQUIRE(line_is_too_small(dv, 0, 10));
|
REQUIRE(line_is_too_small(dv, 0, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Polygon cleaning drops a hole that no ring can parent", "[wagyu]") {
|
||||||
|
// Two mutually reversed self-intersecting rings whose union leaves a hole
|
||||||
|
// that wagyu's topology correction cannot assign to any surviving parent
|
||||||
|
// ring (found by fuzzing; same failure as mapbox/tippecanoe#761). Without
|
||||||
|
// the fix in mapbox/geometry/wagyu/topology_correction.hpp, this exits
|
||||||
|
// through the "Could not properly place hole to a parent." handler in
|
||||||
|
// clean_or_clip_poly instead of returning.
|
||||||
|
static const std::vector<std::vector<std::pair<long long, long long>>> rings = {
|
||||||
|
{{0, 5}, {5, 4}, {5, 1}, {4, 4}, {4, 2}, {7, 1}, {0, 5}},
|
||||||
|
{{0, 5}, {7, 1}, {4, 2}, {4, 4}, {5, 1}, {5, 4}, {0, 0}, {0, 5}},
|
||||||
|
};
|
||||||
|
|
||||||
|
drawvec geom;
|
||||||
|
for (auto const &ring : rings) {
|
||||||
|
for (size_t i = 0; i < ring.size(); i++) {
|
||||||
|
geom.push_back(draw(i == 0 ? VT_MOVETO : VT_LINETO, ring[i].first, ring[i].second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawvec out = clean_or_clip_poly(geom, 0, 0, false, false);
|
||||||
|
|
||||||
|
// The regression signal is getting here at all: without the fix,
|
||||||
|
// clean_or_clip_poly exits the process from its wagyu error handler.
|
||||||
|
SUCCEED("clean_or_clip_poly returned");
|
||||||
|
|
||||||
|
// Anything that survives must be sanely wound: first ring positive.
|
||||||
|
if (out.size() > 0) {
|
||||||
|
size_t j = 1;
|
||||||
|
while (j < out.size() && out[j].op == VT_LINETO) j++;
|
||||||
|
REQUIRE(get_area(out, 0, j) > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user