Location tracking

The Mapbox Navigation SDK uses raw device location to power important navigation features including:

  • Enhancing location updates and snapping them to the road graph.
  • Determining which instruction to show next, and when to show it, based on where the user is along the route.
  • Detecting when the driver has deviated from the current route, prompting the SDK to generate a new route to get them back on track.
  • Proactively downloading map tiles and routing information ahead of the driver's current location in case the driver enters an area with no or poor network connection.

The Navigation SDK depends on two other Mapbox SDKs, the Mapbox Android Core library and the Maps SDK for Android, to handle location permissions, track location updates, and show device location on a map. These SDKs are bundled with the Navigation SDK, and you do not need to install them separately.

Privacy and permissions

Users must grant your application permission before it can access information about their location. During this permission prompt, you can present a custom string explaining how location will be used.

If you're building your Android project targeting API level 23 or higher, your application will need to request permissions at runtime. Handling this directly in your activity produces boilerplate code and can often be hard to manage. One option is to use the Mapbox Core Library for Android's PermissionsManager class. When using the Mapbox Core Library, you can specify the text used in the permission prompt using onExplanationNeeded.

Location provider

By default, the Navigation SDK utilizes the Mapbox Core Library for Android's LocationEngine class to fetch the user's location. The LocationEngine class simplifies the process of getting location information and supports the following location providers:

  • Google's Fused Location Providers
  • Android GPS and Network Providers
Use Google's Fused Location Provider

By default, the Navigation SDK uses the Android GPS and Network Providers to obtain raw location updates. In applications using Android 11, the raw location updates might suffer from precision issues.

The Navigation SDK also comes pre-compiled with support for the Google's Fused Location Provider. If your target devices support Google Play Services, add the following Google Play Location Services dependency to your project, and the Navigation SDK will use the Google's Fused Location Provider in your application automatically:

implementation("com.google.android.gms:play-services-location:18.0.0")
More on LocationEngine

For more details on how LocationEngine works, see the LocationEngine documentation.

Enhanced location updates

To receive location updates while the trip session is running, you can subscribe to the LocationObserver. This will allow you to listen for two types of location updates:

  • Raw location (onNewRawLocation): Invoked as soon as a new Location has been received.
  • Enhanced location (onNewLocationMatcherResult): Invoked when a new Location is snapped to the route or map matched to the road.
private val locationObserver = object : LocationObserver {

    override fun onNewRawLocation(rawLocation: Location) {
        ...
    }

    override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
        ...
    }
}

Register the LocationObserver interface with your already-instantiated MapboxNavigation object.

mapboxNavigation.registerLocationObserver(locationObserver)

Don’t forget to unregister the LocationObserver interface.

override fun onStop() {
    super.onStop()
    mapboxNavigation.unregisterLocationObserver(locationObserver)
}

onNewRawLocation callback

The onNewRawLocation method delivers a raw Location object from the developer-provided or default internal LocationEngine.

onNewLocationMatcherResult callback

The onNewLocationMatcherResult method provides the most accurate location update possible. The location is snapped to the route, or if possible, to the road network.

This Navigation SDK enhanced location logic helps to solve common navigation issues such as:

  • Loss of location coordinate precision in cities with tall buildings
  • Frequency issues
  • Loss of signal in tunnels

If the Navigation SDK isn't able to compute a better Location update, the raw Location will be returned.

locationMatcherResult provides details about the status of the enhanced location. keyPoints is a potentially empty list of predicted Location coordinates that lead up to the latest Location update coordinate. If the list isn't empty, the last coordinate in the list is always equal to the enhancedLocation object.

These coordinates are snapped to a route or road. This is especially helpful for making sure the device location puck doesn't cut intersection corners as it traverses through a turn maneuver.

Location puck on the map

If your application includes a map, you can display the user location puck on the map using the Mapbox Maps SDK's user location component. The plugin can display both a 2D and 3D puck.

Mapbox Maps SDK

For more details on how to work with the Maps SDK, see the Maps SDK documentation.

After you've added a MapView to your application, create the NavigationLocationProvider utility class, which helps manage connecting the Navigation SDK and the Maps SDK to display the user location puck:

val navigationLocationProvider = NavigationLocationProvider()

Then, activate the Maps SDK's user location component, set the puck drawable (included with the Navigation SDK), and set the location data provider:

mapView.location.apply {
    this.locationPuck = LocationPuck2D(
        bearingImage = ContextCompat.getDrawable(
            this@MainActivity,
            R.drawable.mapbox_navigation_puck_icon
        )
    )
    setLocationProvider(navigationLocationProvider)
    enabled = true
}

Once the puck is initialized, you can start passing the enhanced location updates to the puck and render the updated location on the map:

private val locationObserver = object : LocationObserver {

    override fun onNewRawLocation(rawLocation: Location) {
        ...
    }

    override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
        val transitionOptions: (ValueAnimator.() -> Unit) =
            {
                duration = 1000
            }
        navigationLocationProvider.changePosition(
            location = locationMatcherResult.enhancedLocation,
            keyPoints = locationMatcherResult.keyPoints,
            latLngTransitionOptions = transitionOptions,
            bearingTransitionOptions = transitionOptions
        )
    }
}
example
Render current location on a map

Use NavigationLocationProvider to show a device's current location as a puck on a map.

Synchronize with the camera

For linear updates once the SDK sets the camera to a following state, keep the animation duration between the puck animation and the camera animation equal so they stay in sync.

By default, the NavigationCamera uses a duration of 1 second. To keep the puck in sync, use 1 second for the puck transitionOptions as well.

Custom location engine

By default, the Navigation SDK uses a LocationEngine that comes from LocationEngineProvider.getBestLocationEngine(context), but you can override this default. This allows you to provide locations from automobiles, scooters, simulations, games, or your own version of Android's location provider.

Custom location updates have a monotontic-time requirement

The Locations provided by the LocationEngine must override getElapsedRealtimeNanos() with SystemClock.elapsedRealtimeNanos. This specific clock improves the location accuracy.

You can pass the LocationEngine with the NavigationOptions:

val navigationOptions = NavigationOptions.Builder(context)
    .accessToken("{your_access_token}")
    .locationEngine(customLocationEngine)
    .build()
val mapboxNavigation = MapboxNavigationProvider.create(navigationOptions)

Location engine request

You can create your own location engine request if you want precise control over Location querying and receiving Location updates in your project.

First, create a LocationEngineRequest to specify parameters such as the querying frequency or preferred accuracy:

val customLocationEngineRequest = LocationEngineRequest.Builder(DESIRED_INTERVAL_BETWEEN_LOCATION_UPDATES_IN_MILLISECONDS)
    .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
    .setMaxWaitTime(DEFAULT_MAX_WAIT_TIME) // sets the maximum wait time in milliseconds for location updates. Locations determined at intervals but delivered in batch based on wait time. Batching is not supported by all engines.
    .build()

val navigationOptions = NavigationOptions.Builder(context)
    .accessToken("{your_access_token}")
    .locationEngine(customLocationEngine)
    .locationEngineRequest(customLocationEngineRequest)
    .build()
val mapboxNavigation = MapboxNavigationProvider.create(navigationOptions)

Raw location updates when the trip session is stopped

If you still want to receive location updates when the trip session is stopped, create a custom class that implements the LocationEngineCallback interface. This interface comes from Mapbox's Core library for Android:

private class CustomLocationEngineCallback(activity: Activity) :
    LocationEngineCallback<LocationEngineResult> {

    private val activityRef = WeakReference(activity)

    override fun onSuccess(result: LocationEngineResult?) {
        // for example, push a location update to the Map Location Plugin and the NavigationLocationProvider here 
    }

    override fun onFailure(exception: Exception) {

    }
}

Create an instance of the custom callback class:

val customLocationEngineCallback = CustomLocationEngineCallback(activity)

Start requesting Location updates now that you have your LocationEngineRequest and CustomLocationEngineCallback objects:

locationEngine.requestLocationUpdates(
    locationEngineRequest,
    customLocationEngineCallback,
    mainLooper // returns your project's main looper, which lives in your project's main thread.
)
Obtain a location engine instance

To create a location engine instance you can implement the LocationEngine interface.

val locationEngine = LocationEngineProvider.getBestLocationEngine(context)

Make sure to remove location updating:

override fun onStop() {
    super.onStop()
    locationEngine.removeLocationUpdates(locationListenerCallback)
}