diff --git a/CHANGELOG.md b/CHANGELOG.md index ec736c8a..f75ef84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.9.0 + +* Add an option to generate label points in place of polygons + ## 2.8.1 * Improve precision of polygon ring area calculations diff --git a/README.md b/README.md index 38ebfc1e..1680d39d 100644 --- a/README.md +++ b/README.md @@ -518,6 +518,7 @@ the same layer, enclose them in an `all` expression so they will all be evaluate * `-pw` or `--use-source-polygon-winding`: Instead of respecting GeoJSON polygon ring order, use the original polygon winding in the source data to distinguish inner (clockwise) and outer (counterclockwise) polygon rings. * `-pW` or `--reverse-source-polygon-winding`: Instead of respecting GeoJSON polygon ring order, use the opposite of the original polygon winding in the source data to distinguish inner (counterclockwise) and outer (clockwise) polygon rings. * `--clip-bounding-box=`*minlon*`,`*minlat*`,`*maxlon*`,`*maxlat*: Clip all features to the specified bounding box. + * `-aP` or `--convert-polygons-to-label-points`: Replace polygon geometries with a label point or points for the polygon in each tile it intersects. ### Setting or disabling tile size limits diff --git a/geometry.cpp b/geometry.cpp index 595fd7ef..a3598167 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1364,3 +1364,65 @@ drawvec stairstep(drawvec &geom, int z, int detail) { return out; } + +struct polygon_label { + long long size; + draw point; + + bool operator<(const polygon_label &o) const { + return size > o.size; // reverse sort, largest first + } +}; + +// Generate a label point for a polygon feature, currently at the centroid of its largest-area +// outer ring within the tile. +drawvec polygon_to_anchor(const drawvec &geom) { + std::vector labels; + drawvec dv; + + 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; + } + } + + long long xsum = 0; + long long ysum = 0; + size_t count = 0; + + double area = get_area(geom, i, j); + if (area > 0) { // don't generate anchors for holes + // i + 1 to exclude the first point, which is duplicated as the last + for (size_t k = i + 1; k < j; k++) { + xsum += geom[k].x; + ysum += geom[k].y; + count++; + } + + if (count > 0) { + draw d(VT_MOVETO, xsum / count, ysum / count); + + polygon_label pl; + pl.size = area; + pl.point = d; + + labels.push_back(pl); + } + } + + i = j - 1; + } + } + + std::sort(labels.begin(), labels.end()); + + if (labels.size() > 0) { + dv.push_back(labels[0].point); + } + + return dv; +} + diff --git a/geometry.hpp b/geometry.hpp index cfcb2959..74370b60 100644 --- a/geometry.hpp +++ b/geometry.hpp @@ -76,6 +76,7 @@ std::vector chop_polygon(std::vector &geoms); void check_polygon(drawvec &geom); double get_area(const drawvec &geom, size_t i, size_t j); double get_mp_area(drawvec &geom); +drawvec polygon_to_anchor(const drawvec &geom); drawvec simple_clip_poly(drawvec &geom, long long x1, long long y1, long long x2, long long y2); drawvec clip_lines(drawvec &geom, long long x1, long long y1, long long x2, long long y2); diff --git a/main.cpp b/main.cpp index 6c87639a..f6253e01 100644 --- a/main.cpp +++ b/main.cpp @@ -2761,6 +2761,7 @@ int main(int argc, char **argv) { {"use-source-polygon-winding", no_argument, &prevent[P_USE_SOURCE_POLYGON_WINDING], 1}, {"reverse-source-polygon-winding", no_argument, &prevent[P_REVERSE_SOURCE_POLYGON_WINDING], 1}, {"clip-bounding-box", required_argument, 0, '~'}, + {"convert-polygons-to-label-points", no_argument, &additional[A_GENERATE_POLYGON_LABEL_POINTS], 1}, {"Filtering tile contents", 0, 0, 0}, {"prefilter", required_argument, 0, 'C'}, diff --git a/man/tippecanoe.1 b/man/tippecanoe.1 index d13485e5..7b756dc3 100644 --- a/man/tippecanoe.1 +++ b/man/tippecanoe.1 @@ -671,6 +671,8 @@ the line or polygon within one tile unit of its proper location. You can probabl \fB\fC\-pW\fR or \fB\fC\-\-reverse\-source\-polygon\-winding\fR: Instead of respecting GeoJSON polygon ring order, use the opposite of the original polygon winding in the source data to distinguish inner (counterclockwise) and outer (clockwise) polygon rings. .IP \(bu 2 \fB\fC\-\-clip\-bounding\-box=\fR\fIminlon\fP\fB\fC,\fR\fIminlat\fP\fB\fC,\fR\fImaxlon\fP\fB\fC,\fR\fImaxlat\fP: Clip all features to the specified bounding box. +.IP \(bu 2 +\fB\fC\-aP\fR or \fB\fC\-\-convert\-polygons\-to\-label\-points\fR: Replace polygon geometries with a label point or points for the polygon in each tile it intersects. .RE .SS Setting or disabling tile size limits .RS diff --git a/options.hpp b/options.hpp index 94b689d3..5cb4f608 100644 --- a/options.hpp +++ b/options.hpp @@ -26,6 +26,7 @@ #define A_CONVERT_NUMERIC_IDS ((int) 'I') #define A_HILBERT ((int) 'h') #define A_VISVALINGAM ((int) 'v') +#define A_GENERATE_POLYGON_LABEL_POINTS ((int) 'P') #define P_SIMPLIFY ((int) 's') #define P_SIMPLIFY_LOW ((int) 'S') diff --git a/tile.cpp b/tile.cpp index 15c26c1d..0f9f81b4 100644 --- a/tile.cpp +++ b/tile.cpp @@ -2076,6 +2076,11 @@ long long write_tile(FILE *geoms, std::atomic *geompos_in, char *meta } } + if (sf.t == VT_POLYGON && additional[A_GENERATE_POLYGON_LABEL_POINTS]) { + sf.t = VT_POINT; + sf.geometry = polygon_to_anchor(sf.geometry); + } + if (sf.geometry.size() > 0) { if (partials.size() > max_tile_size) { // Even being maximally conservative, each feature is still going to be diff --git a/version.hpp b/version.hpp index 5709b4a0..a5e40054 100644 --- a/version.hpp +++ b/version.hpp @@ -1,6 +1,6 @@ #ifndef VERSION_HPP #define VERSION_HPP -#define VERSION "v2.8.1" +#define VERSION "v2.9.0" #endif