Skip to content

Latest commit

 

History

History
298 lines (228 loc) · 8.79 KB

Filters-Overview.md

File metadata and controls

298 lines (228 loc) · 8.79 KB

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.

Layer 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: ...

Feature filters

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".

Inheritance

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.

Matching

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

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; }

Keyword properties

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: matches Point, MultiPoint
  • line: matches LineString, MultiLineString
  • polygon: matches Polygon, 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

Filter functions

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 of not and any)

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 }

Lists imply any, Mappings imply all

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 }

Matching collisions

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 } }