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

Conversation

danesfeder
Copy link
Contributor

@danesfeder danesfeder commented Apr 17, 2019

Description

Closes #1843
Fixes #1910

This PR adds functionality to the NavigationView and NavigationLauncher that will allow side-loading of a Maps offline database. It also adds functionality to fetch more offline data, if needed, based on the current route geometry + buffer, which we will receive from the current progress.

Goal

Allow developers to utilize offline Maps SDK for less data consumption or addressing connectivity issues.

Implementation

Upon initialization, NavigationViewModel instantiates a MapOfflineManager if an offline database path is passed via the NavigationViewOptions. At that point, if the database loads successfully, we set the map connection to false with Mapbox.setConnected(false).

After navigation has begun, each route will be turned into a geometry with a buffer using a new API from Navigator that @kevinkreiser is hooking us up with. We will request each route with and GL will detect overlap / download offline tiles as needed.

Testing

Please describe the manual tests that you ran to verify your changes

  • I have tested locally (including SNAPSHOT upstream dependencies if needed)
  • I have tested via a test drive, or a simulation/mock location app
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code

@codecov-io
Copy link

codecov-io commented Apr 17, 2019

Codecov Report

Merging #1895 into master will increase coverage by 0.54%.
The diff coverage is 59.19%.

@@             Coverage Diff              @@
##             master    #1895      +/-   ##
============================================
+ Coverage     36.47%   37.01%   +0.54%     
- Complexity     1093     1130      +37     
============================================
  Files           262      273      +11     
  Lines          8894     9055     +161     
  Branches        669      675       +6     
============================================
+ Hits           3244     3352     +108     
- Misses         5359     5404      +45     
- Partials        291      299       +8

@danesfeder danesfeder changed the title WIP: Add option to load offline maps database for NavigationView [WIP] Add option to load offline maps database for NavigationView Apr 17, 2019
Guardiola31337 added a commit to mapbox/mapbox-gl-native that referenced this pull request Apr 24, 2019
Guardiola31337 added a commit to mapbox/mapbox-gl-native that referenced this pull request Apr 24, 2019
@Guardiola31337
Copy link
Contributor

Guardiola31337 commented Apr 24, 2019

Edited:
This is currently blocked upstream by mapbox/mapbox-gl-native#14485 and https://github.com/mapbox/mapbox-gl-native/issues/14504

@Guardiola31337
Copy link
Contributor

Guardiola31337 commented May 10, 2019

mapbox/mapbox-gl-native#14601 is going o go out next Wednesday with 7.5.0-beta.1 and it'd be great if you could test it downstream (or the 7.5.0-SNAPSHOT that's already available).

I run into the following error when trying to build the project using the latest 7.5.0-SNAPSHOT

Android resource linking failed
Output:  error: resource style/mapbox_LocationComponent (aka com.mapbox.services.android.navigation.app:style/mapbox_LocationComponent) not found.
/Users/pguardiola/mapbox/mapbox-navigation-android/app/build/intermediates/incremental/mergeDebugResources/merged.dir/values/values.xml:4128: error: style attribute 'attr/mapbox_trackingAnimationDurationMultiplier (aka com.mapbox.services.android.navigation.app:attr/mapbox_trackingAnimationDurationMultiplier)' not found.
/Users/pguardiola/mapbox/mapbox-navigation-android/app/build/intermediates/incremental/mergeDebugResources/merged.dir/values/values.xml:4129: error: style attribute 'attr/mapbox_trackingGesturesManagement (aka com.mapbox.services.android.navigation.app:attr/mapbox_trackingGesturesManagement)' not found.
error: failed linking references.

Could you think of what could be the causing it @LukasPaczos? Do you know if something has changed recently in that regard? Want to note that I cannot reproduce when using 7.4.0-SNAPSHOT with the same setup 🤔

@LukasPaczos
Copy link

@Guardiola31337 Gradle 5.1/Gradle plugin 3.4.0 is causing the issue. Please use 7.5.0-20190513.125143-36 (I've used this snapshot off of master to verify that Gradle was causing the issue) until mapbox/mapbox-gl-native#14645 is merged.

@Guardiola31337 Guardiola31337 mentioned this pull request May 16, 2019
10 tasks
@danesfeder danesfeder changed the title [WIP] Add option to load offline maps database for NavigationView Add option to load offline maps database for NavigationView May 16, 2019
Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work @danesfeder :shipit:

@danesfeder
Copy link
Contributor Author

@Guardiola31337 huge team lift thanks @Guardiola31337 🍍

@danesfeder danesfeder merged commit 29a7217 into master May 16, 2019
@danesfeder danesfeder deleted the dan-offline-maps-ui branch May 16, 2019 14:00
Guardiola31337 added a commit to mapbox/mapbox-gl-native that referenced this pull request Jun 18, 2019
Guardiola31337 added a commit to mapbox/mapbox-gl-native that referenced this pull request Jun 18, 2019
LukasPaczos pushed a commit to mapbox/mapbox-gl-native that referenced this pull request Nov 14, 2019
LukasPaczos pushed a commit to mapbox/mapbox-gl-native that referenced this pull request Nov 14, 2019
@maty94
Copy link

maty94 commented Nov 25, 2020

Hi i want to implement offline maps for NavigationView.

But I don't understand how to implement it, I'm using the following class in android. I appreciate the help.

https://github.com/mapbox/mapbox-navigation-android/blob/46a3d0b7f6f01b31abc6d243853c63b347683c65/app/src/main/java/com/mapbox/services/android/navigation/testapp/activity/navigationui/EmbeddedNavigationActivity.java
`
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.location.Location;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.view.View;
import android.widget.TextView;

import com.mapbox.api.directions.v5.models.BannerInstructions;
import com.mapbox.api.directions.v5.models.DirectionsResponse;
import com.mapbox.api.directions.v5.models.DirectionsRoute;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.services.android.navigation.testapp.R;
import com.mapbox.services.android.navigation.ui.v5.NavigationView;
import com.mapbox.services.android.navigation.ui.v5.NavigationViewOptions;
import com.mapbox.services.android.navigation.ui.v5.OnNavigationReadyCallback;
import com.mapbox.services.android.navigation.ui.v5.listeners.BannerInstructionsListener;
import com.mapbox.services.android.navigation.ui.v5.listeners.InstructionListListener;
import com.mapbox.services.android.navigation.ui.v5.listeners.NavigationListener;
import com.mapbox.services.android.navigation.ui.v5.listeners.SpeechAnnouncementListener;
import com.mapbox.services.android.navigation.ui.v5.voice.SpeechAnnouncement;
import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute;
import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress;

import retrofit2.Call;
import retrofit2.Response;

public class EmbeddedNavigationActivity extends AppCompatActivity implements OnNavigationReadyCallback,
NavigationListener, ProgressChangeListener, InstructionListListener, SpeechAnnouncementListener,
BannerInstructionsListener {

private static final Point ORIGIN = Point.fromLngLat(-77.03194990754128, 38.909664963450105);
private static final Point DESTINATION = Point.fromLngLat(-77.0270025730133, 38.91057077063121);

private NavigationView navigationView;
private View spacer;
private TextView speedWidget;
private FloatingActionButton fabNightModeToggle;

private boolean bottomSheetVisible = true;
private boolean instructionListShown = false;

@OverRide
protected void onCreate(@nullable Bundle savedInstanceState) {
setTheme(R.style.Theme_AppCompat_Light_NoActionBar);
initNightMode();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_embedded_navigation);
navigationView = findViewById(R.id.navigationView);
fabNightModeToggle = findViewById(R.id.fabToggleNightMode);
speedWidget = findViewById(R.id.speed_limit);
spacer = findViewById(R.id.spacer);
setSpeedWidgetAnchor(R.id.summaryBottomSheet);

navigationView.onCreate(savedInstanceState);
navigationView.initialize(this);

}

@OverRide
public void onNavigationReady(boolean isRunning) {
fetchRoute();
}

@OverRide
public void onStart() {
super.onStart();
navigationView.onStart();
}

@OverRide
public void onResume() {
super.onResume();
navigationView.onResume();
}

@OverRide
public void onLowMemory() {
super.onLowMemory();
navigationView.onLowMemory();
}

@OverRide
public void onBackPressed() {
// If the navigation view didn't need to do anything, call super
if (!navigationView.onBackPressed()) {
super.onBackPressed();
}
}

@OverRide
protected void onSaveInstanceState(Bundle outState) {
navigationView.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}

@OverRide
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
navigationView.onRestoreInstanceState(savedInstanceState);
}

@OverRide
public void onPause() {
super.onPause();
navigationView.onPause();
}

@OverRide
public void onStop() {
super.onStop();
navigationView.onStop();
}

@OverRide
protected void onDestroy() {
super.onDestroy();
navigationView.onDestroy();
if (isFinishing()) {
saveNightModeToPreferences(AppCompatDelegate.MODE_NIGHT_AUTO);
}
}

@OverRide
public void onCancelNavigation() {
// Navigation canceled, finish the activity
finish();
}

@OverRide
public void onNavigationFinished() {
// Intentionally empty
}

@OverRide
public void onNavigationRunning() {
// Intentionally empty
}

@OverRide
public void onProgressChange(Location location, RouteProgress routeProgress) {
setSpeed(location);
}

@OverRide
public void onInstructionListVisibilityChanged(boolean shown) {
instructionListShown = shown;
speedWidget.setVisibility(shown ? View.GONE : View.VISIBLE);
if (instructionListShown) {
fabNightModeToggle.hide();
} else if (bottomSheetVisible) {
fabNightModeToggle.show();
}
}

@OverRide
public SpeechAnnouncement willVoice(SpeechAnnouncement announcement) {
return SpeechAnnouncement.builder().announcement("All announcements will be the same.").build();
}

@OverRide
public BannerInstructions willDisplay(BannerInstructions instructions) {
return instructions;
}

private void startNavigation(DirectionsRoute directionsRoute) {
NavigationViewOptions.Builder options =
NavigationViewOptions.builder()
.navigationListener(this)
.directionsRoute(directionsRoute)
.shouldSimulateRoute(true)
.progressChangeListener(this)
.instructionListListener(this)
.speechAnnouncementListener(this)
.bannerInstructionsListener(this);
setBottomSheetCallback(options);
setupNightModeFab();

navigationView.startNavigation(options.build());

}

private void fetchRoute() {
NavigationRoute.builder(this)
.accessToken(Mapbox.getAccessToken())
.origin(ORIGIN)
.destination(DESTINATION)
.alternatives(true)
.build()
.getRoute(new SimplifiedCallback() {
@OverRide
public void onResponse(Call call, Response response) {
DirectionsRoute directionsRoute = response.body().routes().get(0);
startNavigation(directionsRoute);
}
});
}

/**

  • Sets the anchor of the spacer for the speed widget, thus setting the anchor for the speed widget
  • (The speed widget is anchored to the spacer, which is there because padding between items and
  • their anchors in CoordinatorLayouts is finicky.
  • @param res resource for view of which to anchor the spacer
    */
    private void setSpeedWidgetAnchor(@idres int res) {
    CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) spacer.getLayoutParams();
    layoutParams.setAnchorId(res);
    spacer.setLayoutParams(layoutParams);
    }

private void setBottomSheetCallback(NavigationViewOptions.Builder options) {
options.bottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@OverRide
public void onStateChanged(@nonnull View bottomSheet, int newState) {
switch (newState) {
case BottomSheetBehavior.STATE_HIDDEN:
bottomSheetVisible = false;
fabNightModeToggle.hide();
setSpeedWidgetAnchor(R.id.recenterBtn);
break;
case BottomSheetBehavior.STATE_EXPANDED:
bottomSheetVisible = true;
break;
case BottomSheetBehavior.STATE_SETTLING:
if (!bottomSheetVisible) {
// View needs to be anchored to the bottom sheet before it is finished expanding
// because of the animation
fabNightModeToggle.show();
setSpeedWidgetAnchor(R.id.summaryBottomSheet);
}
break;
default:
return;
}
}

  @Override
  public void onSlide(@NonNull View bottomSheet, float slideOffset) {
  }
});

}

private void setupNightModeFab() {
fabNightModeToggle.setOnClickListener(view -> toggleNightMode());
}

private void toggleNightMode() {
int currentNightMode = getCurrentNightMode();
alternateNightMode(currentNightMode);
}

private void initNightMode() {
int nightMode = retrieveNightModeFromPreferences();
AppCompatDelegate.setDefaultNightMode(nightMode);
}

private int getCurrentNightMode() {
return getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK;
}

private void alternateNightMode(int currentNightMode) {
int newNightMode;
if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
newNightMode = AppCompatDelegate.MODE_NIGHT_NO;
} else {
newNightMode = AppCompatDelegate.MODE_NIGHT_YES;
}
saveNightModeToPreferences(newNightMode);
recreate();
}

private int retrieveNightModeFromPreferences() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
return preferences.getInt(getString(R.string.current_night_mode), AppCompatDelegate.MODE_NIGHT_AUTO);
}

private void saveNightModeToPreferences(int nightMode) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt(getString(R.string.current_night_mode), nightMode);
editor.apply();
}

private void setSpeed(Location location) {
String string = String.format("%d\nMPH", (int) (location.getSpeed() * 2.2369));
int mphTextSize = getResources().getDimensionPixelSize(R.dimen.mph_text_size);
int speedTextSize = getResources().getDimensionPixelSize(R.dimen.speed_text_size);

SpannableString spannableString = new SpannableString(string);
spannableString.setSpan(new AbsoluteSizeSpan(mphTextSize),
  string.length() - 4, string.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);

spannableString.setSpan(new AbsoluteSizeSpan(speedTextSize),
  0, string.length() - 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);

speedWidget.setText(spannableString);
if (!instructionListShown) {
  speedWidget.setVisibility(View.VISIBLE);
}

}
}
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants