Polygon shards (#105)

* Put all of this back in geometry.cpp for conflict resolution

* Stabilize line simplification to behave the same regardless of winding

* Round instead of truncating when clipping lines

* Restore non-Wagyu polygon clipping from prior to 2fdec7d2

* Make it round, not truncate, which reverts the last commit's test diffs

* Clip in floating point, not integers, which makes no difference

* Track nodes added at tile edges during clipping

* Scale geometry up before wagyu to prevent changes from precision loss

* Actually do the shared edge detection

* Fix cases where nodes were not being added at the tile boundary

* One more place I should have rounded

* Narrow down where the discrepancy comes in

* Revert "Narrow down where the discrepancy comes in"

This reverts commit 221c4c5fc0ac9a6567e091c6a94b3d87dc8ade83.

* Another attempt to narrow it down

* The discrepancy seems to be introduced in reordering. No obvious bug

* Was still truncating instead of rounding in projection

* Also makes no difference...

* Just forget that line reversal exists for a minute

* Just reversal no coalescing

* Try clipping in integers instead of floating point

* Don't simplify after coalescing if they said no simplification

* Check whether behavior is consistent with intentional simplification

* Replace more floating point with integer

* Are these three features enough to demonstrate the problem?

* Add a few more nearby borders

* All the features that touch tile 6/16/23

* Stay in integers in line simplification

* More attempts to solve failures to simplify consistently

* Fix most of the overflow errors

* Fix known cases of integer overflow

* All the tests change again

* Pull clipping and scaling code back out into clip.cpp

* Resolve the test conflicts

* Stabilize choice of which three points to keep with different windings

* Almost right, I think!

* Fix collapse of islands to shards

* Self-intersections in the same feature don't count

* Revert "Self-intersections in the same feature don't count"

This reverts commit e04b19916e.

* Don't scale down geometry if we are going to look for shared nodes

* Fix the missing multiply that was keeping simplification from happening

* Fix one more opportunity for overflow

* Somehow I deleted this test?

* Lost this test too

* Clean up debugging printfs

* Restore code sequence from main to make it reviewable

* Remove unneeded rounding

* Update documentation

* This test is no longer useful

* Back to floating point Douglas-Peucker to fix undersimplification

* Try an older ubuntu

* Revert "Try an older ubuntu"

This reverts commit 13fefacfd7.

* Log OS info

* Remove tests that are no longer needed

* Oops, did need that one after all

* Fix the arm vs x86 discrepancy?

* Try another quantization

* Cleanup from review

* Add a test for the actual purpose of this PR
This commit is contained in:
Erica Fischer
2023-09-14 16:51:38 -07:00
committed by GitHub
parent dee40802fd
commit 390771f855
113 changed files with 25649 additions and 23189 deletions
+153 -10
View File
@@ -573,7 +573,9 @@ double simplify_partial(partial *p, drawvec &shared_nodes) {
// clean coalesced polygons before simplification to avoid
// introducing shards between shapes that otherwise would have
// unioned exactly
geom = clean_or_clip_poly(geom, 0, 0, false);
//
// don't try to scale up because these are still world coordinates
geom = clean_or_clip_poly(geom, 0, 0, false, false);
}
// continues to simplify to line_detail even if we have extra detail
@@ -587,6 +589,7 @@ double simplify_partial(partial *p, drawvec &shared_nodes) {
}
if (t == VT_LINE && additional[A_REVERSE]) {
geom = remove_noop(geom, t, 0);
geom = reorder_lines(geom);
}
@@ -613,7 +616,8 @@ void *partial_feature_worker(void *v) {
// Give Clipper a chance to try to fix it.
{
drawvec before = geom;
geom = clean_or_clip_poly(geom, 0, 0, false);
// we can try scaling up because this is now tile scale
geom = clean_or_clip_poly(geom, 0, 0, false, true);
if (additional[A_DEBUG_POLYGON]) {
check_polygon(geom);
}
@@ -1290,6 +1294,40 @@ long long choose_minextent(std::vector<long long> &extents, double f) {
return extents[(extents.size() - 1) * (1 - f)];
}
struct joint {
draw p1;
draw mid;
draw p2;
joint(draw one, draw hinge, draw two) {
if (one < two) {
p1 = one;
p2 = two;
} else {
p1 = two;
p2 = one;
}
mid = hinge;
}
bool operator<(const joint &o) const {
if (mid < o.mid) {
return true;
} else if (mid == o.mid) {
if (p1 < o.p1) {
return true;
} else if (p1 == o.p1) {
if (p2 < o.p2) {
return true;
}
}
}
return false;
}
};
struct write_tile_args {
struct task *tasks = NULL;
char *stringpool = NULL;
@@ -1372,10 +1410,18 @@ bool clip_to_tile(serial_feature &sf, int z, long long buffer) {
}
}
if (quick == 0) {
if (quick == 0) { // entirely outside the tile
return true;
}
// if quick == 3 the feature touches the buffer, not just the tile proper,
// so we need to clip to add intersection points at the tile edge.
// if quick == 2 it touches the buffer and beyond, so likewise
// if quick == 1 we should be able to get away without clipping, because
// the feature is entirely within the tile proper.
// Can't accept the quick check if guaranteeing no duplication, since the
// overlap might have been in the buffer.
if (quick != 1 || prevent[P_DUPLICATION]) {
@@ -1389,7 +1435,7 @@ bool clip_to_tile(serial_feature &sf, int z, long long buffer) {
clipped = clip_lines(sf.geometry, z, buffer);
}
if (sf.t == VT_POLYGON) {
clipped = simple_clip_poly(sf.geometry, z, buffer);
clipped = simple_clip_poly(sf.geometry, z, buffer, sf.edge_nodes, prevent[P_SIMPLIFY_SHARED_NODES]);
}
if (sf.t == VT_POINT) {
clipped = clip_point(sf.geometry, z, buffer);
@@ -1936,6 +1982,7 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
double coalesced_area = 0;
drawvec shared_nodes;
std::vector<joint> shared_joints;
int tile_detail = line_detail;
size_t skipped = 0;
@@ -2218,8 +2265,87 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
kept++;
if (prevent[P_SIMPLIFY_SHARED_NODES]) {
for (auto &g : sf.geometry) {
shared_nodes.push_back(g);
if (sf.t == VT_LINE) {
for (auto &g : sf.geometry) {
shared_nodes.push_back(g);
}
} else if (sf.t == VT_POLYGON) {
sf.geometry = remove_noop(sf.geometry, sf.t, 0);
for (size_t i = 0; i < sf.geometry.size(); i++) {
if (sf.geometry[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < sf.geometry.size(); j++) {
if (sf.geometry[j].op != VT_LINETO) {
break;
}
}
// j - 1 because we don't want the duplicate last point
for (size_t k = i; k < j - 1; k++) {
shared_joints.emplace_back(
sf.geometry[k],
sf.geometry[(k + 1 - i) % (j - 1 - i) + i],
sf.geometry[(k + 2 - i) % (j - 1 - i) + i]);
}
// since the starting point is never simplified away,
// don't let it be simplified away in any other polygons either.
// Needs to appear twice here so that the check below will see
// it as appearing in multiple features.
shared_nodes.push_back(sf.geometry[i]);
shared_nodes.push_back(sf.geometry[i]);
// To avoid letting polygons get simplified away to nothing,
// also keep the furthest-away point from the initial point
// (which Douglas-Peucker simplification would keep anyway,
// if its search weren't being split up by polygon side).
double far = 0;
size_t which = i;
for (size_t k = i + 1; k < j - 1; k++) {
double xd = sf.geometry[k].x - sf.geometry[i].x;
double yd = sf.geometry[k].y - sf.geometry[i].y;
double d = xd * xd + yd * yd;
if (d > far ||
((d == far) && (sf.geometry[k] < sf.geometry[which]))) {
far = d;
which = k;
}
}
shared_nodes.push_back(sf.geometry[which]);
shared_nodes.push_back(sf.geometry[which]);
// And, likewise, the point most distant from those two points,
// which probably would also be the one that Douglas-Peucker
// would keep next.
far = 0;
size_t which2 = i;
for (size_t k = i + 1; k < j - 1; k++) {
double d = distance_from_line(sf.geometry[k].x, sf.geometry[k].y,
sf.geometry[i].x, sf.geometry[i].y,
sf.geometry[which].x, sf.geometry[which].y);
if ((d > far) ||
((d == far) && (sf.geometry[k] < sf.geometry[which2]))) {
far = d;
which2 = k;
}
}
shared_nodes.push_back(sf.geometry[which2]);
shared_nodes.push_back(sf.geometry[which2]);
i = j - 1;
}
}
}
for (auto &p : sf.edge_nodes) {
shared_nodes.push_back(p);
shared_nodes.push_back(p);
}
}
@@ -2272,7 +2398,8 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
for (auto &g : partials[simplified_geometry_through].geoms) {
if (partials[simplified_geometry_through].t == VT_POLYGON) {
g = clean_or_clip_poly(g, 0, 0, false);
// don't scale up because this is still world coordinates
g = clean_or_clip_poly(g, 0, 0, false, false);
}
}
}
@@ -2303,6 +2430,19 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
}
shared_nodes = just_shared_nodes;
std::sort(shared_joints.begin(), shared_joints.end());
for (size_t i = 0; i + 1 < shared_joints.size(); i++) {
if (shared_joints[i].mid == shared_joints[i + 1].mid) {
if (shared_joints[i].p1 != shared_joints[i + 1].p1 ||
shared_joints[i].p2 != shared_joints[i + 1].p2) {
shared_nodes.push_back(shared_joints[i].mid);
}
}
}
std::sort(shared_nodes.begin(), shared_nodes.end());
shared_joints.clear();
}
for (size_t i = 0; i < partials.size(); i++) {
@@ -2517,13 +2657,16 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
for (size_t x = 0; x < layer_features.size(); x++) {
if (layer_features[x].coalesced && layer_features[x].type == VT_LINE) {
layer_features[x].geom = remove_noop(layer_features[x].geom, layer_features[x].type, 0);
layer_features[x].geom = simplify_lines(layer_features[x].geom, 32, 0,
!(prevent[P_CLIPPING] || prevent[P_DUPLICATION]), simplification, layer_features[x].type == VT_POLYGON ? 4 : 0, shared_nodes);
if (!(prevent[P_SIMPLIFY] || (z == maxzoom && prevent[P_SIMPLIFY_LOW]))) {
layer_features[x].geom = simplify_lines(layer_features[x].geom, 32, 0,
!(prevent[P_CLIPPING] || prevent[P_DUPLICATION]), simplification, layer_features[x].type == VT_POLYGON ? 4 : 0, shared_nodes);
}
}
if (layer_features[x].type == VT_POLYGON) {
if (layer_features[x].coalesced) {
layer_features[x].geom = clean_or_clip_poly(layer_features[x].geom, 0, 0, false);
// we can try scaling up because this is tile coordinates
layer_features[x].geom = clean_or_clip_poly(layer_features[x].geom, 0, 0, false, true);
}
layer_features[x].geom = close_poly(layer_features[x].geom);