|
1 | 1 | #include <algorithm> |
2 | 2 | #include <iostream> |
| 3 | +#include <sstream> |
3 | 4 | #include "tile_data.h" |
4 | 5 | #include "coordinates_geom.h" |
5 | 6 | #include "leased_store.h" |
|
8 | 9 | using namespace std; |
9 | 10 | extern bool verbose; |
10 | 11 |
|
| 12 | +// Human-readable name for a Boost.Geometry validity failure, used for diagnostics. |
| 13 | +static const char* validityFailureName(geom::validity_failure_type failure) { |
| 14 | + switch (failure) { |
| 15 | + case geom::no_failure: return "no_failure"; |
| 16 | + case geom::failure_few_points: return "few_points"; |
| 17 | + case geom::failure_wrong_topological_dimension: return "wrong_topological_dimension"; |
| 18 | + case geom::failure_spikes: return "spikes"; |
| 19 | + case geom::failure_duplicate_points: return "duplicate_points"; |
| 20 | + case geom::failure_not_closed: return "not_closed"; |
| 21 | + case geom::failure_self_intersections: return "self_intersections"; |
| 22 | + case geom::failure_wrong_orientation: return "wrong_orientation"; |
| 23 | + case geom::failure_interior_rings_outside: return "interior_rings_outside"; |
| 24 | + case geom::failure_nested_interior_rings: return "nested_interior_rings"; |
| 25 | + case geom::failure_disconnected_interior: return "disconnected_interior"; |
| 26 | + case geom::failure_intersecting_interiors: return "intersecting_interiors"; |
| 27 | + case geom::failure_wrong_corner_order: return "wrong_corner_order"; |
| 28 | + case geom::failure_invalid_coordinate: return "invalid_coordinate"; |
| 29 | + default: return "unknown"; |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +// Thin wrapper around the shared repair_multi_polygon() that adds per-object |
| 34 | +// verbose diagnostics. See geom.cpp for the dissolve + zero-width buffer logic. |
| 35 | +static bool repairMultiPolygon(MultiPolygon &mp, NodeID objectID) { |
| 36 | + bool ok = repair_multi_polygon(mp); |
| 37 | + if (!ok && verbose) |
| 38 | + std::cerr << ("multipolygon repair failed for object " + std::to_string(objectID) + "\n"); |
| 39 | + return ok; |
| 40 | +} |
| 41 | + |
11 | 42 | thread_local LeasedStore<TileDataSource::point_store_t> pointStore; |
12 | 43 | thread_local LeasedStore<TileDataSource::linestring_store_t> linestringStore; |
13 | 44 | thread_local LeasedStore<TileDataSource::multi_linestring_store_t> multilinestringStore; |
@@ -332,21 +363,39 @@ Geometry TileDataSource::buildWayGeometry(OutputGeometryType const geomType, |
332 | 363 | geom::validity_failure_type failure = geom::validity_failure_type::no_failure; |
333 | 364 | bool valid = geom::is_valid(mp,failure); |
334 | 365 | if (!valid) { |
| 366 | + if (verbose) { |
| 367 | + // Build the whole line first and emit it with a single stream write: |
| 368 | + // tilemaker runs multi-threaded and chained operator<< calls are not |
| 369 | + // atomic, so per-token writes interleave into unreadable output. |
| 370 | + std::ostringstream msg; |
| 371 | + msg << "invalid multipolygon for object " << objectID |
| 372 | + << " at z" << bbox.zoom << " " << bbox.index.x << "/" << bbox.index.y |
| 373 | + << ": " << validityFailureName(failure) << "\n"; |
| 374 | + std::cerr << msg.str(); |
| 375 | + } |
335 | 376 | if (failure==geom::failure_spikes) { |
336 | 377 | geom::remove_spikes(mp); |
337 | 378 | failure = geom::validity_failure_type::no_failure; |
338 | 379 | valid = geom::is_valid(mp,failure); |
339 | 380 | } |
340 | 381 | if (!valid && (failure==geom::failure_self_intersections || failure==geom::failure_intersecting_interiors)) { |
| 382 | + // fast_clip can introduce self-intersections; redo the clip with the |
| 383 | + // slower but robust Boost intersection against the original geometry. |
341 | 384 | MultiPolygon output; |
342 | 385 | geom::intersection(input, box, output); |
343 | 386 | geom::correct(output); |
344 | 387 |
|
345 | | - // retry with Boost intersection if fast_clip has caused self-intersections |
| 388 | + // The intersection result can itself still be invalid for very complex |
| 389 | + // multipolygons (e.g. large reservoirs), which previously produced |
| 390 | + // dropped or holey tiles. Repair it before returning. |
| 391 | + repairMultiPolygon(output, objectID); |
346 | 392 | multiPolygonClipCache.add(bbox, objectID, output); |
347 | 393 | return output; |
348 | 394 | } else if (!valid) { |
349 | | - // occasionally also wrong_topological_dimension, disconnected_interior |
| 395 | + // occasionally also wrong_topological_dimension, disconnected_interior: |
| 396 | + // defects geom::correct cannot mend. Repair mp in place; on failure it |
| 397 | + // is left unchanged so behaviour never regresses. |
| 398 | + repairMultiPolygon(mp, objectID); |
350 | 399 | } |
351 | 400 | } |
352 | 401 |
|
|
0 commit comments