diff --git a/include/geom.h b/include/geom.h index 2e8f9466..d99acc1e 100644 --- a/include/geom.h +++ b/include/geom.h @@ -77,6 +77,8 @@ void make_valid(GeometryT &geom) { } void make_valid(MultiPolygon &mp); +void union_many(std::vector &mps); + Point intersect_edge(Point const &a, Point const &b, char edge, Box const &bbox); char bit_code(Point const &p, Box const &bbox); void fast_clip(Ring &points, Box const &bbox); diff --git a/include/output_object.h b/include/output_object.h index d994fbfa..f1f7390f 100644 --- a/include/output_object.h +++ b/include/output_object.h @@ -76,6 +76,9 @@ class OutputObject { vtzero::feature_builder& fbuilder, char zoom ) const; + + bool compatible(const OutputObject &other); + }; #pragma pack(pop) diff --git a/src/geom.cpp b/src/geom.cpp index 8c4e1aaa..69633372 100644 --- a/src/geom.cpp +++ b/src/geom.cpp @@ -144,6 +144,30 @@ void make_valid(MultiPolygon &mp) mp = result; } +// --------------- +// Union multipolygons +// from https://github.com/boostorg/geometry/discussions/947 +void union_many(std::vector &to_unify) { + if (to_unify.size()<2) return; + size_t step = 1; + size_t half_step; + + // the outer loop doubles the distance between two polygons to be merged at every iteration + do { + half_step = step; + step *= 2; + size_t i = 0; + + // the inner loop merges polygons at i and i+half_step storing the result at i + do { + MultiPolygon unified; + boost::geometry::union_(to_unify.at(i), to_unify.at(i + half_step), unified); + to_unify.at(i) = std::move(unified); + i += step; + } while (i + half_step < to_unify.size()); + } while (step < to_unify.size()); +} + // --------------- // Sutherland-Hodgeman clipper // ported from Volodymyr Agafonkin's https://github.com/mapbox/lineclip diff --git a/src/output_object.cpp b/src/output_object.cpp index 0deaec84..e0097a7b 100644 --- a/src/output_object.cpp +++ b/src/output_object.cpp @@ -55,6 +55,11 @@ void OutputObject::writeAttributes( } } +bool OutputObject::compatible(const OutputObject &other) { + return geomType == other.geomType && + z_order == other.z_order && + attributes == other.attributes; +} // Comparision functions bool operator==(const OutputObject& x, const OutputObject& y) { diff --git a/src/tile_worker.cpp b/src/tile_worker.cpp index 89e0ad9b..369e7855 100644 --- a/src/tile_worker.cpp +++ b/src/tile_worker.cpp @@ -73,68 +73,6 @@ void ReorderMultiLinestring(MultiLinestring &input, MultiLinestring &output) { } } -// Merge two multilinestrings by simply appending -// (the constituent parts will be matched up in subsequent call to ReorderMultiLinestring) -void MergeIntersecting(MultiLinestring &input, MultiLinestring &to_merge) { - for (auto ls : to_merge) input.emplace_back(ls); -} - -// Merge two multipolygons by doing intersection checks for each constituent polygon -void MergeIntersecting(MultiPolygon &input, MultiPolygon &to_merge) { - if (boost::geometry::intersects(input, to_merge)) { - for (std::size_t i=0; i -void CheckNextObjectAndMerge( - TileDataSource* source, - OutputObjectsConstIt& jt, - OutputObjectsConstIt ooSameLayerEnd, - const TileBbox& bbox, - T& g -) { - if (jt + 1 == ooSameLayerEnd) - return; - - // If an object is a linestring/polygon that is followed by - // other linestrings/polygons with the same attributes, - // the following objects are merged into the first object, by taking union of geometries. - OutputObjectID oo = *jt; - OutputObjectID ooNext = *(jt + 1); - - // TODO: do we need ooNext? Could we instead just update jt and dereference it? - // put differently: we don't need to keep overwriting oo/ooNext - OutputGeometryType gt = oo.oo.geomType; - while (jt + 1 != ooSameLayerEnd && - ooNext.oo.geomType == gt && - ooNext.oo.z_order == oo.oo.z_order && - ooNext.oo.attributes == oo.oo.attributes) { - jt++; - oo = *jt; - if(jt + 1 != ooSameLayerEnd) { - ooNext = *(jt + 1); - } - - try { - T to_merge = boost::get(source->buildWayGeometry(oo.oo.geomType, oo.oo.objectID, bbox)); - MergeIntersecting(g, to_merge); - } catch (std::out_of_range &err) { cerr << "Geometry out of range " << gt << ": " << static_cast(oo.oo.objectID) <<"," << err.what() << endl; - } catch (boost::bad_get &err) { cerr << "Type error while processing " << gt << ": " << static_cast(oo.oo.objectID) << endl; - } catch (geom::inconsistent_turns_exception &err) { cerr << "Inconsistent turns error while processing " << gt << ": " << static_cast(oo.oo.objectID) << endl; - } - } -} - void RemovePartsBelowSize(MultiPolygon &g, double filterArea) { g.erase(std::remove_if( g.begin(), @@ -362,13 +300,28 @@ void ProcessObjects( //This may increment the jt iterator if (oo.oo.geomType == LINESTRING_ && zoom < sharedData.config.combineBelow) { - CheckNextObjectAndMerge(source, jt, ooSameLayerEnd, bbox, boost::get(g)); + // Append successive linestrings, then reorder afterwards + while (jt<(ooSameLayerEnd-1) && oo.oo.compatible((jt+1)->oo)) { + jt++; + MultiLinestring to_merge = boost::get(source->buildWayGeometry(jt->oo.geomType, jt->oo.objectID, bbox)); + for (auto &ls : to_merge) boost::get(g).emplace_back(ls); + } MultiLinestring reordered; ReorderMultiLinestring(boost::get(g), reordered); g = move(reordered); oo = *jt; + } else if (oo.oo.geomType == POLYGON_ && combinePolygons) { - CheckNextObjectAndMerge(source, jt, ooSameLayerEnd, bbox, boost::get(g)); + // Append successive multipolygons, then union afterwards + std::vector mps; + while (jt<(ooSameLayerEnd-1) && oo.oo.compatible((jt+1)->oo)) { + jt++; + mps.emplace_back( boost::get(source->buildWayGeometry(jt->oo.geomType, jt->oo.objectID, bbox)) ); + } + if (!mps.empty()) { + mps.emplace_back(boost::get(g)); + union_many(mps); g = mps.front(); + } oo = *jt; }