From 5dc9f5034574554c6e46c47f5cabf4c0a66b9aff Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Wed, 7 Oct 2015 16:52:52 -0700 Subject: [PATCH] Clean up polygon generation and clipping. Make sure winding order is correct. --- geojson.c | 14 +++++-- geometry.cc | 104 +++++++++++++++++++++++++++++++++++++++++++++++----- geometry.hh | 1 + tile.cc | 4 ++ 4 files changed, 109 insertions(+), 14 deletions(-) diff --git a/geojson.c b/geojson.c index 4938f04..6696d72 100644 --- a/geojson.c +++ b/geojson.c @@ -123,7 +123,6 @@ void parse_geometry(int t, json_object *j, unsigned *bbox, long long *fpos, FILE } int within = geometry_within[t]; - long long began = *fpos; if (within >= 0) { int i; for (i = 0; i < j->length; i++) { @@ -187,9 +186,16 @@ void parse_geometry(int t, json_object *j, unsigned *bbox, long long *fpos, FILE } if (t == GEOM_POLYGON) { - if (*fpos != began) { - serialize_byte(out, VT_CLOSEPATH, fpos, fname); - } + // Note that this is not using the correct meaning of closepath. + // + // We are using it here to close an entire Polygon, to distinguish + // the Polygons within a MultiPolygon from each other. + // + // This will be undone in fix_polygon(), which needs to know which + // rings come from which Polygons so that it can make the winding order + // of the outer ring be the opposite of the order of the inner rings. + + serialize_byte(out, VT_CLOSEPATH, fpos, fname); } } diff --git a/geometry.cc b/geometry.cc index b0f2e86..57c9a40 100644 --- a/geometry.cc +++ b/geometry.cc @@ -298,6 +298,19 @@ static drawvec clip_poly1(drawvec &geom, int z, int detail, int buffer) { } if (out.size() > 0) { + // If the polygon begins and ends outside the edge, + // the starting and ending points will be left as the + // places where it intersects the edge. Need to add + // another point to close the loop. + + if (out[0].x != out[out.size() - 1].x || out[0].y != out[out.size() - 1].y) { + out.push_back(out[0]); + } + + if (out.size() < 3) { + fprintf(stderr, "Polygon degenerated to a line segment\n"); + } + out[0].op = VT_MOVETO; for (unsigned i = 1; i < out.size(); i++) { out[i].op = VT_LINETO; @@ -318,7 +331,7 @@ drawvec clip_poly(drawvec &geom, int z, int detail, int buffer) { if (geom[i].op == VT_MOVETO) { unsigned j; for (j = i + 1; j < geom.size(); j++) { - if (geom[j].op == VT_CLOSEPATH || geom[j].op == VT_MOVETO) { + if (geom[j].op != VT_LINETO) { break; } } @@ -328,20 +341,20 @@ drawvec clip_poly(drawvec &geom, int z, int detail, int buffer) { tmp.push_back(geom[k]); } tmp = clip_poly1(tmp, z, detail, buffer); + if (tmp.size() > 0) { + if (tmp[0].x != tmp[tmp.size() - 1].x || tmp[0].y != tmp[tmp.size() - 1].y) { + fprintf(stderr, "Internal error: Polygon ring not closed\n"); + exit(EXIT_FAILURE); + } + } for (unsigned k = 0; k < tmp.size(); k++) { out.push_back(tmp[k]); } - if (j >= geom.size() || geom[j].op == VT_CLOSEPATH) { - if (out.size() > 0 && out[out.size() - 1].op != VT_CLOSEPATH) { - out.push_back(draw(VT_CLOSEPATH, 0, 0)); - } - i = j; - } else { - i = j - 1; - } + i = j - 1; } else { - out.push_back(geom[i]); + fprintf(stderr, "Unexpected operation in polygon %d\n", (int) geom[i].op); + exit(EXIT_FAILURE); } } @@ -665,3 +678,74 @@ drawvec reorder_lines(drawvec &geom) { return geom; } + +drawvec fix_polygon(drawvec &geom) { + int outer = 1; + drawvec out; + + unsigned i; + for (i = 0; i < geom.size(); i++) { + if (geom[i].op == VT_CLOSEPATH) { + outer = 1; + } else if (geom[i].op == VT_MOVETO) { + // Find the end of the ring + + unsigned j; + for (j = i + 1; j < geom.size(); j++) { + if (geom[j].op != VT_LINETO) { + break; + } + } + + // Make a temporary copy of the ring. + // Close it if it isn't closed. + + drawvec ring; + for (unsigned a = i; a < j; a++) { + ring.push_back(geom[a]); + } + if (j - i != 0 && (ring[0].x != ring[j - i - 1].x || ring[0].y != ring[j - i - 1].y)) { + ring.push_back(ring[0]); + } + + // Reverse ring if winding order doesn't match + // inner/outer expectation + + double area = 0; + for (unsigned k = 0; k < ring.size(); k++) { + area += (long double) ring[k].x * (long double) ring[(k + 1) % ring.size()].y; + area -= (long double) ring[k].y * (long double) ring[(k + 1) % ring.size()].x; + } + + if ((area > 0) != outer) { + drawvec tmp; + for (int a = ring.size() - 1; a >= 0; a--) { + tmp.push_back(ring[a]); + } + ring = tmp; + } + + // Copy ring into output, fixing the moveto/lineto ops if necessary because of + // reversal or closing + + for (unsigned a = 0; a < ring.size(); a++) { + if (a == 0) { + out.push_back(draw(VT_MOVETO, ring[a].x, ring[a].y)); + } else { + out.push_back(draw(VT_LINETO, ring[a].x, ring[a].y)); + } + } + + // Next ring or polygon begins on the non-lineto that ended this one + // and is not an outer ring unless there is a terminator first + + i = j - 1; + outer = 0; + } else { + fprintf(stderr, "Internal error: polygon ring begins with %d, not moveto\n", geom[i].op); + exit(EXIT_FAILURE); + } + } + + return out; +} diff --git a/geometry.hh b/geometry.hh index 8848e40..26dcf58 100644 --- a/geometry.hh +++ b/geometry.hh @@ -26,3 +26,4 @@ drawvec clip_lines(drawvec &geom, int z, int detail, long long buffer); int quick_check(long long *bbox, int z, int detail, long long buffer); drawvec simplify_lines(drawvec &geom, int z, int detail); drawvec reorder_lines(drawvec &geom); +drawvec fix_polygon(drawvec &geom); diff --git a/tile.cc b/tile.cc index ea3bc21..9c0e01d 100644 --- a/tile.cc +++ b/tile.cc @@ -566,6 +566,10 @@ long long write_tile(char **geoms, char *metabase, char *stringpool, unsigned *f original_features++; + if (z == 0 && t == VT_POLYGON) { + geom = fix_polygon(geom); + } + int quick = quick_check(bbox, z, line_detail, buffer); if (quick == 0) { continue;