TIPPECANOE(1)

✨ tippecanoe(1) ✨

okay so bestie, welcome to tippecanoe β€” the manual page, but make it make sense πŸ’–

This bad boy builds vector tilesets (read: https://github.com/mapbox/vector-tile-spec/) from large (or small) collections of GeoJSON (http://geojson.org/), FlatGeobuf (https://github.com/flatgeobuf/flatgeobuf), or CSV (https://en.wikipedia.org/wiki/Comma-separated_values) features β€” like the ones in MADE_WITH.md.

This is the official home of Tippecanoe, developed and actively maintained by Erica Fischer (https://github.com/e-n-f) at Felt (https://felt.com).

Wanna self-hosted, API-driven version? Contact a technical sales engineer at sales@felt.com. Felt produces highly performant, automatically projected versions of your data, and utilizes a rendering engine, built on top of MapLibre GL JS (https://github.com/maplibre/maplibre-gl-js), to style vector and raster data. slay πŸ’…

Version 2.0.0 is equivalent to 1.36.0 (https://github.com/mapbox/tippecanoe/tree/1.36.0) in the original repository. Thank you Mapbox for the many years of early support 🫢


🌸 INTENT 🌸

The goal of Tippecanoe is lowkey iconic: it enables making a scale-independent view of your data, so that at any level β€” from the entire world down to a single building β€” you can see the density and texture of the data, rather than a simplification from dropping supposedly unimportant features, or clustering, or aggregating them. no cap.

  • Give it all of OpenStreetMap and zoom out? It should hand you back something that looks like "All Streets" (http://benfry.com/allstreets/map5.html) rather than something that looks like an Interstate road atlas. πŸ’€
  • Give it all the building footprints in Los Angeles and zoom out far enough that most individual buildings are no longer discernible? You should still be able to see the extent and variety of development in every neighborhood β€” not just the largest downtown buildings. πŸ‘‘
  • Give it a collection of years of tweet locations? You should be able to see the shape and relative popularity of every point of interest and every significant travel corridor. ✨

πŸŽ€ INSTALLATION πŸŽ€

The easiest way to install tippecanoe on OSX is with Homebrew (http://brew.sh/):

$ brew install tippecanoe

On Ubuntu it will usually be easiest to build from the source repository:

$ git clone https://github.com/felt/tippecanoe.git
$ cd tippecanoe
$ make -j
$ make install

See Development (#development) below for how to upgrade your C++ compiler or install prerequisite packages if you get compiler errors. bestie, we've all been there 😭


πŸ’… USAGE πŸ’…

$ tippecanoe -o file.mbtiles [options] [file.json file.json.gz file.fgb ...]

If no files are specified, it reads GeoJSON from the standard input. If multiple files are specified, each is placed in its own layer (#input-files-and-layer-names). one layer per file, main character energy.

The GeoJSON features need not be wrapped in a FeatureCollection. You can concatenate multiple GeoJSON features or files together, and it will parse out the features and ignore whatever other objects it encounters. it literally does not care about the extra stuff πŸ¦‹


⭐ TRY THIS FIRST ⭐

If you aren't sure what options to use, try this:

$ tippecanoe -zg -o out.mbtiles --drop-densest-as-needed in.geojson

The -zg option will make Tippecanoe choose a maximum zoom level that should be high enough to reflect the precision of the original data. (If it turns out still not to be as detailed as you want, use -z manually with a higher number.)

If the tiles come out too big, the --drop-densest-as-needed option will make Tippecanoe try dropping what should be the least visible features at each zoom level. (If it drops too many features, use -x to leave out some feature attributes that you didn't really need.) fr fr πŸ™ˆ


🌈 EXAMPLES 🌈

Create a tileset of TIGER roads for Alameda County, to zoom level 13, with a custom layer name and description:

$ tippecanoe -o alameda.mbtiles -l alameda -n "Alameda County from TIGER" -z13 tl_2014_06001_roads.json

Create a tileset of all TIGER roads, at only zoom level 12, but with higher detail than normal, with a custom layer name and description, and leaving out the LINEARID and RTTYP attributes:

$ 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

🍳 COOKBOOK 🍳

okay bestie this is the part where we actually cook. these are real recipes for real datasets, all of them tested, all of them slay. copy-paste energy. let's go πŸ’–


🌸 Linear features (world railroads), visible at all zoom levels

curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_railroads.zip
unzip ne_10m_railroads.zip
ogr2ogr -f GeoJSON ne_10m_railroads.geojson ne_10m_railroads.shp

then the actual magic:

tippecanoe -zg -o ne_10m_railroads.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping ne_10m_railroads.geojson

and here's what each of those flags is doing, because i know you wanna know:

  • -zg β€” Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
  • --drop-densest-as-needed β€” If the tiles are too big at low zoom levels, drop the least-visible features to allow tiles to be created with those features that remain
  • --extend-zooms-if-still-dropping β€” If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features

πŸ¦‹ Discontinuous polygon features (buildings of Rhode Island), visible at all zoom levels

curl -L -O https://usbuildingdata.blob.core.windows.net/usbuildings-v1-1/RhodeIsland.zip
unzip RhodeIsland.zip
tippecanoe -zg -o RhodeIsland.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping RhodeIsland.geojson

the flag breakdown, and watch this because one of these descriptions is slightly different from the railroads one, no cap:

  • -zg β€” Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
  • --drop-densest-as-needed β€” If the tiles are too big at low or medium zoom levels, drop the least-visible features to allow tiles to be created with those features that remain
  • --extend-zooms-if-still-dropping β€” If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features

πŸŽ€ Continuous polygon features (states and provinces), visible at all zoom levels

curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip
unzip -o ne_10m_admin_1_states_provinces.zip
ogr2ogr -f GeoJSON ne_10m_admin_1_states_provinces.geojson ne_10m_admin_1_states_provinces.shp
tippecanoe -zg -o ne_10m_admin_1_states_provinces.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping ne_10m_admin_1_states_provinces.geojson
  • -zg β€” Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
  • --coalesce-densest-as-needed β€” If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished
  • --extend-zooms-if-still-dropping β€” If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features

note the coalesce instead of drop β€” with continuous stuff you merge, you don't yeet. periodt.


🚌 Large point dataset (GPS bus locations), for visualization at all zoom levels

curl -L -O ftp://avl-data.sfmta.com/avl_data/avl_raw/sfmtaAVLRawData01012013.csv
sed 's/PREDICTABLE.*/PREDICTABLE/' sfmtaAVLRawData01012013.csv > sfmta.csv
tippecanoe -zg -o sfmta.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping sfmta.csv
(The sed line is to clean the corrupt CSV header, which contains the wrong number of fields.)

like, it's giving "the data is a little broken and we're fixing it with one line of sed." iconic.

  • -zg β€” Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
  • --drop-densest-as-needed β€” If the tiles are too big at low or medium zoom levels, drop the least-visible features to allow tiles to be created with those features that remain
  • --extend-zooms-if-still-dropping β€” If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features

πŸ‘‘ Clustered points (world cities), summing the clustered population, visible at all zoom levels

curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_populated_places.zip
unzip -o ne_10m_populated_places.zip
ogr2ogr -f GeoJSON ne_10m_populated_places.geojson ne_10m_populated_places.shp
tippecanoe -zg -o ne_10m_populated_places.mbtiles -r1 --cluster-distance=10 --accumulate-attribute=POP_MAX:sum ne_10m_populated_places.geojson
  • -zg β€” Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
  • -r1 β€” Do not automatically drop a fraction of points at low zoom levels, since clustering will be used instead
  • --cluster-distance=10 β€” Cluster together features that are closer than about 10 pixels from each other
  • --accumulate-attribute=POP_MAX:sum β€” Sum the POP_MAX (population) attribute in features that are clustered together. Other attributes will be arbitrarily taken from the first feature in the cluster.

okay the way that --accumulate-attribute=POP_MAX:sum just adds up whole cities' worth of people when they get smooshed into one cluster?? we love math πŸ’…


🌈 Show countries at low zoom levels but states at higher zoom levels

two datasets, two tilesets, one tile-join, one flawless finale:

curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_countries.zip
unzip ne_10m_admin_0_countries.zip
ogr2ogr -f GeoJSON ne_10m_admin_0_countries.geojson ne_10m_admin_0_countries.shp

curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip
unzip -o ne_10m_admin_1_states_provinces.zip
ogr2ogr -f GeoJSON ne_10m_admin_1_states_provinces.geojson ne_10m_admin_1_states_provinces.shp

tippecanoe -z3 -o countries-z3.mbtiles --coalesce-densest-as-needed ne_10m_admin_0_countries.geojson
tippecanoe -zg -Z4 -o states-Z4.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping ne_10m_admin_1_states_provinces.geojson
tile-join -o states-countries.mbtiles countries-z3.mbtiles states-Z4.mbtiles

Countries:

  • -z3 β€” Only generate zoom levels 0 through 3
  • --coalesce-densest-as-needed β€” If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished

States and Provinces:

  • -Z4 β€” Only generate zoom levels 4 and beyond
  • -zg β€” Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
  • --coalesce-densest-as-needed β€” If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished
  • --extend-zooms-if-still-dropping β€” If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features

see how -z3 and -Z4 are like... the ✨chosen ones✨ that split the zoom range between them so nothing overlaps? main character energy on both sides fr fr.


πŸ’œ Represent multiple sources (Illinois and Indiana counties) as separate layers

curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_17_county10.zip
unzip tl_2010_17_county10.zip
ogr2ogr -f GeoJSON tl_2010_17_county10.geojson tl_2010_17_county10.shp

curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_18_county10.zip
unzip tl_2010_18_county10.zip
ogr2ogr -f GeoJSON tl_2010_18_county10.geojson tl_2010_18_county10.shp

tippecanoe -zg -o counties-separate.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping tl_2010_17_county10.geojson tl_2010_18_county10.geojson
  • -zg β€” Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
  • --coalesce-densest-as-needed β€” If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished
  • --extend-zooms-if-still-dropping β€” If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features

two input files, no -l, so each source keeps its own layer. separate but together. situationship goals honestly.


πŸ’– Merge multiple sources (Illinois and Indiana counties) into the same layer

curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_17_county10.zip
unzip tl_2010_17_county10.zip
ogr2ogr -f GeoJSON tl_2010_17_county10.geojson tl_2010_17_county10.shp

curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_18_county10.zip
unzip tl_2010_18_county10.zip
ogr2ogr -f GeoJSON tl_2010_18_county10.geojson tl_2010_18_county10.shp

tippecanoe -zg -o counties-merged.mbtiles -l counties --coalesce-densest-as-needed --extend-zooms-if-still-dropping tl_2010_17_county10.geojson tl_2010_18_county10.geojson

As above, but

  • -l counties β€” Specify the layer name instead of letting it be derived from the source file names

one -l and suddenly they're all in the same layer. besties sharing a room 🫢


⭐ Selectively remove and replace features (Census tracts) to update a tileset

this one is the glow-up recipe. we take a tileset, yeet one county out of it, and drop the newer version in its place:

# Retrieve and tile California 2000 Census tracts
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/TRACT/2000/tl_2010_06_tract00.zip
unzip tl_2010_06_tract00.zip
ogr2ogr -f GeoJSON tl_2010_06_tract00.shp.json tl_2010_06_tract00.shp
tippecanoe -z11 -o tracts.mbtiles -l tracts tl_2010_06_tract00.shp.json

# Create a copy of the tileset, minus Alameda County (FIPS code 001)
tile-join -j '{"*":["none",["==","COUNTYFP00","001"]]}' -f -o tracts-filtered.mbtiles tracts.mbtiles

# Retrieve and tile Alameda County Census tracts for 2010
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/TRACT/2010/tl_2010_06001_tract10.zip
unzip tl_2010_06001_tract10.zip
ogr2ogr -f GeoJSON tl_2010_06001_tract10.shp.json tl_2010_06001_tract10.shp
tippecanoe -z11 -o tracts-added.mbtiles -l tracts tl_2010_06001_tract10.shp.json

# Merge the filtered tileset and the tileset of new tracts into a final tileset
tile-join -o tracts-final.mbtiles tracts-filtered.mbtiles tracts-added.mbtiles

The -z11 option explicitly specifies the maxzoom, to make sure both the old and new tilesets have the same zoom range.

The -j option to tile-join specifies a filter, so that only the desired features will be copied to the new tileset. This filter excludes (using none) any features whose FIPS code (COUNTYFP00) is the code for Alameda County (001).

lowkey obsessed with how tile-join -j '{"*":["none",["==","COUNTYFP00","001"]]}' is just a tiny JSON bouncer at the door going "not you, 001." πŸ’€ and then -f lets it overwrite the output file without throwing a fit.

and that's the move: filter out the old, add in the new, join them back together. flawless. a whole tileset refresh with zero drama β€” well, zero drama after you get the zoom ranges matching, iykyk.

✨ OPTIONS ✨

okay bestie, deep breath πŸ’– there are a LOT of options here. like, a concerning amount. but the tea is: most of the time you're gonna ignore basically all of them except -o output.mbtiles to name your output file, and probably -f to delete the file that already exists with that name. that's it. that's the whole vibe. πŸŽ€

not sure what the right maxzoom is for your data? no thoughts head empty? that's fine, -zg will guess one for you based on the density of features. she's psychic bestie πŸ¦‹

here's the thing tho: Tippecanoe will normally drop a fraction of point features at zooms below the maxzoom, so your low-zoom tiles don't get all bloated and huge. so:

  • got a smallish data set where literally every point would fit without dropping any of them? use -r1 to keep them all. no point left behind πŸ’…
  • want point dropping BUT you still want the tiles denser than -zg thinks they should be? use -B to set a basezoom lower than the maxzoom. you're the main character, you decide.

if some of your tiles are still coming out too big in spite of all the settings above, you'll often want --drop-densest-as-needed β€” it drops whatever fraction of the features is necessary at each zoom level to make that zoom level's tiles actually work. lowkey a lifesaver 😭

if your features have a lot of attributes, use -y to keep only the ones you really need. declutter, it's giving minimalism 🧚

and if your input is newline-delimited GeoJSON, use -P and watch input parsing get so much faster. zoom zoom ⭐


πŸ“¦ Output tileset

  • -o file.mbtiles, file.pmtiles or --output=file.mbtiles β€” Name the output file. ✨
  • -e directory or --output-to-directory=directory β€” Write tiles to the specified directory instead of to an mbtiles file.
  • -f or --force β€” Delete the mbtiles file if it already exists instead of giving an error
  • -F or --allow-existing β€” Proceed (without deleting existing data) if the metadata or tiles table already exists or if metadata fields can't be set. You probably don't want to use this. πŸ‘€
okay real talk on -F: they said "probably don't want to use this" and they MEANT it bestie. proceed with caution πŸ™ˆ

🏷️ Tileset description and attribution

  • -n name or --name=name β€” Human-readable name for the tileset (default file.json)
  • -A text or --attribution=text β€” Attribution (HTML) to be shown with maps that use data from this tileset.
  • -N description or --description=description β€” Description for the tileset (default file.mbtiles)

πŸ“ Input files and layer names

  • name.json or name.geojson β€” Read the named GeoJSON input file into a layer called name.
  • name.json.gz or name.geojson.gz β€” Read the named gzipped GeoJSON input file into a layer called name.
  • name.fgb β€” Read the named FlatGeobuf input file into a layer called name.
  • name.csv β€” Read the named CSV input file into a layer called name.
  • -l name or --layer=name β€” Use the specified layer name instead of deriving a name from the input filename or output tileset. If there are multiple input files specified, the files are all merged into the single named layer, even if they try to specify individual names with -L.
  • -L name:file.json or --named-layer=name:file.json β€” Specify layer names for individual files. If your shell supports it, you can use a subshell redirect like -L name:<(cat dir/*.json) to specify a layer name for the output of streamed input. 🐚
  • -L{layer-json} or --named-layer={layer-json} β€” Specify an input file and layer options by a JSON object. The JSON object must contain a "file" key to specify the filename to read from. (If the "file" key is an empty string, it means to read from the standard input stream.) It may also contain a "layer" field to specify the name of the layer, and/or a "description" field to specify the layer's description in the tileset metadata, and/or a "format" field to specify csv or fgb file format if it is not obvious from the name. Example:
tippecanoe -z5 -o world.mbtiles -L'{"file":"ne_10m_admin_0_countries.json", "layer":"countries", "description":"Natural Earth countries"}'

CSV input files currently support only Point geometries, from columns named latitude, longitude, lat, lon, long, lng, x, or y. so if you were hoping for fancy polygons in your CSV... no πŸ’€

🧡 Parallel processing of input

  • -P or --read-parallel β€” Use multiple threads to read different parts of each GeoJSON input file at once. This will only work if the input is line-delimited JSON with each Feature on its own line, because it knows nothing of the top-level structure around the Features. Spurious "EOF" error messages may result otherwise. Performance will be better if the input is a named file that can be mapped into memory rather than a stream that can only be read sequentially.

if the input file begins with the RFC 8142 record separator (https://tools.ietf.org/html/rfc8142), parallel processing of input will be invoked automatically, splitting at record separators rather than at all newlines. ✨

Parallel processing will also be automatic if the input file is in FlatGeobuf format. she just does it herself, iconic πŸ‘‘

🌍 Projection of input

  • -s projection or --projection=projection β€” Specify the projection of the input data. Currently supported are EPSG:4326 (WGS84, the default) and EPSG:3857 (Web Mercator). In general you should use WGS84 for your input files if at all possible.

πŸ” Zoom levels

  • -z zoom or --maximum-zoom=zoom β€” Maxzoom: the highest zoom level for which tiles are generated (default 14)
  • -zg or --maximum-zoom=g β€” Guess what is probably a reasonable maxzoom based on the spacing of features.
  • --smallest-maximum-zoom-guess=zoom β€” Guess what is probably a reasonable maxzoom based on the spacing of features, but using the specified zoom if a lower maxzoom is guessed. If -Bg is also set, the base zoom will be set to the guessed maxzoom, with all the points carried forward into additional zooms through the one specified.
  • -Z zoom or --minimum-zoom=zoom β€” Minzoom: the lowest zoom level for which tiles are generated (default 0)
  • -ae or --extend-zooms-if-still-dropping β€” Increase the maxzoom if features are still being dropped at that zoom level. The detail and simplification options that ordinarily apply only to the maximum zoom level will apply both to the originally specified maximum zoom and to any levels added beyond that.
  • --extend-zooms-if-still-dropping-maximum=count β€” Increase the maxzoom if features are still being dropped at that zoom level by up to count zoom levels.
  • -at or --generate-variable-depth-tile-pyramid β€” Don't produce child tiles for any tile that should be sufficient to be overzoomed to any higher zoom level. Such tiles will be produced with maximum detail and no simplification or polygon cleaning. Tiles with point features below the basezoom or where any features have to be dropped dynamically, or which contain too many features or bytes with full detail, will be written out with normal detail and split into child tiles. Tilesets generated with this option are suitable for use only with tile servers that will find the appropriate tile to overzoom from and will simplify and clean the geometries appropriately before serving the tile.
fr fr on that last one, bestie β€” that's a hard requirement, not a suggestion. if your tile server isn't doing the overzooming homework, this option is not for you 😭
  • -R zoom/x/y or --one-tile=zoom/x/y β€” Set the minzoom and maxzoom to zoom and produce only the single specified tile at that zoom level. just one lil tile. a single serving πŸ“

Okay so real talk: if you already know the exact precision you want your data at, or the map scale of the printed map you're matching, this table is gonna be your new roman empire πŸ’– it shows the approximate precision and scale you get from the various -z options when you're using the default -d detail of 12:

  • -z0 β€” precision 32000 ft, 10000 m, map scale 1:320,000,000
  • -z1 β€” precision 16000 ft, 5000 m, map scale 1:160,000,000
  • -z2 β€” precision 8000 ft, 2500 m, map scale 1:80,000,000
  • -z3 β€” precision 4000 ft, 1250 m, map scale 1:40,000,000
  • -z4 β€” precision 2000 ft, 600 m, map scale 1:20,000,000
  • -z5 β€” precision 1000 ft, 300 m, map scale 1:10,000,000
  • -z6 β€” precision 500 ft, 150 m, map scale 1:5,000,000
  • -z7 β€” precision 250 ft, 80 m, map scale 1:2,500,000
  • -z8 β€” precision 125 ft, 40 m, map scale 1:1,250,000
  • -z9 β€” precision 64 ft, 20 m, map scale 1:640,000
  • -z10 β€” precision 32 ft, 10 m, map scale 1:320,000
  • -z11 β€” precision 16 ft, 5 m, map scale 1:160,000
  • -z12 β€” precision 8 ft, 2 m, map scale 1:80,000
  • -z13 β€” precision 4 ft, 1 m, map scale 1:40,000
  • -z14 β€” precision 2 ft, 0.5 m, map scale 1:20,000
  • -z15 β€” precision 1 ft, 0.25 m, map scale 1:10,000
  • -z16 β€” precision 6 in, 15 cm, map scale 1:5000
  • -z17 β€” precision 3 in, 8 cm, map scale 1:2500
  • -z18 β€” precision 1.5 in, 4 cm, map scale 1:1250
  • -z19 β€” precision 0.8 in, 2 cm, map scale 1:600
  • -z20 β€” precision 0.4 in, 1 cm, map scale 1:300
  • -z21 β€” precision 0.4 in, 1 cm, map scale 1:300
  • -z22 β€” precision 0.4 in, 1 cm, map scale 1:300

✨ Tile resolution ✨

  • -d detail, --full-detail=detail β€” the detail at max zoom level (default 12, for tile resolution of 2
  • -D detail, --low-detail=detail β€” the detail at lower zoom levels (default 12, for tile resolution of 2
  • -m detail, --minimum-detail=detail β€” the minimum detail it'll even try if the tiles come out too big at regular detail (default 7)
  • --extra-detail=detail β€” generates tiles with even more detail than the "full" detail at the max zoom level, so you can max out location precision. Heads up bestie: these tiles may not work with some rendering software that internally limits detail to 12 or 13. The extra detail does not affect the choice of maxzoom guessing, the amount of simplification, or the "tiny polygon" threshold the way --full-detail does. Other than that, the tiles should look exactly like they did without it β€” except they'll be way more precise when overzoomed. πŸ’…

All the internal math runs on a 32-bit tile coordinate system, so 1/(2 of the size of Earth, or about 1cm, is the smallest distinguishable distance. If maxzoom + detail > 32, you get no extra resolution compared to just using a smaller maxzoom or detail, and the detail of the tiles will be reduced to the max that can actually be used with the specified maxzoom. No cap.


🌸 Filtering feature attributes 🌸

  • -x name, --exclude=name β€” excludes the named attributes from all features. You can stack multiple -x options to exclude several attributes. (And pls do NOT comma-separate names inside a single -x, that's not the vibe.)
  • -y name, --include=name β€” includes the named attributes in all features, which excludes everything you didn't explicitly name. Stack multiple -y options to explicitly include several attributes. (Same rule: don't comma-separate names within a single -y.)
  • -X, --exclude-all β€” excludes all the attributes and encodes only geometry. sheer minimalism, actually πŸ¦‹

πŸ’œ Modifying feature attributes πŸ’œ

  • -Tattribute:type, --attribute-type=attribute:type β€” coerces the named feature attribute to be the type you specified. The type may be string, float, int, or bool. If the type is bool, then original attributes of 0 (or, if numeric, 0.0, etc.), false, null, or the empty string all become false, and literally anything else becomes true. If the type is float or int and the original attribute was non-numeric, it becomes 0. If the type is int and the original attribute was floating-point, it gets rounded to the nearest integer. periodt.
  • -Yattribute:description, --attribute-description=attribute:description β€” sets the description for the specified attribute in the tileset metadata to description, instead of the usual String, Number, or Boolean.
  • -Eattribute:operation, --accumulate-attribute=attribute:operation β€” preserves the named attribute from features that get dropped, coalesced-as-needed, or clustered πŸ‘‘ The operation may be sum, product, mean, max, min, concat, or comma, and it says how the named attribute gets accumulated onto the attribute of the same name in a feature that does survive. You can also hand these over as JSON keys and values:
--accumulate-attribute='{"attr": "operation", "attr2", "operation2"}'
  • --set-attribute attribute:value β€” sets the value of the specified attribute in each feature to the value you gave. This is mostly useful for giving each feature's attribute an initial value for --accumulate-attribute. The attributes and values may also be specified as JSON keys and values:
--set-attribute='{"attr": value, "attr2", value}'
  • -pe, --empty-csv-columns-are-null β€” treats empty CSV columns as nulls instead of as empty strings. tiny change, massive difference 😭
  • -aI, --convert-stringified-ids-to-numbers β€” if a feature ID is the string representation of a number, converts it to a plain number to use as the feature ID. slay ✨
  • --use-attribute-for-id=name β€” bestie, this says "hey, use the attribute with this name as if it were literally the feature ID." Caution though: if that attribute is a stringified number, you also gotta pass -aI so it gets converted into an actual number. Don't skip that part, fr.
  • -pN, --single-precision β€” writes those double-precision numeric attribute values into tiles as single-precision instead, so your tiles get smaller. cute little diet for your data πŸ“
  • --maximum-string-attribute-length=length β€” string attributes longer than length bytes get truncated. bye bye extra characters βœ‚οΈ

🌸 Filtering features by attributes 🌸

  • -j filter, --feature-filter=filter β€” checks every feature against a per-layer filter and only keeps the ones that match. The filter format is defined in the Mapbox GL Style Specification or in a Felt filter specification that's still to be finalized (so, slay but unstable). Any features sitting in layers where you specified no filter at all just pass straight through, untouched, no questions asked. Fun fact: filters for the layer "*" apply to all layers. And the special variable $zoom refers to the current zoom level. πŸ‘‘
  • -J filter-file, --feature-filter-file=filter-file β€” same energy as -j, except the filter lives in a file instead of on your command line.

Example: to find the Natural Earth countries with low scalerank but high LABELRANK:

tippecanoe -z5 -o filtered.mbtiles -j '{ "ne_10m_admin_0_countries": [ "all", [ "<", "scalerank", 3 ], [ ">", "LABELRANK", 5 ] ] }' ne_10m_admin_0_countries.geojson

Example: to retain only major TIGER roads at low zoom levels:

tippecanoe -o roads.mbtiles -j '{ "*": [ "any", [ ">=", "$zoom", 11 ], [ "in", "MTFCC", "S1100", "S1200" ] ] }' tl_2015_06001_roads.json

Tippecanoe also accepts expressions of the form [ "attribute-filter", name, expression ], and those filter individual feature attributes instead of yeeting whole features. So like, want to drop the road names at low zoom levels? Literally just do this:

tippecanoe -o roads.mbtiles -j '{ "*": [ "attribute-filter", "FULLNAME", [ ">=", "$zoom", 9 ] ] }' tl_2015_06001_roads.json
⚠️ Heads up bestie: an attribute-filter expression is always considered to evaluate to true β€” meaning it keeps the feature instead of dropping it. So if you wanna stack multiple attribute-filter expressions, or mix in other expressions that remove features from the same layer, you have to wrap them all in an all expression so every single one actually gets evaluated. Otherwise some are just… not. πŸ’€

πŸ¦‹ Dropping a fixed fraction of features by zoom level πŸ¦‹

  • -r rate, --drop-rate=rate β€” the rate at which dots get dropped at zoom levels below basezoom (default 2.5). Use -rg and it'll guess a drop rate that keeps at most 50,000 features in the densest tile. You can also specify a marker-width with -rgwidth so fewer features survive in the densest tile (compensating for the bigger marker), or use -rfnumber to allow at most number features in the densest tile. And if you use -rp together with -zg or --smallest-maximum-zoom-guess, it'll pick a drop rate from the same distance-between-features metrics that are used to choose the maxzoom. it's giving ✨adaptive✨
  • -B zoom, --base-zoom=zoom β€” the base zoom, aka the level at and above which all points are included in the tiles (default maxzoom). Use -Bg and it guesses a zoom level that keeps at most 50,000 features in the densest tile. You can also specify a marker-width with -Bgwidth to allow fewer features in the densest tile to compensate for the larger marker, or -Bfnumber to allow at most number features in the densest tile.
  • --retain-points-multiplier=multiple β€” retains the specified multiple of points instead of just the number of points the drop rate would ordinarily keep. Then later you can thin them back out with the -m option to tippecanoe-overzoom. The start of each cluster gets marked in the feature sequence by the tippecanoe:retain_points_multiplier_first attribute. Oh and --tile-size-limit will also be extended at low zoom levels to make room for all those multiplied features. no cap πŸ“ˆ
  • --drop-denser=percentage β€” when dropping dots at zoom levels below the base zoom, give the specified percentage preference to retaining points in sparse areas and dropping points in dense areas. sparse girlies stay, dense girlies go 🫢
  • --limit-base-zoom-to-maximum-zoom, -Pb β€” limits the guessed base zoom so it never exceeds the maxzoom, even if that means putting more than the requested number of features into a base zoom tile.
  • -al, --drop-lines β€” lets that "dot" dropping at lower zooms apply to lines too.
  • -ap, --drop-polygons β€” lets that "dot" dropping at lower zooms apply to polygons too.
  • -K distance, --cluster-distance=distance β€” clusters points (like --cluster-densest-as-needed, but without the experimental discovery process) that are approximately within distance of each other. The units are tile coordinates within a nominally 256-pixel tile, so the maximum value of 255 allows only one feature per tile. Values around 10 are probably appropriate for typical marker sizes. See --cluster-densest-as-needed below for the behavior details.
  • -k zoom, --cluster-maxzoom=zoom β€” the max zoom on which to cluster points, if clustering is enabled.
  • -kg, --cluster-maxzoom=g β€” sets --cluster-maxzoom= to maxzoom - 1 so that literally all the features are visible at the maximum zoom level. iconic behavior πŸ’…
  • --preserve-point-density-threshold=level β€” at the low zoom levels, don't reduce point density below the specified level, even if the specified drop rate would normally call for it β€” this is so that low-density areas of the map don't just come out looking blank and sad. The unit is the distance between preserved points, as a fraction of the size of a tile. Values of 32 or 64 are probably appropriate for typical marker sizes.

πŸ’œ Dropping a fraction of features to keep under tile size limits πŸ’œ

  • -as, --drop-densest-as-needed β€” if a tile is too large, it tries to shrink it to under 500K by increasing the minimum spacing between features. The discovered spacing applies to the entire zoom level.
  • -ad, --drop-fraction-as-needed β€” dynamically drops some fraction of features from each zoom level to keep large tiles under the 500K size limit. (This is like -pd but it applies to the entire zoom level, not to each tile.)
  • -an, --drop-smallest-as-needed β€” dynamically drops the smallest features (physically smallest: the shortest lines or the smallest polygons) from each zoom level to keep large tiles under the 500K size limit.
  • -aN, --coalesce-smallest-as-needed β€” dynamically combines the smallest features (physically smallest: the shortest lines, or the smallest polygons, or the densest points) from each zoom level into other nearby features to keep large tiles under the 500K size limit. Real talk: this option will probably not help very much with LineStrings. It's mostly intended for polygons, to maintain the full original area covered by polygons while still reducing the feature count somehow. The attributes of the small polygons are not preserved into the combined features (except through --accumulate-attribute), only their geometry. And one more thing β€” the polygons that nested polygons get coalesced into may not necessarily be the immediately enclosing features. 🫠
  • -aD, --coalesce-densest-as-needed β€” dynamically combines the densest features from each zoom level into other nearby features to keep large tiles under the 500K size limit. (Again, mostly useful for polygons.)
  • -aS, --coalesce-fraction-as-needed β€” dynamically combines a fraction of features from each zoom level into other nearby features to keep large tiles under the 500K size limit. (Again, mostly useful for polygons.)
  • -pd, --force-feature-limit β€” dynamically drops some fraction of features from large tiles to keep them under the 500K size limit. It will probably look ugly at the tile boundaries, ngl. (This is like -ad but it applies to each tile individually, not to the entire zoom level.) You probably don't want to use this. like, genuinely. πŸ’€
  • -aC, --cluster-densest-as-needed β€” if a tile is too large, it tries to reduce its size by increasing the minimum spacing between features and leaving one placeholder feature from each group. That surviving feature gets a "clustered": true attribute to show it represents a cluster, a "point_count" attribute showing how many features got clustered into it, and a "sqrt_point_count" attribute showing the relative width of a feature to represent the cluster. If the features being clustered are points, the representative feature sits at the average of the original points' locations; otherwise, one of the original features is just left as the representative. main character feature energy πŸ‘‘

🧚 Dropping tightly overlapping features 🧚

  • -g gamma, --gamma=gamma β€” the rate at which especially dense dots get dropped (default 0, for no effect). A gamma of 2 reduces the number of dots less than a pixel apart down to the square root of their original number. math ate πŸ”₯
  • -aG, --increase-gamma-as-needed β€” if a tile is too large, it tries to shrink it to under 500K by increasing the -g gamma. The discovered gamma applies to the entire zoom level. You probably want to use --drop-densest-as-needed instead. (we said what we said πŸ’…)

🌈 Line and polygon simplification 🌈

  • -S scale, --simplification=scale β€” multiplies the tolerance for line and polygon simplification by scale. The standard tolerance tries to keep the line or polygon within one tile unit of its proper location. You can probably go up to about 10 without too much visible difference.
  • -ps, --no-line-simplification β€” doesn't simplify lines and polygons. at all. periodt.
  • -pS, --simplify-only-low-zooms β€” doesn't simplify lines and polygons at maxzoom (but does simplify at lower zooms).
  • --simplification-at-maximum-zoom=scale β€” uses the specified scale at maxzoom instead of the standard simplification scale (which still applies at lower zooms).
  • -pn, --no-simplification-of-shared-nodes β€” bestie, this one refuses to simplify away any node where LineStrings or Polygon rings converge, diverge, or cross. Heads up tho: it will not be effective if you also use --coalesce. In between intersection nodes, LineString segments or polygon edges get simplified identically in each feature if possible. Use this instead of --detect-shared-borders, ok?
  • -pt, --no-tiny-polygon-reduction β€” don't smoosh the area of teeny tiny polygons into small squares that represent their combined area.
  • -pT, --no-tiny-polygon-reduction-at-maximum-zoom β€” same energy as above, but it combines the area of very small polygons into small squares only at zoom levels below the maximum.
  • --tiny-polygon-size=size β€” set the size for tiny polygons instead of the default 2. Real talk: anything above 6 or so will lead to visible artifacts with the default tile detail 🌸
  • -av, --visvalingam β€” use Visvalingam's simplification algorithm instead of Douglas-Peucker's. Algorithm switch-up, periodt.

πŸ’… Attempts to improve shared polygon boundaries (iconic behaviour)

  • -ab, --detect-shared-borders β€” DEPRECATED. In the manner of TopoJSON (https://github.com/mbostock/topojson/wiki/Introduction), it detects borders shared between multiple polygons and simplifies them identically in each polygon. The catch? It takes more time and memory than just considering each polygon individually. Use no-simplification-of-shared-nodes instead β€” it's faster and more correct. no cap.
  • -aL, --grid-low-zooms β€” at all zoom levels below maxzoom, this snaps all lines and polygons to a stairstep grid instead of letting diagonals exist. You'll also want to specify a tile resolution, probably -D8. It gives you a way to display continuous parcel, gridded, or binned data at low zooms without drowning your tiles in tiny polygons, since features either get stretched out to the grid unit or lost entirely β€” depends on how they happened to be aligned in the original data. You probably don't want to use this πŸ™ˆ

πŸŽ€ Controlling clipping to tile boundaries

  • -b pixels, --buffer=pixels β€” buffer size where features are duplicated from adjacent tiles. Units are "screen pixels" β€” that's 1/256th of the tile width or height. Default is 5.
  • -pc, --no-clipping β€” don't clip features to the size of the tile. If a feature overlaps the tile's bounds or buffer at all, it gets included completely. Be careful bestie: this can produce very large tilesets, especially with large polygons 😭
  • -pD, --no-duplication β€” like --no-clipping, each feature is included intact instead of being cut to tile boundaries. But also: it's included only in a single tile per zoom level rather than potentially in multiple copies. Which means clients of the tileset must check adjacent tiles (possibly some distance away) to make sure they've got all the features. Yes, they have to do the work themselves πŸ’€

πŸ¦‹ Reordering features within each tile

  • -pi, --preserve-input-order β€” keeps the original input order of features as the drawing order instead of ordering them geographically. (Lowkey it's implemented as a restoration of the original order at the end, so that dot-dropping stays geographic β€” which means it also undoes -ao.)
  • -ac, --coalesce β€” coalesce consecutive features that have the same attributes. Super useful if you have a ton of small polygons with identical attributes and you wanna merge them together πŸ’–
  • -ao, --reorder β€” reorder features so ones with the same attributes sit in sequence (instead of ones that are approximately spatially adjacent), to try to get them to coalesce. If you use --coalesce, you probably want this too.
  • -ar, --reverse β€” tries reversing the directions of lines so they coalesce and compress better. You probably don't want to use this one ngl.
  • -ah, --hilbert β€” puts features in Hilbert Curve order instead of the usual Z-Order. This improves the odds that spatially adjacent features will be sequentially adjacent, and should improve density calculations and spatial coalescing. It should be the default eventually ✨
  • --order-by=attribute β€” order features by the specified attribute, in alphabetical or numerical order. You can spec multiple --order-by and --order-descending-by options, and the first one is the primary sort key.
  • --order-descending-by=attribute β€” order features by the specified attribute, in reverse alphabetical or numerical order. Same deal: multiple --order-by and --order-descending-by options may be specified, first one is 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, 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, first being the primary sort key.

🌈 Adding calculated attributes

  • -ag, --calculate-feature-density β€” adds a new attribute, tippecanoe_feature_density, to each feature, to record how densely features are spaced in that area of the tile. Use it 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.
  • -ai, --generate-ids β€” add an id (a feature ID, not an attribute named id) to each feature that doesn't already have one. RN there is no guarantee that the id added will be stable between runs, or that it won't conflict with manually-assigned feature IDs. Future versions of Tippecanoe may change the mechanism for allocating IDs. iykyk.
  • -aX, --calculate-feature-index β€” add a tippecanoe:index field to each feature, giving its index in the quadkey or hilbert sequence.

🧚 Trying to correct bad source geometry

  • -aw, --detect-longitude-wraparound β€” detects when consecutive points within a feature jump to the other side of the world, and tries to fix the geometry. main character energy honestly.
  • -pw, --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, --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, --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

  • -M bytes, --maximum-tile-bytes=bytes β€” use the specified number of bytes as the maximum compressed tile size instead of 500K.
  • -O features, --maximum-tile-features=features β€” use the specified number of features as the maximum in a tile instead of 200,000.
  • --limit-tile-feature-count=features β€” abruptly limit each tile to the specified number of features, after ordering them if specified.
  • --limit-tile-feature-count-at-maximum-zoom=features β€” abruptly limit each tile at the maximum zoom level to the specified number of features, after ordering them if specified.
  • -pf, --no-feature-limit β€” don't limit tiles to 200,000 features
  • -pk, --no-tile-size-limit β€” don't limit tiles to 500K bytes
  • -pC, --no-tile-compression β€” don't compress the PBF vector tile data. If you're getting "Unimplemented type 3" error messages from a renderer, it's probably because it expects uncompressed tiles using this option rather than the normal gzip-compressed tiles πŸ”₯
  • -pg, --no-tile-stats β€” don't generate the tilestats row in the tileset metadata. Uploads without tilestats (https://github.com/mapbox/mapbox-geostats) will take longer to process.
  • --tile-stats-attributes-limit=count β€” include tilestats information about at most count attributes instead of the default 1000.
  • --tile-stats-sample-values-limit=count β€” calculate tilestats attribute statistics based on count values instead of the default 1000.
  • --tile-stats-values-limit=count β€” report count unique attribute values in tilestats instead of the default 100.

πŸ“ Temporary storage

  • -t directory, --temporary-directory=directory β€” put the temporary files in directory. If you don't specify, it'll use /tmp.

⭐ Progress indicator

  • -q, --quiet β€” work quietly instead of reporting progress or warning messages 🀫
  • -Q, --no-progress-indicator β€” don't report progress, but still give warnings
  • -U seconds, --progress-interval=seconds β€” don't report progress more often than the specified number of seconds.
  • -u, --json-progress β€” like -quiet but logs progress as a JSON object. Use in combination with -U.
  • -v, --version β€” report Tippecanoe's version number πŸ’œ

πŸ’… Filters

  • -C command, --prefilter=command β€” specify a shell filter command to be run at the start of assembling each tile
  • -c command, --postfilter=command β€” specify a shell filter command to be run at the end of assembling each tile

The pre- and post-filter commands let you do optional filtering or transformation on the features of each tile as it's created 🫢 They are shell commands, run with the zoom level, X, and Y as the $1, $2, and $3 arguments. Future versions of Tippecanoe may add additional arguments for more context.

So like β€” your features get handed to the filter as a whole stack of newline-delimited GeoJSON objects on standard input, and tippecanoe is sitting there, tapping its foot, expecting to read another set of GeoJSON features back from the filter's standard output. So the filter genuinely has to feed it something, bestie. Don't leave it on read. πŸ™ˆ

The prefilter gets the features at the highest resolution available β€” before line simplification, polygon topology repair, gamma calculation, dynamic feature dropping, or any of that other internal processing. So you're getting the tea in its completely unedited form. ✨

The postfilter gets them wayyyyy later β€” at tile resolution, after simplification, cleaning, and dropping. It's the final cut, basically.

The layer name comes along inside the tippecanoe element of the feature and must be passed through, or the feature won't stay in its correct layer. And for the prefilter specifically, that tippecanoe element might also carry index, sequence, extent, and dropped elements β€” and those have to be passed through too, for internal operations like --drop-densest-as-needed, --drop-smallest-as-needed, and --preserve-input-order to actually work. So don't drop them. Periodt. πŸ’…

✨ Examples: ✨

  • Make a tileset of the Natural Earth countries to zoom level 5, and also copy the GeoJSON features to files in a tiles/z/x/y.geojson directory hierarchy.
tippecanoe -o countries.mbtiles -z5 -C 'mkdir -p tiles/$1/$2; tee tiles/$1/$2/$3.geojson' ne_10m_admin_0_countries.json
  • Make a tileset of the Natural Earth countries to zoom level 5, but including only those tiles that intersect the bounding box of Germany ⟨https://www.flickr.com/places/info/23424829⟩. (The limit-tiles-to-bbox script is in the Tippecanoe source directory ⟨filters/limit-tiles-to-bbox⟩.)
tippecanoe -o countries.mbtiles -z5 -C './filters/limit-tiles-to-bbox 5.8662 47.2702 15.0421 55.0581 $*' ne_10m_admin_0_countries.json
tippecanoe -o roads.mbtiles -c 'if [ $1 -lt 11 ]; then grep "\"MTFCC\": \"S1[12]00\""; else cat; fi' tl_2016_18157_roads.json

🌍 Environment 🌍

Tippecanoe normally uses as many parallel threads as the operating system claims there are CPUs available. You can override that number by setting the TIPPECANOE_MAX_THREADS environmental variable. Slay.


πŸ¦‹ GeoJSON extension πŸ¦‹

Tippecanoe defines a GeoJSON extension you can use to set the minimum and/or maximum zoom level at which an individual feature gets included in the vector tileset you're producing. Like, per-feature zoom control. Iconic.

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 specifying a maxzoom of 9 and a minzoom of 4, then 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. If you specify a minzoom for a feature, it will be preserved down to that zoom level even if dot-dropping with -r would otherwise have dropped it. The feature said "I'm staying," and it meant it. 🫢

You can also specify a layer name in the tippecanoe object, and it will take precedence over the filename or the name specified using --layer, like this:

{
    "type" : "Feature",
    "tippecanoe" : { "layer" : "streets" },
    "properties" : { "FULLNAME" : "N Vasco Rd" },
    "geometry" : {
        "type" : "LineString",
        "coordinates" : [ [ -121.733350, 37.767671 ], [ -121.733600, 37.767483 ], [ -121.733131, 37.766952 ] ]
    }
}

If your source GeoJSON only has minzoom, maxzoom and/or layer within properties, you can use ndjson-cli ⟨https://github.com/mbostock/ndjson-cli/blob/master/README.md⟩ to move them into the required tippecanoe object by piping the GeoJSON like this:

ndjson-map 'd.tippecanoe = { minzoom: d.properties.minzoom, maxzoom: d.properties.maxzoom, layer: d.properties.layer }, delete d.properties.minzoom, delete d.properties.maxzoom, delete d.properties.layer, d'

βœ‚οΈ Geometric simplifications βœ‚οΈ

At every zoom level, line and polygon features get subjected to Douglas-Peucker simplification to the resolution of the tile. That's just the vibe, it's giving efficient.

For point features, it drops 1/2.5 of the dots for each zoom level above the point base zoom (which is normally the same as the -z max zoom, but can be a different zoom specified with -B if you have precise but sparse data). I don't know why 2.5 is the appropriate number, but the densities of many different data sets fall off at about this same rate. You can use -r to specify a different rate. πŸ“

You can use the gamma option to thin out especially dense clusters of points. For any area where dots are closer than one pixel together (at whatever zoom level), a gamma of 3, for example, will reduce these clusters to the cube root of their original density. Math is math.

For line features, it drops any features that are too small to draw at all. This still leaves the lower zooms too dark (and too dense for the 500K tile limit, in some places), so I need to figure out an equitable way to throw features away. 😭

Unless you specify --no-tiny-polygon-reduction, any polygons that are smaller than a minimum area (currently 4 square subpixels) will have their probability diffused, so that some of them will be drawn as a square of this minimum size and others will not be drawn at all β€” preserving the total area that all of them should have had together. Cute AND mathematically balanced. πŸ’–

Features in the same tile that share the same type and attributes are coalesced together into a single geometry if you use --coalesce. You are strongly encouraged to use -x to exclude any unnecessary attributes to reduce wasted file size. Do it. Your tilesets will thank you.

If a tile is larger than 500K, it will try encoding that tile at progressively lower resolutions before failing if it still doesn't fit.


πŸ› οΈ Development πŸ› οΈ

Requires sqlite3 and zlib (should already be installed on MacOS). Rebuilding the manpage uses md2man (gem install md2man).

Linux:

sudo apt-get install gcc g++ make libsqlite3-dev zlib1g-dev

Then build:

make

and perhaps

make install

Tippecanoe now requires features from the 2011 C++ standard. If your compiler is older than that, you will need to install a newer one. On MacOS, updating to the latest XCode should get you a new enough version of clang++. On Linux, you should be able to upgrade g++ with:

sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -y
sudo apt-get install -y g++-5
export CXX=g++-5

🐳 Docker Image 🐳

A tippecanoe Docker image can be built from source and executed as a task to automatically install dependencies and allow tippecanoe to run on any system supported by Docker. Main character energy, honestly.

$ docker build -t tippecanoe:latest .
$ docker run -it --rm \
  -v /tiledata:/data \
  tippecanoe:latest \
  tippecanoe --output=/data/output.mbtiles /data/example.geojson

The commands above will build a Docker image from the source and compile the latest version. The image supports all tippecanoe flags and options. πŸ‘‘


🌈 Examples 🌈

Check out some examples of maps made with tippecanoe ⟨MADE_WITH.md⟩


πŸ‘‘ Name πŸ‘‘

The name is a joking reference ⟨http://en.wikipedia.org/wiki/Tippecanoe_and_Tyler_Too⟩ to a "tiler" for making map tiles. Iykyk. πŸ’€βœ¨

πŸŽ€ tile-join πŸŽ€

Okay bestie, meet tile-join πŸ’– β€” the tool that copies and merges vector mbtiles files, and also glues fresh attributes from a CSV file onto the features already living inside them.

Here's the vibe: it reads the tiles out of an existing .mbtiles file, a .pmtiles file, or a directory of tiles, matches them up against the records in the CSV (only if you gave it one, no pressure), and writes out a whole new tileset. Slay. ✨

And listen β€” if you hand it multiple source mbtiles files or multiple source tile directories, it reads all of them and writes their combined contents into the new mbtiles output. If they define the same layers or the same tiles? Merged. Periodt. πŸ‘‘

The options are:

πŸ“¦ Output tileset

  • -o out.mbtiles, out.pmtiles or --output=out.mbtiles β€” write the new tiles to the specified .mbtiles file πŸ’Ύ
  • -e directory or --output-to-directory=directory β€” write the new tiles into the specified directory instead of into an mbtiles file πŸ“
  • -f or --force β€” remove out.mbtiles if it already exists (yes, it will just delete it, be so serious) πŸ”₯
  • -r or --read-from β€” list of input mbtiles to read from πŸ“₯

πŸ” Overzooming

  • --overzoom β€” if one of the source tilesets has a bigger maxzoom than the others, scale up the tiles from the tilesets with the lower maxzooms so they all end up with the same maxzoom in the output tileset πŸ”­
  • --buffer=pixels or -b pixels β€” set the size of the tile buffer in the overzoomed tiles 🧴

🏷️ Tileset description and attribution

  • -A attribution or --attribution=attribution β€” set the attribution string πŸ“
  • -n name or --name=name β€” set the tileset name ✨
  • -N description or --description=description β€” set the tileset description πŸ’¬

πŸ§… Layer filtering and naming

  • -l layer or --layer=layer β€” include the named layer in the output. You can drop multiple -l options to keep multiple layers. Don't specify any? They all get retained. Generous queen πŸ’…
  • -L layer or --exclude-layer=layer β€” remove the named layer from the output. Multiple -L options remove multiple layers 🚫
  • -Rold:new or --rename-layer=old:new β€” rename the layer called old to be called new instead. Multiple -R options rename multiple layers. Heads up: renaming happens before filtering, iykyk πŸ‘‘
  • Zoom levels
  • -z zoom or --maximum-zoom=zoom β€” don't copy tiles from zoom levels higher than the specified zoom ⬆️
  • -Z zoom or --minimum-zoom=zoom β€” don't copy tiles from zoom levels lower than the specified zoom ⬇️

🧬 Merging attributes from a CSV file

  • -c match.csv or --csv=match.csv β€” use match.csv as the source of the new attributes to join onto the features. First line of the file should be the key names, the rest of the lines are values. First column is the one that gets matched against the existing features; the other columns are the new data getting added πŸ“

🎯 Filtering features and feature attributes

  • -x key or --exclude=key β€” remove the attributes named key from the output. Use this to ditch the field you were matching against once you don't need it anymore after the join, or to remove literally any other attribute you're over. Multiple -x options remove multiple attributes πŸ™ˆ
  • -X or --exclude-all β€” remove all attributes from the output πŸ’€
  • -y key or --include=key β€” remove every attribute except the ones named key from the output. Multiple -y options retain multiple attributes 🫢
  • -i or --if-matched β€” only include features that matched the CSV 🎯
  • -j filter or --feature-filter=filter β€” check features against a per-layer filter (as defined in the Mapbox GL Style Specification at https://docs.mapbox.com/mapbox-gl-js/style-spec/#other-filter) and only include the ones that match. Any features in layers where no filter was specified just get passed through. And filters for the layer "*" apply to all the layers 🌈
  • -J filter-file or --feature-filter-file=filter-file β€” same energy as -j, but it reads the filter out of a file πŸ“„
  • -pe or --empty-csv-columns-are-null β€” treat empty CSV columns as nulls instead of as empty strings πŸ•³οΈ

βš–οΈ Setting or disabling tile size limits

  • -pk or --no-tile-size-limit β€” don't skip tiles that are bigger than 500K 🐘
  • -pC or --no-tile-compression β€” don't compress the PBF vector tile data 😀
  • -pg or --no-tile-stats β€” don't generate the tilestats row in the tileset metadata. Uploads without tilestats (https://github.com/mapbox/mapbox-geostats) will take longer to process ⏳
  • --tile-stats-attributes-limit=count β€” include tilestats info about at most count attributes instead of the default 1000 πŸ”’
  • --tile-stats-sample-values-limit=count β€” calculate tilestats attribute statistics based on count values instead of the default 1000 πŸ“Š
  • --tile-stats-values-limit=count β€” report count unique attribute values in tilestats instead of the default 100 πŸ”
Real talk: because tile-join literally just copies the geometries into the new .mbtiles without processing them (except to rescale the extents if it has to), it does not have any of tippecanoe's recourses if the new tiles are bigger than the 500K tile limit. If a tile is too big and you haven't specified -pk, it's just… left out of the new tileset. Gone. Not invited πŸ’€

🌟 Example

Picture it: you've got a tileset of census blocks 🏘️

curl -L -O http://www2.census.gov/geo/tiger/TIGER2010/TABBLOCK/2010/tl_2010_06001_tabblock10.zip
unzip tl_2010_06001_tabblock10.zip
ogr2ogr -f GeoJSON tl_2010_06001_tabblock10.json tl_2010_06001_tabblock10.shp
./tippecanoe -o tl_2010_06001_tabblock10.mbtiles tl_2010_06001_tabblock10.json

…and a CSV of their populations πŸ“ˆ

curl -L -O http://www2.census.gov/census_2010/01-Redistricting_File--PL_94-171/California/ca2010.pl.zip
unzip -p ca2010.pl.zip cageo2010.pl |
awk 'BEGIN {
    print "GEOID10,population"
}
(substr($0, 9, 3) == "750") {
    print "\"" substr($0, 28, 2) substr($0, 30, 3) substr($0, 55, 6) substr($0, 62, 4) "\"," (0 + substr($0, 328, 9))
}' > population.csv

which is giving ✨this✨

GEOID10,population
"060014277003018",0
"060014283014046",0
"060014284001020",0
...
"060014507501001",202
"060014507501002",119
"060014507501003",193
"060014507501004",85
...

Then you can join those populations onto the geometries and throw out the no-longer-needed ID field, like the icon you are πŸ’…

./tile-join -o population.mbtiles -x GEOID10 -c population.csv tl_2010_06001_tabblock10.mbtiles

πŸ”’ tippecanoe-enumerate

The tippecanoe-enumerate utility lists the tiles that an mbtiles file defines πŸ“‹ Each line of the output gives you the name of the mbtiles file plus the zoom, x, and y coordinates of one of the tiles. Basically it does the same thing as

select zoom_level, tile_column, (1 << zoom_level) - 1 - tile_row from tiles;

run on the file in sqlite3 πŸ—ƒοΈ


🧚 tippecanoe-decode

The tippecanoe-decode utility turns vector mbtiles back into GeoJSON πŸ” You can run it on a whole entire file:

tippecanoe-decode file.mbtiles
tippecanoe-decode file.pmtiles

or on just one single tile 🎯

tippecanoe-decode file.mbtiles zoom x y
tippecanoe-decode file.vector.pbf zoom x y

Unless you pass -c, the output is a set of nested FeatureCollections identifying each tile and layer separately. Just so you know: the same features generally show up at all the zooms, so the output for the file will have a bunch of copies of the same features at different resolutions. Not a bug, that's just how it is bestie 😌

βš™οΈ Options

  • -s projection or --projection=projection β€” specify the projection of the output data. Currently supported: EPSG:4326 (WGS84, the default) and EPSG:3857 (Web Mercator) πŸ—ΊοΈ
  • -z maxzoom or --maximum-zoom=maxzoom β€” specify the highest zoom level to decode from the tileset ⬆️
  • -Z minzoom or --minimum-zoom=minzoom β€” specify the lowest zoom level to decode from the tileset ⬇️
  • -l layer or --layer=layer β€” decode only the layers with the specified names. (Multiple -l options can be specified.) πŸ§…
  • -c or --tag-layer-and-zoom β€” include each feature's layer and zoom level as part of its tippecanoe object rather than as a FeatureCollection wrapper 🏷️
  • -S or --stats β€” just report statistics about each tile's size and the number of features in it, as a JSON structure πŸ“Š
  • -f or --force β€” decode tiles even if polygon ring order or closure problems are detected 😀
  • -I or --integer β€” report coordinates in integer tile coordinates πŸ”’
  • -F or --fraction β€” report coordinates as a fraction of the tile extent βž—

🧚 tippecanoe-json-tool πŸ’…

Okay bestie, this one pulls GeoJSON features or standalone geometries out of a bigger JSON file as line-delimited JSON objects, following literally the same extraction rules Tippecanoe uses when it's parsing JSON. Same rules, same energy, no cap.

tippecanoe-json-tool file.json [... file.json]

And then, if you want, it'll:

  • wrap them in a FeatureCollection or a GeometryCollection as appropriate ✨
  • extract an attribute out of the GeoJSON properties so you can sort by it
  • join a sorted CSV of new attributes onto a sorted GeoJSON file 🫢

Why the whole "must be sorted" thing, you ask? Because that's the only way to work on CSV and GeoJSON files that are bigger than can comfortably fit in memory β€” you stream through them in parallel, exactly like the Unix join command does. And to get your giant files sorted first? That's what the Unix sort command is for, queen. πŸ‘‘

Real talk though: the sorting interface is weird, and a future version of tippecanoe-json-tool is gonna replace it with something better. So don't get attached. 😭

✨ Options ✨

  • -w, --wrap β€” adds the FeatureCollection or GeometryCollection wrapper for you πŸŽ€
  • -e attribute, --extract=attribute β€” pulls out the named attribute as a prefix to each feature. Heads up: the formatting makes excessive use of \u quoting, so it follows JSON string rules but still gets sorted correctly by tools that only do plain ASCII comparisons. It's giving unhinged but it works. πŸ’€
  • -c file.csv, --csv=file.csv β€” joins attributes from the named sorted CSV file, using its first column as the join key. Geometries get passed through even if they don't match the CSV, but CSV lines that don't match a geometry get discarded. Bye. πŸ‘‹
  • -pe, --empty-csv-columns-are-null β€” treat empty CSV columns as nulls instead of empty strings πŸ“

πŸ’– Example πŸ’–

Let's join Census LEHD (Longitudinal Employer-Household Dynamics) employment data β€” link's at https://lehd.ces.census.gov/ β€” onto a file of Census block geography for Tippecanoe County, Indiana. Yes, the county is real, and yes, naming stuff after it is a whole vibe. 🌸

Download the Census block geometry and convert it to GeoJSON:

$ curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/TABBLOCK/2010/tl_2010_18157_tabblock10.zip
$ unzip tl_2010_18157_tabblock10.zip
$ ogr2ogr -f GeoJSON tl_2010_18157_tabblock10.json tl_2010_18157_tabblock10.shp

Download the Indiana employment data and fix the name of the join key in the header:

$ curl -L -O https://lehd.ces.census.gov/data/lodes/LODES7/in/wac/in_wac_S000_JT00_2015.csv.gz
$ gzip -dc in_wac_S000_JT00_2015.csv.gz | sed '1s/w_geocode/GEOID10/' > in_wac_S000_JT00_2015.csv

Now sort the GeoJSON block geometry so it's ordered by block ID. And like, actually do it, because if you don't you will get a "GeoJSON file is out of sort" error and the whole thing just refuses. πŸ™ˆ

$ tippecanoe-json-tool -e GEOID10 tl_2010_18157_tabblock10.json | LC_ALL=C sort > tl_2010_18157_tabblock10.sort.json

And finally, join the block geometries to the employment attributes:

$ tippecanoe-json-tool -c in_wac_S000_JT00_2015.csv tl_2010_18157_tabblock10.sort.json > blocks-wac.json

Slay. You just joined two giant datasets with streaming, bestie. πŸ†


πŸ¦‹ tippecanoe-overzoom πŸ¦‹

Now tippecanoe-overzoom is the icon of the family. It creates a vector tile from one of its parent tiles β€” clipping and scaling the geometry from that parent, and excluding features that are clipped away. The whole idea: if you make very high resolution tiles (with --extra-detail) at a moderate zoom level, you can use tippecanoe-overzoom to turn those into moderate detail tiles at high zoom levels, for the benefit of renderers that can't internally overzoom high-resolution tiles without losing some of the precision. Main character problem-solving energy tbh. ✨

So if you run:

tippecanoe-overzoom -o out.mvt.gz in.mvt.gz inz/inx/iny outz/outx/outy

it reads tile inz/inx/iny of in.mvt.gz and produces tile outz/outx/outy of out.mvt.gz. πŸ’«

And if you run:

tippecanoe-overzoom -o out.mvt.gz -t outz/outx/outy in.mvt.gz inz/inx/iny in2.mvt.gz in2z/in2x/in2y in3.mvt.gz in3z/in3x/in3y

it reads tile inz/inx/iny of in.mvt.gz, tile in2z/in2x/in2y of in2.mvt.gz, and tile in3z/in3x/in3y of in3.mvt.gz, and produces tile outz/outx/outy of out.mvt.gz from them. Three tiles in, one tile out β€” a whole glow-up. πŸŽ€

✨ Options ✨

  • -b buffer β€” sets the tile buffer in the output tile (default 5) 🫢
  • -d detail β€” sets the detail of the output tile (default 12) 🧚
  • -y attribute β€” retain the specified attribute in the output features. Fair warning: all attributes that are not named in a -y option get removed. So if you forget one, it's gone gone. 😭
  • -j filter β€” filter features using the same expression syntax as in tippecanoe πŸ”₯
  • -m β€” if a tile was created with the --retain-points-multiplier option, thin the tile back down to its normal feature count during overzooming. The first feature from each cluster will be retained β€” unless -j is used to specify a filter, in which case the first matching filter from each cluster gets retained instead πŸ‘‘
  • --preserve-input-order β€” restores a set of filtered features to its original input order 🌈
  • --accumulate-attribute β€” behaves as in tippecanoe to sum attributes from the features of a multiplier cluster that are not included in the final output. One important thing though: the attributes from features that get filtered away with -j are not accumulated onto the output feature. No sneaky extras. πŸ’…

And that's the whole manual, bestie!! πŸ’– Every section, every flag, every \u-quoted weirdness β€” we got through all of it, and I'm so proud of you fr fr. 🫢

This whole thing comes from the tippecanoe() manual page, so if you ever need to cite it, that's your source. Remember: sort your files, read your warnings, and go build something iconic. Periodt. 🌟

πŸ“¦ where this page comes from