Skip to main content

Map layers

The Navigation UX Framework (UXF) allows you to add an unlimited number of custom layers to a map and manage their visibility. The framework uses Jetpack Compose for building the UI and Mapbox as the underlying map engine.

GUIDE
Styles

Learn how Mapbox styles work and get started developing your map.

GUIDE
Style layers

Learn how Mapbox layers work.

EXAMPLE
Jetpack Compose Examples in Maps SDK

A set of examples for working with maps using Compose extensions.

Key Features

  • Custom Layers: Add custom layers to the map.
  • Visibility Management: Control the visibility of each layer independently.
  • Compose Integration: Built using Jetpack Compose.

Example Use Case

Suppose you need to add two custom layers. You can achieve this using the setMapLayer function in DashNavigationFragment.

To add layers, use the setMapLayer function, which accepts a MapLayerComposer. The MapLayerComposer is a lambda function that provides access to DashMapLayerScope.

DashMapLayerScope

DashMapLayerScope gives you several important capabilities:

  1. Authorization Rules: You can add authorization rules if you are working with layer sources that require additional or custom headers.
  2. Layer Slots: You can assign layers to different slots:
  • topSlot: Content for the top slot of the Mapbox Standard style.
  • middleSlot: Content for the middle slot of the Mapbox Standard style.
  • bottomSlot: Content for the bottom slot of the Mapbox Standard style.

Each slot accepts a lambda content: @Composable @MapboxMapComposable () -> Unit.

Example Code

Let’s break this down with code. First, we’ll add authorization for retrieving hurricane data:

CustomNavigationFragment.kt
addAuthRule(
RequestAuthorizationRule(
urlMatcher = Regex("https://cdn\\.weather\\.com(/.*)?"),
headers = mapOf(
"Cache-Control" to "no-cache",
"api-key" to "custom-api-key"
)
)
)

Here, urlMatcher is defined as a Regex, and you can add multiple addAuthRule entries to configure various rules.

Adding Hurricane and Fire Hydrant Layers

We’ll now add layers for hurricanes and fire hydrants. The weather data will go in the topSlot, and fire hydrant data will go in the middleSlot.

CustomNavigationFragment.kt
fragment.setMapLayer {
middleSlot {
FireHydrantsLayer()
}

topSlot {
WeatherLayer()
}
}

Layer Implementation

Fire Hydrants Layer

FireHydrantsLayer.kt
@SuppressLint("IncorrectNumberOfArgumentsInExpression")
@Composable
@MapboxMapComposable
fun FireHydrantsLayer() {
val day = rememberStyleImage(imageId = "fire-station-day", resourceId = R.drawable.ic_fire_extinguisher_day)
val night = rememberStyleImage(imageId = "fire-station-night", resourceId = R.drawable.ic_fire_extinguisher_night)

MapEffect(Unit) {
it.mapboxMap.style?.let { style ->
style.addImage(day.imageId, day.image)
style.addImage(night.imageId, night.image)
}
}

val symbolTextColor = AppTheme.colors.textColor.accent

SymbolLayer(
sourceState = rememberGeoJsonSourceState {
data = GeoJSONData(POINTS_URL)
},
) {
iconImage = ImageValue(
expression = switchCase {
boolean {
gt { get("MEASURE"); literal(2160.0) }
literal(false)
}
literal(night.imageId)
literal(day.imageId)
}
)
textField = FormattedValue(
expression = switchCase {
boolean {
gt { get("MEASURE"); literal(2160.0) }
literal(false)
}
literal("▲")
literal("▼")
}
)
iconSize = DoubleValue(0.25)
iconAnchor = IconAnchorValue.BOTTOM
textColor = ColorValue(symbolTextColor)
textColorTransition = Transition(durationMillis = 1000)
textSize = DoubleValue(
Expression.interpolate {
linear()
zoom()
stop { literal(0.0); literal(10.0) }
stop { literal(10.0); literal(20.0) }
}
)

interactionsState.onClicked { interactiveFeature, _ ->
Log.d(TAG) { "Feature clicked: ${interactiveFeature.id}" }
interactiveFeature.setFeatureState(FeatureState { addBooleanState("active", true) })
true
}
}
}

In this layer, we use a SymbolLayer with remote GeoJSONData containing the fire hydrants' locations. The MapEffect block loads day and night icons into the style.

Weather Layer

WeatherLayer.kt
@Composable
@MapboxMapComposable
fun WeatherLayer() {
val bitmaps = listOf(
ImageBitmap.imageResource(R.drawable.southeast_radar_0).asAndroidBitmap(),
ImageBitmap.imageResource(R.drawable.southeast_radar_1).asAndroidBitmap(),
ImageBitmap.imageResource(R.drawable.southeast_radar_2).asAndroidBitmap(),
ImageBitmap.imageResource(R.drawable.southeast_radar_3).asAndroidBitmap()
)

MapEffect(Unit) {
val imageSource: ImageSource = it.mapboxMap.getSourceAs(ID_IMAGE_SOURCE)!!
var index = 0
while (true) {
imageSource.updateImage(bitmaps[index])
delay(1000)
index = (index + 1) % 4
}
}

RasterLayer(
sourceState = rememberImageSourceState(sourceId = ID_IMAGE_SOURCE) {
coordinates = PointListValue(
Point.fromLngLat(-81.98844, 43.150429),
Point.fromLngLat(-73.07944, 43.150429),
Point.fromLngLat(-73.07944, 34.649429),
Point.fromLngLat(-81.98844, 34.649429)
)
}
)
}

For the WeatherLayer, we use a RasterLayer with an animation in MapEffect to simulate moving clouds by cycling through radar images.

This example demonstrates how you can add layers to the map using the Navigation UX Framework (UXF), load your custom resources, and configure them for reuse in different layers.

Was this page helpful?