Work with layers
Add and update layers
You can use the Maps SDK to add more styled data to the map at runtime. There are two key concepts to understand when preparing to add a layer to a style at runtime: layers and sources. Sources contain geographic data. They determine the shape of the features you’re adding to the map and where in the world they belong. Layers contain styling information. They determine how the data in a source should look on the map.
A layer is a styled representation of data of a single type (for example polygons, lines, or points) that make up a map style. For example, roads, city labels, and rivers would be three separate layers in a map. There are several layer types (for example fill, line, and symbol). You can read more about layers in the Mapbox Style Specification.
Most layers also require a source. The source provides map data that the Maps SDK can use with a style document to render a visual representation of that data. There are several source types (for example vector tilesets, GeoJSON, and raster data). You can read more about sources in the Mapbox Style Specification.
In the Maps SDK, the Style
class exposes the entry point for all methods related to the style object including sources and layers.
Rendering order
Typically, layers are displayed in the order of their declaration, with later layers appearing on top of earlier ones as rendered by the SDK. When using the Mapbox Standard, the display order primarily depends on the layer's slot, while the relative order determines their arrangement within the slot. Layers not associated with slots appear above the entire map, also maintaining their relative order.
If the Globe projection or Terrain is active, the SDK optimizes performance by batching multiple layers together, which may cause layer rearrangements. Layers draped over the globe and terrain, including fill
, line
, background
, hillshade
, and raster
, are rendered below symbols, regardless of slot placement or the absence of a designated slot.
This means that layers placed with methods such as addLayerAbove()
, addLayerAt()
, and addLayerBelow()
might not appear at the expected layer position for some layer types. If your layer is not positioned in the expected place, try disabling terrain and changing the map's projection to Mercator.
Style domain-specific language
The Style domain-specific language (DSL) is a collection of functions that allows composition of a style in a block that is applied to a receiver class. DSL functions are provided to construct Style
, Layer
, Source
, Light
, and Expression
classes. The DSL function names match the class name being created, but the first character in the name is lowercase (for example, the DSL function for the CircleLayer
class is circleLayer
). In cases where there are mandatory constructor parameters of the class, assign the mandatory constructor parameters before the code block. Inside the DSL block, use code completion in Android Studio to find all available receiver functions.
With the Style DSL, authoring or updating map styles is like writing style JSON directly. The higher-level style API is exposed as DSL, allowing construction of a StyleExtension
object using the same paradigm as creating Layer
, Source
, and Light
classes using DSL and using the overloaded operator and inside the Style DSL closure to add layers, sources, or light to the StyleExtension
.
Below is an example using the Style DSL to add a GeoJsonSource
and a circle layer:
mapView.mapboxMap.subscribeMapLoadingError {
// Error occurred when loading the map, try to handle it gracefully here
}
mapView.mapboxMap.loadStyle(
style(style = Style.TRAFFIC_DAY) {
+geoJsonSource(id = "earthquakes") {
data(GEOJSON_URL)
cluster(false)
}
+circleLayer(layerId = "earthquakeCircle", sourceId = "earthquakes") {
circleRadius(get { literal("mag") })
circleColor(Color.RED)
circleOpacity(0.3)
circleStrokeColor(Color.WHITE)
}
}
) { style ->
// Map is set up and the style has loaded. Now you can add data or make other map adjustments.
}
val coroutineScope = rememberCoroutineScope()
val mapState = rememberMapState {
coroutineScope.launch {
mapLoadingErrorEvents.onEach {
// Error occurred when loading the map, try to handle it gracefully here
}
}
coroutineScope.launch {
styleLoadedEvents.first().let {
// Map is setup and style has loaded, Now you can add data or make other map adjustments.
}
}
}
MapboxMap(
modifier = Modifier.fillMaxSize(),
mapState = mapState
) {
CircleLayer(
sourceState = rememberGeoJsonSourceState {
data = GeoJSONData(GEOJSON_URL)
cluster = BooleanValue(false)
}
) {
circleRadius = DoubleValue(get { literal("mag") })
circleColor = ColorValue(Color.Red)
circleOpacity = DoubleValue(0.3)
circleStrokeColor = ColorValue(Color.White)
}
}
Sample code throughout this guide uses the Style DSL.
Add a layer at runtime
To add a new layer to the map at runtime, start by adding a source using the Style
’s addSource
method. It is important that you add the source for a new layer before attempting to add the layer itself because the source is a required parameter for most layer types.
Then, you’ll use the addLayer
method to add the layer to the style. When adding the style layer, you will specify:
- A unique ID that you assign to the new layer
- The layer type (for example fill, line, or symbol)
- What data to use by referencing a source
- The appearance of the data by setting various properties (for example color, opacity, and language)
The sample code below illustrates how to add a GeoJSON source and then add and style a line layer that uses the data in that source.
// Get the style
mapView.mapboxMap.getStyle { style ->
// Specify a unique string as the source ID (SOURCE_ID)
// and reference the location of source data
style.addSource(
geoJsonSource(SOURCE_ID) {
data("asset://from_crema_to_council_crest.geojson")
}
)
// Specify a unique string as the layer ID (LAYER_ID)
// and reference the source ID (SOURCE_ID) added above.
style.addLayer(
lineLayer(LAYER_ID, SOURCE_ID) {
lineColor(ContextCompat.getColor(context, R.color.black))
lineWidth(3.0)
}
)
}
MapboxMap(
modifier = Modifier.fillMaxSize(),
) {
LineLayer(
sourceState = rememberGeoJsonSourceState {
data = GeoJSONData("asset://from_crema_to_council_crest.geojson")
}
) {
lineColor = ColorValue(Color.Black)
lineWidth = DoubleValue(3.0)
}
}
The exact available properties available when adding a source and layer varies by source type and layer type. Read more about source types and layer types below.
Update a layer at runtime
You can also update the style of any layer at runtime using the layer's unique layer ID with Style
's getLayer
or getLayerAs
method and defining style properties.
The sample code below illustrates how to get an existing layer by referencing a layer ID and updating the value of the fillOpacity
's value.
Note for Jetpack Compose extension, you can drive layer properties for runtime-added layer through mutable state, and for layers from style JSON, you can use imperative API within a MapEffect
.
// Get the style
mapView.mapboxMap.getStyle { style ->
// Get an existing layer by referencing its
// unique layer ID (LAYER_ID)
val layer = style.getLayerAs<FillLayer>(LAYER_ID)
// Update layer properties
layer?.fillOpacity(0.7)
}
The exact available properties available when updating a layer varies by layer type. Read more about layer types below.
Specify order of a layer at runtime for Mapbox Standard and Standard Satellite
Mapbox Standard and Standard Satellite uses slots to specify where custom data layers can be added. Slots are predefined locations in the Standard and Standard Satellite basemaps where your layer can be inserted. To add custom layers in the appropriate location in the Standard basemap layer stack, Standard and Standard Satellite offers 3 carefully designed slots that you can leverage to place your layer. These slots will stay stable, and you can be sure that your own map won't break even as the basemap updates over time.
Slot | Description |
---|---|
bottom | Above polygons (land, landuse, water, etc.) |
middle | Above lines (roads, etc.) and behind 3D buildings |
top | Above POI labels and behind Place and Transit labels. Designed to be used with the symbol layers. |
not specified | Above all existing layers in the style |
Here’s an example of how to assign a slot to a layer:
+lineLayer(layerId = "line-layer", sourceId = "line-layer") {
lineColor(rgb(255.0, 165.0, 0.0))
slot("middle")
}
Make sure to use slots instead of layer ids when inserting a custom layer into the Standard or Standard Satellite basemap. If you want to order custom layers relative to each other, you can use the methods addLayerAbove
, addLayerAt
and addLayerBelow
, or the Style DSL method layerAtPosition
.
LayerPosition
is only applicable to custom layers you have added yourself. If you add two layers to the same slot with a specified layer position the latter will define the order of the layers in that slot.Specify order of a layer at runtime for other styles
Map styles contains many individual layers (for example, roads, buildings, labels, and more) that are stacked on top of each other. You can change the position of a layer at runtime using the moveStyleLayer
API. The sample code below illustrates how to move an existing layer with the ID "population"
below the layer with the ID "state-labels"
.
// Get the style
mapView.mapboxMap.getStyle { style ->
// Move position of the population layer
// below the state-labels layer
style.moveStyleLayer("population", LayerPosition(null, "state-labels", null))
}
By default, when you add a new layer to the style, it is placed on top of all the other layers. You can specify where the new layer is positioned relative to existing layers using layerAtPosition
and specify if the layer should be positioned below
or above
another layer by referencing the layer ID (a string) or at
a specific index (an integer).
// Get the style
mapView.mapboxMap.getStyle { style ->
style.addSource(
vectorSource("terrain-data") {
url("mapbox://mapbox.mapbox-terrain-v2")
}
)
style.addLayerBelow(
lineLayer("terrain-data", "terrain-data") {
sourceLayer("contour")
lineWidth(1.9)
},
below = "road-label"
)
}
MapboxMap(
Modifier.fillMaxSize(),
style = {
GenericStyle(
style = Style.MAPBOX_STREETS,
layerPositionedContent = layerPositionedContent {
belowLayer("road-label") {
LineLayer(
sourceState = rememberVectorSourceState {
url = StringValue("mapbox://mapbox.mapbox-terrain-v2")
}
) {
sourceLayer = StringValue("contour")
lineWidth = DoubleValue(1.9)
}
}
}
)
}
)
Remove a layer at runtime
You can remove a layer from a style using Style
's removeStyleLayer
.
// Where LAYER_ID is a valid id of a layer that already
// exists in the style
mapView.mapboxMap.getStyle { style ->
// If a layer with a given layer ID exists in the
// style, remove the layer
if (style.styleLayerExists(LAYER_ID)) {
style.removeStyleLayer(LAYER_ID)
}
}