Skip to content

Commit

Permalink
Pull out more geometry functions into now-badly-named clip.cpp
Browse files Browse the repository at this point in the history
  • Loading branch information
e-n-f committed Jul 21, 2023
1 parent ecf9b97 commit 626f63f
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 328 deletions.
328 changes: 328 additions & 0 deletions clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,331 @@ int clip(double *x0, double *y0, double *x1, double *y1, double xmin, double ymi
}
}


static void decode_clipped(mapbox::geometry::multi_polygon<long long> &t, drawvec &out) {
out.clear();

for (size_t i = 0; i < t.size(); i++) {
for (size_t j = 0; j < t[i].size(); j++) {
drawvec ring;

for (size_t k = 0; k < t[i][j].size(); k++) {
ring.push_back(draw((k == 0) ? VT_MOVETO : VT_LINETO, t[i][j][k].x, t[i][j][k].y));
}

if (ring.size() > 0 && ring[ring.size() - 1] != ring[0]) {
fprintf(stderr, "Had to close ring\n");
ring.push_back(draw(VT_LINETO, ring[0].x, ring[0].y));
}

double area = get_area(ring, 0, ring.size());

if ((j == 0 && area < 0) || (j != 0 && area > 0)) {
fprintf(stderr, "Ring area has wrong sign: %f for %zu\n", area, j);
exit(EXIT_IMPOSSIBLE);
}

for (size_t k = 0; k < ring.size(); k++) {
out.push_back(ring[k]);
}
}
}
}

drawvec clean_or_clip_poly(drawvec &geom, int z, int buffer, bool clip) {
mapbox::geometry::wagyu::wagyu<long long> wagyu;

geom = remove_noop(geom, VT_POLYGON, 0);
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
}

if (j >= i + 4) {
mapbox::geometry::linear_ring<long long> lr;

for (size_t k = i; k < j; k++) {
lr.push_back(mapbox::geometry::point<long long>(geom[k].x, geom[k].y));
}

if (lr.size() >= 3) {
wagyu.add_ring(lr);
}
}

i = j - 1;
}
}

if (clip) {
long long area = 0xFFFFFFFF;
if (z != 0) {
area = 1LL << (32 - z);
}
long long clip_buffer = buffer * area / 256;

mapbox::geometry::linear_ring<long long> lr;

lr.push_back(mapbox::geometry::point<long long>(-clip_buffer, -clip_buffer));
lr.push_back(mapbox::geometry::point<long long>(-clip_buffer, area + clip_buffer));
lr.push_back(mapbox::geometry::point<long long>(area + clip_buffer, area + clip_buffer));
lr.push_back(mapbox::geometry::point<long long>(area + clip_buffer, -clip_buffer));
lr.push_back(mapbox::geometry::point<long long>(-clip_buffer, -clip_buffer));

wagyu.add_ring(lr, mapbox::geometry::wagyu::polygon_type_clip);
}

mapbox::geometry::multi_polygon<long long> result;
try {
wagyu.execute(mapbox::geometry::wagyu::clip_type_union, result, mapbox::geometry::wagyu::fill_type_positive, mapbox::geometry::wagyu::fill_type_positive);
} catch (std::runtime_error &e) {
FILE *f = fopen("/tmp/wagyu.log", "w");
fprintf(f, "%s\n", e.what());
fprintf(stderr, "%s\n", e.what());
fprintf(f, "[");

for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
}

if (j >= i + 4) {
mapbox::geometry::linear_ring<long long> lr;

if (i != 0) {
fprintf(f, ",");
}
fprintf(f, "[");

for (size_t k = i; k < j; k++) {
lr.push_back(mapbox::geometry::point<long long>(geom[k].x, geom[k].y));
if (k != i) {
fprintf(f, ",");
}
fprintf(f, "[%lld,%lld]", geom[k].x, geom[k].y);
}

fprintf(f, "]");

if (lr.size() >= 3) {
}
}

i = j - 1;
}
}

fprintf(f, "]");
fprintf(f, "\n\n\n\n\n");

fclose(f);
fprintf(stderr, "Internal error: Polygon cleaning failed. Log in /tmp/wagyu.log\n");
exit(EXIT_IMPOSSIBLE);
}

drawvec ret;
decode_clipped(result, ret);
return ret;
}


void to_tile_scale(drawvec &geom, int z, int detail) {
for (size_t i = 0; i < geom.size(); i++) {
geom[i].x = std::round((double) geom[i].x / (1LL << (32 - detail - z)));
geom[i].y = std::round((double) geom[i].y / (1LL << (32 - detail - z)));
}
}

drawvec from_tile_scale(drawvec const &geom, int z, int detail) {
drawvec out;
for (size_t i = 0; i < geom.size(); i++) {
draw d = geom[i];
d.x *= (1LL << (32 - detail - z));
d.y *= (1LL << (32 - detail - z));
out.push_back(d);
}
return out;
}

drawvec remove_noop(drawvec geom, int type, int shift) {
// first pass: remove empty linetos

long long x = 0, y = 0;
drawvec out;

for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_LINETO && std::round((double) geom[i].x / (1LL << shift)) == x && std::round((double) geom[i].y / (1LL << shift)) == y) {
continue;
}

if (geom[i].op == VT_CLOSEPATH) {
out.push_back(geom[i]);
} else { /* moveto or lineto */
out.push_back(geom[i]);
x = std::round((double) geom[i].x / (1LL << shift));
y = std::round((double) geom[i].y / (1LL << shift));
}
}

// second pass: remove unused movetos

if (type != VT_POINT) {
geom = out;
out.resize(0);

for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
if (i + 1 >= geom.size()) {
continue;
}

if (geom[i + 1].op == VT_MOVETO) {
continue;
}

if (geom[i + 1].op == VT_CLOSEPATH) {
fprintf(stderr, "Shouldn't happen\n");
i++; // also remove unused closepath
continue;
}
}

out.push_back(geom[i]);
}
}

// second pass: remove empty movetos

if (type == VT_LINE) {
geom = out;
out.resize(0);

for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
if (i > 0 && geom[i - 1].op == VT_LINETO && std::round((double) geom[i - 1].x / (1LL << shift)) == std::round((double) geom[i].x / (1LL << shift)) && std::round((double) geom[i - 1].y / (1LL << shift)) == std::round((double) geom[i].y / (1LL << shift))) {
continue;
}
}

out.push_back(geom[i]);
}
}

return out;
}


double get_area_scaled(const drawvec &geom, size_t i, size_t j) {
const double max_exact_double = (double) ((1LL << 53) - 1);

// keep scaling the geometry down until we can calculate its area without overflow
for (long long scale = 2; scale < (1LL << 30); scale *= 2) {
long long bx = geom[i].x;
long long by = geom[i].y;
bool again = false;

// https://en.wikipedia.org/wiki/Shoelace_formula
double area = 0;
for (size_t k = i; k < j; k++) {
area += (double) ((geom[k].x - bx) / scale) * (double) ((geom[i + ((k - i + 1) % (j - i))].y - by) / scale);
if (std::fabs(area) >= max_exact_double) {
again = true;
break;
}
area -= (double) ((geom[k].y - by) / scale) * (double) ((geom[i + ((k - i + 1) % (j - i))].x - bx) / scale);
if (std::fabs(area) >= max_exact_double) {
again = true;
break;
}
}

if (again) {
continue;
} else {
area /= 2;
return area * scale * scale;
}
}

fprintf(stderr, "get_area_scaled: can't happen\n");
exit(EXIT_IMPOSSIBLE);
}

double get_area(const drawvec &geom, size_t i, size_t j) {
const double max_exact_double = (double) ((1LL << 53) - 1);

// Coordinates in `geom` are 40-bit integers, so there is no good way
// to multiply them without possible precision loss. Since they probably
// do not use the full precision, shift them nearer to the origin so
// their product is more likely to be exactly representable as a double.
//
// (In practice they are actually 34-bit integers: 32 bits for the
// Mercator world plane, plus another two bits so features can stick
// off either the left or right side. But that is still too many bits
// for the product to fit either in a 64-bit long long or in a
// double where the largest exact integer is 2^53.)
//
// If the intermediate calculation still exceeds 2^53, start trying to
// recalculate the area by scaling down the geometry. This will not
// produce as precise an area, but it will still be close, and the
// sign will be correct, which is more important, since the sign
// determines the winding order of the rings. We can then use that
// sign with this generally more precise area calculation.

long long bx = geom[i].x;
long long by = geom[i].y;

// https://en.wikipedia.org/wiki/Shoelace_formula
double area = 0;
bool overflow = false;
for (size_t k = i; k < j; k++) {
area += (double) (geom[k].x - bx) * (double) (geom[i + ((k - i + 1) % (j - i))].y - by);
if (std::fabs(area) >= max_exact_double) {
overflow = true;
}
area -= (double) (geom[k].y - by) * (double) (geom[i + ((k - i + 1) % (j - i))].x - bx);
if (std::fabs(area) >= max_exact_double) {
overflow = true;
}
}
area /= 2;

if (overflow) {
double scaled_area = get_area_scaled(geom, i, j);
if ((area < 0 && scaled_area > 0) || (area > 0 && scaled_area < 0)) {
area = -area;
}
}

return area;
}

double get_mp_area(drawvec &geom) {
double ret = 0;

for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
size_t j;

for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
}

ret += get_area(geom, i, j);
i = j - 1;
}
}

return ret;
}

Loading

0 comments on commit 626f63f

Please sign in to comment.