Using Style Functions at Runtime
Runtime Styling enables you to modify every aspect of the map’s appearance dynamically as a user interacts with your application. Much of the runtime styling API allows you to specify style functions instead of constant values. A style function allows you to specify in advance how a layout or paint attribute will vary as the zoom level changes or how the appearance of individual features vary based on metadata provided by a content source.
Style functions spare you the inconvenience of manually calculating intermediate values between different zoom levels or creating a multitude of style layers to handle homogeneous features in the map content. For example, if your content source indicates the prices of hotels in an area, you can color-code the hotels by price, relying on a style function to smoothly interpolate among desired colors without having to specify the color for each exact price.
Data-driven styling specifically refers to the use of style functions to vary the map’s appearance based on data in a content source.
You can also specify style functions in a style JSON file, to be applied automatically when the map loads. See the Mapbox Style Specification for details.
This guide uses earthquake data from the U.S. Geological Survey and data-driven styling to style a map based on attributes. For more information about how to work with GeoJSON data in our iOS SDK, please see our working with GeoJSON data guide.
A style function is represented at runtime by the MGLStyleFunction
class. There are three subclasses of MGLStyleFunction
:
MGLCameraStyleFunction
is a style value that changes with zoom level. For example, you can make the radius of a circle increase according to zoom level.MGLSourceStyleFunction
is a style value that changes with the attributes of a feature. For example, you can adjust the radius of a circle based on the magnitude of an earthquake.MGLCompositeStyleFunction
is a style value that changes with both zoom level and attribute values. For example, you can add a circle layer where each circle has a radius based on both zoom level and the magnitude of an earthquake.
The documentation for each individual style layer property notes which style functions are enabled for that property.
Stops
Stops are key-value pairs that that determine a style value. With a MGLCameraSourceFunction
stop, you can use a dictionary with a zoom level for a key and a MGLStyleValue
for the value. For example, you can use a stops dictionary with zoom levels 0, 10, and 20 as keys, and yellow, orange, and red as the values. A MGLSourceStyleFunction
uses the relevant attribute value as the key.
let stops = [
0: MGLStyleValue<NSColor>(rawValue: .yellow),
2.5: MGLStyleValue(rawValue: .orange),
5: MGLStyleValue(rawValue: .red),
7.5: MGLStyleValue(rawValue: .blue),
10: MGLStyleValue(rawValue: .white),
]
Interpolation mode
The effect a key has on the style value is determined by the interpolation mode. There are four interpolation modes that can be used with a source style function: exponential, interval, categorical, and identity. You can also use exponential and interval interpolation modes with a camera style function.
Linear
MGLInterpolationModeExponential
interpolates linearly or exponentially between style function stop values. By default, the MGLStyleFunction
options parameter MGLStyleFunctionOptionInterpolationBase
equals 1
, which represents linear interpolation and doesn’t need to be included in the options dictionary.
The stops dictionary below, for example, shows colors that continuously shift from yellow to orange to red to blue to white based on the attribute value.
let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
let symbolSource = MGLSource(identifier: "source")
let symbolLayer = MGLSymbolStyleLayer(identifier: "place-city-sm", source: symbolSource)
let source = MGLShapeSource(identifier: "earthquakes", url: url, options: nil)
mapView.style?.addSource(source)
let stops = [
0: MGLStyleValue<NSColor>(rawValue: .yellow),
2.5: MGLStyleValue(rawValue: .orange),
5: MGLStyleValue(rawValue: .red),
7.5: MGLStyleValue(rawValue: .blue),
10: MGLStyleValue(rawValue: .white),
]
let layer = MGLCircleStyleLayer(identifier: "circles", source: source)
layer.circleColor = MGLStyleValue(interpolationMode: .exponential,
sourceStops: stops,
attributeName: "mag",
options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .green)])
layer.circleRadius = MGLStyleValue(rawValue: 10)
mapView.style?.insertLayer(layer, below: symbolLayer)
Exponential
By combining MGLInterpolationModeExponential
with an MGLStyleFunctionOptionInterpolationBase
greater than 0
(other than 1
), you can interpolate between values exponentially, create an accelerated ramp effect.
Here’s a visualization from Mapbox Studio (see Working with Mapbox Studio) comparing interpolation base values of 1.5
and 0.5
based on zoom.
The example below increases a layer’s circleRadius
exponentially based on a map’s zoom level. The MGLStyleFunctionOptionInterpolationBase
is 1.5
.
let stops = [
12: MGLStyleValue<NSNumber>(rawValue: 0.5),
14: MGLStyleValue(rawValue: 2),
18: MGLStyleValue(rawValue: 18),
]
layer.circleRadius = MGLStyleValue(interpolationMode: .exponential,
cameraStops: stops,
options: [.interpolationBase: 1.5])
Interval
MGLInterpolationModeInterval
creates a range using the keys from the stops dictionary. The range is from the given key to just less than the next key. The attribute values that fall into that range are then styled using the style value assigned to that key.
When we use the stops dictionary given above with an interval interpolation mode, we create ranges where earthquakes with a magnitude of 0 to just less than 2.5 would be yellow, 2.5 to just less than 5 would be orange, and so on.
let stops = [
0: MGLStyleValue<NSColor>(rawValue: .yellow),
2.5: MGLStyleValue(rawValue: .orange),
5: MGLStyleValue(rawValue: .red),
7.5: MGLStyleValue(rawValue: .blue),
10: MGLStyleValue(rawValue: .white),
]
layer.circleColor = MGLStyleValue(interpolationMode: .interval,
sourceStops: stops,
attributeName: "mag",
options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .green)])
Categorical
At each stop, MGLInterpolationModeCategorical
produces an output value equal to the function input. We’re going to use a different stops dictionary than we did for the previous two modes.
There are three main types of events in the dataset: earthquakes, explosions, and quarry blasts. In this case, the color of the circle layer will be determined by the type of event, with a default value of blue to catch any events that do not fall into any of those categories.
let categoricalStops = [
"earthquake": MGLStyleValue<NSColor>(rawValue: .orange),
"explosion": MGLStyleValue(rawValue: .red),
"quarry blast": MGLStyleValue(rawValue: .yellow),
]
layer.circleColor = MGLStyleValue(interpolationMode: .categorical,
sourceStops: categoricalStops,
attributeName: "type",
options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .blue)])
Identity
MGLInterpolationModeIdentity
uses the attribute’s value as the style value. For example, you can set the circleRadius
to the earthquake’s magnitude. Since the attribute value itself will be used as the style value, sourceStops
should be set to nil
.
layer.circleRadius = MGLStyleValue(interpolationMode: .identity,
sourceStops: nil,
attributeName: "mag",
options: [.defaultValue: MGLStyleValue<NSNumber>(rawValue: 0)])