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.
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 modecameraForOverview: frames the remaining part 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.
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 work 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.
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 the rest of the current leg 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.
The frame will contain the remaining part 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.
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.
MapboxNavigationViewportDataSource initializes empty. Make sure to first provide the data to the viewport data source, check 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
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.