Skip to content

Commit d35be46

Browse files
committed
Add ability to display / select alternative routes (#1333)
* Add example test Activity for Navigation Test Application * Add ability to display / select alternative routes
1 parent 3984572 commit d35be46

File tree

11 files changed

+78
-123
lines changed

11 files changed

+78
-123
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ android {
1818
versionCode 1
1919
versionName "0.1"
2020
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
21+
vectorDrawables.useSupportLibrary = true
2122
}
2223

2324
buildTypes {

app/src/main/java/com/mapbox/services/android/navigation/testapp/example/ui/ExampleActivity.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class ExampleActivity : AppCompatActivity(), ExampleView {
3535

3636
companion object {
3737
const val ZERO_PADDING = 0
38-
const val BOTTOMSHEET_MULTIPLER = 4
38+
const val BOTTOMSHEET_MULTIPLIER = 4
3939
}
4040

4141
override fun onCreate(savedInstanceState: Bundle?) {
@@ -93,6 +93,7 @@ class ExampleActivity : AppCompatActivity(), ExampleView {
9393

9494
override fun onMapReady(mapboxMap: MapboxMap) {
9595
map = NavigationMapboxMap(mapView, mapboxMap)
96+
map?.setOnRouteSelectionChangeListener(this)
9697
mapboxMap.addOnMapLongClickListener{ presenter.onMapLongClick(it) }
9798
presenter.buildDynamicCameraFrom(mapboxMap)
9899
}
@@ -101,6 +102,10 @@ class ExampleActivity : AppCompatActivity(), ExampleView {
101102
presenter.onDestinationFound(feature)
102103
}
103104

105+
override fun onNewPrimaryRouteSelected(directionsRoute: DirectionsRoute) {
106+
presenter.onNewRouteSelected(directionsRoute)
107+
}
108+
104109
override fun onPermissionResult(granted: Boolean) {
105110
presenter.onPermissionResult(granted)
106111
}
@@ -137,8 +142,8 @@ class ExampleActivity : AppCompatActivity(), ExampleView {
137142
map?.updateLocation(location)
138143
}
139144

140-
override fun updateRoute(route: DirectionsRoute) {
141-
map?.drawRoute(route)
145+
override fun updateRoutes(routes: List<DirectionsRoute>) {
146+
map?.drawRoutes(routes)
142147
}
143148

144149
override fun updateDestinationMarker(destination: Point) {
@@ -229,7 +234,7 @@ class ExampleActivity : AppCompatActivity(), ExampleView {
229234
override fun adjustMapPaddingForNavigation() {
230235
val mapViewHeight = mapView.height
231236
val bottomSheetHeight = resources.getDimension(R.dimen.bottom_sheet_peek_height).toInt()
232-
val topPadding = mapViewHeight - bottomSheetHeight * BOTTOMSHEET_MULTIPLER
237+
val topPadding = mapViewHeight - bottomSheetHeight * BOTTOMSHEET_MULTIPLIER
233238
map?.retrieveMap()?.setPadding(ZERO_PADDING, topPadding, ZERO_PADDING, ZERO_PADDING)
234239
}
235240

app/src/main/java/com/mapbox/services/android/navigation/testapp/example/ui/ExamplePresenter.kt

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@ import com.mapbox.mapboxsdk.geometry.LatLngBounds
1717
import com.mapbox.mapboxsdk.maps.MapboxMap
1818
import com.mapbox.services.android.navigation.testapp.NavigationApplication
1919
import com.mapbox.services.android.navigation.testapp.R
20-
import com.mapbox.services.android.navigation.testapp.example.ui.offline.OfflineFilesLoadedCallback
2120
import com.mapbox.services.android.navigation.testapp.example.utils.formatArrivalTime
2221
import com.mapbox.services.android.navigation.ui.v5.camera.DynamicCamera
2322
import com.mapbox.services.android.navigation.v5.milestone.BannerInstructionMilestone
2423
import com.mapbox.services.android.navigation.v5.milestone.Milestone
2524
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress
26-
import timber.log.Timber
2725
import kotlin.math.roundToInt
2826

2927
class ExamplePresenter(private val view: ExampleView, private val viewModel: ExampleViewModel) {
@@ -37,11 +35,6 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
3735
}
3836

3937
private var state: PresenterState = PresenterState.SHOW_LOCATION
40-
private val offlineCallback = object : OfflineFilesLoadedCallback {
41-
override fun onFilesLoaded() {
42-
Timber.d("Offline files loaded")
43-
}
44-
}
4538

4639
fun onPermissionResult(granted: Boolean) {
4740
if (granted) {
@@ -75,16 +68,16 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
7568
}
7669

7770
fun onNavigationFabClick() {
78-
viewModel.route.value?.let {
71+
if (viewModel.canNavigate()) {
7972
state = PresenterState.NAVIGATE
8073
view.addMapProgressChangeListener(viewModel.retrieveNavigation())
81-
viewModel.startNavigationWith(it)
8274
view.updateNavigationFabVisibility(INVISIBLE)
8375
view.updateCancelFabVisibility(VISIBLE)
8476
view.updateNavigationDataVisibility(VISIBLE)
8577
view.updateAutocompleteBottomSheetHideable(true)
8678
view.updateAutocompleteBottomSheetState(BottomSheetBehavior.STATE_HIDDEN)
8779
view.adjustMapPaddingForNavigation()
80+
viewModel.startNavigation()
8881
}
8982
}
9083

@@ -143,20 +136,20 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
143136
}
144137
}
145138

146-
fun onRouteFound(route: DirectionsRoute?) {
147-
route?.let { directionsRoute ->
139+
fun onRouteFound(routes: List<DirectionsRoute>?) {
140+
routes?.let { directionsRoutes ->
148141
when (state) {
149142
PresenterState.FIND_ROUTE -> {
150143
state = PresenterState.ROUTE_FOUND
151-
view.updateRoute(directionsRoute)
144+
view.updateRoutes(directionsRoutes)
152145
view.updateDirectionsFabVisibility(INVISIBLE)
153146
view.updateNavigationFabVisibility(VISIBLE)
154147
viewModel.destination.value?.let { destination ->
155148
moveCameraToInclude(destination)
156149
}
157150
}
158151
PresenterState.NAVIGATE -> {
159-
viewModel.startNavigationWith(directionsRoute)
152+
view.updateRoutes(directionsRoutes)
160153
}
161154
else -> {
162155
// TODO no impl
@@ -165,6 +158,13 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
165158
}
166159
}
167160

161+
fun onNewRouteSelected(directionsRoute: DirectionsRoute) {
162+
viewModel.updatePrimaryRoute(directionsRoute)
163+
if (state == PresenterState.NAVIGATE) {
164+
viewModel.startNavigation()
165+
}
166+
}
167+
168168
fun onProgressUpdate(progress: RouteProgress?) {
169169
progress?.let {
170170
view.updateArrivalTime(it.formatArrivalTime(NavigationApplication.instance))
@@ -174,9 +174,9 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
174174
}
175175

176176
fun onMapLongClick(point: LatLng) {
177-
viewModel.reverseGeocode(point);
177+
viewModel.reverseGeocode(point)
178178
}
179-
179+
180180
fun onMilestoneUpdate(milestone: Milestone?) {
181181
milestone?.let {
182182
if (milestone is BannerInstructionMilestone) {
@@ -187,13 +187,21 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
187187
}
188188
}
189189

190+
fun onBackPressed(): Boolean {
191+
if (!viewModel.collapsedBottomSheet) {
192+
view.updateAutocompleteBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED)
193+
return false
194+
}
195+
return true
196+
}
197+
190198
fun onDestroy() {
191199
viewModel.onDestroy()
192200
}
193201

194202
fun subscribe(owner: LifecycleOwner) {
195203
viewModel.location.observe(owner, Observer { onLocationUpdate(it) })
196-
viewModel.route.observe(owner, Observer { onRouteFound(it) })
204+
viewModel.routes.observe(owner, Observer { onRouteFound(it) })
197205
viewModel.progress.observe(owner, Observer { onProgressUpdate(it) })
198206
viewModel.milestone.observe(owner, Observer { onMilestoneUpdate(it) })
199207
viewModel.geocode.observe(owner, Observer {
@@ -202,7 +210,6 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
202210
}
203211
})
204212
viewModel.activateLocationEngine()
205-
viewModel.loadOfflineFiles(offlineCallback)
206213
}
207214

208215
fun buildDynamicCameraFrom(mapboxMap: MapboxMap) {
@@ -246,12 +253,4 @@ class ExamplePresenter(private val view: ExampleView, private val viewModel: Exa
246253
view.updateMapCameraFor(bounds, padding, TWO_SECONDS)
247254
}
248255
}
249-
250-
fun onBackPressed(): Boolean {
251-
if (!viewModel.collapsedBottomSheet) {
252-
view.updateAutocompleteBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED)
253-
return false
254-
}
255-
return true
256-
}
257256
}

app/src/main/java/com/mapbox/services/android/navigation/testapp/example/ui/ExampleView.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import com.mapbox.geojson.Point
88
import com.mapbox.mapboxsdk.camera.CameraUpdate
99
import com.mapbox.mapboxsdk.geometry.LatLngBounds
1010
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback
11+
import com.mapbox.services.android.navigation.ui.v5.route.OnRouteSelectionChangeListener
1112
import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation
1213

13-
interface ExampleView: PermissionsListener, OnMapReadyCallback, OnFeatureClickListener {
14+
interface ExampleView: PermissionsListener, OnMapReadyCallback,
15+
OnFeatureClickListener, OnRouteSelectionChangeListener {
1416

1517
fun initialize()
1618

@@ -26,7 +28,7 @@ interface ExampleView: PermissionsListener, OnMapReadyCallback, OnFeatureClickLi
2628

2729
fun updateMapLocation(location: Location?)
2830

29-
fun updateRoute(route: DirectionsRoute)
31+
fun updateRoutes(routes: List<DirectionsRoute>)
3032

3133
fun updateDestinationMarker(destination: Point)
3234

app/src/main/java/com/mapbox/services/android/navigation/testapp/example/ui/ExampleViewModel.kt

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.app.Application
44
import android.arch.lifecycle.AndroidViewModel
55
import android.arch.lifecycle.MutableLiveData
66
import android.location.Location
7-
import android.preference.PreferenceManager
87
import com.mapbox.android.core.location.LocationEngine
98
import com.mapbox.android.core.location.LocationEnginePriority
109
import com.mapbox.android.core.location.LocationEngineProvider
@@ -15,12 +14,9 @@ import com.mapbox.api.geocoding.v5.models.GeocodingResponse
1514
import com.mapbox.geojson.Point
1615
import com.mapbox.mapboxsdk.Mapbox
1716
import com.mapbox.mapboxsdk.geometry.LatLng
18-
import com.mapbox.services.android.navigation.testapp.NavigationApplication
1917
import com.mapbox.services.android.navigation.testapp.NavigationApplication.Companion.instance
2018
import com.mapbox.services.android.navigation.testapp.R
2119
import com.mapbox.services.android.navigation.testapp.example.ui.navigation.*
22-
import com.mapbox.services.android.navigation.testapp.example.ui.offline.OfflineFileLoader
23-
import com.mapbox.services.android.navigation.testapp.example.ui.offline.OfflineFilesLoadedCallback
2420
import com.mapbox.services.android.navigation.ui.v5.voice.NavigationSpeechPlayer
2521
import com.mapbox.services.android.navigation.ui.v5.voice.SpeechPlayerProvider
2622
import com.mapbox.services.android.navigation.v5.milestone.Milestone
@@ -39,7 +35,7 @@ class ExampleViewModel(application: Application) : AndroidViewModel(application)
3935
}
4036

4137
val location: MutableLiveData<Location> = MutableLiveData()
42-
val route: MutableLiveData<DirectionsRoute> = MutableLiveData()
38+
val routes: MutableLiveData<List<DirectionsRoute>> = MutableLiveData()
4339
val progress: MutableLiveData<RouteProgress> = MutableLiveData()
4440
val milestone: MutableLiveData<Milestone> = MutableLiveData()
4541
val destination: MutableLiveData<Point> = MutableLiveData()
@@ -75,32 +71,34 @@ class ExampleViewModel(application: Application) : AndroidViewModel(application)
7571
navigation.addOffRouteListener(ExampleOffRouteListener(this))
7672

7773
// For fetching new routes
78-
routeFinder = ExampleRouteFinder(navigation, route, accessToken)
74+
routeFinder = ExampleRouteFinder(routes, accessToken)
7975
}
8076

8177
fun activateLocationEngine() {
8278
locationEngine.activate()
8379
}
8480

85-
fun loadOfflineFiles(callback: OfflineFilesLoadedCallback) {
86-
OfflineFileLoader(navigation, callback)
87-
}
88-
8981
fun findRouteToDestination() {
9082
location.value?.let { location ->
9183
destination.value?.let { destination ->
92-
if (isOfflineEnabled()) {
93-
routeFinder.findOfflineRoute(location, destination)
94-
} else {
95-
routeFinder.findRoute(location, destination)
96-
}
84+
routeFinder.findRoute(location, destination)
9785
}
9886
}
9987
}
10088

101-
fun startNavigationWith(route: DirectionsRoute) {
102-
navigation.startNavigation(route)
103-
removeLocationEngineListener()
89+
fun updatePrimaryRoute(primaryRoute: DirectionsRoute) {
90+
routeFinder.primaryRoute = primaryRoute
91+
}
92+
93+
fun canNavigate(): Boolean {
94+
return routeFinder.primaryRoute != null
95+
}
96+
97+
fun startNavigation() {
98+
routeFinder.primaryRoute?.let {
99+
navigation.startNavigation(it)
100+
removeLocationEngineListener()
101+
}
104102
}
105103

106104
fun stopNavigation() {
@@ -123,8 +121,8 @@ class ExampleViewModel(application: Application) : AndroidViewModel(application)
123121
geocode.value = response.body()
124122
}
125123

126-
override fun onFailure(call: Call<GeocodingResponse>, t: Throwable) {
127-
Timber.e(t, "Geocoding request failed")
124+
override fun onFailure(call: Call<GeocodingResponse>, throwable: Throwable) {
125+
Timber.e(throwable, "Geocoding request failed")
128126
}
129127
})
130128
}
@@ -142,10 +140,4 @@ class ExampleViewModel(application: Application) : AndroidViewModel(application)
142140
private fun removeLocationEngineListener() {
143141
locationEngine.removeLocationEngineListener(locationEngineListener)
144142
}
145-
146-
private fun isOfflineEnabled(): Boolean {
147-
val offlineKey = getApplication<NavigationApplication>().getString(R.string.offline_preference_key)
148-
val preferences = PreferenceManager.getDefaultSharedPreferences(getApplication())
149-
return preferences.getBoolean(offlineKey, true)
150-
}
151143
}

app/src/main/java/com/mapbox/services/android/navigation/testapp/example/ui/navigation/ExampleRouteFinder.kt

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,25 @@ import com.mapbox.api.directions.v5.models.DirectionsResponse
66
import com.mapbox.api.directions.v5.models.DirectionsRoute
77
import com.mapbox.geojson.Point
88
import com.mapbox.services.android.navigation.testapp.NavigationApplication
9-
import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation
109
import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute
1110
import retrofit2.Call
1211
import retrofit2.Callback
1312
import retrofit2.Response
1413
import timber.log.Timber
1514

16-
class ExampleRouteFinder(private val navigation: MapboxNavigation,
17-
private val route: MutableLiveData<DirectionsRoute>,
15+
class ExampleRouteFinder(private val routes: MutableLiveData<List<DirectionsRoute>>,
1816
private val accessToken: String) : Callback<DirectionsResponse> {
1917

2018
companion object {
2119
const val BEARING_TOLERANCE = 90.0
2220
}
2321

22+
var primaryRoute: DirectionsRoute? = null
23+
2424
fun findRoute(location: Location, destination: Point) {
2525
find(location, destination)
2626
}
2727

28-
fun findOfflineRoute(location: Location, destination: Point) {
29-
// doAsync {
30-
// val waypoints = arrayListOf(destination)
31-
// val route = navigation.findOfflineRouteFor(location, waypoints)
32-
// uiThread {
33-
// route?.let {
34-
// updateRoute(it)
35-
// }
36-
// }
37-
// }
38-
}
39-
4028
override fun onResponse(call: Call<DirectionsResponse>, response: Response<DirectionsResponse>) {
4129
handle(response.body())
4230
}
@@ -52,19 +40,21 @@ class ExampleRouteFinder(private val navigation: MapboxNavigation,
5240
.accessToken(accessToken)
5341
.origin(origin, bearing, BEARING_TOLERANCE)
5442
.destination(destination)
43+
.alternatives(true)
5544
.build()
5645
.getRoute(this)
5746
}
5847

5948
private fun handle(directionsResponse: DirectionsResponse?) {
6049
directionsResponse?.routes()?.let {
6150
if (it.isNotEmpty()) {
62-
updateRoute(it.first())
51+
updateRoutes(it)
6352
}
6453
}
6554
}
6655

67-
private fun updateRoute(directionsRoute: DirectionsRoute) {
68-
route.value = directionsRoute
56+
private fun updateRoutes(routes: List<DirectionsRoute>) {
57+
this.routes.value = routes
58+
primaryRoute = routes.first()
6959
}
7060
}

0 commit comments

Comments
 (0)