Skip to content

Commit

Permalink
Add option to load offline maps database for NavigationView (#1895)
Browse files Browse the repository at this point in the history
* Add option to load offline maps database for NavigationView

* Wire up geometry and route geometry with buffer

* add download offline map tiles support into offline region download activity to help testing

* [WIP] add offline map database path option to navigation launcher

* [WIP] fix route geometry comparison, extract callbacks into classes, hardcode offline tile pyramid region definition values to match offline region definition provider ones and release a snapshot to test / debug upstream in gl-native

* extract offline region observer into a separate class, bump nn to route_buffer-SNAPSHOT-7 version, retrieve a geometry from nn instead of a feature collection and test downloading a geometry using offline geometry region definition via offline region download activity

* fix maps connected state that was preventing the route buffer downloading to finish

* [WIP] add tests - create offline region callback and map offline manager tests (for now)

* Add NavigationUiOption for offline map style URL

* [WIP] add offline map style url option to mapbox navigation activity

* reset map connectivity state when navigation is stopped

* Add tests for OfflineRegion provider, observer, and region download callback

* add missing merge offline regions callback, navigation offline database callback, navigation view model progress change listener and offline metadata provider tests

* Add MapOfflineOptions to make sure offline path and style URL are provided together

* convert mapbox navigator retrieve route geometry and retrieve route geometry with buffer checks into guard clauses

* fix offline map callbacks issue

* bump mapboxNavigator to 6.2.0 version and cleanup

* add pr changelog entry

* Cleanup remaining SDK TODOs and add javadoc
  • Loading branch information
danesfeder authored May 16, 2019
1 parent 61a7a6e commit 29a7217
Show file tree
Hide file tree
Showing 39 changed files with 1,158 additions and 33 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

Mapbox welcomes participation and contributions from everyone.

### v0.38.0 - May 15, 2019
### v0.38.0 - May 16, 2019

* Add option to load offline maps database for NavigationView [#1895](https://github.com/mapbox/mapbox-navigation-android/pull/1895)
* Update Maps SDK to 7.4.0 [#1907](https://github.com/mapbox/mapbox-navigation-android/pull/1907)
* Added walking options [#1934](https://github.com/mapbox/mapbox-navigation-android/pull/1934)
* SoundButton clicklistener wasn't set properly [#1937](https://github.com/mapbox/mapbox-navigation-android/pull/1937)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@ import android.widget.AdapterView.OnItemSelectedListener
import android.widget.ArrayAdapter
import android.widget.Toast
import com.mapbox.geojson.BoundingBox
import com.mapbox.geojson.Geometry
import com.mapbox.geojson.gson.GeometryGeoJson
import com.mapbox.mapboxsdk.Mapbox
import com.mapbox.mapboxsdk.geometry.LatLngBounds
import com.mapbox.mapboxsdk.maps.MapboxMap
import com.mapbox.mapboxsdk.maps.Style
import com.mapbox.mapboxsdk.offline.*
import com.mapbox.mapboxsdk.style.layers.FillLayer
import com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillColor
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
import com.mapbox.services.android.navigation.testapp.R
import com.mapbox.services.android.navigation.v5.navigation.*
import kotlinx.android.synthetic.main.activity_offline_region_download.*
import org.json.JSONObject
import timber.log.Timber

class OfflineRegionDownloadActivity : AppCompatActivity(), RouteTileDownloadListener, OnOfflineTilesRemovedCallback {
Expand Down Expand Up @@ -55,6 +60,40 @@ class OfflineRegionDownloadActivity : AppCompatActivity(), RouteTileDownloadList
get() {
return MapboxOfflineRouter(obtainOfflineDirectory())
}
private lateinit var offlineManager: OfflineManager
private var offlineRegion: OfflineRegion? = null
private val offlineRegionCallback = object : OfflineManager.CreateOfflineRegionCallback {
override fun onCreate(offlineRegion: OfflineRegion?) {
Timber.d("Offline region created: %s", "NavigationOfflineMapsRegion")
this@OfflineRegionDownloadActivity.offlineRegion = offlineRegion
launchMapsDownload()
}

override fun onError(error: String?) {
Timber.e("Error: %s", error)
}
}

private val offlineRegionObserver = object : OfflineRegion.OfflineRegionObserver {
override fun mapboxTileCountLimitExceeded(limit: Long) {
Timber.e("Mapbox tile count limit exceeded: %s", limit)
}

override fun onStatusChanged(status: OfflineRegionStatus?) {
Timber.d("%s/%s resources; %s bytes downloaded.",
status?.completedResourceCount,
status?.requiredResourceCount,
status?.completedResourceSize)
if (status?.isComplete!!) {
downloadSelectedRegion()
}
}

override fun onError(error: OfflineRegionError?) {
Timber.e("onError reason: %s", error?.reason)
Timber.e("onError message: %s", error?.message)
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -124,6 +163,7 @@ class OfflineRegionDownloadActivity : AppCompatActivity(), RouteTileDownloadList
it.addSource(GeoJsonSource("bounding-box-source"))
it.addLayer(FillLayer("bounding-box-layer", "bounding-box-source")
.withProperties(fillColor(Color.parseColor("#50667F"))))
offlineManager = OfflineManager.getInstance(this)
}
this.mapboxMap = mapboxMap
mapboxMap.uiSettings.isRotateGesturesEnabled = false
Expand All @@ -140,7 +180,7 @@ class OfflineRegionDownloadActivity : AppCompatActivity(), RouteTileDownloadList
this, WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(WRITE_EXTERNAL_STORAGE), 1)
} else {
downloadSelectedRegion()
downloadMapsRegion()
}
}

Expand All @@ -157,16 +197,48 @@ class OfflineRegionDownloadActivity : AppCompatActivity(), RouteTileDownloadList
when (requestCode) {
EXTERNAL_STORAGE_PERMISSION -> {
if ((grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED)) {
downloadSelectedRegion()
downloadMapsRegion()
} else {
setDownloadButtonEnabled(false)
}
}
}
}

private fun downloadSelectedRegion() {
private fun downloadMapsRegion() {
showDownloading(false, "Requesting tiles....")
// TODO Hardcoding OfflineRegionDefinitionProvider values for testing / debugging purposes
val styleUrl: String? = "mapbox://styles/mapbox/navigation-guidance-day-v4"
//val styleUrl: String? = mapboxMap.style?.url
val bounds: LatLngBounds = LatLngBounds.from(boundingBox.north(), boundingBox.east(), boundingBox.south(), boundingBox.west())
// TODO Testing downloading a Geometry
val geometry: Geometry = GeometryGeoJson.fromJson("{\"type\":\"Polygon\",\"coordinates\":[[[-77.152533,39.085537],[-77.152533,39.083038],[-77.150031,39.083038],[-77.150031,39.085537],[-77.147529,39.085537],[-77.147529,39.088039],[-77.147529,39.090538],[-77.150031,39.090538],[-77.150031,39.093037],[-77.150031,39.095539],[-77.150031,39.098038],[-77.150031,39.100540],[-77.150031,39.103039],[-77.152533,39.103039],[-77.152533,39.105537],[-77.155028,39.105537],[-77.155028,39.108040],[-77.155028,39.110538],[-77.157531,39.110538],[-77.157531,39.113037],[-77.160033,39.113037],[-77.160033,39.115536],[-77.162528,39.115540],[-77.162528,39.118038],[-77.165030,39.118038],[-77.165030,39.115536],[-77.167533,39.115536],[-77.167533,39.113037],[-77.167533,39.110538],[-77.165030,39.110538],[-77.165030,39.108040],[-77.162536,39.108036],[-77.162536,39.105537],[-77.162536,39.103039],[-77.160033,39.103039],[-77.160033,39.100540],[-77.157531,39.100536],[-77.157531,39.098038],[-77.157531,39.095535],[-77.157531,39.093037],[-77.157531,39.090538],[-77.157531,39.088039],[-77.155036,39.088036],[-77.155036,39.085537],[-77.152533,39.085537]]]}")
// TODO Hardcoding OfflineRegionDefinitionProvider values for testing / debugging purposes
val minZoom: Double = 11.0
val maxZoom: Double = 17.0
//val minZoom: Double = mapboxMap.cameraPosition.zoom
//val maxZoom: Double = mapboxMap.maxZoomLevel
val pixelRatio: Float = this.resources.displayMetrics.density
val definition: OfflineTilePyramidRegionDefinition = OfflineTilePyramidRegionDefinition(
styleUrl, bounds, minZoom, maxZoom, pixelRatio)
// TODO Testing downloading a Geometry using OfflineGeometryRegionDefinition as definition
//val definition: OfflineGeometryRegionDefinition = OfflineGeometryRegionDefinition(
// styleUrl, geometry, minZoom, maxZoom, pixelRatio)

val metadata: ByteArray
val jsonObject: JSONObject = JSONObject()
jsonObject.put("FIELD_REGION_NAME", "NavigationOfflineMapsRegion")
val json: String = jsonObject.toString()
metadata = json.toByteArray()
offlineManager.createOfflineRegion(definition, metadata, offlineRegionCallback)
}

private fun launchMapsDownload() {
offlineRegion?.setObserver(offlineRegionObserver)
offlineRegion?.setDownloadState(OfflineRegion.STATE_ACTIVE)
}

private fun downloadSelectedRegion() {
val builder = OfflineTiles.builder()
.accessToken(Mapbox.getAccessToken())
.version(versionSpinner.selectedItem as String)
Expand Down Expand Up @@ -278,6 +350,7 @@ class OfflineRegionDownloadActivity : AppCompatActivity(), RouteTileDownloadList

override fun onDestroy() {
super.onDestroy()
offlineRegion?.setObserver(null)
mapView.onDestroy()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,13 @@ private void launchNavigationWithRoute() {
if (!offlineVersion.isEmpty()) {
optionsBuilder.offlineRoutingTilesVersion(offlineVersion);
}
// TODO Testing dynamic offline
/**
* File downloadDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
* String databaseFilePath = downloadDirectory + "/" + "kingfarm.db";
* String offlineStyleUrl = "mapbox://styles/mapbox/navigation-guidance-day-v4";
* optionsBuilder.offlineMapOptions(new MapOfflineOptions(databaseFilePath, offlineStyleUrl));
*/
NavigationLauncher.startNavigation(this, optionsBuilder.build());
}

Expand Down
8 changes: 5 additions & 3 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ ext {
version = [
mapboxMapSdk : '7.4.0',
mapboxSdkServices : '4.8.0',
mapboxEvents : '4.3.0',
mapboxEvents : '4.4.1',
mapboxCore : '1.2.0',
mapboxNavigator : '6.1.3',
mapboxNavigator : '6.2.0',
mapboxCrashMonitor : '2.0.0',
mapboxAnnotationPlugin : '0.6.0',
mapboxSearchSdk : '0.1.0-SNAPSHOT',
Expand All @@ -39,7 +39,8 @@ ext {
ankoCommon : '0.10.0',
firebaseCore : '16.0.7',
crashlytics : '2.9.9',
multidex : '1.0.3'
multidex : '1.0.3',
json : '20180813'
]

dependenciesList = [
Expand Down Expand Up @@ -100,6 +101,7 @@ ext {
hamcrest : "org.hamcrest:hamcrest-junit:${version.hamcrest}",
commonsIO : "commons-io:commons-io:${version.commonsIO}",
robolectric : "org.robolectric:robolectric:${version.robolectric}",
json : "org.json:json:${version.json}",

// play services
gmsLocation : "com.google.android.gms:play-services-location:${version.gmsLocation}",
Expand Down
1 change: 1 addition & 0 deletions libandroid-navigation-ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ dependencies {
testImplementation dependenciesList.junit
testImplementation dependenciesList.mockito
testImplementation dependenciesList.robolectric
testImplementation dependenciesList.json
}

apply from: 'javadoc.gradle'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.mapbox.services.android.navigation.ui.v5;

import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.offline.OfflineRegion;

class CreateOfflineRegionCallback implements OfflineManager.CreateOfflineRegionCallback {

private final OfflineRegionDownloadCallback callback;

CreateOfflineRegionCallback(OfflineRegionDownloadCallback callback) {
this.callback = callback;
}

@Override
public void onCreate(OfflineRegion offlineRegion) {
offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);
offlineRegion.setObserver(new OfflineRegionObserver(callback));
}

@Override
public void onError(String error) {
callback.onError(error);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mapbox.services.android.navigation.ui.v5;

import com.mapbox.mapboxsdk.Mapbox;

class MapConnectivityController {

void assign(Boolean state) {
Mapbox.setConnected(state);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.mapbox.services.android.navigation.ui.v5;

import android.location.Location;
import android.support.annotation.NonNull;

import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.offline.OfflineGeometryRegionDefinition;
import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress;

class MapOfflineManager implements ProgressChangeListener {

private final OfflineManager offlineManager;
private final OfflineRegionDefinitionProvider definitionProvider;
private final OfflineMetadataProvider metadataProvider;
private final MapConnectivityController connectivityController;
private final RegionDownloadCallback regionDownloadCallback;
private Geometry previousRouteGeometry;
private MergeOfflineRegionsCallback mergeOfflineRegionsCallback;

MapOfflineManager(OfflineManager offlineManager, OfflineRegionDefinitionProvider definitionProvider,
OfflineMetadataProvider metadataProvider, MapConnectivityController connectivityController,
RegionDownloadCallback regionDownloadCallback) {
this.offlineManager = offlineManager;
this.definitionProvider = definitionProvider;
this.metadataProvider = metadataProvider;
this.connectivityController = connectivityController;
this.regionDownloadCallback = regionDownloadCallback;
}

// Package private (no modifier) for testing purposes
MapOfflineManager(OfflineManager offlineManager, OfflineRegionDefinitionProvider definitionProvider,
OfflineMetadataProvider metadataProvider, MapConnectivityController connectivityController,
RegionDownloadCallback regionDownloadCallback,
MergeOfflineRegionsCallback mergeOfflineRegionsCallback) {
this.offlineManager = offlineManager;
this.definitionProvider = definitionProvider;
this.metadataProvider = metadataProvider;
this.connectivityController = connectivityController;
this.regionDownloadCallback = regionDownloadCallback;
this.mergeOfflineRegionsCallback = mergeOfflineRegionsCallback;
}

@Override
public void onProgressChange(Location location, RouteProgress routeProgress) {
Geometry currentRouteGeometry = routeProgress.routeGeometryWithBuffer();
if (previousRouteGeometry == null || !previousRouteGeometry.equals(currentRouteGeometry)) {
previousRouteGeometry = currentRouteGeometry;
String routeSummary = routeProgress.directionsRoute().routeOptions().requestUuid();
download(routeSummary, previousRouteGeometry, regionDownloadCallback);
}
}

void loadDatabase(@NonNull String offlineDatabasePath, OfflineDatabaseLoadedCallback callback) {
mergeOfflineRegionsCallback = new MergeOfflineRegionsCallback(callback);
offlineManager.mergeOfflineRegions(offlineDatabasePath, mergeOfflineRegionsCallback);
}

void onDestroy() {
if (mergeOfflineRegionsCallback != null) {
mergeOfflineRegionsCallback.onDestroy();
}
}

private void download(@NonNull String routeSummary, @NonNull Geometry routeGeometry,
final OfflineRegionDownloadCallback callback) {
OfflineGeometryRegionDefinition definition = definitionProvider.buildRegionFor(routeGeometry);
byte[] metadata = metadataProvider.buildMetadataFor(routeSummary);
connectivityController.assign(null);
offlineManager.createOfflineRegion(definition, metadata, new CreateOfflineRegionCallback(callback));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mapbox.services.android.navigation.ui.v5;

import android.support.annotation.NonNull;

public class MapOfflineOptions {

private final String databasePath;
private final String styleUrl;

/**
* Add an offline path and style URL for loading an offline map database.
*
* @param databaseFilePath to the offline database on the device
* @param styleUrl for the offline database data
*/
public MapOfflineOptions(@NonNull String databaseFilePath, @NonNull String styleUrl) {
this.databasePath = databaseFilePath;
this.styleUrl = styleUrl;
}

/**
* The offline path to the offline map database.
*
* @return the database path
*/
@NonNull
public String getDatabasePath() {
return databasePath;
}

/**
* The map style URL for the offline map database.
*
* @return the style URL
*/
@NonNull
public String getStyleUrl() {
return styleUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ private void extractConfiguration(NavigationViewOptions.Builder options) {
if (!offlineVersion.isEmpty()) {
options.offlineRoutingTilesVersion(offlineVersion);
}
String offlineMapDatabasePath = preferences.getString(NavigationConstants.MAP_DATABASE_PATH_KEY, "");
String offlineMapStyleUrl = preferences.getString(NavigationConstants.MAP_STYLE_URL_KEY, "");
if (!offlineMapDatabasePath.isEmpty() && !offlineMapStyleUrl.isEmpty()) {
MapOfflineOptions mapOfflineOptions = new MapOfflineOptions(offlineMapDatabasePath, offlineMapStyleUrl);
options.offlineMapOptions(mapOfflineOptions);
}
}

private void finishNavigation() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.mapbox.services.android.navigation.ui.v5;

import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.offline.OfflineRegion;

class MergeOfflineRegionsCallback implements OfflineManager.MergeOfflineRegionsCallback {

private OfflineDatabaseLoadedCallback callback;

MergeOfflineRegionsCallback(OfflineDatabaseLoadedCallback callback) {
this.callback = callback;
}

@Override
public void onMerge(OfflineRegion[] offlineRegions) {
if (callback != null) {
callback.onComplete();
}
}

@Override
public void onError(String error) {
if (callback != null) {
callback.onError(error);
}
}

OfflineDatabaseLoadedCallback onDestroy() {
callback = null;
return callback;
}
}
Loading

0 comments on commit 29a7217

Please sign in to comment.