Skip to main content

Route alerts

A newer version of the Navigation SDK is available
This page uses v1.6.2 of the Mapbox Navigation SDK. A newer version of the SDK is available. Learn about the latest version, v2.20.1, in the Navigation SDK documentation.

This example depends on other parts of the examples test app:

  • The utility function for setting the user's public Mapbox token.

  • A raw resource: mock_response_all_route_alerts_polyline6.json.

  • Four images: ic_circle_blue, ic_circle_red, ic_circle_green, ic_circle_yellow.

  • A second layout file: activity_replay_route_layout.xml.

    Note

    This example is a part of the Navigation SDK Examples. You can find the values for all referenced resources in the res directory. For example, see res/values/strings.xml for R.string.* references used in this example. The dependencies can be found here.The examples use View binding.See setup documention if necessary.

    activity_route_alerts
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".core.RouteAlertsActivity">

    <com.mapbox.mapboxsdk.maps.MapView
    android:id="@+id/mapView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

    <TextView
    android:id="@+id/distanceRemainingText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAlignment="textEnd"
    android:textColor="@android:color/holo_red_dark"
    android:textSize="18sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
    RouteAlertsActivity.kt
    package com.mapbox.navigation.examples.core

    import android.annotation.SuppressLint
    import android.graphics.Color
    import android.os.Bundle
    import androidx.annotation.ColorInt
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.content.ContextCompat
    import com.mapbox.api.directions.v5.models.DirectionsResponse
    import com.mapbox.api.directions.v5.models.DirectionsRoute
    import com.mapbox.core.constants.Constants
    import com.mapbox.geojson.Feature
    import com.mapbox.geojson.FeatureCollection
    import com.mapbox.geojson.LineString
    import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
    import com.mapbox.mapboxsdk.location.modes.RenderMode
    import com.mapbox.mapboxsdk.style.expressions.Expression
    import com.mapbox.mapboxsdk.style.layers.LineLayer
    import com.mapbox.mapboxsdk.style.layers.Property
    import com.mapbox.mapboxsdk.style.layers.PropertyFactory
    import com.mapbox.mapboxsdk.style.layers.PropertyValue
    import com.mapbox.mapboxsdk.style.layers.SymbolLayer
    import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
    import com.mapbox.navigation.base.trip.model.RouteProgress
    import com.mapbox.navigation.base.trip.model.alert.CountryBorderCrossingAlert
    import com.mapbox.navigation.base.trip.model.alert.IncidentAlert
    import com.mapbox.navigation.base.trip.model.alert.RestStopAlert
    import com.mapbox.navigation.base.trip.model.alert.RestStopType
    import com.mapbox.navigation.base.trip.model.alert.RestrictedAreaAlert
    import com.mapbox.navigation.base.trip.model.alert.RouteAlert
    import com.mapbox.navigation.base.trip.model.alert.RouteAlertType
    import com.mapbox.navigation.base.trip.model.alert.TollCollectionAlert
    import com.mapbox.navigation.base.trip.model.alert.TollCollectionType
    import com.mapbox.navigation.base.trip.model.alert.TunnelEntranceAlert
    import com.mapbox.navigation.base.trip.model.alert.toLineString
    import com.mapbox.navigation.core.MapboxNavigation
    import com.mapbox.navigation.core.replay.MapboxReplayer
    import com.mapbox.navigation.core.replay.ReplayLocationEngine
    import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
    import com.mapbox.navigation.core.trip.session.RouteAlertsObserver
    import com.mapbox.navigation.core.trip.session.RouteProgressObserver
    import com.mapbox.navigation.examples.R
    import com.mapbox.navigation.examples.utils.Utils
    import com.mapbox.navigation.ui.camera.NavigationCamera
    import com.mapbox.navigation.ui.internal.route.RouteConstants
    import com.mapbox.navigation.ui.map.NavigationMapboxMap
    import kotlinx.android.synthetic.main.activity_replay_route_layout.mapView
    import kotlinx.android.synthetic.main.activity_route_alerts.distanceRemainingText
    import kotlinx.android.synthetic.main.fragment_basic_navigation.*
    import kotlin.math.absoluteValue
    import kotlin.math.roundToInt

    class RouteAlertsActivity : AppCompatActivity() {

    private val directionsRoute: DirectionsRoute by lazy {
    val directionsResponseJson = resources.openRawResource(
    R.raw.mock_response_all_route_alerts_polyline6
    )
    .bufferedReader()
    .use { it.readText() }
    DirectionsResponse.fromJson(directionsResponseJson).routes()[0]
    }
    private val routeLineString: LineString by lazy {
    LineString.fromPolyline(directionsRoute.geometry()!!, Constants.PRECISION_6)
    }

    private val blueCircleImageId = "circle_blue_image"
    private val redCircleImageId = "circle_red_image"
    private val greenCircleImageId = "circle_green_image"
    private val yellowCircleImageId = "circle_yellow_image"
    private val blackCircleImageId = "circle_black_image"

    // tunnels
    private val tunnelSource = GeoJsonSource("tunnel_source")
    private val tunnelLayer = LineLayer("tunnel_layer", "tunnel_source")
    .withProperties(
    *generateLineLayerProperties(Color.DKGRAY)
    )

    private val tunnelNamesTextPropertyId = "tunnel_names_text_property"
    private val tunnelNamesSource = GeoJsonSource("tunnel_names_source")
    private val tunnelNamesLayer = SymbolLayer(
    "tunnel_names_layer",
    "tunnel_names_source"
    ).withProperties(
    *generateTunnelNamesLayerProperties(yellowCircleImageId, tunnelNamesTextPropertyId)
    )

    // country border crossings
    private val countryBorderCrossingsTextPropertyId = "country_border_crossings_text_property"
    private val countryBorderCrossingsSource = GeoJsonSource("country_border_crossings_source")
    private val countryBorderCrossingsLayer = SymbolLayer(
    "country_border_crossings_layer",
    "country_border_crossings_source"
    ).withProperties(
    *generateSymbolLayerProperties(blueCircleImageId, countryBorderCrossingsTextPropertyId)
    )

    // toll collection
    private val tollCollectionTextPropertyId = "toll_collections_text_property"
    private val tollCollectionSource = GeoJsonSource("toll_collections_source")
    private val tollCollectionLayer = SymbolLayer(
    "toll_collections_layer",
    "toll_collections_source"
    ).withProperties(
    *generateSymbolLayerProperties(redCircleImageId, tollCollectionTextPropertyId)
    )

    // rest stop
    private val restStopTextPropertyId = "rest_stops_text_property"
    private val restStopSource = GeoJsonSource("rest_stops_source")
    private val restStopLayer = SymbolLayer(
    "rest_stops_layer",
    "rest_stops_source"
    ).withProperties(
    *generateSymbolLayerProperties(greenCircleImageId, restStopTextPropertyId)
    )

    // tunnels
    private val restrictedAreasSource = GeoJsonSource("restricted_areas_source")
    private val restrictedAreasLayer = LineLayer(
    "restricted_areas_layer",
    "restricted_areas_source"
    ).withProperties(
    *generateLineLayerProperties(Color.RED)
    )

    // incidents
    private val incidentSource = GeoJsonSource("incident_source")
    private val incidentLayer = LineLayer(
    "incident_layer",
    "incident_source"
    ).withProperties(
    *generateLineLayerProperties(Color.rgb(255, 20, 147)) // deep pink
    )

    private val incidentNamesTextPropertyId = "incident_names_text_property"
    private val incidentNamesSource = GeoJsonSource("incident_names_source")
    private val incidentNamesLayer = SymbolLayer(
    "incident_names_layer",
    "incident_names_source"
    ).withProperties(
    *generateTunnelNamesLayerProperties(blackCircleImageId, incidentNamesTextPropertyId)
    )

    private val mapboxReplayer: MapboxReplayer by lazy {
    MapboxReplayer().apply {
    val replayEvents = ReplayRouteMapper().mapGeometry(directionsRoute.geometry()!!)
    pushEvents(replayEvents)
    seekTo(replayEvents.first())
    }
    }

    private var navigationMapboxMap: NavigationMapboxMap? = null
    private val mapboxNavigation: MapboxNavigation by lazy {
    val mapboxNavigationOptions = MapboxNavigation
    .defaultNavigationOptionsBuilder(this, Utils.getMapboxAccessToken(this))
    .locationEngine(ReplayLocationEngine(mapboxReplayer))
    .build()

    MapboxNavigation(mapboxNavigationOptions)
    }

    @SuppressLint("MissingPermission")
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_route_alerts)
    mapView.onCreate(savedInstanceState)

    mapView.getMapAsync { mapboxMap ->
    mapboxMap.moveCamera(CameraUpdateFactory.zoomTo(16.0))
    mapboxMap.moveCamera(CameraUpdateFactory.tiltTo(45.0))
    mapboxMap.setStyle(getString(R.string.mapbox_navigation_guidance_day)) { style ->
    navigationMapboxMap = NavigationMapboxMap.Builder(mapView, mapboxMap, this).build()
    mapboxNavigation.setRoutes(listOf(directionsRoute))
    navigationMapboxMap?.apply {
    updateLocationLayerRenderMode(RenderMode.GPS)
    updateCameraTrackingMode(NavigationCamera.NAVIGATION_TRACKING_MODE_GPS)
    addProgressChangeListener(mapboxNavigation)
    drawRoute(directionsRoute)
    startCamera(directionsRoute)
    mapboxReplayer.play()
    }

    style.addImage(
    blueCircleImageId,
    ContextCompat.getDrawable(this, R.drawable.ic_circle_blue)!!
    )
    style.addImage(
    redCircleImageId,
    ContextCompat.getDrawable(this, R.drawable.ic_circle_red)!!
    )
    style.addImage(
    greenCircleImageId,
    ContextCompat.getDrawable(this, R.drawable.ic_circle_green)!!
    )
    style.addImage(
    yellowCircleImageId,
    ContextCompat.getDrawable(this, R.drawable.ic_circle_yellow)!!
    )
    style.addImage(
    blackCircleImageId,
    ContextCompat.getDrawable(this, R.drawable.ic_circle_black)!!
    )
    style.addSource(tunnelSource)
    style.addLayerBelow(tunnelLayer, RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID)
    style.addSource(tunnelNamesSource)
    style.addLayerBelow(
    tunnelNamesLayer,
    RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID
    )
    style.addSource(countryBorderCrossingsSource)
    style.addLayerBelow(
    countryBorderCrossingsLayer,
    RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID
    )
    style.addSource(tollCollectionSource)
    style.addLayerBelow(
    tollCollectionLayer,
    RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID
    )
    style.addSource(restStopSource)
    style.addLayerBelow(restStopLayer, RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID)
    style.addSource(restrictedAreasSource)
    style.addLayerBelow(
    restrictedAreasLayer,
    RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID
    )
    style.addSource(incidentSource)
    style.addLayerBelow(incidentLayer, RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID)
    style.addSource(incidentNamesSource)
    style.addLayerBelow(
    incidentNamesLayer,
    RouteConstants.ARROW_SHAFT_CASING_LINE_LAYER_ID
    )
    }
    }
    mapboxNavigation.toggleHistory(true)
    mapboxNavigation.startTripSession()
    mapboxNavigation.registerRouteProgressObserver(
    object : RouteProgressObserver {
    @SuppressLint("SetTextI18n")
    override fun onRouteProgressChanged(routeProgress: RouteProgress) {
    // in this listener we're constantly updating
    // the distance to the start of the upcoming tunnel
    val upcomingTunnel = routeProgress
    .upcomingRouteAlerts
    .firstOrNull { it.routeAlert.alertType == RouteAlertType.TunnelEntrance }
    if (upcomingTunnel != null) {
    val distanceToStart = upcomingTunnel.distanceToStart.roundToInt()
    if (distanceToStart > 0) {
    distanceRemainingText.text =
    """
    |Distance to the nearest tunnel:
    |$distanceToStart meters
    """.trimMargin()
    } else {
    // if the distance to start is negative,
    // it means that we're inside of the tunnel
    distanceRemainingText.text =
    """
    |You're in the tunnel.
    |You've traveled ${distanceToStart.absoluteValue} meters since entry.
    """.trimMargin()
    }
    } else {
    distanceRemainingText.text = ""
    }
    }
    }
    )

    mapboxNavigation.registerRouteAlertsObserver(
    object : RouteAlertsObserver {
    override fun onNewRouteAlerts(routeAlerts: List<RouteAlert>) {
    // in this part of the example we're listening for the full list of alerts
    // whenever a new route is set and marking all of the tunnels on the map
    val tunnelFeatures = mutableListOf<Feature>()
    val tunnelNamesFeatures = mutableListOf<Feature>()
    val countryBorderCrossingsFeatures = mutableListOf<Feature>()
    val tollCollectionFeatures = mutableListOf<Feature>()
    val restStopsFeatures = mutableListOf<Feature>()
    val restrictedAreasFeatures = mutableListOf<Feature>()
    val incidentsFeatures = mutableListOf<Feature>()
    val incidentsNamesFeatures = mutableListOf<Feature>()

    // when received, we're filling up features for all alert types
    routeAlerts.forEach { routeAlert ->
    when (routeAlert) {
    is TunnelEntranceAlert -> {
    val alertGeometry = routeAlert.alertGeometry
    if (alertGeometry != null) {
    val tunnelLineString =
    alertGeometry.toLineString(routeLineString)
    tunnelFeatures.add(Feature.fromGeometry(tunnelLineString))
    routeAlert.info?.name.let {
    val feature = Feature.fromGeometry(routeAlert.coordinate)
    feature.addStringProperty(
    tunnelNamesTextPropertyId,
    it
    )
    tunnelNamesFeatures.add(feature)
    }
    } else {
    throw IllegalArgumentException("missing tunnel geometry")
    }
    }
    is CountryBorderCrossingAlert -> {
    val from = routeAlert.from!!.codeAlpha3
    val to = routeAlert.to!!.codeAlpha3
    val feature = Feature.fromGeometry(routeAlert.coordinate)
    feature.addStringProperty(
    countryBorderCrossingsTextPropertyId,
    "$from -> $to"
    )
    countryBorderCrossingsFeatures.add(feature)
    }
    is TollCollectionAlert -> {
    val typeString = when (routeAlert.tollCollectionType) {
    TollCollectionType.TollGantry -> {
    "toll gantry"
    }
    TollCollectionType.TollBooth -> {
    "toll booth"
    }
    TollCollectionType.Unknown -> {
    "unknown"
    }
    else -> {
    throw IllegalArgumentException(
    "unknown toll collection point type"
    )
    }
    }
    val feature = Feature.fromGeometry(routeAlert.coordinate)
    feature.addStringProperty(
    tollCollectionTextPropertyId,
    typeString
    )
    tollCollectionFeatures.add(feature)
    }
    is RestStopAlert -> {
    val typeString = when (routeAlert.restStopType) {
    RestStopType.RestArea -> {
    "rest area"
    }
    RestStopType.Unknown -> {
    "unknown"
    }
    else -> {
    throw IllegalArgumentException(
    "unknown rest stop type"
    )
    }
    }
    val feature = Feature.fromGeometry(routeAlert.coordinate)
    feature.addStringProperty(
    restStopTextPropertyId,
    typeString
    )
    restStopsFeatures.add(feature)
    }
    is RestrictedAreaAlert -> {
    val alertGeometry = routeAlert.alertGeometry
    if (alertGeometry != null) {
    val restrictedAreaLineString =
    alertGeometry.toLineString(routeLineString)
    restrictedAreasFeatures.add(
    Feature.fromGeometry(restrictedAreaLineString)
    )
    } else {
    throw IllegalArgumentException(
    "missing restricted area geometry"
    )
    }
    }
    is IncidentAlert -> {
    val alertGeometry = routeAlert.alertGeometry
    if (alertGeometry != null) {
    val incidentAreaLineString =
    alertGeometry.toLineString(routeLineString)
    incidentsFeatures.add(
    Feature.fromGeometry(incidentAreaLineString)
    )
    }
    routeAlert.info?.subType?.let { incidentType ->
    val feature = Feature.fromGeometry(routeAlert.coordinate)
    feature.addStringProperty(
    incidentNamesTextPropertyId,
    incidentType
    )
    incidentsNamesFeatures.add(feature)
    }
    }
    }
    }

    // update all sources
    tunnelSource.setGeoJson(FeatureCollection.fromFeatures(tunnelFeatures))
    tunnelNamesSource.setGeoJson(
    FeatureCollection.fromFeatures(tunnelNamesFeatures)
    )
    countryBorderCrossingsSource.setGeoJson(
    FeatureCollection.fromFeatures(countryBorderCrossingsFeatures)
    )
    tollCollectionSource.setGeoJson(
    FeatureCollection.fromFeatures(tollCollectionFeatures)
    )
    restStopSource.setGeoJson(
    FeatureCollection.fromFeatures(restStopsFeatures)
    )
    restrictedAreasSource.setGeoJson(
    FeatureCollection.fromFeatures(restrictedAreasFeatures)
    )
    incidentSource.setGeoJson(
    FeatureCollection.fromFeatures(incidentsFeatures)
    )
    incidentNamesSource.setGeoJson(
    FeatureCollection.fromFeatures(incidentsNamesFeatures)
    )
    }
    }
    )
    }

    private fun generateSymbolLayerProperties(
    imageId: String,
    textId: String
    ): Array<PropertyValue<out Any>> = arrayOf(
    PropertyFactory.iconImage(imageId),
    PropertyFactory.iconSize(3f),
    PropertyFactory.textField(
    Expression.get(Expression.literal(textId))
    ),
    PropertyFactory.textAnchor(Property.TEXT_ANCHOR_BOTTOM_LEFT),
    PropertyFactory.iconAllowOverlap(true),
    PropertyFactory.iconIgnorePlacement(true)
    )

    private fun generateTunnelNamesLayerProperties(
    imageId: String,
    textId: String
    ): Array<PropertyValue<out Any>> = arrayOf(
    PropertyFactory.iconImage(imageId),
    PropertyFactory.iconSize(3f),
    PropertyFactory.textField(
    Expression.get(Expression.literal(textId))
    ),
    PropertyFactory.textAnchor(Property.TEXT_ANCHOR_BOTTOM_RIGHT),
    PropertyFactory.iconAllowOverlap(true),
    PropertyFactory.iconIgnorePlacement(true),
    PropertyFactory.textAllowOverlap(true),
    PropertyFactory.textIgnorePlacement(true)
    )

    private fun generateLineLayerProperties(
    @ColorInt color: Int
    ): Array<PropertyValue<out Any>> =
    arrayOf(
    PropertyFactory.lineColor(color),
    PropertyFactory.lineWidth(10f)
    )

    override fun onStart() {
    super.onStart()
    mapView.onStart()
    }

    public override fun onResume() {
    super.onResume()
    mapView.onResume()
    }

    public override fun onPause() {
    super.onPause()
    mapView.onPause()
    }

    override fun onStop() {
    super.onStop()
    mapView.onStop()
    }

    override fun onDestroy() {
    super.onDestroy()
    mapView.onDestroy()
    mapboxReplayer.finish()
    mapboxNavigation.stopTripSession()
    }

    override fun onLowMemory() {
    super.onLowMemory()
    mapView.onLowMemory()
    }
    }