diff --git a/README.md b/README.md index 1226e7b..c5da962 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,29 @@ $ tippecanoe -o alameda.mbtiles -l alameda -n "Alameda County from TIGER" -z13 t $ cat tiger/tl_2014_*_roads.json | tippecanoe -o tiger.mbtiles -l roads -n "All TIGER roads, one zoom" -z12 -Z12 -d14 -x LINEARID -x RTTYP ``` +GeoJSON extension +----------------- + +Tippecanoe defines a GeoJSON extension that you can use to specify the minimum and/or maximum zoom level +at which an individual feature will be included in the vector tile dataset being produced. +If you have a feature like this: + +``` +{ + "type" : "Feature", + "tippecanoe" : { "maxzoom" : 9, "minzoom" : 4 }, + "properties" : { "FULLNAME" : "N Vasco Rd" }, + "geometry" : { + "type" : "LineString", + "coordinates" : [ [ -121.733350, 37.767671 ], [ -121.733600, 37.767483 ], [ -121.733131, 37.766952 ] ] + } +} +``` + +with a `tippecanoe` object specifiying a `maxzoom` of 9 and a `minzoom` of 4, the feature +will only appear in the vector tiles for zoom levels 4 through 9. Note that the `tippecanoe` +object belongs to the Feature, not to its `properties`. + Point styling ------------- diff --git a/geojson.c b/geojson.c index d4e4e6e..4938f04 100644 --- a/geojson.c +++ b/geojson.c @@ -409,7 +409,7 @@ long long addpool(struct memfile *poolfile, struct memfile *treefile, char *s, c return off; } -int serialize_geometry(json_object *geometry, json_object *properties, const char *reading, json_pull *jp, long long *seq, long long *metapos, long long *geompos, long long *indexpos, struct pool *exclude, struct pool *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, const char *fname, int maxzoom, int n, double droprate, unsigned *file_bbox) { +int serialize_geometry(json_object *geometry, json_object *properties, const char *reading, json_pull *jp, long long *seq, long long *metapos, long long *geompos, long long *indexpos, struct pool *exclude, struct pool *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, const char *fname, int maxzoom, int layer, double droprate, unsigned *file_bbox, json_object *tippecanoe) { json_object *geometry_type = json_hash_get(geometry, "type"); if (geometry_type == NULL) { static int warned = 0; @@ -443,6 +443,27 @@ int serialize_geometry(json_object *geometry, json_object *properties, const cha return 0; } + int tippecanoe_minzoom = -1; + int tippecanoe_maxzoom = -1; + + if (tippecanoe != NULL) { + json_object *min = json_hash_get(tippecanoe, "minzoom"); + if (min != NULL && min->type == JSON_NUMBER) { + tippecanoe_minzoom = min->number; + } + if (min != NULL && min->type == JSON_STRING) { + tippecanoe_minzoom = atoi(min->string); + } + + json_object *max = json_hash_get(tippecanoe, "maxzoom"); + if (max != NULL && max->type == JSON_NUMBER) { + tippecanoe_maxzoom = max->number; + } + if (max != NULL && max->type == JSON_STRING) { + tippecanoe_maxzoom = atoi(max->string); + } + } + unsigned bbox[] = {UINT_MAX, UINT_MAX, 0, 0}; int nprop = 0; @@ -500,7 +521,15 @@ int serialize_geometry(json_object *geometry, json_object *properties, const cha serialize_byte(geomfile, mb_geometry[t], geompos, fname); serialize_long_long(geomfile, *seq, geompos, fname); - serialize_long_long(geomfile, n, geompos, fname); + + serialize_long_long(geomfile, (layer << 2) | ((tippecanoe_minzoom != -1) << 1) | (tippecanoe_maxzoom != -1), geompos, fname); + if (tippecanoe_minzoom != -1) { + serialize_int(geomfile, tippecanoe_minzoom, geompos, fname); + } + if (tippecanoe_maxzoom != -1) { + serialize_int(geomfile, tippecanoe_maxzoom, geompos, fname); + } + serialize_long_long(geomfile, metastart, geompos, fname); long long wx = initial_x, wy = initial_y; parse_geometry(t, coordinates, bbox, geompos, geomfile, VT_MOVETO, fname, jp, &wx, &wy, &initialized); @@ -654,8 +683,8 @@ int read_json(int argc, char **argv, char *fname, const char *layername, int max nlayers = 1; } - int n; - for (n = 0; n < nlayers; n++) { + int layer; + for (layer = 0; layer < nlayers; layer++) { json_pull *jp; const char *reading; FILE *fp; @@ -663,14 +692,14 @@ int read_json(int argc, char **argv, char *fname, const char *layername, int max long long found_features = 0; long long found_geometries = 0; - if (n >= argc) { + if (layer >= argc) { reading = "standard input"; fp = stdin; } else { - reading = argv[n]; - fp = fopen(argv[n], "r"); + reading = argv[layer]; + fp = fopen(argv[layer], "r"); if (fp == NULL) { - perror(argv[n]); + perror(argv[layer]); continue; } } @@ -737,7 +766,7 @@ int read_json(int argc, char **argv, char *fname, const char *layername, int max } found_geometries++; - serialize_geometry(j, NULL, reading, jp, &seq, &metapos, &geompos, &indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, maxzoom, n, droprate, file_bbox); + serialize_geometry(j, NULL, reading, jp, &seq, &metapos, &geompos, &indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, maxzoom, layer, droprate, file_bbox, NULL); json_free(j); continue; } @@ -766,14 +795,16 @@ int read_json(int argc, char **argv, char *fname, const char *layername, int max continue; } + json_object *tippecanoe = json_hash_get(j, "tippecanoe"); + json_object *geometries = json_hash_get(geometry, "geometries"); if (geometries != NULL) { int g; for (g = 0; g < geometries->length; g++) { - serialize_geometry(geometries->array[g], properties, reading, jp, &seq, &metapos, &geompos, &indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, maxzoom, n, droprate, file_bbox); + serialize_geometry(geometries->array[g], properties, reading, jp, &seq, &metapos, &geompos, &indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, maxzoom, layer, droprate, file_bbox, tippecanoe); } } else { - serialize_geometry(geometry, properties, reading, jp, &seq, &metapos, &geompos, &indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, maxzoom, n, droprate, file_bbox); + serialize_geometry(geometry, properties, reading, jp, &seq, &metapos, &geompos, &indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, maxzoom, layer, droprate, file_bbox, tippecanoe); } json_free(j); diff --git a/man/tippecanoe.1 b/man/tippecanoe.1 index 72322df..0fc4a73 100644 --- a/man/tippecanoe.1 +++ b/man/tippecanoe.1 @@ -137,6 +137,29 @@ $ tippecanoe \-o alameda.mbtiles \-l alameda \-n "Alameda County from TIGER" \-z $ cat tiger/tl_2014_*_roads.json | tippecanoe \-o tiger.mbtiles \-l roads \-n "All TIGER roads, one zoom" \-z12 \-Z12 \-d14 \-x LINEARID \-x RTTYP .fi .RE +.SH GeoJSON extension +.PP +Tippecanoe defines a GeoJSON extension that you can use to specify the minimum and/or maximum zoom level +at which an individual feature will be included in the vector tile dataset being produced. +If you have a feature like this: +.PP +.RS +.nf +{ + "type" : "Feature", + "tippecanoe" : { "maxzoom" : 9, "minzoom" : 4 }, + "properties" : { "FULLNAME" : "N Vasco Rd" }, + "geometry" : { + "type" : "LineString", + "coordinates" : [ [ \-121.733350, 37.767671 ], [ \-121.733600, 37.767483 ], [ \-121.733131, 37.766952 ] ] + } +} +.fi +.RE +.PP +with a \fB\fCtippecanoe\fR object specifiying a \fB\fCmaxzoom\fR of 9 and a \fB\fCminzoom\fR of 4, the feature +will only appear in the vector tiles for zoom levels 4 through 9. Note that the \fB\fCtippecanoe\fR +object belongs to the Feature, not to its \fB\fCproperties\fR\&. .SH Point styling .PP To provide a consistent density gradient as you zoom, the Mapbox Studio style needs to be diff --git a/tile.cc b/tile.cc index 80a96db..ea3bc21 100644 --- a/tile.cc +++ b/tile.cc @@ -369,7 +369,7 @@ void evaluate(std::vector &features, char *metabase, struct pool *file } #endif -void rewrite(drawvec &geom, int z, int nextzoom, int file_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, long long seq) { +void rewrite(drawvec &geom, int z, int nextzoom, int file_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, long long seq, int tippecanoe_minzoom, int tippecanoe_maxzoom) { if (geom.size() > 0 && nextzoom <= file_maxzoom) { int xo, yo; int span = 1 << (nextzoom - z); @@ -436,7 +436,13 @@ void rewrite(drawvec &geom, int z, int nextzoom, int file_maxzoom, long long *bb // printf("type %d, meta %lld\n", t, metastart); serialize_byte(geomfile[j], t, &geompos[j], fname); serialize_long_long(geomfile[j], seq, &geompos[j], fname); - serialize_long_long(geomfile[j], layer, &geompos[j], fname); + serialize_long_long(geomfile[j], (layer << 2) | ((tippecanoe_minzoom != -1) << 1) | (tippecanoe_maxzoom != -1), &geompos[j], fname); + if (tippecanoe_minzoom != -1) { + serialize_int(geomfile[j], tippecanoe_minzoom, geompos, fname); + } + if (tippecanoe_maxzoom != -1) { + serialize_int(geomfile[j], tippecanoe_maxzoom, geompos, fname); + } serialize_long_long(geomfile[j], metastart, &geompos[j], fname); long long wx = initial_x, wy = initial_y; @@ -531,6 +537,14 @@ long long write_tile(char **geoms, char *metabase, char *stringpool, unsigned *f long long layer; deserialize_long_long(geoms, &layer); + int tippecanoe_minzoom = -1, tippecanoe_maxzoom = -1; + if (layer & 2) { + deserialize_int(geoms, &tippecanoe_minzoom); + } + if (layer & 1) { + deserialize_int(geoms, &tippecanoe_maxzoom); + } + layer >>= 2; long long metastart; deserialize_long_long(geoms, &metastart); @@ -576,13 +590,20 @@ long long write_tile(char **geoms, char *metabase, char *stringpool, unsigned *f } if (line_detail == detail && fraction == 1) { /* only write out the next zoom once, even if we retry */ - rewrite(geom, z, nextzoom, file_maxzoom, bbox, tx, ty, buffer, line_detail, within, geompos, geomfile, fname, t, layer, metastart, feature_minzoom, original_seq); + rewrite(geom, z, nextzoom, file_maxzoom, bbox, tx, ty, buffer, line_detail, within, geompos, geomfile, fname, t, layer, metastart, feature_minzoom, original_seq, tippecanoe_minzoom, tippecanoe_maxzoom); } if (z < file_minzoom) { continue; } + if (tippecanoe_minzoom != -1 && z < tippecanoe_minzoom) { + continue; + } + if (tippecanoe_maxzoom != -1 && z > tippecanoe_maxzoom) { + continue; + } + if (t == VT_LINE && z + line_detail <= feature_minzoom) { continue; }