> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/android/navigation/ux/llms.txt)

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

> **Related content (guide): [Styles](https://docs.mapbox.com/android/maps/guides/styles/)**
> 
> Learn how Mapbox styles work and get started developing your map.

> **Related content (guide): [Style layers](https://docs.mapbox.com/android/maps/guides/styles/style-layers/)**
> 
> Learn how Mapbox layers work.

> **Related content (example): [Jetpack Compose Examples in Maps SDK](https://docs.mapbox.com/android/maps/examples/compose/)**
> 
> 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:

```kotlin
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`.

```kotlin
fragment.setMapLayer {
    middleSlot {
        FireHydrantsLayer()
    }

    topSlot {
        WeatherLayer()
    }
}
```

### Layer Implementation

#### Fire Hydrants Layer

```kotlin
@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

```kotlin
@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.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/android/android/assets/medias/dash-sdk-map-layers-22015a401214c47a19c8e4d623c4f0b2.mp4).