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 source 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 an offset from the center of this padding, specified by the FocalPoint
. 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.
Controlling user location indicator position on screen
When following frame is used and user location samples are provided to the viewport data source, you can control the position of the user location indicator on screen by:
- Changing the size of the padding and in consequence its center.
- Updating the focal point's x and y factors, where, for example,
(x=0, y=0)
positions the user location indicator in the top left of the padding,(x=0.5, y=0.5)
positions it in the center of the padding, and(x=1.0, y=1.0)
positions it in the bottom right of the padding. Read more on how to update theFocalPoint
in the Options section.
FocalPoint
's x
or y
components are set to either 0.0
or 1.0
, the user location indicator will be placed on edge of the padding. In this case, the SDK will ignore all geometries that are beyond that edge based on camera's bearing when computing the new camera position, because there's no space between the focal point and the edge of the padding to fit them.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
.