Beta
Mapbox Tiling Service
All docsMapbox Tiling ServiceRecipe specification

Recipe specification

Current version: 1

A tileset recipe is a JSON document containing configuration options that tell Mapbox Tiling Service (MTS) how to turn tileset source data into vector tiles.

Recipes must be constructed according to the rules in this recipe reference.

Recipe top-level fields

A recipe is a JSON object that must contain the following top-level fields:

Required fieldsDescriptionData type
version The version of the Mapbox Tiling Service recipe reference that the recipe uses.Integer
layers The names of the layers and their configuration options.Object<String, LayerObject>
{
  "version": 1,
  "layers": { layername: LayerObject, ... }
}

Recipe version

The recipe version indicates the version of the Mapbox Tiling Service recipe reference that the recipe uses. The current version is 1. Using any version other than the current version or a previous version will result in an error.

Layers

The recipe layers object contains key-value pairs associating the names of tile layers with their configuration. For each key-value pair, the key is the name of the layer, and the value is a LayerObject containing the configuration and options for the layer.

Each layer must have a name (e.g. "trees", "parks", and "paths" in the example below). This is the unique identifier for the layer of data in your final tileset. In any tileset, each layer name must be unique. The layer name must be a string with only underscores (_) and alphanumeric characters.

The corresponding LayerObject for each layer name describes how that vector tile layer should be created. It specifies where to retrieve source data, the precision of the data through zoom levels, and how to transform features and render them into tiles.

Layer example

The simplest example tileset recipe includes a layer name, a tileset source ID, a minimum zoom value, and a maximum zoom.

{
  "version": 1,
  "layers": {
    "trees": {
      "source": "mapbox://tileset-source/{username}/trees-data",
      "minzoom": 4,
      "maxzoom": 8
    }
  }
}

Multilayer recipes

Recipes can also contain definitions for multiple layers. This format can be used to create multilayer tilesets. Recipes can have a maximum of 20 layers defined.

{
  "version": 1,
  "layers": {
    "trees": { ... },
    "parks": { ... },
    "paths": { ... }
  }
}

Although you can refer to multiple tilesets from the same style, a single multilayer tileset can contain more layers and can be accessed more quickly over the network than a composite of multiple single-layer tilesets.

Layer configuration

These are the required top-level fields for each layer:

Required fieldsDescriptionData type
source The source data to use for this layer. For more information about this field, see the Tileset source section.String
minzoom The lowest zoom level for the tileset. For more information about this field, see the Zoom levels section.Integer
maxzoom The highest zoom level for the tileset. For more information about this field, see the Zoom levels section.Integer

Tileset source

The features that make up each layer come from a tileset source, which is a collection of geographic data stored as line-delimited GeoJSON on Mapbox.com. Tileset sources can be created using MTS's Create a tileset source endpoint.

Zoom levels

Each tileset layer contains tiles at one or more zoom levels that will be visible at different times as the user zooms in and out of the map.

The minzoom and maxzoom configurations control the zoom levels at which your data will be tiled. They are required for a recipe. These values must be integers. The minzoom must be less than or equal to maxzoom, and both must be between the values of 0 and 16.

Note

maxzoom does not limit the zoom level your maps can be viewed at, as long as your layers all have the same maxzoom. Your map will still zoom past the max zoom in your recipe because of overzooming. If you do have a different maxzoom in different layers, only the layers with the highest maxzoom will be overzoomed.

The minimum and maximum zoom levels you choose directly impact the time it takes MTS to finish processing your data. Each additional zoom level increases the amount of data in the resulting tileset exponentially. So it’s important to first consider the use cases your map is trying to solve for when choosing your minzoom and maxzoom zoom levels so that you don't incur any performance costs unnecessarily.

The following table will help you decide the appropriate minzoom and maxzoom based on your data precision:

Zoom levelPrecision in feetPrecision in metersUse CaseExample
032000 ft10000 m
116000 ft5000 m
28000 ft2500 m
34000 ft1250 m
42000 ft600 m
51000 ft300 m
6500 ft150 m
7250 ft80 m
8125 ft40 m
964 ft20 m
1032 ft10 mlow-resolution administrative boundaries (for example, countries and state boundaries)
1116 ft5 m
128 ft2 m
134 ft1 m
142 ft0.5 mhigh-resolution administrative boundaries (such as neighborhoods, census tracts, and towns), national park polygon data, national land use, and land cover polygon data
151 ft0.25 m
160.5 ft0.125 mbuilding/parcel polygon data, road polygon or line data, points of interest (such as hotels, restaurants, and venues)

Zoom levels greater than 16, which are applicable for use-cases like lane-level navigation maps or high-definition indoor maps, are not possible by default. If you have a use case that requires a zoom level greater than 16, reach out to us and let us know.

Additional layer options

You can further refine the resulting tileset with the following optional fields:

Optional fieldsDescriptionData type
features Specifies the output on a per feature basis. For more information about this field, see the Feature configuration section.Object
tiles Specifies the output on a per tile basis. For more information about this field, see the Tile configuration section.Object

Layer configuration summary

The following describes every available field for a LayerObject and the JSON type for that field.

{
  "source": String,
  "minzoom": Integer,
  "maxzoom": Integer,
  "features": {
    "id": Expression,
    "bbox": [ Number, Number, Number, Number ],
    "attributes": {
      "zoom_element": Array<String>,
      "set": Object<String, Expression>,
      "allowed_output": Array<String>
    },
    "filter": Expression<Boolean>,
    "simplification": Expression<Number>,
    "simplification": {
      "distance": Expression<Number>,
      "outward_only": Expression<Boolean>
    }
  },
  "tiles": {
    "bbox": [ Number, Number, Number, Number ],
    "extent": Expression<Number>,
    "buffer_size": Expression<Number>,
    "limit": [
      [ String, Expression<Boolean>, Integer, String ], ...
    ],
    "union": [
      {
        "where": Expression<Boolean>,
        "group_by": Array<String>,
        "aggregate": Object<String, String>,
        "maintain_direction": Boolean,
        "simplification": Expression<Number>,
        "simplification": {
          "distance": Expression<Number>,
          "outward_only": Expression<Boolean>
        }
      }, ...
    ],
    "filter": Expression<Boolean>,
    "attributes": {
      "set": Object<String, Expression>
    },
    "order": String,
    "remove_filled": Expression<Boolean>,
    "id": Expression,
    "layer_size": Integer
  }
}

Expressions

When writing rules in a recipe document, you can use "expressions" to base them on the properties in your data. Example rules that you might specify in a recipe document could include:

  • Tile uploaded tornado data from zooms 5 through 16.
  • Only tile the most populated cities within a dataset at zooms less than 7, then from zooms 8 through 16, only tile the top 20 most populated cities or towns per tile.
  • Only tile the top 5 flood-affected regions by area from the uploaded dataset until zoom 10, at zoom levels greater than 10, only show flooded regions with an area greater than 8 pixels.

The expressions used to control recipe features are from the expression language described in the Expressions reference section, and are closely related to the expressions used in Mapbox GL styles.

Plain numbers, strings, true, false, and null are all valid expressions, so you don't need to learn all the details of the expression language to start using MTS.

Feature configuration

The features configuration object is used to describe how features are individually processed into vector tiles. This field contains the following elements, which are applied in the order indicated in the list below:

Optional fieldsDescriptionData type
id Sets the ID of each feature. For more information about this field, see the ID expression section.Expression
bbox Clips each feature to the specified bounding box. For more information about this field, see the Bounding box section.Array<Number>
attributes Adds, removes, and modifies feature attributes. For more information about this field, see the Feature attributes section.FeaturesAttributesObject
filter Removes features that do not match a pattern. For more information about this field, see the Feature filters section.Expression<Boolean>
simplification Controls simplification of the feature geometry. For more information about this field, see the Feature simplification section.Expression<Number> or SimplificationObject

ID expression

Each feature that is tiled by MTS must have a numeric or string ID that uniquely identifies it, which by default comes from the id field of the GeoJSON Feature object in the source. If no ID is present for a feature in the GeoJSON source, or if the provided ID is not a string or number, an ID will be randomly assigned by MTS.

If you want to use a value from a feature attribute (or some other calculation) for your feature's ID, you can use an expression to retrieve that attribute. For example, if you have an attribute called source_identifier, you can use it for the feature ID by specifying:

{
  "features": {
    "id": [ "get", "source_identifier" ]
  }
}

If you do not want your features to have an ID in the output tiles, or if you want to make additional changes to the ID, you can do this later in the recipe, in the Tiles section.

Read more about feature IDs and their custom operators in the "Identification" section.

Bounding box

If you want to clip your features to a bounding box instead of creating tiles for the full geographic extent of your source data, you can specify the bounding box with bbox. It must contain an array of four numbers in this order: the minimum longitude, minimum latitude, maximum longitude, and maximum latitude. For example, if you wanted to clip all your features to the bounds of the state of Wyoming, you could specify:

{
  "features": {
    "bbox": [ -111, 41, -104, 45 ]
  }
}

Feature attributes

The attributes configuration allows for manipulation of attribute data, generation of new attributes, and removal of attributes. The available options in the FeaturesAttributesObject are described below, and performed in the order they are listed:

Optional fieldDescriptionData type
zoom_element A list of attributes whose values vary by zoom level. For more information about this option, see the Zoom element section.Array<String>
set Adds feature attributes or changes the value of attributes. For more information about this option, see the Set attributes section.Object<String, Expression>
allowed_output A list of attributes that will appear in the final tileset. For more information about this option, see the Allowed output section.Array<String>

Zoom element

You may want certain attributes to vary by zoom level. For example, you might want to provide abbreviated street names at low zoom levels and the full names at high zoom levels. You can specify which attributes are provided per zoom level in the source data by providing a zoom_element array in the attributes object.

For each attribute listed in the zoom_element, the final output attribute at zoom level N will be the Nth element in the array in the source data. If no zoom_element is defined, no attributes are altered. If the zoom level is greater than or equal to the number of elements in the array, the last element is used.

For example, the following recipe would specify that name should be treated as a zoom_element attribute:

{
  "features": {
    "attributes": {
      "zoom_element": [ "name" ]
    }
  }
}

The name attribute in individual GeoJSON features would then be expected to contain an array of possible name values by zoom level, like this:

{
  "type": "Feature",
  "geometry": { ... },
  "properties": { "name": [ null, null, "Main", "Main St.", "Main Street" ] }
}

Using the attributes from the GeoJSON above, this feature would have no name attribute at zoom levels 0 and 1, and would have "Main" at zoom level 2, "Main St." at zoom level 3, and "Main Street" at zoom levels 4 and above.

Set attributes

You can add attributes or change the values of attributes by evaluating expressions in a set specification.

The set object contains key-value pairs where each key is the name of an attribute and each value is an expression that should be evaluated to provide the new value for that attribute.

Consider the following attributes for a single GeoJSON feature:

{
  "type": "Feature",
  "geometry": { ... },
  "properties": { "area": 12345, "population": 123, "name": "Smallville" }
}

If you wanted to add a new attribute, density, that is calculated from the population per area, and also to change the value of the name attribute to convert it to all capitals, you could use this recipe:

{
  "features": {
    "attributes": {
      "set": {
        "density": [ "/", [ "get", "population" ], [ "get", "area" ] ],
        "name": [ "upcase", [ "get", "name" ] ]
      }
    }
  }
}

If you specify multiple set operations, the order in which they will happen is not defined.

Allowed output

If an allowed_output array is provided, only the attributes specified in the array will be saved to the tileset. This does not prevent the attributes that are not listed in the allowed_output from being used in filter expressions and other steps throughout the publish job; the other attributes are removed in the final step of processing.

In this example, features in the resulting tileset will only have two attributes, name_en and name_es:

{
  "features": {
    "attributes": {
      "allowed_output": [ "name_en", "name_es" ]
    }
  }
}

Feature filters

The filter configuration is a single expression that results in a true or false evaluation. It is common to determine this result by considering the zoom level being created and an attribute of the feature. For example, if a feature has minzoom and maxzoom attributes to determine the zoom levels at which the feature appears, you can compare these attributes to the zoom level being created by specifying:

{
  "features": {
    "filter": [ "all",
      [ ">=", [ "zoom" ], [ "get", "minzoom" ] ],
      [ "<=", [ "zoom" ], [ "get", "maxzoom" ] ]
    ]
  }
}

Or you could exclude residential roads from zoom levels below 12 by specifying:

{
  "features": {
    "filter": [ "any",
      [ "!=", [ "get", "road" ], "residential" ],
      [ ">=", [ "zoom" ], 12 ]
    ]
  }
}

The filter specification is the primary way to control which features are allowed into the final tileset on a per-feature basis. If no filter is provided, by default a value of true is used for all features.

Feature simplification

MTS normally simplifies the geometry of features so that at each zoom level they correspond to the resolution of the display screen. If you want to preserve additional detail or if you want to reduce tile size by further simplifying the geometry, you can specify a simplification to do that.

The simplification expression or value describes the maximum distance that a point can be from the straight line that connects its two neighbors, and still be considered to be effectively on the line and thus safe to remove. Any point with a distance that is larger than the simplification value is considered to be away from the line, and must be preserved.

As you increase the simplification value, the number of vertices in each feature decreases. The MTS recipe validator will reject any simplification value that is more than 4096 or less than 0. If you do not specify a value for simplification, MTS will use 4, the default value.

The resulting shape depends on the original shape of your feature. For instance, if your original feature is circular and you increase simplification, the result will look more like a polygon. If your original feature is a curved line and you increase simplification, the result will look more like a straight line.

The simplification may be a constant number, or it may be an expression that evaluates to a number. A typical use for an expression for the simplification would be to use a minimal simplification at the layer's maximum zoom level, so it can be overzoomed with high precision, but to use a larger simplification at lower zoom levels that will never be magnified far, as in the example below.

Note: Even if the simplification is specified as 0, complex features will receive additional simplification so that they can be rendered correctly by Mapbox GL. Polygons or MultiPolygons with more than 65535 vertices and LineStrings and polygon rings with more than 6553 points will be simplified to reduce their complexity to these limits.

MTS uses the Ramer–Douglas–Peucker algorithm to simplify features.

This sample recipe uses the normal simplification of 4 at most zoom levels, but reduces the simplification to 1 at the highest zoom level so that overzoomed tiles will look better.

{
  "minzoom": 0,
  "maxzoom": 10,
  "features": {
    "simplification": [ "case",
      [ "==", [ "zoom" ], 10 ], 1,
      4
    ]
  }
}

Simplification objects

If the simplification is a SimplificationObject, it can contain the following fields:

Optional fieldDescriptionData type
distance Controls simplification of the feature geometry. For more information about this field, see the Simplification distance section.Expression<Number>
outward_only Places limits on polygon simplification to improve union quality. For more information about this field, see the Outward-only simplification section.Expression<Boolean>

This sample recipe simplifies features where type is rowhouse outward, so that MTS can union each block of row houses into a single block feature instead of leaving them as separate features. MTS then performs normal simplification on the row house blocks after it unions them to further reduce the tile size. MTS simplifies other building types normally and does not union them together.

{
  "features": {
    "simplification": {
      "outward_only": [ "==", [ "get", "type" ], "rowhouse" ]
    }
  },
  "tiles": {
    "union": [
      {
        "where": [ "==", [ "get", "type" ], "rowhouse" ],
        "simplification": {
          "outward_only": false
        }
      }
    ]
  }
}

Simplification distance

The simplification distance is a numeric expression, as described in the Feature simplification section. It describes the maximum distance that a point can be from the straight line that connects its two neighbors, and still be considered to be effectively on the line and thus safe to remove. Any point with a distance that is larger than the simplification value is considered to be away from the line, and must be preserved. Larger values cause the feature geometry to become increasingly coarse.

Outward-only simplification

If the outward_only expression evaluates to true, then Polygon and MultiPolygon features will be simplified only in ways that slightly increase their area, never in ways that slightly decrease their area, so the borders of adjacent polygons will often overlap slightly instead of sometimes being separated by small gaps. This will increase the quality of polygon unioning if the recipe contains a union rule, at the cost of increased tile size because of reduced opportunity for simplification. (See the "Feature union" section.)

Tile configuration

The tiles object contains the following elements, which are applied in this order as each tile is being assembled from its component features:

Optional fieldDescriptionData type
bbox Clips each feature to the specified bounding box. For more information about this field, see the Tile bounding box section.Array<Number>
extent Sets the precision of a vector tile. For more information about this field, see the Tile extent section.Expression
buffer_size Keeps extra data from beyond the edge of each vector tile. For more information about this field, see the Buffer size section.Expression<Number>
limit Limits the number of features of a specified type that can appear in the tile. For more information about this field, see the Limit section.Array<LimitArray>
union Joins features based on whether or not a defined specified attribute matches. For more information about this field, see the Feature union section.Array<UnionObject>
filter Removes features that do not match a specified pattern. For more information about this field, see the Post-union filter section.Expression<Boolean>
attributes Adds attributes or changes the values of attributes. For more information about this field, see the Post-union attributes section.TilesAttributesObject
order Specifies the order of a sequence of features in the final output tile. For more information about this field, see the Order section.String
remove_filled Removes features that cover the entire tile. For more information about this field, see the Remove filled section.Expression<Boolean>
id Controls the ID of features in the final output tileset. For more information about this field, see the Tiles ID section.Expression
layer_size Sets the maximum allowed size (in KiB) of the tile layer. For more information about this field, see the Layer size section.Integer

Tile bounding box

If you want to generate tiles for only a geographic subset of your data, so you can test a recipe without the time and expense of generating all the tiles, you can specify a bounding box with bbox. It must contain an array of four numbers in this order: the minimum longitude, minimum latitude, maximum longitude, and maximum latitude. Only the tiles that intersect this bounding box will be generated.

For example, if you wanted to generate only those tiles that include part of the state of Wyoming, you could specify:

{
  "tiles": {
    "bbox": [ -111, 41, -104, 45 ]
  }
}

Note that the tiles bounding box is only useful for testing, not for final tilesets. Because the sizes of tiles are different in each zoom level, different areas will come into view as you zoom in and out. To clip features consistently in every zoom level, use the [features bounding box](/mapbox-tiling-service/reference/#bounding-box/mapbox-tiling-service/reference/#feature-configuration-bbox

The reason to use the tiles bounding box instead of the features bounding box for testing is that with the tiles bounding box, exactly the same set of features appear in each tile as in the full tileset, so you your union and limit rules will have the same set of features to work with as in the final tiles. With the featuresbounding box, some of the features will already have been removed from consideration before the union and limit happen, so you may see different results from these than with the full tileset.

Tile extent

You can change the geometric precision within each vector tile by setting the extent.

The extent is a precision control for vector tiles, and by default is 4096. In most situations, changing this value is not recommended. If you set this value, it must be a power of 2 between 256 and 8192. You may also set this number using an expression.

Buffer size

Sets the size of the buffer that will be created around the edges of your vector tiles. buffer_size is a percentage of the size of a tile. The default buffer size is 0.5. Note that the value of this attribute cannot be larger than 100. An expression can also be used in buffer_size, but must still return a number between 0 and 100. Buffers are particularly helpful for label point layers to avoid cutting labels of at tile boundaries.

Limit

A limitation rule that reduces the number of features for the specified type to a specific number, choosing the lowest or highest-numbered features according to some attribute.

The limit field must be an array of limitation rules, each of which is evaluated in sequence to potentially limit the total number of features to be included in the final tile. There are two ways in which data is limited using the limit field, where and where_in_distance.

The where style limits the total number of features in the tile and its buffer. Only the first number of features that match a filter-expression are kept and the rest are dropped from the tile. The sorting of these is based on attribute which is the key to a feature's attribute and either sorted lowest to highest or highest to lowest.

The format for a where style limit are the following syntaxes:

  • [ "lowest_where", filter-expression, number, attribute ]
  • [ "highest_where", filter-expression, number, attribute ]

The where_in_distance is the second style of limit and subdivides a tile into a grid of regions. Within each region only one feature is kept from the features selected by the filter-expression for the limit. The number of regions contained within a single tile is controlled by region_count. This should always be a value equal to 4 to a power in size (e.g. 1, 4, 16, 64, 256, 1024, 4096, or 16384). If the region_count is not equal to one of these numbers it will be rounded to the nearest one of these numbers in processing. The feature that is selected in each region is the one that has the highest or lowest value for the attribute named in the attribute field.

The format for a where_in_distance style limit are the following syntaxes:

  • [ "lowest_where_in_distance", filter-expression, region_count , attribute ]
  • [ "highest_where_in_distance", filter-expression, region_count , attribute ]

The only valid values for region_count are 1, 4, 16, 64, 256, 1024, 4096, or 16384.

These where_in_distance limit forms also cause the tile buffer for the limited features to be rounded down to a multiple of the fraction of the tile indicated by the region_count:

region_countassociated buffer size
1100
450
1625
6412.5
2566.25
10243.125
40961.5625
163840.78125

Feature union

You can union features together if a specified set of their attributes match. The simplest case unions all features that have exactly the same attributes:

"tiles": { "union": [ { } ] }

The union specification object UnionObject can contain an expression to union only features that match the specified expression as well as having matching attributes:

Optional fieldDescriptionData type
where Selects which features are considered for this union.Expression<Boolean>
group_by Unions features where all of the specified attributes are identical in each feature.Array<String>
aggregate Specifies how to handle other attributes that are not specified in group_by when features are unioned.Object<String, String>
maintain_direction Permits LineStrings to be reversed if it helps to connect them.Boolean
simplification Specifies how features will be resimplified after unioning, as described in the Feature simplification section.Number or SimplificationObject

Union where

You can specify which features a union rule applies to by providing a where expression that selects a subset of features. For example, if your layer contains some building footprints that you want to union if they have the same height, and some parks that you want to union if they have the same maintainer, you could do this:

"tiles": {
  "union": [
    {
      "where": [ "==", [ "get", "type" ], "building" ],
      "group_by": [ "height" ]
    },
    {
      "where": [ "==", [ "get", "type" ], "park" ],
      "group_by": [ "maintainer" ]
    }
  ]
}

Union group_by

Within a set of features that are union candidates, you can specify which of them should become part of the same union by listing the attributes that must be the same in all features of the union. For example, if you want to union all buildings that have the same height and construction_type together, you could specify:

"tiles": {
  "union": [
    {
      "group_by": [ "height", "construction_type" ]
    }
  ]
}

All attributes that are not part of the group_by and that do not have anaggregate rule specified will be removed at the end of unioning.

If you do not specify a group_by, then all feature attributes must be the same for features to be unioned together.

If you specify an empty group_by list, then all features will be unioned together regardless of their attributes.

Union aggregate

You can specify which attributes MTS should keep that are not part of a union's group_byand how it should handle them by specifying aggregate rules for them.

Each key of the object is the name of an attribute. Each value is a string listed below that indicates how the named attribute should be aggregated from the source features. Attributes that are not specified in either group_by or aggregate will be removed.

These strings are the available aggregation methods:

  • sum: add numbers
  • product: multiply numbers`
  • min: choose the lowest number
  • max: choose the highest number
  • mean: take the average of numbers
  • comma: concatenate with a comma
  • concat: concatenate the attribute data without a delimiter
  • arbitrary: take the attribute from one of the source features
  • arbitrary-non-null: take the attribute from one of the source features where the attribute is not null

Union maintain_direction

Use maintain_direction: false to make more compact unions of LineStrings for which directionality doesn't matter. This reverses some of the LineStrings if that helps to connect them.

Union simplification

For polygon unions, it is often useful to specify outward_only: true in the main simplification section, so the source features will slightly overlap and be merged into a single geometry. Then you can specify outward_only: false in the union's post-unioning simplification section, so additional simplification will be applied to the polygon after unioning has already taken their overlap into account.

Union example

For example, you could specify the following to union only features where highway=motorway, and keep the average of their speed_limit attributes:

"tiles": {
  "union": [
    {
      "where": [ "==", [ "get", "highway" ], "motorway" ],
      "maintain_direction": true,
      "aggregate": { "speed_limit": "mean" }
    }
  ]
}

This example uses maintain_direction: true (the default) because motorway roads are generally mapped as pairs of roadways whose direction indicates their one-way direction.

For a more elaborate union example, see the Census Blocks recipe example.

Using union with zoom based properties

You can use union on zoom-element properties. If you are using union with a zoom-element property, the union will occur after the feature has been assigned the value based on the zoom level.

Post-union filter

You can further limit the set of features in a tile after unioning by specifying a filter rule.

For instance, if your union rule was unioning street segments together, summing theirlength attribute, you could use filter here to remove the unioned streets whose combined length still did not reach a minimum visible threshold, as in this example:

"tiles": {
  "union": [
    {
      "aggregate": { "length": "sum" }
    }
  ],
  "filter": [ ">=", [ "get", "length" ], 200 ]
}

Post-union attributes

You can add or change feature attributes after unioning by specifying an attributes.set rule.

For instance, if your union rule was unioning census blocks together, summing theirpopulation and area attributes, you could calculate the density of each unioned feature afterward, as in this example:

"tiles": {
  "union": [
    {
      "group_by": [ "tract" ]
      "aggregate": { "population": "sum", "area": sum }
    }
  ],
  "attributes": {
    "set": {
      "density": [ "/", [ "get", "population" ], [ "get", area ] ]
    }
  }
}

Order

You can order the sequence of features in the final output tile by a specified attribute. The attribute values must be comparable (all strings or all numbers).

This example orders the features by the sequence attribute:

"tiles": { "order": "sequence" }

Renderers, including Mapbox GL, draw or place features in the order they appear in the tile. LineString and Polygon features that are drawn early in the sequence may be covered by overlapping features that are drawn later in the sequence. Conversely, labels that are placed early in the sequence will prevent labels of features later in the sequence from being placed nearby.

You can follow these general guidelines:

  • The most important LineStrings and Polygons should have the highest values for their order attribute, so they are not covered by overlapping features.
  • The most important labels should have the lowest values for their order attribute, so they are placed first.

Read more about how label visibility is affected by the order of features in the Optimize map label placement guide.

If no order attribute is specified, the features will appear in an unpredictable order.

Remove filled

During tile creation it is possible for polygons to completely cover a tile and its surrounding buffer area. These are called "filled features". If all features within a tile are filled features, it may be useful in some tilesets not to create these tiles at all and to expect that clients will look to lower zoom levels instead.

The common situation that this applies to are large areas of water, where if the water polygon covers the entire tile, it is more efficient to overzoom the water from a lower zoom level rather than creating duplicate high-zoom tiles for an entire ocean.

To do this, you can use the remove_filled recipe option. The syntax utilizes a filter-expression to allow control over what features exactly should be removed.

"tiles": {
  "remove_filled": expression
}

No tile will be created if:

  • All features within the tile are considered filled features
  • All features are matched by the filter-expression provided in remove_filled

The following example removes all tiles that contain only filled features:

"tiles": {
  "remove_filled": true
}

The following example removes all tiles that contain only filled features after zoom level 5:

"tiles": {
  "remove_filled": [">", [ "zoom" ], 5]
}

Tiles ID

You can change the final output ID of features by specifing an id expression. For instance, you might want all the features that have the same road name to have the same ID, so that if one of them is selected, they are all selected, even though the source features had to have different IDs because in earlier stages of processing, IDs are required to be distinct. So you could set a new ID at the end of processing, like this:

"tiles": {
  "id": [ "hash", [ "get", "road-name" ] ]
}

Within the id expression you can refer to the ID assigned earlier with [ "id" ], so you can make conditional expressions that leave the ID unchanged in some circumstances or use it in calculations, like this example, which replaces the ID if there is a special_id attribute, or otherwise multiplies the existing ID by 2:

"tiles": {
  "id": [ "case",
    [ "has", "special_id" ], [ "get", "special_id" ],
    [ "*", [ "id" ], 2 ]
  ]
}

If you do not want your features to have an ID in the final output tiles, set the ID to null:

"tiles": {
  "id": null
}

Allowed output

In the features.attributes section of the recipe, you can specify a list of attributes in allowed_output that will appear in the final tile. The removal of other attributes happens at this point, at the end of tiles processing. As a result, in limit, union, or id processing you can continue to refer to attributes that will not appear in the final output features because they do not appear in allowed_output.

Layer size

MTS limits the size (in bytes) of tile layers for the sake of network speed and rendering time. The normal limit is 500, for 500 kilobytes. You can further limit the layer size by specifying a lower layer_size number. When the size of a tile layer exceeds the layer_size, the features that came last in the specified orderwill be the ones that will be dropped to reduce the size.

If you want to exclude specific features, it is generally better to use a limitspecification instead. Setting layer_size is more useful for holding yourself to a performance budget than as a general feature limiting mechanism.

Identification

You can configure the identification of features in both "Features" and "Tiles". Both follow the same patterns and have the same options. In general, features.id is used for defining or generating input IDs, while tiles.id is used for defining or generating output IDs in the final vector tiles.

{
  "features": {
    "id": [ <Expression> ]
  },
  "tiles": {
    "id": [ <Expression> ]
  }
}

All IDs encoded in the final vector tiles will be integers, as defined by the vector tile specification. If you provide a string value to tiles.id the value will be automatically hashed to an integer between 0 and 2^53.

Order of operations

The following describes the order of operations and type conversions applied when using the features.id and tiles.id options. Remember the Mapbox Vector Tile specification requires all IDs to be encoded as positive integers between 0 and 2^53.

  • In general ID value in either features.id or tiles.id will be converted to an Integer or Float if possible and will otherwise return a string or Null value.
  • If features.id is not defined, the top-level id of the GeoJSON feature will be used.
    • If there is no top-level id from the GeoJSON feature, a random integer will be generated.
    • If the value is set to null, no feature ID is set.
  • If features.id is defined, the result of the expression will be converted to an Integer or Float if possible and will otherwise return a string.
    • If the expression returns a null value, a random integer will be generated. If you are trying to prevent feature IDs in your final tile, set tiles.id to null.
  • If tiles.id is not defined, the resulting value from features.id will be used.
  • If tiles.id is defined, the value of the resulting expression replaces whatever value was defined in features.id.
  • If the result of the tiles.id expression is a...
    • string, it is hashed to an integer.
    • string representation of a number, it will be converted to the absolute value of that number truncated to an integer, modulo 2^53.
    • empty string, it will be treated as null and not encoded.
    • negative number, the absolute value will be used.
    • floating point number, it will be rounded and fit within 0 to 2^53.
    • boolean, it will be treated as null and not encoded.

If you don't want to encode any IDs in your final vector tile you can pass null to tiles.id as your value instead of an expression array.

Duplicate IDs

While ID collision for unidentified features is unlikely, unique IDs aren't guaranteed since feature parsing occurs in a distributed environment. To guarantee unique identifiers, bring your own IDs. Duplicate feature IDs can cause unexpected behavior in tilesets and should be avoided.

Expressions reference

Instead of applying a recipe option across your entire layer, some recipe options allow you to filter your source data on a per feature, per tile, or per zoom level basis. These filtering capabilities are possible with Mapbox expressions.

An expression defines a formula for computing the value of a property using the operators described below. The set of expression operators provided by MTS includes:

  • Mathematical operators for performing arithmetic and other operations on numeric values
  • Logical operators for manipulating boolean values and making conditional decisions
  • String operators for manipulating strings
  • Data operators that provide access to the properties of features in your tileset source

Expressions are represented as JSON arrays. The first element of an expression array is a string naming the expression operator, for example "*" or "case". Elements that follow (if any) are the arguments to the expression. Each argument is either a literal value (a string, number, boolean, or null), or another expression array.

[expression_name, argument_0, argument_1, ...]

Expressions allow you to dynamically filter source data to optimize your output tileset. The LayerObject houses all the available recipe options and denotes which fields accept expressions by listing a Expression value.

Data expressions

A data expression is any expression that accesses data in your tileset source — that is, any expression that uses one of the data operators: get, has, id, geometry-type, or properties. Data expressions allow a feature's properties to determine its appearance. They can be used to differentiate features within the same layer and to create data visualizations.

{
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "building_age": ['-', 2020, ['number', ['get', 'construction_completion_date'], 2020]]
          }
        }
      }
    }
  }
}

This example uses the get operator to get the construction_completion_date value of each feature. That value is then used to calculate the building_age, by using the math operator - to subtract the current year, 2020, from the construction_completion_date. Finally, we use the set operator to set a building_age property on each feature, which contains the age of the building.

Types

The expressions in this section are for testing for and converting between different data types like strings, numbers, and boolean values.

Often, such tests and conversions are unnecessary, but they are helpful if a feature's data type is ambiguous or inconsistent. They can also be useful in cases where your feature data has inconsistent types; for example, you could use to-number to make sure that values like "1.5" (instead of 1.5) are treated as numeric values.

In the example below, we use the operation to-number to set the type value of construction-date from a string to a numeric value. If a value does not exist, it returns the default value: 2020.

{
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "building_age": ['-', 2020, ['to-number', ['get', construction-date], 2020]]
          }
        }
      }
    }
  }
}

array

Asserts that the input is an array (optionally with a specific item type and length). If, when the input expression is evaluated, it is not of the asserted type, then this assertion will cause the whole expression to be aborted.

["array", value]: array
["array", type: "string" | "number" | "boolean", value]: array<type>
["array",
    type: "string" | "number" | "boolean",
    N: number (literal),
    value
]: array<type, N>

boolean

Asserts that the input value is a boolean. If multiple values are provided, each one is evaluated in order until a boolean is obtained. If none of the inputs are booleans, the expression is an error.

["boolean", value]: boolean
["boolean", value, fallback: value, fallback: value, ...]: boolean

literal

Provides a literal array or object value.

["literal", [...] (JSON array literal)]: array<T, N>
["literal", {...} (JSON object literal)]: object

number

Asserts that the input value is a number. If multiple values are provided, each one is evaluated in order until a number is obtained. If none of the inputs are numbers, the expression is an error.

["number", value]: number
["number", value, fallback: value, fallback: value, ...]: number

object

Asserts that the input value is an object. If multiple values are provided, each one is evaluated in order until an object is obtained. If none of the inputs are objects, the expression is an error.

["object", value]: object
["object", value, fallback: value, fallback: value, ...]: object

string

Asserts that the input value is a string. If multiple values are provided, each one is evaluated in order until a string is obtained. If none of the inputs are strings, the expression is an error.

["string", value]: string
["string", value, fallback: value, fallback: value, ...]: string

to-boolean

Converts the input value to a boolean. The result is false when then input is an empty string, 0, false, null, or NaN; otherwise it is true.

["to-boolean", value]: boolean

to-number

Converts the input value to a number, if possible. If the input is null or false, the result is 0. If the input is true, the result is 1. If the input is a string, it is converted to a number as specified by the "ToNumber Applied to the String Type" algorithm of the ECMAScript Language Specification. If multiple values are provided, each one is evaluated in order until the first successful conversion is obtained. If none of the inputs can be converted, the expression is an error.

["to-number", value, fallback: value, fallback: value, ...]: number

to-string

Converts the input value to a string. If the input is null, the result is "". If the input is a boolean, the result is "true" or "false". If the input is a number, it is converted to a string as specified by the "NumberToString" algorithm of the ECMAScript Language Specification. Otherwise, the input is converted to a string in the format specified by the JSON.stringify function of the ECMAScript Language Specification.

["to-string", value]: string

typeof

Returns a string describing the type of the given value.

["typeof", value]: string

Feature data

The expressions in this section (geometry-type, properties, and id) are for retrieving information about the attributes contained in the GeoJSON specification.

The example below uses the id operation to set an ID from an attribute instead of the top-level id field of the feature. In this case, company_integer_id is the field used for the id of each feature.

{
  "version": 1,
  "layers": {
    "countries": {
      "source": "mapbox://tileset-source/{username}/feature-ids",
      "minzoom": 7,
      "maxzoom": 7,
      "features": {
        "id": [ "get", "company_integer_id" ]
      }
    }
  }
}

feature

Provides a static, integer representation of the feature. The hash is generated from the geometry, geometry type, and properties sorted alphabetically. If this option is used in the tiles.id section, it is only the part of the feature within the individual tile, not the entire original feature.

["feature"]: number

geometry-type

Gets the feature's geometry type: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon.

["geometry-type"]: string

id

Gets the feature's ID, if it has one.

["id"]: value

properties

Gets the feature properties object. Note that in some cases, it may be more efficient to use ["get", "property_name"] directly.

["properties"]: object

Lookup

The expressions in this section are for retrieving information about feature properties contained in the GeoJSON specification.

The example below uses the operation get to retrieve the construction date, subtract the date from the current year, and then set a building_age property on each feature. 2020 is set as the default if construction-date is missing or invalid.

{
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "building_age": ['-', 2020, ['number', ['get', construction-date], 2020]]
          }
        }
      }
    }
  }
}

at

Retrieves an item from an array.

["at", number, array]: ItemType

get

Retrieves a property value from the current feature's properties, or from another object if a second argument is provided. Returns null if the requested property is missing.

["get", string]: value
["get", string, object]: value

has

Tests for the presence of an property value in the current feature's properties, or from another object if a second argument is provided.

["has", string]: boolean
["has", string, object]: boolean

in

Determines whether an item exists in an array or a substring exists in a string.

["in",
    keyword: InputType (boolean, string, or number),
    input: InputType (array or string)
]: boolean

index-of

Returns the first position at which an item can be found in an array or a substring can be found in a string, or -1 if the input cannot be found. Accepts an optional index from where to begin the search.

["index-of",
    keyword: InputType (boolean, string, or number),
    input: InputType (array or string)
]: number
["index-of",
    keyword: InputType (boolean, string, or number),
    input: InputType (array or string),
    index: number
]: number

length

Gets the length of an array or string.

["length", string | array | value]: number

slice

Returns an item from an array or a substring from a string from a specified start index, or between a start index and an end index if set. The return value is inclusive of the start index but not of the end index.

["slice",
    input: InputType (array or string),
    index: number
]: OutputType (ItemType or string)
["slice",
    input: InputType (array or string),
    index: number,
    index: number
]: OutputType (ItemType or string)

Decision

The expressions in this section can be used to add conditional logic to your styles. For example, the 'case' expression provides "if/then/else" logic, and 'match' allows you to map specific values of an input expression to different output expressions.

The example below uses the match operation to include any features that contain the highway property with a value of secondary or motorway at zoom 6 or higher.

{
  "version": 1,
  "layers": {
    "road_network": {
      "source": "mapbox://tileset-source/username/roads",
      "minzoom": 0,
      "maxzoom": 14,
      "features": {
        "filter": [
          "any",
          [
            "all",
            [ ">=", [ "zoom" ], 6 ],
            [ "match", [ "get", "highway" ],
              "secondary", true, "motorway", true, false ]
          ]
        ]
      }
    }
  }
}

!

Logical negation. Returns true if the input is false, and false if the input is true.

["!", boolean]: boolean

!=

Returns true if the input values are not equal, false otherwise. The comparison is strictly typed: values of different runtime types are always considered unequal. Cases where the types are known to be different at parse time are considered invalid and will produce a parse error.

["!=", value, value]: boolean

<

Returns true if the first input is strictly less than the second, false otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered invalid and will produce a parse error.

["<", value, value]: boolean

<=

Returns true if the first input is less than or equal to the second, false otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered invalid and will produce a parse error.

["<=", value, value]: boolean

==

Returns true if the input values are equal, false otherwise. The comparison is strictly typed: values of different runtime types are always considered unequal. Cases where the types are known to be different at parse time are considered invalid and will produce a parse error.

["==", value, value]: boolean

>

Returns true if the first input is strictly greater than the second, false otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered invalid and will produce a parse error.

[">", value, value]: boolean

>=

Returns true if the first input is greater than or equal to the second, false otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered invalid and will produce a parse error.

[">=", value, value]: boolean

all

Returns true if all the inputs are true, false otherwise. The inputs are evaluated in order, and evaluation is short-circuiting: once an input expression evaluates to false, the result is false and no further input expressions are evaluated.

["all", boolean, boolean]: boolean
["all", boolean, boolean, ...]: boolean

any

Returns true if any of the inputs are true, false otherwise. The inputs are evaluated in order, and evaluation is short-circuiting: once an input expression evaluates to true, the result is true and no further input expressions are evaluated.

["any", boolean, boolean]: boolean
["any", boolean, boolean, ...]: boolean

case

Selects the first output whose corresponding test condition evaluates to true, or the fallback value otherwise.

["case",
    condition: boolean, output: OutputType,
    condition: boolean, output: OutputType,
    ...,
    fallback: OutputType
]: OutputType

coalesce

Evaluates each expression in turn until the first non-null value is obtained, and returns that value.

["coalesce", OutputType, OutputType, ...]: OutputType

match

Selects the output whose label value matches the input value, or the fallback value if no match is found. The input can be any expression (e.g. ["get", "building_type"]). Each label must be either:

  • a single literal value; or
  • an array of literal values, whose values must be all strings or all numbers (e.g. [100, 101] or ["c", "b"]). The input matches if any of the values in the array matches, similar to the "in" operator. Each label must be unique. If the input type does not match the type of the labels, the result will be the fallback value.
["match",
    input: InputType (number or string),
    label: InputType | [InputType, InputType, ...], output: OutputType,
    label: InputType | [InputType, InputType, ...], output: OutputType,
    ...,
    fallback: OutputType
]: OutputType

Scales

The expression in this section provides incremental filters using the step operation. The example below uses step to set simplification at each zoom level. The default value is 10, and then the each step value indicates the simplification to be applied within a zoom range.

  • Zoom 0 - 2 has a simplification value of 10 because that is the default value.
  • Zoom 3 - 6 has a simplification value of 2.
  • Zoom 7 - 9 has a simplification value of 1.
  • Zoom 10+ has a simplification value of 0. These zoom-based step values result in features that are more precise as you zoom in.
{
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 3,
      "maxzoom": 12,
      "features": {
             "simplification": [
               "step",
               ["zoom"],
               10,
               3,
               2,
               7,
               1,
             10,
             0
             ]
      }
    }
  }
}

interpolate

Produces continuous, smooth results by interpolating between pairs of input and output values ("stops"). The input may be any numeric expression (e.g., ["get", "population"]). Stop inputs must be numeric literals in strictly ascending order. The output type must be number, or array<number>.

Interpolation types:

  • ["linear"]: interpolates linearly between the pair of stops just less than and just greater than the input.
  • ["exponential", base]: interpolates exponentially between the stops just less than and just greater than the input. base controls the rate at which the output increases: higher values make the output increase more towards the high end of the range. With values close to 1 the output increases linearly.
  • ["cubic-bezier", x1, y1, x2, y2]: interpolates using the cubic bezier curve defined by the given control points.
["interpolate",
    interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2],
    input: number,
    stop_input_1: number, stop_output_1: OutputType,
    ...,
    stop_input_n: number, stop_output_n: OutputType
]: OutputType (number, array<number>)

step

Produces discrete, stepped results by evaluating a piecewise-constant function defined by pairs of input and output values ("stops"). The input may be any numeric expression (e.g., ["get", "population"]). Stop inputs must be numeric literals in strictly ascending order. Returns the output value of the stop just less than the input, or the first output if the input is less than the first stop.

["step",
    input: number,
    stop_output_0: OutputType,
    stop_input_1: number, stop_output_1: OutputType,
    stop_input_n: number, stop_output_n: OutputType, ...
]: OutputType

Variable binding

The expressions in this section are for binding variables. In most cases, recipes will use the set recipe field to create a new feature property, but var and let allow performing and referring to partial calculations within a single expression. This may be necessary instead of referring to another set attribute, because the order that set operations will take place in is not defined.

let

Binds expressions to named variables, which can then be referenced in the result expression using ["var", "variable_name"].

["let",
    string (alphanumeric literal), any, string (alphanumeric literal), any, ...,
    OutputType
]: OutputType

var

References variable bound using "let".

["var", previously bound variable name]: the type of the bound expression

String

The expressions in this section are for manipulating string values.

The example below uses the string concat operation to combine the string construction-date value with the description value of each feature. The building_description of each property is then set to this string value.

{
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "building_description": ['concat', ['get', `construction-date`],2020], ":", ['get', ‘description’], ‘no description provided’]]
          }
        }
      }
    }
  }
}

concat

Returns a string consisting of the concatenation of the inputs. Each input is converted to a string as if by to-string.

["concat", value, value, ...]: string

downcase

Returns the input string converted to lowercase. Follows the Unicode Default Case Conversion algorithm and the locale-insensitive case mappings in the Unicode Character Database.

["downcase", string]: string

upcase

Returns the input string converted to uppercase. Follows the Unicode Default Case Conversion algorithm and the locale-insensitive case mappings in the Unicode Character Database.

["upcase", string]: string

Math

The expressions in this section are for applying mathematical operations.

In the example below, we retrieve the construction date, use the Math operation - to subtract the date from the current year, and then set a building_age property on each feature. We set 2020 as the default if the construction-date is missing.

{
  "version": 1,
  "layers": {
    "roads": {
      "source": "mapbox://tileset-source/{username}/building-data",
      "minzoom": 0,
      "maxzoom": 5,
      "features": {
        "attributes": {
          "set": {
            "building_age": ['-', 2020, ['number', ['get', construction-date], 2020]]
          }
        }
      }
    }
  }
}

-

For two inputs, returns the result of subtracting the second input from the first. For a single input, returns the result of subtracting it from 0.

["-", number, number]: number
["-", number]: number

*

Returns the product of the inputs.

["*", number, number, ...]: number

/

Returns the result of floating point division of the first input by the second.

["/", number, number]: number

%

Returns the remainder after integer division of the first input by the second.

["%", number, number]: number

^

Returns the result of raising the first input to the power specified by the second.

["^", number, number]: number

+

Returns the sum of the inputs.

["+", number, number, ...]: number

abs

Returns the absolute value of the input.

["abs", number]: number

acos

Returns the arccosine of the input.

["acos", number]: number

asin

Returns the arcsine of the input.

["asin", number]: number

atan

Returns the arctangent of the input.

["atan", number]: number

ceil

Returns the smallest integer that is greater than or equal to the input.

["ceil", number]: number

cos

Returns the cosine of the input.

["cos", number]: number

e

Returns the mathematical constant e.

["e"]: number

floor

Returns the largest integer that is less than or equal to the input.

["floor", number]: number

hash

Generates a unique integer from a string value. Particularly helpful for generating IDs from a combination of the feature attributes.

["hash", string]: number

ln

Returns the natural logarithm of the input.

["ln", number]: number

ln2

Returns mathematical constant ln(2).

["ln2"]: number

log10

Returns the base-ten logarithm of the input.

["log10", number]: number

log2

Returns the base-two logarithm of the input.

["log2", number]: number

max

Returns the maximum value of the inputs.

["max", number, number, ...]: number

min

Returns the minimum value of the inputs.

["min", number, number, ...]: number

pi

Returns the mathematical constant pi.

["pi"]: number

random

Generates a random integer between 0 and 2^53. This operator is used by default for any features that do not have an ID defined.

["random"]: number

round

Rounds the input to the nearest integer. Halfway values are rounded away from zero. For example, ["round", -1.5] evaluates to -2.

["round", number]: number

sin

Returns the sine of the input.

["sin", number]: number

sqrt

Returns the square root of the input.

["sqrt", number]: number

tan

Returns the tangent of the input.

["tan", number]: number

Zoom

The expressions in this section are for applying zoom specific filtering to your data.

In the example below, we use the zoom operation to only apply the match filters to data at zoom 6 or higher.

{
  "version": 1,
  "layers": {
    "road_network": {
      "source": "mapbox://tileset-source/username/roads",
      "minzoom": 0,
      "maxzoom": 14,
      "features": {
        "filter": [
          "any",
          [
            "all",
            [ ">=", [ "zoom" ], 6 ],
            [ "match", [ "get", "highway" ],
              "secondary", true, "motorway", true, false ]
          ]
        ]
      }
    }
  }
}

zoom

Gets the current zoom level.

["zoom"]: number

More resources