Navigation camera

The Mapbox Navigation SDK for Android includes a set of tools that let you combine the current user location information with the active route geometry to continuously track user movement and frame upcoming maneuvers.

In the Navigation SDK, there are two parts to managing the camera: specifying a data souce and passing that data to the navigation camera. The SDK's MapboxNavigationViewportDataSource produces the data in form of map camera targets, and NavigationCamera consumes and executes that data to determine the camera's behavior.

Mapbox Maps SDK for Android

This functionality uses the Mapbox Maps SDK for Android. When you install the Navigation SDK, the Maps SDK will also be installed. You do not need to install it separately.

Specify the data source

The Navigation SDK exposes a ViewportDataSource interface and its default implementation of MapboxNavigationViewportDataSource. The viewport data source produces two types of CameraOptions objects encapsulated in the ViewportData object:

  • cameraForFollowing: frames the user location in the following mode
  • cameraForOverview: frames the remaining portion of the route in the overview mode

Initialize data source

To initialize the default viewport data source, create an instance of MapboxNavigationViewportDataSource and pass it your map:

// initialize Viewport Data Source
viewportDataSource = MapboxNavigationViewportDataSource(mapboxMap)

Provide data

For the viewport data source to produce meaningful camera positions, you need to provide the location, route, or route progress data. Do this by calling a set of methods with available data updates.

For route updates, use RoutesObserver:

private val routesObserver = RoutesObserver { routes ->
    if (routes.isNotEmpty()) {
        viewportDataSource.onRouteChanged(routes.first())
        viewportDataSource.evaluate()
    } else {
        viewportDataSource.clearRouteData()
        viewportDataSource.evaluate()
    }
}

For location updates, use LocationObserver:

private val locationObserver = object : LocationObserver {
    override fun onRawLocationChanged(rawLocation: Location) {
        // not handled
    }
    override fun onEnhancedLocationChanged(
        enhancedLocation: Location,
        keyPoints: List<Location>
    ) {
        viewportDataSource.onLocationChanged(enhancedLocation)
        viewportDataSource.evaluate()
    }
}

For route progress updates, use RouteProgressObserver:

private val routeProgressObserver = RouteProgressObserver { routeProgress ->
    viewportDataSource.onRouteProgressChanged(routeProgress)
    viewportDataSource.evaluate()
}

Whenever you provide a new piece of data, remember to call MapboxNavigationViewportDataSource#evaluate so that the camera targets stored in the ViewportData are regenerated and relevant observers are called. With the continuous stream of latest location and routing data the class will be able to keep producing relevant camera targets to best frame the available geometries.

Initialize the viewport data source before usage

By itself, the MapboxNavigationViewportDataSource has no access to location or route data and will initialize at coordinates (0.0, 0.0). Make sure to manually initialize the source with the latest available user location update before executing any transitions.

The MapboxNavigationViewportDataSource can operate with and without a route object, both for free-drive and turn-by-turn navigation experiences. When the route reference is available, it will continuously change zoom level to frame the upcoming maneuver together with the user location. When there's no route, it will stay at a constant zoom level while tracking the user location.

Clear route reference when switching between types of navigation

Whenever an Active Guidance trip finishes, make sure to call MapboxNavigationViewportDataSource#clearRouteData and then MapboxNavigationViewportDataSource#evaluate to remove route and route progress context from camera targets calculations.

Customize the data source

The viewport data source is one of the main contributors to how your navigation experience will be presented on the map.

Padding

You can provide padding for both the following and overview frames, which will make sure that framed geometries will be presented at the designated space on the map and not overlap with other UI elements, for example:

val pixelDensity = context.resources.displayMetrics.density
viewportDataSource.followingPadding = EdgeInsets(
    180.0 * pixelDensity,
    40.0 * pixelDensity,
    150.0 * pixelDensity,
    40.0 * pixelDensity
)
viewportDataSource.overviewPadding = EdgeInsets(
    140.0 * pixelDensity,
    40.0 * pixelDensity,
    120.0 * pixelDensity,
    40.0 * pixelDensity
)

Make sure to use pixel-density adjusted padding values to have a consistent experience on different devices.

Overview frame

The padded overview frame will contain the entirety of the route provided via MapboxNavigationViewportDataSource#onRouteChanged (or its current leg's remainder if MapboxNavigationViewportDataSource#onRouteProgressChanged is also available) and any additional points you've specified using MapboxNavigationViewportDataSource#additionalPointsToFrameForOverview.

Following frame

For the following frame, the first point of the framed geometry list will be placed at the bottom edge of this padding, centered horizontally. This typically refers to the user's location provided via MapboxNavigationViewportDataSource#onLocationChanged, if available. The first points positioning can be influenced by FollowingFrameOptions.maximizeViewableGeometryWhenPitchZero when there are at least two points available for framing.

Control the position of the user location indicator on screen

When user location samples are provided to the viewport data source, you control the position of the user location indicator on screen by moving the bottom edge of the padding and changing the width of the padding.

Geometries below bottom edge of the padding

The focal point of the camera in following mode is placed on top of the user location indicator, which is positioned at the bottom edge of the padding so the SDK cannot frame any geometries that are below the bottom edge of the padding. Any geometries below the bottom edge are ignored from frame generation.

The frame will contain the remaining portion of the current LegStep of the route provided via MapboxNavigationViewportDataSource#onRouteChanged. If the RouteProgress is not available, the frame will focus on the location sample from MapboxNavigationViewportDataSource#onLocationChanged with FollowingFrameOptions.maxZoom zoom and FollowingFrameOptions.defaultPitch.

Options

You can adjust the way location and geometries are framed within the padding by changing the MapboxNavigationViewportDataSourceOptions, which are accessible via MapboxNavigationViewportDataSource#options. For example, to change the default pitch during following:

viewportDataSource.options.followingFrameOptions.defaultPitch = 35.0
viewportDataSource.evaluate()

To understand all possible camera behaviors, see the available properties for both FollowingFrameOptions and OverviewFrameOptions.

Set up the navigation camera

NavigationCamera is a class that simplifies management of the map's camera object in typical navigation scenarios. It ingests camera targets produced by the ViewportDataSource and executes map camera transitions to those targets.

example
Example of Navigation Camera integration

Learn how to integrate NavigationCamera together with the MapboxNavigationViewportDataSource and their data streams in a navigation app.

Initialize navigation camera

To initialize the navigation camera, create an instance of NavigationCamera and pass it your map, camera, and viewport data source:

// initialize Navigation Camera
navigationCamera = NavigationCamera(
    mapboxMap,
    binding.mapView.camera,
    viewportDataSource
)

Camera state

NavigationCamera maintains a state of any of the following:

enum class NavigationCameraState {
    IDLE,
    TRANSITION_TO_FOLLOWING,
    FOLLOWING,
    TRANSITION_TO_OVERVIEW,
    OVERVIEW
}

You can change the camera state at any time by calling either requestNavigationCameraToFollowing() or requestNavigationCameraToOverview(), and executing camera transitions.

navigationCamera.requestNavigationCameraToFollowing()
navigationCamera.requestNavigationCameraToOverview()

NavigationCamera does not produce any camera position values, the positions that the camera should transition to are generated by the ViewportDataSource interface. The default implementation of that interface is described above.

Provide data before executing transitions

The default MapboxNavigationViewportDataSource initializes empty. Make sure to first provide the data to the viewport data source, evaluate it, and only then request a following or overview state with NavigationCamera.

Once NavigationCamera is in a following or overview state, it will keep listening for updates from the viewport data source and execute transitions to new targets whenever the data source is evaluated.

To stop listening for updates, use requestNavigationCameraToIdle():

navigationCamera.requestNavigationCameraToIdle()

Debugging

Experimental feature

This feature is experimental and subject to change.

Use MapboxNavigationViewportDataSourceDebugger to attach a debugger to both MapboxNavigationViewportDataSource and NavigationCamera that will draw on top of the map the padding values and geometries that the viewport data source tries to frame.

val debugger = MapboxNavigationViewportDataSourceDebugger(
    context,
    mapView,
    layerAbove = "road-label"
).apply {
    enabled = true
}
viewportDataSource = MapboxNavigationViewportDataSource(
    mapView.getMapboxMap()
)
viewportDataSource.debugger = debugger
navigationCamera = NavigationCamera(
    mapView.getMapboxMap(),
    mapView.camera,
    viewportDataSource
)
navigationCamera.debugger = debugger

For the correct operation, the same exact instance of the MapboxNavigationViewportDataSourceDebugger has to be provided to both the MapboxNavigationViewportDataSource and NavigationCamera.