Tangram is designed to work with vector tiles in a number of formats. Data sources are specified in the sources
block of Tangram's scene file. Once a datasource is specified, filters allow you to style different parts of your data in different ways.
The Tangram scene file filters data in two ways: with top-level layer filters and lower-level feature filters.
Vector tiles typically contain top-level structures which can be thought of as "layers" – inside a GeoJSON file, these would be the FeatureCollection objects. Inside a Tangram scene file, the layers
object allows you to split the data by layer, by matching against the layer name.
layers:
my-roads-layer:
data:
source: osm
layer: roads
style: ...
Specifying layer: roads
in the data
block matches this GeoJSON object:
{"roads":
{"type":"FeatureCollection","features":[
{"geometry":"..."}
]}
}
If a layer
filter is not specified, Tangram will attempt to use the layer name as the filter. In this example, the layer name "roads" matches a layer in the data:
layers:
roads:
data:
source: osm
style: ...
Once a top-level layer
filter has been applied, feature-level filter
objects can be defined to further narrow down the data of interest and refine the styles applied to the data.
layers:
roads:
data: { source: osm }
style: ...
highway:
filter:
kind: highway
style: ...
Here, a top-level layer named "roads" matches the "roads" layer in the "osm" data source. It has a style
block, which will apply to all features in the "roads" layer unless it is overridden, functioning as a kind of "default" style.
Then, a sublayer named "highway" is declared, with its own filter
and style
. Its style
block will apply only to roads which match its filter
– in this case, those with the property "kind", with a value of "highway".
Higher-level filters continue to apply at lower levels, which means that higher-level styles will be inherited by lower levels, unless the lower level explicitly overrides it.
Using sublayers and inheritance, you may specify increasingly specific filters and styles to account for as many special cases as you like.
Each feature in a layer
is first tested against each top-level filter
, and if the feature's data matches the filter, that feature will be assigned any associated draw
styles, and passed on to any sublayers. If any sublayer filters match the feature, that sublayer's draw
styles will overwrite any previously-assigned styling rules for those matching features, and so on down the chain of inheritance.
Feature filters can match any named feature property in the data, as well as a few special reserved keywords.
Feature properties in a GeoJSON datasource are listed in a JSON member specifically named "properties":
{
"type": "Feature",
"id": "248156318",
"properties": {
"kind": "commercial",
"area": 12148,
"height": 63.4000000
}
Analogous property structures exist in other data formats such as TopoJSON and Mapbox Vector Tiles. Tangram makes these structures available to filter
blocks by property name, and also to any JavaScript filter functions under the feature
keyword.
The json feature above will match these two filters:
filter:
kind: commercial
filter: function() { return feature.kind == "commercial"; }
The simplest type of feature filter is a statement about one named property of a feature.
A filter can match an exact value:
filter:
kind: residential
any value in a list:
filter:
kind: [residential, commercial]
or a value in a numeric range:
filter:
area: { min: 100, max: 500 }
A Boolean value of "true" will pass a feature that contains the named property, ignoring the property's value. A value of "false" will pass a feature that does not contain the named property:
filter:
kind: true
area: false
To match a property whose value is a boolean, use the list syntax:
filter:
bridge: [true]
A feature filter can also evaluate one or more properties in a JavaScript function:
filter:
function() { return feature.area > 100000 }
For example, let's say we have a feature with a single property called "height":
{ "type":"Feature", "properties":{ "height":200 } }
This feature will match these filters:
filter: { height: 200 }
filter: { height: { max: 300 } }
filter: { height: true }
filter: { unicycle: false }
filter: function() { return feature.height >= 100; }
filter: function() { return true; }
and will not match these filters:
filter: { height: 100 }
filter: { height: { min: 300 } }
filter: { height: false }
filter: { unicycle: true }
filter: function() { return feature.height <= 100; }
filter: function() { return false; }
The keyword $geometry
matches the feature's geometry type, for cases when a FeatureCollection includes more than one type of kind of geometry. Valid geometry types are:
point
: matchesPoint
,MultiPoint
line
: matchesLineString
,MultiLineString
polygon
: matchesPolygon
,MultiPolygon
filter: { $geometry: polygon } # matches polygons only
filter: { $geometry: [point, line] } # matches points and lines, but not polygons
The keyword $layer
matches the feature's layer name, for cases when a data layer includes more than one source layer. In the case below, a data layer is created from two source layers, which can then be separated again by layer for styling:
labels:
data: { source: osm, layer: [places, pois] }
draw:
...
pois-only:
filter: { $layer: pois } # matches features from the "pois" layer only
draw:
...
The keyword $zoom
matches the current zoom level of the map. It can be used with the min
and max
functions.
filter: { $zoom: 14 }
filter: { $zoom: { min: 10 } } # matches zooms 10 and up
filter:
$zoom: { min: 12, max: 15 } # matches zooms 12-14
The filter functions min
and max
are equivalent to >=
and <
in a JavaScript function, and can be used in combination.
filter:
area: { max: 1000 } } # matches areas up to 1000 sq meters
filter:
height: { min: 70 } } # matches heights 70 and up
filter:
$zoom: { min: 5, max: 10 } # matches zooms 5-9
The following Boolean filter functions are also available:
not
any
all
none
(a combination ofnot
andany
)
not
takes a single filter object as its input:
filter:
not: { kind: restaurant }
filter:
not: { kind: [bar, pub] }
any
, all
, and none
take lists of filter objects:
filter:
all:
- { kind: museum }
- function() { return feature.area > 100000 }
filter:
any:
- { height: { min: 100 } }
- { name: true }
filter:
none:
- { kind: cemetery }
- { kind: graveyard }
- { kind: aerodrome }
A list of several filters is a shortcut for using the any
function. These two filters are equivalent:
filter: [ kind: minor_road, railway: true ]
filter:
any:
- kind: minor_road
- railway: true
A mapping of several filters is a shortcut for using the all
function. These two filters are equivalent:
filter: { kind: hamlet, $zoom: { min: 13 } }
filter:
all:
- kind: hamlet
- $zoom: { min: 13 }
In some cases, filters at the same level may return overlapping results:
roads:
data: { source: osm }
highway:
filter: { kind: highway }
draw: { lines: { color: red } }
bridges:
filter: { kind: bridge }
draw: { lines: { color: blue } }
In this case, "highways" are colored red, and "bridges" are blue. However, if any feature is both a "highway" and a "bridge", it will match twice. Because YAML maps are technically "orderless", there's no way to guarantee that one of these styles will consistently be shown over the other. The solution here is to restructure the styles so that each case matches explicitly:
roads:
highway:
filter: { kind: highway }
draw: { lines: { color: red } }
highway-bridges:
filter: { kind: bridge }
draw: { lines: { color: blue } }
other-bridges:
filter: { kind: bridge, not: { kind: highway} }
draw: { lines: { color: green } }