From 692112ec3abb5bc7a28051f8e4d00e2f00a750eb Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Fri, 27 May 2016 16:25:40 -0700 Subject: [PATCH] Add an option to calculate feature density as a feature attribute --- CHANGELOG.md | 4 ++++ README.md | 1 + main.cpp | 8 ++++++++ man/tippecanoe.1 | 2 ++ options.hpp | 1 + tile.cpp | 46 +++++++++++++++++++++++++++++++++++++++++----- version.hpp | 2 +- 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf004ccd..b8b0a9e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.11.8 + +* Add an option to calculate the density of features as a feature attribute + ## 1.11.7 * Keep metadata together with geometry for features that don't span many tiles, diff --git a/README.md b/README.md index 56804c2e..b190e140 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ resolution is obtained than by using a smaller _maxzoom_ or _detail_. * -ao or --reorder: Reorder features to put ones with the same properties in sequence, to try to get them to coalesce * -al or --drop-lines: Let "dot" dropping at lower zooms apply to lines too * -ap or --drop-polygons: Let "dot" dropping at lower zooms apply to polygons too + * -ag or --calculate-feature-density: Add a new attribute, `tippecanoe_feature_density`, to each feature, to record how densely features are spaced in that area of the tile. You can use this attribute in the style to produce a glowing effect where points are densely packed. It can range from 0 in the sparsest areas to 255 in the densest. ### Doing less diff --git a/main.cpp b/main.cpp index bcb7f491..d86afb68 100644 --- a/main.cpp +++ b/main.cpp @@ -987,6 +987,13 @@ int read_input(std::vector &sources, char *fname, const char *layername, std::string reading; int fd; + if (additional[A_CALCULATE_FEATURE_DENSITY]) { + type_and_string tas; + tas.type = VT_NUMBER; + tas.string = "tippecanoe_feature_density"; + file_keys[source].insert(tas); + } + if (source >= sources.size()) { reading = "standard input"; fd = 0; @@ -1771,6 +1778,7 @@ int main(int argc, char **argv) { {"check-polygons", no_argument, &additional[A_DEBUG_POLYGON], 1}, {"drop-polygons", no_argument, &additional[A_POLYGON_DROP], 1}, {"prefer-radix-sort", no_argument, &additional[A_PREFER_RADIX_SORT], 1}, + {"calculate-feature-density", no_argument, &additional[A_CALCULATE_FEATURE_DENSITY], 1}, {"no-line-simplification", no_argument, &prevent[P_SIMPLIFY], 1}, {"simplify-only-low-zooms", no_argument, &prevent[P_SIMPLIFY_LOW], 1}, diff --git a/man/tippecanoe.1 b/man/tippecanoe.1 index bfc79ddf..542e39b9 100644 --- a/man/tippecanoe.1 +++ b/man/tippecanoe.1 @@ -145,6 +145,8 @@ which may not be what you want. \-al or \-\-drop\-lines: Let "dot" dropping at lower zooms apply to lines too .IP \(bu 2 \-ap or \-\-drop\-polygons: Let "dot" dropping at lower zooms apply to polygons too +.IP \(bu 2 +\-ag or \-\-calculate\-feature\-density: Add a new attribute, \fB\fCtippecanoe_feature_density\fR, to each feature, to record how densely features are spaced in that area of the tile. You can use this attribute in the style to produce a glowing effect where points are densely packed. It can range from 0 in the sparsest areas to 255 in the densest. .RE .SS Doing less .RS diff --git a/options.hpp b/options.hpp index d8c0a54b..a3855fdd 100644 --- a/options.hpp +++ b/options.hpp @@ -5,6 +5,7 @@ #define A_DEBUG_POLYGON ((int) 'd') #define A_POLYGON_DROP ((int) 'p') #define A_PREFER_RADIX_SORT ((int) 'R') +#define A_CALCULATE_FEATURE_DENSITY ((int) 'g') #define P_SIMPLIFY ((int) 's') #define P_SIMPLIFY_LOW ((int) 'S') diff --git a/tile.cpp b/tile.cpp index 4909d1b8..610341a5 100644 --- a/tile.cpp +++ b/tile.cpp @@ -73,6 +73,7 @@ struct coalesce { int type; int m; bool coalesced; + double spacing; bool operator<(const coalesce &o) const { int cmp = coalindexcmp(this, &o); @@ -259,7 +260,7 @@ struct sll { } }; -void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, unsigned tx, unsigned ty, int buffer, int line_detail, int *within, long long *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, int m, std::vector &metakeys, std::vector &metavals) { +void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, unsigned tx, unsigned ty, int buffer, int line_detail, int *within, long long *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, int m, std::vector &metakeys, std::vector &metavals) { if (geom.size() > 0 && nextzoom <= maxzoom) { int xo, yo; int span = 1 << (nextzoom - z); @@ -387,6 +388,7 @@ struct partial { int z; int line_detail; int maxzoom; + double spacing; signed char t; }; @@ -599,9 +601,10 @@ long long write_tile(FILE *geoms, long long *geompos_in, char *metabase, char *s double fraction_accum = 0; - unsigned long long previndex = 0; + unsigned long long previndex = 0, density_previndex = 0; double scale = (double) (1LL << (64 - 2 * (z + 8))); - double gap = 0; + double gap = 0, density_gap = 0; + double spacing = 0; long long original_features = 0; long long unclipped_features = 0; @@ -796,6 +799,11 @@ long long write_tile(FILE *geoms, long long *geompos_in, char *metabase, char *s continue; } + unsigned long long index = 0; + if (additional[A_CALCULATE_FEATURE_DENSITY] || gamma > 0) { + index = encode(bbox[0] / 2 + bbox[2] / 2, bbox[1] / 2 + bbox[3] / 2); + } + if (gamma >= 0 && (t == VT_POINT || (additional[A_LINE_DROP] && t == VT_LINE) || (additional[A_POLYGON_DROP] && t == VT_POLYGON))) { @@ -806,14 +814,25 @@ long long write_tile(FILE *geoms, long long *geompos_in, char *metabase, char *s continue; } - if (gamma > 0) { - unsigned long long index = encode(bbox[0] / 2 + bbox[2] / 2, bbox[1] / 2 + bbox[3] / 2); + if (gamma > 0 || additional[A_CALCULATE_FEATURE_DENSITY]) { if (manage_gap(index, &previndex, scale, gamma, &gap)) { continue; } } } + if (additional[A_CALCULATE_FEATURE_DENSITY]) { + // Gamma is always 1 for this calculation so there is a reasonable + // interpretation when no features are being dropped. + // The spacing is only calculated if a feature would be retained by + // that standard, so that duplicates aren't reported as infinitely dense. + + double o_density_previndex = density_previndex; + if (!manage_gap(index, &density_previndex, scale, 1, &density_gap)) { + spacing = (index - o_density_previndex) / scale; + } + } + fraction_accum += fraction; if (fraction_accum < 1) { continue; @@ -840,6 +859,7 @@ long long write_tile(FILE *geoms, long long *geompos_in, char *metabase, char *s p.maxzoom = maxzoom; p.keys = metakeys; p.values = metavals; + p.spacing = spacing; partials.push_back(p); } } @@ -900,6 +920,7 @@ long long write_tile(FILE *geoms, long long *geompos_in, char *metabase, char *s c.stringpool = stringpool + pool_off[partials[i].segment]; c.keys = partials[i].keys; c.values = partials[i].values; + c.spacing = partials[i].spacing; features[layer].push_back(c); } @@ -995,6 +1016,21 @@ long long write_tile(FILE *geoms, long long *geompos_in, char *metabase, char *s features[k][x].geom.clear(); decode_meta(features[k][x].m, features[k][x].keys, features[k][x].values, features[k][x].stringpool, layer, feature); + + if (additional[A_CALCULATE_FEATURE_DENSITY]) { + int glow = 255; + if (features[k][x].spacing > 0) { + glow = (1 / features[k][x].spacing); + if (glow > 255) { + glow = 255; + } + } + mvt_value v; + v.type = mvt_sint; + v.numeric_value.sint_value = glow; + layer.tag(feature, "tippecanoe_feature_density", v); + } + layer.features.push_back(feature); } diff --git a/version.hpp b/version.hpp index bc26cbfa..5205ffca 100644 --- a/version.hpp +++ b/version.hpp @@ -1 +1 @@ -#define VERSION "tippecanoe v1.11.7\n" +#define VERSION "tippecanoe v1.11.8\n"