Offline

There are two main kinds of offline functionality: predictive caching and offline regions. Predictive caching is fully automatic and based on the driver’s behavior. It allows offline navigation around the user’s current location, the destination, and the route itself. Offline regions need to be created and loaded ahead of time, enabling routing functionality in non-connected environments.

Predictive caching

To use predictive caching, you will need to initialize a PredictiveCacheController and, optionally, configure a radius and cache size. Once these items are configured, the Navigation SDK will automatically create caches and make them available to the user.

In order to start caching map data, call createMapControllers with a MapboxMap instance and, optionally, pass a list of source IDs to specify sources to cache. Sources must be hosted on Mapbox, and the source ID must start with “mapbox://” (for example, “mapbox://mapbox.mapbox-terrain-v2”). If you do not pass any IDs to createMapControllers, all available style sources will be cached by default. There can be multiple instances of the map caching resources at a time.

predictiveCacheController = new PredictiveCacheController(predictiveCacheLocationOptions, predictiveCacheControllerErrorHandler);
List<String> sourceIdsToCache = new ArrayList<>();
sourceIdsToCache.add("mapbox://mapbox.mapbox-terrain-v2");
// cache only passed sources for the mapboxMap instance
predictiveCacheController.createMapControllers(mapboxMap, sourceIdsToCache);
// cache all sources for the anotherMapboxMap instance
predictiveCacheController.createMapControllers(anotherMapboxMap);

Lifecycle

The controller and its MapboxNavigation instance have a different lifecycle than the MapboxMap instance, so make sure to call removeMapControllers whenever the MapView is destroyed to avoid leaking references or downloading unnecessary resources.

predictiveCacheController.removeMapControllers(mapboxMap);

When the navigation session is finished and predictive caching is no longer needed, call onDestroy to remove all map and navigation references.

@Override
protected void onDestroy() {
super.onDestroy();
if (predictiveCacheController != null) {
predictiveCacheController.onDestroy();
}
mapView.onDestroy();
mapboxNavigation.onDestroy();
}

Configure predictive caching

When predictive caching is enabled, the Navigation SDK will create a cache of data within three configurable boundaries, each with its own default value:

  1. Radius around the user's location. Defaults to 2,000 meters. Use currentLocationRadiusInMeters to configure this value.
  2. Buffer around the route. Defaults to 500 meters. Use routeBufferRadiusInMeters to configure this value.
  3. Radius around the destination. Defaults to 5,000 meters. Use destinationLocationRadiusInMeters to configure this value.

To configure predictive caching, pass values, in meters, to any of the PredictiveCacheLocationOptions.

mapboxNavigation = new MapboxNavigation(
new NavigationOptions.Builder(this)
.accessToken(getMapboxAccessTokenFromResources())
.predictiveCacheLocationOptions(
new PredictiveCacheLocationOptions.Builder()
.currentLocationRadiusInMeters(50000)
.routeBufferRadiusInMeters(30000)
.destinationLocationRadiusInMeters(30000)
.build()
)
.build()
);

When predictive caching is enabled, the Navigation SDK will automatically stop caching data when a given cache size limit is reached. The default cache size limit is 50MB, but can be configured with cacheSize with ResourceOptions.

MapboxMapOptions mapboxMapOptions = new MapboxMapOptions(this, getResources().getDisplayMetrics().density, null);
ResourceOptions resourceOptions = new ResourceOptions.Builder()
.accessToken(getMapboxAccessTokenFromResources())
.assetPath(getFilesDir().getAbsolutePath())
.cachePath(getFilesDir().getAbsolutePath() + "/mbx.db")
.cacheSize(100_000_000L) // 100 MB
.tileStorePath(getFilesDir().getAbsolutePath() + "/maps_tile_store/")
.build();
mapboxMapOptions.setResourceOptions(resourceOptions);
mapView = new MapView(this, mapboxMapOptions);
// ...
predictiveCacheController = new PredictiveCacheController(
new PredictiveCacheLocationOptions.Builder().build(),
message -> {
Log.e(TAG, "predictive cache error: " + message);
}
);
predictiveCacheController.createMapControllers(mapboxMap);

Estimate predictive caching data use

Data usage depends on the configured radius and on map style. For the Mapbox Streets style, cache size is:

  • Berlin, 15km radius: 136 MB maps, 42 MB navigation
  • Chicago, 15km radius: 74 MB maps, 43 MB navigation
  • London, 15km radius: 145 MB maps, 90 MB navigation
  • Atlanta, 100 miles radius: 659 MB maps, 156 MB navigation

Offline regions

TileStore allows users to create and download offline regions ahead of time, enabling routing functionality in non-connected environments. In areas with no cellular connectivity, or on a device with no SIM card, your users can use turn-by-turn navigation and request new routes. If they go off-route, the system can reroute and keep them headed to their destination without requiring network connectivity. TileStore copies the routing data from the server onto the user’s device, so there’s no need to make HTTP API calls for routing information.

TileStore and the Maps SDK

TileStore handles offline regions data for both navigation data for generating routes and map tiles for displaying a map in your application. Find more details on how to use TileStore for map data in the Maps SDK documentation.

Initialize TileStore instance

To initialize a TileStore instance, provide a path to a cache directory where tiles will be stored and access tokens for both maps and navigation.

TileStore tileStore = TileStore.create("path/to/cache/directory/");
tileStore.setOption(
TileStoreOptions.MAPBOX_ACCESS_TOKEN,
TileDataDomain.MAPS,
new Value("mapToken")
);
tileStore.setOption(
TileStoreOptions.MAPBOX_ACCESS_TOKEN,
TileDataDomain.NAVIGATION,
new Value("navToken")
);

Pass TileStore instance to maps and navigation

After the TileStore instance is initialized, pass it to both MapboxNavigation and MapView via the corresponding options.

Note

For both offline and predictive-caching use cases, you must pass the same TileStore instance to the Maps SDK's ResourceOptions and the Navigation SDK's RoutingTilesOptions. This will guarantee the same tile regions will be used for both maps-related and navigation-related data.

Pass the TileStore instance to RoutingTilesOptions options and pass those options to NavigationOptions to be applied to the MapboxNavigation object:

RoutingTilesOptions routingTilesOptions = new RoutingTilesOptions.Builder()
.tileStore(tileStore)
.build();
NavigationOptions navOptions = new NavigationOptions.Builder(context)
.accessToken("accessToken")
.routingTilesOptions(routingTilesOptions)
.build();
MapboxNavigation mapboxNavigation = new MapboxNavigation(navOptions);

Pass the TileStore instance to ResourceOptions options to be applied to the MapView object:

ResourceOptions resourceOptions = new ResourceOptions.Builder()
.accessToken("accessToken")
.tileStore(tileStore)
.tileStoreUsageMode(TileStoreUsageMode.READ_AND_UPDATE)
.build();
MapView mapView = MapView(
context,
new MapInitOptions(context, resourceOptions)
);

Download a tile region

To load a new tile region or update an existing one, create an asynchronous tile region download by calling the tile store's loadTileRegion method and passing in the TileRegionLoadOptions.

To create a new tile region, start by defining TileRegionLoadOptions. You must provide at least two pieces of information to TileRegionLoadOptions:

  • geometry: GeoJson object describing some area (for example Polygon or MultiPolygon).
  • descriptors: tileset descriptor is a bundle that encapsulates tilesets creation for the tile store implementation. Maps tileset descriptor contains metadata about the tilesets, zoom ranges, and pixel ratio that cached tile packs should include. Navigation tileset descriptor contains version of the navigation tiles that cached tile packs should include.

If one or both pieces of information are missing, the load request will fail with RegionNotFound error.

To begin loading the new tile region, pass the options to loadTileRegion. Each tile region requires a unique tile region id. This can be any alphanumeric string. To create a new tile region, provide a new tile region id.

To update an existing tile region, use the tile region id of the existing region. When you call loadTileRegion, expired resources will be updated and any missing resources will be loaded to the existing offline region. When updating a tile region, you do not have to provide TileRegionLoadOptions, but you can if you need to alter the geometry or descriptors. A failed load request can be reattempted with another loadTileRegion() call.

If there is already a pending loading operation for the tile region with the given id the pending loading operation will fail with an error of Canceled type.

TilesetDescriptor mapsTilesetDescriptor = offlineManager.createTilesetDescriptor(
new TilesetDescriptorOptions.Builder()
.styleURI(Style.OUTDOORS)
.minZoom((byte) 0)
.maxZoom((byte) 16)
.build()
);
TilesetDescriptor navTilesetDescriptor = mapboxNavigation.getTilesetDescriptorFactory().getLatest();
List<TilesetDescriptor> tilesetDescriptors = new ArrayList<>();
tilesetDescriptors.add(mapsTilesetDescriptor);
tilesetDescriptors.add(navTilesetDescriptor);
TileRegionLoadOptions tileRegionLoadOptions = new TileRegionLoadOptions.Builder()
.geometry(TOKYO)
.descriptors(tilesetDescriptors)
.build();
Cancelable tileRegionCancelable = tileStore.loadTileRegion(
TILE_REGION_ID,
tileRegionLoadOptions,
(TileRegionLoadProgressCallback) progress -> {
// Handle the download progress
},
(TileRegionCallback) region -> {
if (region.isValue()) {
// Tile region download finishes successfully
} else {
// Handle errors that occurred during the tile region download
}
});

List tile regions

Call TileStore#getAllTileRegions to get a list of available tile regions. This will return either an object (Expected) that includes a list of available tile regions or an error.

Note

The callbacks will be executed on a TileStore-controlled worker thread. It is the responsibility of the developer to dispatch them to a controlled thread.

tileStore.getAllTileRegions(regions -> {
if (regions.isValue()) {
Log.d("Existing tile regions: " + regions.getValue())
} else {
Log.e("TileRegionError: " + regions.getError())
}
});

Delete tile regions

You can delete a tile region with TileStore#removeTileRegion.

tileStore.removeTileRegion(TILE_REGION_ID);
Note

This may not delete the downloaded tile packs immediately. Instead, it will mark the tileset as not being a part of an offline tile region, and the tileset will be removed from the disk cache during its normal cleanup process.

You can fully remove tiles that have been downloaded by setting the disk quota to zero. This will make sure tile regions are fully removed.

tileStore.setOption(TileStoreOptions.DISK_QUOTA, new Value(0));

Observe tile regions state

You can add TileStoreObserver to be notified when the state of any tile region changes.

TileStoreObserver tileStoreObserver = new TileStoreObserver() {
@Override
public void onRegionLoadProgress(@NonNull String id, @NonNull TileRegionLoadProgress progress) {
// Called whenever the load progress of a TileRegion changes.
}
@Override
public void onRegionLoadFinished(@NonNull String id, @NonNull Expected<TileRegionError, TileRegion> region) {
// Called once a TileRegion load completes successfully, or is aborted due to cancellation or errors.
}
@Override
public void onRegionRemoved(@NonNull String id) {
// Called when a TileRegion was removed
}
@Override
public void onRegionGeometryChanged(@NonNull String id, @NonNull Geometry geometry) {
// Called when the geometry of a TileRegion was modified.
}
@Override
public void onRegionMetadataChanged(@NonNull String id, @NonNull Value value) {
// Called when the user-provided metadata associated with a TileRegion was changed.
}
};
tileStore.addObserver(tileStoreObserver);
// remove observer when you don't need it
tileStore.removeObserver(tileStoreObserver);