Skip to content

Commit

Permalink
Faster polygon combining (#681)
Browse files Browse the repository at this point in the history
  • Loading branch information
systemed authored Feb 17, 2024
1 parent 8156369 commit a393cef
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 64 deletions.
2 changes: 2 additions & 0 deletions include/geom.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ void make_valid(GeometryT &geom) { }

void make_valid(MultiPolygon &mp);

void union_many(std::vector<MultiPolygon> &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);
Expand Down
3 changes: 3 additions & 0 deletions include/output_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class OutputObject {
vtzero::feature_builder& fbuilder,
char zoom
) const;

bool compatible(const OutputObject &other);

};
#pragma pack(pop)

Expand Down
24 changes: 24 additions & 0 deletions src/geom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<MultiPolygon> &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
Expand Down
5 changes: 5 additions & 0 deletions src/output_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
81 changes: 17 additions & 64 deletions src/tile_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<input.size(); i++) {
if (boost::geometry::intersects(input[i], to_merge)) {
MultiPolygon union_result;
boost::geometry::union_(input[i], to_merge, union_result);
for (auto output : union_result) input.emplace_back(output);
input.erase(input.begin() + i);
return;
}
}
}
for (auto output : to_merge) input.emplace_back(output);
}

template <typename T>
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<T>(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<int>(oo.oo.objectID) <<"," << err.what() << endl;
} catch (boost::bad_get &err) { cerr << "Type error while processing " << gt << ": " << static_cast<int>(oo.oo.objectID) << endl;
} catch (geom::inconsistent_turns_exception &err) { cerr << "Inconsistent turns error while processing " << gt << ": " << static_cast<int>(oo.oo.objectID) << endl;
}
}
}

void RemovePartsBelowSize(MultiPolygon &g, double filterArea) {
g.erase(std::remove_if(
g.begin(),
Expand Down Expand Up @@ -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<MultiLinestring>(g));
// Append successive linestrings, then reorder afterwards
while (jt<(ooSameLayerEnd-1) && oo.oo.compatible((jt+1)->oo)) {
jt++;
MultiLinestring to_merge = boost::get<MultiLinestring>(source->buildWayGeometry(jt->oo.geomType, jt->oo.objectID, bbox));
for (auto &ls : to_merge) boost::get<MultiLinestring>(g).emplace_back(ls);
}
MultiLinestring reordered;
ReorderMultiLinestring(boost::get<MultiLinestring>(g), reordered);
g = move(reordered);
oo = *jt;

} else if (oo.oo.geomType == POLYGON_ && combinePolygons) {
CheckNextObjectAndMerge(source, jt, ooSameLayerEnd, bbox, boost::get<MultiPolygon>(g));
// Append successive multipolygons, then union afterwards
std::vector<MultiPolygon> mps;
while (jt<(ooSameLayerEnd-1) && oo.oo.compatible((jt+1)->oo)) {
jt++;
mps.emplace_back( boost::get<MultiPolygon>(source->buildWayGeometry(jt->oo.geomType, jt->oo.objectID, bbox)) );
}
if (!mps.empty()) {
mps.emplace_back(boost::get<MultiPolygon>(g));
union_many(mps); g = mps.front();
}
oo = *jt;
}

Expand Down

0 comments on commit a393cef

Please sign in to comment.