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.
Learn how Mapbox styles work and get started developing your map.
Learn how Mapbox layers work.
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:
- Authorization Rules: You can add authorization rules if you are working with layer sources that require additional or custom headers.
- 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:
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
.
fragment.setMapLayer {
middleSlot {
FireHydrantsLayer()
}
topSlot {
WeatherLayer()
}
}
Layer Implementation
Fire Hydrants Layer
@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
@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.