diff --git a/README.md b/README.md index d1183a94..3e680b50 100644 --- a/README.md +++ b/README.md @@ -508,6 +508,8 @@ the same layer, enclose them in an `all` expression so they will all be evaluate * `--order-descending-by=`_attribute_: Order features by the specified _attribute_, in reverse alphabetical or numerical order. Multiple `--order-by` and `--order-descending-by` options may be specified, the first being the primary sort key. * `--order-smallest-first`: Order features so the smallest geometry comes first in each tile. Multiple `--order-by` and `--order-descending-by` options may be specified, the first being the primary sort key. * `--order-largest-first`: Order features so the largest geometry comes first in each tile. Multiple `--order-by` and `--order-descending-by` options may be specified, the first being the primary sort key. + * `--order-prominent-first`: Order features so the most prominent geometry (a combination of size and complexity) comes first in each tile. Multiple `--order-by` and `--order-descending-by` options may be specified, the first being the primary sort key. + * `--order-prominent-last`: Order features so the most prominent geometry (a combination of size and complexity) comes last in each tile. Multiple `--order-by` and `--order-descending-by` options may be specified, the first being the primary sort key. ### Adding calculated attributes diff --git a/decode.cpp b/decode.cpp index 9a7869b2..245f16f2 100644 --- a/decode.cpp +++ b/decode.cpp @@ -212,7 +212,7 @@ void handle(std::string message, int z, unsigned x, unsigned y, std::set order_by; bool order_reverse; bool order_by_size = false; +bool order_by_prominence = false; int prevent[256]; int additional[256]; @@ -2754,6 +2755,8 @@ int main(int argc, char **argv) { {"order-descending-by", required_argument, 0, '~'}, {"order-smallest-first", no_argument, 0, '~'}, {"order-largest-first", no_argument, 0, '~'}, + {"order-prominent-first", no_argument, 0, '~'}, + {"order-prominent-last", no_argument, 0, '~'}, {"Adding calculated attributes", 0, 0, 0}, {"calculate-feature-density", no_argument, &additional[A_CALCULATE_FEATURE_DENSITY], 1}, @@ -2896,6 +2899,12 @@ int main(int argc, char **argv) { } else if (strcmp(opt, "order-largest-first") == 0) { order_by.push_back(order_field(ORDER_BY_SIZE, true)); order_by_size = true; + } else if (strcmp(opt, "order-prominent-first") == 0) { + order_by.push_back(order_field(ORDER_BY_PROMINENCE, false)); + order_by_prominence = true; + } else if (strcmp(opt, "order-prominent-last") == 0) { + order_by.push_back(order_field(ORDER_BY_PROMINENCE, true)); + order_by_prominence = true; } else if (strcmp(opt, "simplification-at-maximum-zoom") == 0) { maxzoom_simplification = atof_require(optarg, "Mazoom simplification"); if (maxzoom_simplification <= 0) { diff --git a/main.hpp b/main.hpp index 97ac4b9e..6bf8008a 100644 --- a/main.hpp +++ b/main.hpp @@ -69,6 +69,8 @@ extern std::vector order_by; // not legal UTF-8, so can't appear as a real attribute name #define ORDER_BY_SIZE "\200size" extern bool order_by_size; +#define ORDER_BY_PROMINENCE "\200prominence" +extern bool order_by_prominence; int mkstemp_cloexec(char *name); FILE *fopen_oflag(const char *name, const char *mode, int oflag); diff --git a/man/tippecanoe.1 b/man/tippecanoe.1 index c3eeb8a9..d3729ba8 100644 --- a/man/tippecanoe.1 +++ b/man/tippecanoe.1 @@ -657,6 +657,10 @@ the line or polygon within one tile unit of its proper location. You can probabl \fB\fC\-\-order\-smallest\-first\fR: Order features so the smallest geometry comes first in each tile. Multiple \fB\fC\-\-order\-by\fR and \fB\fC\-\-order\-descending\-by\fR options may be specified, the first being the primary sort key. .IP \(bu 2 \fB\fC\-\-order\-largest\-first\fR: Order features so the largest geometry comes first in each tile. Multiple \fB\fC\-\-order\-by\fR and \fB\fC\-\-order\-descending\-by\fR options may be specified, the first being the primary sort key. +.IP \(bu 2 +\fB\fC\-\-order\-prominent\-first\fR: Order features so the most prominent geometry (a combination of size and complexity) comes first in each tile. Multiple \fB\fC\-\-order\-by\fR and \fB\fC\-\-order\-descending\-by\fR options may be specified, the first being the primary sort key. +.IP \(bu 2 +\fB\fC\-\-order\-prominent\-last\fR: Order features so the most prominent geometry (a combination of size and complexity) comes last in each tile. Multiple \fB\fC\-\-order\-by\fR and \fB\fC\-\-order\-descending\-by\fR options may be specified, the first being the primary sort key. .RE .SS Adding calculated attributes .RS diff --git a/plugin.cpp b/plugin.cpp index c6a87617..8d7d96af 100644 --- a/plugin.cpp +++ b/plugin.cpp @@ -54,7 +54,7 @@ void *run_writer(void *a) { json_writer state(fp); for (size_t i = 0; i < wa->layers->size(); i++) { - layer_to_geojson((*(wa->layers))[i], wa->z, wa->x, wa->y, false, true, false, true, 0, 0, 0, true, state, 0); + layer_to_geojson((*(wa->layers))[i], wa->z, wa->x, wa->y, false, true, false, true, 0, 0, 0, 0, true, state, 0); } if (fclose(fp) != 0) { diff --git a/serial.cpp b/serial.cpp index cc059fd1..25e138a6 100644 --- a/serial.cpp +++ b/serial.cpp @@ -192,8 +192,9 @@ static void write_geometry(drawvec const &dv, std::atomic *fpos, FILE void serialize_feature(FILE *geomfile, serial_feature *sf, std::atomic *geompos, const char *fname, long long wx, long long wy, bool include_minzoom) { serialize_byte(geomfile, sf->t, geompos, fname); -#define FLAG_LAYER 7 +#define FLAG_LAYER 8 +#define FLAG_PROMINENCE 7 #define FLAG_LABEL_POINT 6 #define FLAG_SEQ 5 #define FLAG_INDEX 4 @@ -208,6 +209,7 @@ void serialize_feature(FILE *geomfile, serial_feature *sf, std::atomicseq != 0) << FLAG_SEQ; layer |= (sf->index != 0) << FLAG_INDEX; layer |= (sf->area != 0) << FLAG_AREA; + layer |= (sf->prominence != 0) << FLAG_PROMINENCE; layer |= sf->has_id << FLAG_ID; layer |= sf->has_tippecanoe_minzoom << FLAG_MINZOOM; layer |= sf->has_tippecanoe_maxzoom << FLAG_MAXZOOM; @@ -239,6 +241,9 @@ void serialize_feature(FILE *geomfile, serial_feature *sf, std::atomicarea != 0) { serialize_long_long(geomfile, sf->area, geompos, fname); } + if (sf->prominence != 0) { + serialize_long_long(geomfile, sf->prominence, geompos, fname); + } serialize_long_long(geomfile, sf->metapos, geompos, fname); @@ -291,6 +296,7 @@ serial_feature deserialize_feature(FILE *geoms, std::atomic *geompos_ sf.index = 0; sf.label_point = 0; sf.area = 0; + sf.prominence = 0; sf.geometry = decode_geometry(geoms, geompos_in, z, tx, ty, sf.bbox, initial_x[sf.segment], initial_y[sf.segment]); if (sf.layer & (1 << FLAG_INDEX)) { @@ -302,6 +308,9 @@ serial_feature deserialize_feature(FILE *geoms, std::atomic *geompos_ if (sf.layer & (1 << FLAG_AREA)) { deserialize_long_long_io(geoms, &sf.area, geompos_in); } + if (sf.layer & (1 << FLAG_PROMINENCE)) { + deserialize_long_long_io(geoms, &sf.prominence, geompos_in); + } sf.layer >>= FLAG_LAYER; @@ -539,7 +548,16 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf) { } double area = 0; - if (additional[A_DROP_SMALLEST_AS_NEEDED] || additional[A_COALESCE_SMALLEST_AS_NEEDED] || order_by_size) { + if (additional[A_DROP_SMALLEST_AS_NEEDED] || additional[A_COALESCE_SMALLEST_AS_NEEDED] || order_by_size || order_by_prominence) { + double dist = 0; + for (size_t i = 1; i < scaled_geometry.size(); i++) { + if (scaled_geometry[i].op == VT_LINETO) { + double xd = SHIFT_LEFT(scaled_geometry[i].x - scaled_geometry[i - 1].x); + double yd = SHIFT_LEFT(scaled_geometry[i].y - scaled_geometry[i - 1].y); + dist += sqrt(xd * xd + yd * yd); + } + } + if (sf.t == VT_POLYGON) { for (size_t i = 0; i < scaled_geometry.size(); i++) { if (scaled_geometry[i].op == VT_MOVETO) { @@ -555,18 +573,12 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf) { } } } else if (sf.t == VT_LINE) { - double dist = 0; - for (size_t i = 1; i < scaled_geometry.size(); i++) { - if (scaled_geometry[i].op == VT_LINETO) { - double xd = SHIFT_LEFT(scaled_geometry[i].x - scaled_geometry[i - 1].x); - double yd = SHIFT_LEFT(scaled_geometry[i].y - scaled_geometry[i - 1].y); - dist += sqrt(xd * xd + yd * yd); - } - } // treat lines as having the area of a circle with the line as diameter area = M_PI * (dist / 2) * (dist / 2); } + sf.prominence = sqrt(dist * dist * area); + // VT_POINT area will be calculated in write_tile from the distance between adjacent features. } diff --git a/serial.hpp b/serial.hpp index eb4eb4eb..155fff69 100644 --- a/serial.hpp +++ b/serial.hpp @@ -58,6 +58,7 @@ struct serial_feature { unsigned long long index = 0; unsigned long long label_point = 0; long long area = 0; + long long prominence = 0; std::vector keys{}; std::vector values{}; diff --git a/tile.cpp b/tile.cpp index 2234395a..9dd7358d 100644 --- a/tile.cpp +++ b/tile.cpp @@ -97,6 +97,7 @@ struct coalesce { bool has_id = false; unsigned long long id = 0; long long area = 0; + long long prominence = 0; bool operator<(const coalesce &o) const { int cmp = coalindexcmp(this, &o); @@ -256,6 +257,11 @@ static mvt_value find_attribute_value(const struct coalesce *c1, std::string key v.type = mvt_double; v.numeric_value.double_value = c1->area; return v; + } else if (key == ORDER_BY_PROMINENCE) { + mvt_value v; + v.type = mvt_double; + v.numeric_value.double_value = c1->prominence; + return v; } const std::vector &keys1 = c1->keys; @@ -324,7 +330,7 @@ struct ordercmp { } } ordercmp; -void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, unsigned tx, unsigned ty, int buffer, int *within, std::atomic *geompos, FILE **geomfile, const char *fname, signed char t, int layer, long long metastart, signed char feature_minzoom, int child_shards, int max_zoom_increment, long long seq, int tippecanoe_minzoom, int tippecanoe_maxzoom, int segment, unsigned *initial_x, unsigned *initial_y, std::vector &metakeys, std::vector &metavals, bool has_id, unsigned long long id, unsigned long long index, unsigned long long label_point, long long area) { +void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, unsigned tx, unsigned ty, int buffer, int *within, std::atomic *geompos, FILE **geomfile, const char *fname, signed char t, int layer, long long metastart, signed char feature_minzoom, int child_shards, int max_zoom_increment, long long seq, int tippecanoe_minzoom, int tippecanoe_maxzoom, int segment, unsigned *initial_x, unsigned *initial_y, std::vector &metakeys, std::vector &metavals, bool has_id, unsigned long long id, unsigned long long index, unsigned long long label_point, long long area, long long prominence) { if (geom.size() > 0 && (nextzoom <= maxzoom || additional[A_EXTEND_ZOOMS])) { int xo, yo; int span = 1 << (nextzoom - z); @@ -416,6 +422,7 @@ void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, u sf.index = index; sf.label_point = label_point; sf.area = area; + sf.prominence = prominence; sf.feature_minzoom = feature_minzoom; if (metastart < 0) { @@ -460,6 +467,7 @@ struct partial { bool has_id = 0; ssize_t renamed = 0; long long area = 0; + long long prominence = 0; long long clustered = 0; std::set need_tilestats; std::map attribute_accum_state; @@ -1431,7 +1439,7 @@ serial_feature next_feature(FILE *geoms, std::atomic *geompos_in, cha if (*first_time && pass == 1) { /* only write out the next zoom once, even if we retry */ if (sf.tippecanoe_maxzoom == -1 || sf.tippecanoe_maxzoom >= nextzoom) { - rewrite(sf.geometry, z, nextzoom, maxzoom, sf.bbox, tx, ty, buffer, within, geompos, geomfile, fname, sf.t, sf.layer, sf.metapos, sf.feature_minzoom, child_shards, max_zoom_increment, sf.seq, sf.tippecanoe_minzoom, sf.tippecanoe_maxzoom, sf.segment, initial_x, initial_y, sf.keys, sf.values, sf.has_id, sf.id, sf.index, sf.label_point, sf.area); + rewrite(sf.geometry, z, nextzoom, maxzoom, sf.bbox, tx, ty, buffer, within, geompos, geomfile, fname, sf.t, sf.layer, sf.metapos, sf.feature_minzoom, child_shards, max_zoom_increment, sf.seq, sf.tippecanoe_minzoom, sf.tippecanoe_maxzoom, sf.segment, initial_x, initial_y, sf.keys, sf.values, sf.has_id, sf.id, sf.index, sf.label_point, sf.area, sf.prominence); } } @@ -1606,7 +1614,7 @@ void *run_prefilter(void *v) { decode_meta(sf.keys, sf.values, rpa->stringpool + rpa->pool_off[sf.segment], tmp_layer, tmp_feature); tmp_layer.features.push_back(tmp_feature); - layer_to_geojson(tmp_layer, 0, 0, 0, false, true, false, true, sf.index, sf.seq, sf.area, true, state, 0); + layer_to_geojson(tmp_layer, 0, 0, 0, false, true, false, true, sf.index, sf.seq, sf.area, sf.prominence, true, state, 0); } if (fclose(rpa->prefilter_fp) != 0) { @@ -1963,12 +1971,14 @@ long long write_tile(FILE *geoms, std::atomic *geompos_in, char *meta if (sf.t == VT_POINT) { if (area_previndex >= sf.index) { sf.area = 1; + sf.prominence = 1; } else { double radius = sqrt(sf.index - area_previndex) / 4.0; sf.area = M_PI * radius * radius; if (sf.area < 1) { sf.area = 1; } + sf.prominence = sf.area; } area_previndex = sf.index; @@ -2128,6 +2138,7 @@ long long write_tile(FILE *geoms, std::atomic *geompos_in, char *meta p.index = sf.index; p.renamed = -1; p.area = sf.area; + p.prominence = sf.prominence; p.clustered = 0; if (line_detail == detail && extra_detail >= 0 && z == maxzoom) { @@ -2297,6 +2308,7 @@ long long write_tile(FILE *geoms, std::atomic *geompos_in, char *meta c.id = partials[i].id; c.has_id = partials[i].has_id; c.area = partials[i].area; + c.prominence = partials[i].prominence; // printf("segment %d layer %lld is %s\n", partials[i].segment, partials[i].layer, (*layer_unmaps)[partials[i].segment][partials[i].layer].c_str()); diff --git a/write_json.cpp b/write_json.cpp index 5a3e1870..5ceeb768 100644 --- a/write_json.cpp +++ b/write_json.cpp @@ -258,7 +258,7 @@ void write_coords(json_writer &state, lonlat const &ll, double scale) { } } -void layer_to_geojson(mvt_layer const &layer, unsigned z, unsigned x, unsigned y, bool comma, bool name, bool zoom, bool dropped, unsigned long long index, long long sequence, long long extent, bool complain, json_writer &state, double scale) { +void layer_to_geojson(mvt_layer const &layer, unsigned z, unsigned x, unsigned y, bool comma, bool name, bool zoom, bool dropped, unsigned long long index, long long sequence, long long area, long long prominence, bool complain, json_writer &state, double scale) { for (size_t f = 0; f < layer.features.size(); f++) { mvt_feature const &feat = layer.features[f]; @@ -271,7 +271,7 @@ void layer_to_geojson(mvt_layer const &layer, unsigned z, unsigned x, unsigned y state.json_write_unsigned(feat.id); } - if (name || zoom || index != 0 || sequence != 0 || extent != 0) { + if (name || zoom || index != 0 || sequence != 0 || area != 0 || prominence != 0) { state.json_write_string("tippecanoe"); state.json_write_hash(); @@ -303,9 +303,13 @@ void layer_to_geojson(mvt_layer const &layer, unsigned z, unsigned x, unsigned y state.json_write_signed(sequence); } - if (extent != 0) { - state.json_write_string("extent"); - state.json_write_signed(extent); + if (area != 0) { + state.json_write_string("extent"); // keep misleading terminology for compatibility + state.json_write_signed(area); + } + if (prominence != 0) { + state.json_write_string("prominence"); + state.json_write_signed(prominence); } state.json_end_hash(); @@ -505,16 +509,16 @@ void layer_to_geojson(mvt_layer const &layer, unsigned z, unsigned x, unsigned y int outer = 0; for (size_t i = 0; i < rings.size(); i++) { - double area = 0; + double poly_area = 0; for (size_t k = 0; k < rings[i].size(); k++) { if (rings[i][k].op != VT_CLOSEPATH) { - area += (double) rings[i][k].x * (double) rings[i][(k + 1) % rings[i].size()].y; - area -= (double) rings[i][k].y * (double) rings[i][(k + 1) % rings[i].size()].x; + poly_area += (double) rings[i][k].x * (double) rings[i][(k + 1) % rings[i].size()].y; + poly_area -= (double) rings[i][k].y * (double) rings[i][(k + 1) % rings[i].size()].x; } } - area /= 2; + poly_area /= 2; - areas[i] = area; + areas[i] = poly_area; if (areas[i] >= 0 || i == 0) { outer++; } diff --git a/write_json.hpp b/write_json.hpp index 5534b2b7..51b2c710 100644 --- a/write_json.hpp +++ b/write_json.hpp @@ -60,7 +60,7 @@ struct json_writer { void adds(std::string const &s); }; -void layer_to_geojson(mvt_layer const &layer, unsigned z, unsigned x, unsigned y, bool comma, bool name, bool zoom, bool dropped, unsigned long long index, long long sequence, long long extent, bool complain, json_writer &state, double scale); +void layer_to_geojson(mvt_layer const &layer, unsigned z, unsigned x, unsigned y, bool comma, bool name, bool zoom, bool dropped, unsigned long long index, long long sequence, long long area, long long prominence, bool complain, json_writer &state, double scale); void fprintq(FILE *f, const char *s); #endif