Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to load offline maps database for NavigationView #1895

Merged
merged 19 commits into from
May 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ca00b5a
Add option to load offline maps database for NavigationView
danesfeder Apr 17, 2019
4237e1b
add download offline map tiles support into offline region download a…
Guardiola31337 Apr 19, 2019
1cbccef
Wire up geometry and route geometry with buffer
danesfeder Apr 19, 2019
553cc28
[WIP] add offline map database path option to navigation launcher
Guardiola31337 Apr 22, 2019
cf1ca22
[WIP] fix route geometry comparison, extract callbacks into classes, …
Guardiola31337 Apr 24, 2019
76f76fd
extract offline region observer into a separate class, bump nn to rou…
Guardiola31337 Apr 24, 2019
d4f1a89
fix maps connected state that was preventing the route buffer downloa…
Guardiola31337 Apr 25, 2019
0b04dc6
[WIP] add tests - create offline region callback and map offline mana…
Guardiola31337 Apr 26, 2019
59a1230
Add NavigationUiOption for offline map style URL
danesfeder Apr 29, 2019
53b986b
[WIP] add offline map style url option to mapbox navigation activity
Guardiola31337 Apr 29, 2019
98e57c6
reset map connectivity state when navigation is stopped
Guardiola31337 Apr 30, 2019
c56491f
Add tests for OfflineRegion provider, observer, and region download c…
danesfeder Apr 30, 2019
62f84c8
add missing merge offline regions callback, navigation offline databa…
Guardiola31337 Apr 30, 2019
eee81b2
Add MapOfflineOptions to make sure offline path and style URL are pro…
danesfeder May 1, 2019
820a210
convert mapbox navigator retrieve route geometry and retrieve route g…
Guardiola31337 May 1, 2019
9f64e87
fix offline map callbacks issue
Guardiola31337 May 13, 2019
17d7551
bump mapboxNavigator to 6.2.0 version and cleanup
Guardiola31337 May 16, 2019
edf8306
add pr changelog entry
Guardiola31337 May 16, 2019
43700cb
Cleanup remaining SDK TODOs and add javadoc
danesfeder May 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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