Please run code below, simple replace BasicNavigationFragment from your examples. Can you please tell me how to fix it so the off-route road will not disappear (it is the section at the end of the road)? Basically the point is to navigate also through waypoints I add manually, they are off the road. Thanks
This fragment shows how to use the UI SDK standalone components
including the InstructionView and SummaryBottomSheet and voice to
build the turn-by-turn navigation experience with Navigation Core SDK.
*/
class BasicNavigationFragment :
Fragment(),
OnMapReadyCallback,
FeedbackBottomSheetListener,
OnWayNameChangedListener, OffRouteObserver {
private val routeOverviewPadding by lazy { buildRouteOverviewPadding() }
private lateinit var mapboxNavigation: MapboxNavigation
private var navigationMapboxMap: NavigationMapboxMap? = null
private lateinit var speechPlayer: NavigationSpeechPlayer
private lateinit var destination: LatLng
private lateinit var summaryBehavior: BottomSheetBehavior
private lateinit var routeOverviewButton: ImageButton
private lateinit var cancelBtn: AppCompatImageButton
private lateinit var feedbackButton: NavigationButton
private lateinit var instructionSoundButton: NavigationButton
private lateinit var alertView: NavigationAlertView
private val mapboxReplayer = MapboxReplayer()
private var mapboxMap: MapboxMap? = null
private var locationComponent: LocationComponent? = null
private var directionRoute: DirectionsRoute? = null
private var feedbackItem: FeedbackItem? = null
private var feedbackEncodedScreenShot: String? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_basic_navigation, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
// This is not the most efficient way to preserve the route on a device rotation.
// This is here to demonstrate that this event needs to be handled in order to
// redraw the route line after a rotation.
directionRoute?.let {
outState.putString(PRIMARY_ROUTE_BUNDLE_KEY, it.toJson())
}
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
directionRoute = getRouteFromBundle(savedInstanceState)
}
// Callbacks and Observers
private val routesReqCallback = object : RoutesRequestCallback {
override fun onRoutesReady(routes: List) {
Timber.d("route request success %s", routes.toString())
if (routes.isNotEmpty()) {
directionRoute = routes[0]
var routeJson = Gson().toJson(directionRoute)
val waypoints = PolylineUtils.decode(directionRoute!!.geometry()!!, 6)
val s = directionRoute!!.geometry()
val newPoints = ArrayList<Point>()
newPoints.add(Point.fromLngLat(21.306749, 48.714061))
newPoints.add(Point.fromLngLat(21.304810, 48.713679))
newPoints.add(Point.fromLngLat(21.304005, 48.714631))
newPoints.add(Point.fromLngLat(21.303678, 48.714644))
waypoints.addAll(newPoints)
val offroadGeometry = PolylineUtils.encode(newPoints, 6)
directionRoute!!.routeOptions()!!.coordinates().addAll(newPoints)
val newLegs = ArrayList<RouteLeg>()
directionRoute!!.legs()?.forEach {
newLegs.add(RouteLeg.builder().distance(it.distance()!!.plus(10.toDouble()))
.admins(it.admins())
.annotation(it.annotation())
.duration(it.duration())
.durationTypical(it.durationTypical())
.incidents(it.incidents())
.steps(getSteps(it.steps()!!, offroadGeometry))
.summary(it.summary()).build())
}
val newRoute = DirectionsRoute.builder().legs(newLegs).voiceLanguage(directionRoute!!.voiceLanguage())
.routeOptions(directionRoute!!.routeOptions())
.distance(directionRoute!!.distance() + 1000).duration(directionRoute!!.duration()).geometry(PolylineUtils.encode(waypoints, 6)).build()
directionRoute = newRoute
routeJson = Gson().toJson(directionRoute)
navigationMapboxMap?.drawRoute(directionRoute!!)
mapboxNavigation.setRoutes(listOf(newRoute))
startNavigation.visibility = View.VISIBLE
startNavigation.isEnabled = true
} else {
startNavigation.isEnabled = false
}
}
override fun onRoutesRequestFailure(throwable: Throwable, routeOptions: RouteOptions) {
}
override fun onRoutesRequestCanceled(routeOptions: RouteOptions) {
}
}
private fun getSteps(steps: List, geometry: String): MutableList {
val newSteps = ArrayList()
newSteps.addAll(steps)
newSteps.removeLast()
val stepToAlter = steps.get(steps.size - 1)
val alteredStep = LegStep.builder().bannerInstructions(stepToAlter.bannerInstructions()!!).distance(stepToAlter.distance())
.drivingSide(stepToAlter.drivingSide()).geometry(stepToAlter.geometry()).maneuver(
StepManeuver.builder().bearingBefore(stepToAlter.maneuver().bearingBefore())
.bearingAfter(stepToAlter.maneuver().bearingAfter())
.instruction("You will arrive to the dirt road entry point")
.modifier("right")
.rawLocation(arrayOf(stepToAlter.maneuver().location().longitude(), stepToAlter.maneuver().location().latitude()).toDoubleArray())
.type("turn")
.build()
).weight(stepToAlter.weight())
.intersections(stepToAlter.intersections()!!)
.mode(stepToAlter.mode())
.name(stepToAlter.name())
.duration(stepToAlter.duration())
.ref(stepToAlter.ref())
.build()
newSteps.add(alteredStep)
val newStep = LegStep.builder().distance(1000.toDouble()).duration(100.toDouble()).
drivingSide("right").mode("driving").name("destination").
maneuver(StepManeuver.builder().instruction("You are close to the end lucky star.").rawLocation(arrayOf(21.303678.toDouble(), 48.714644.toDouble()).toDoubleArray()).bearingAfter(100.toDouble()).bearingBefore(300.toDouble()).type("arrive").build())
.geometry(geometry).weight(300.toDouble()).build()
newSteps.add(newStep)
return newSteps
}
private val tripSessionStateObserver = object : TripSessionStateObserver {
override fun onSessionStateChanged(tripSessionState: TripSessionState) {
when (tripSessionState) {
TripSessionState.STARTED -> {
updateViews(TripSessionState.STARTED)
// If shouldSimulateRoute is true a ReplayRouteLocationEngine will be used which is intended
// for testing else a real location engine is used.
private fun getLocationEngine(): LocationEngine {
return if (shouldSimulateRoute()) {
ReplayLocationEngine(mapboxReplayer)
} else {
LocationEngineProvider.getBestLocationEngine(activity!!)
}
}
private fun shouldSimulateRoute(): Boolean {
return PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(this.getString(R.string.simulate_route_key), true)
}
override fun onOffRouteStateChanged(offRoute: Boolean) {
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Please run code below, simple replace BasicNavigationFragment from your examples. Can you please tell me how to fix it so the off-route road will not disappear (it is the section at the end of the road)? Basically the point is to navigate also through waypoints I add manually, they are off the road. Thanks
package com.mapbox.navigation.examples.core
import android.annotation.SuppressLint import android.graphics.Bitmap import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.widget.ImageButton import androidx.appcompat.widget.AppCompatImageButton import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.snackbar.Snackbar import com.google.gson.Gson import com.mapbox.android.core.location.LocationEngine import com.mapbox.android.core.location.LocationEngineCallback import com.mapbox.android.core.location.LocationEngineProvider import com.mapbox.android.core.location.LocationEngineResult import com.mapbox.api.directions.v5.models. import com.mapbox.geojson.Point import com.mapbox.geojson.utils.PolylineUtils import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.camera.CameraUpdateFactory import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.location.LocationComponent import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions import com.mapbox.mapboxsdk.location.OnCameraTrackingChangedListener import com.mapbox.mapboxsdk.location.modes.CameraMode import com.mapbox.mapboxsdk.location.modes.RenderMode import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.OnMapReadyCallback import com.mapbox.mapboxsdk.maps.Style import com.mapbox.navigation.base.internal.extensions.applyDefaultParams import com.mapbox.navigation.base.internal.extensions.coordinates import com.mapbox.navigation.base.internal.route.RouteUrl import com.mapbox.navigation.base.trip.model.RouteProgress import com.mapbox.navigation.core.MapboxNavigation import com.mapbox.navigation.core.directions.session.RoutesRequestCallback import com.mapbox.navigation.core.replay.MapboxReplayer import com.mapbox.navigation.core.replay.ReplayLocationEngine import com.mapbox.navigation.core.replay.route.ReplayProgressObserver import com.mapbox.navigation.core.telemetry.events.FeedbackEvent.UI import com.mapbox.navigation.core.trip.session. import com.mapbox.navigation.examples.R import com.mapbox.navigation.examples.utils.Utils import com.mapbox.navigation.examples.utils.Utils.PRIMARY_ROUTE_BUNDLE_KEY import com.mapbox.navigation.examples.utils.Utils.getRouteFromBundle import com.mapbox.navigation.ui.NavigationButton import com.mapbox.navigation.ui.NavigationConstants import com.mapbox.navigation.ui.SoundButton import com.mapbox.navigation.ui.camera.DynamicCamera import com.mapbox.navigation.ui.camera.NavigationCamera import com.mapbox.navigation.ui.feedback.FeedbackBottomSheet import com.mapbox.navigation.ui.feedback.FeedbackBottomSheetListener import com.mapbox.navigation.ui.feedback.FeedbackItem import com.mapbox.navigation.ui.instruction.NavigationAlertView import com.mapbox.navigation.ui.internal.utils.ViewUtils import com.mapbox.navigation.ui.map.NavigationMapboxMap import com.mapbox.navigation.ui.map.OnWayNameChangedListener import com.mapbox.navigation.ui.summary.SummaryBottomSheet import com.mapbox.navigation.ui.voice.NavigationSpeechPlayer import com.mapbox.navigation.ui.voice.SpeechPlayerProvider import com.mapbox.navigation.ui.voice.VoiceInstructionLoader import kotlinx.android.synthetic.main.fragment_basic_navigation.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import okhttp3.Cache import timber.log.Timber import java.io.File import java.lang.ref.WeakReference import java.util.Locale
/**
build the turn-by-turn navigation experience with Navigation Core SDK. */ class BasicNavigationFragment : Fragment(), OnMapReadyCallback, FeedbackBottomSheetListener, OnWayNameChangedListener, OffRouteObserver {
private val routeOverviewPadding by lazy { buildRouteOverviewPadding() }
private lateinit var mapboxNavigation: MapboxNavigation private var navigationMapboxMap: NavigationMapboxMap? = null private lateinit var speechPlayer: NavigationSpeechPlayer private lateinit var destination: LatLng private lateinit var summaryBehavior: BottomSheetBehavior
private lateinit var routeOverviewButton: ImageButton
private lateinit var cancelBtn: AppCompatImageButton
private lateinit var feedbackButton: NavigationButton
private lateinit var instructionSoundButton: NavigationButton
private lateinit var alertView: NavigationAlertView
private val mapboxReplayer = MapboxReplayer()
private var mapboxMap: MapboxMap? = null private var locationComponent: LocationComponent? = null private var directionRoute: DirectionsRoute? = null
private var feedbackItem: FeedbackItem? = null private var feedbackEncodedScreenShot: String? = null
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_basic_navigation, container, false) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViews()
}
override fun onResume() { super.onResume() mapView.onResume() }
override fun onPause() { super.onPause() mapView.onPause() }
override fun onStart() { super.onStart() mapView.onStart() mapboxNavigation.registerOffRouteObserver(this) }
override fun onStop() { super.onStop() mapView.onStop() }
override fun onLowMemory() { super.onLowMemory() mapView.onLowMemory() }
override fun onDestroyView() { super.onDestroyView() mapView.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) mapView.onSaveInstanceState(outState) // This is not the most efficient way to preserve the route on a device rotation. // This is here to demonstrate that this event needs to be handled in order to // redraw the route line after a rotation. directionRoute?.let { outState.putString(PRIMARY_ROUTE_BUNDLE_KEY, it.toJson()) } }
override fun onViewStateRestored(savedInstanceState: Bundle?) { super.onViewStateRestored(savedInstanceState) directionRoute = getRouteFromBundle(savedInstanceState) }
@SuppressLint("MissingPermission") override fun onMapReady(mapboxMap: MapboxMap) { Timber.d("onMapReady") this.mapboxMap = mapboxMap mapboxMap.moveCamera(CameraUpdateFactory.zoomTo(15.0))
// locationComponent?.lastKnownLocation?.let { originLocation -> mapboxNavigation.requestRoutes( RouteOptions.builder().applyDefaultParams() .profile(RouteUrl.PROFILE_DRIVING_TRAFFIC) // .radiuses("100;100;100;10;10;10;10") .accessToken(Utils.getMapboxAccessToken(requireContext())) .coordinates( originLocation, null, Point.fromLngLat(21.306181, 48.715949), ) .alternatives(true) .build(), routesReqCallback ) // } }
}
// InstructionView Feedback Bottom Sheet listener override fun onFeedbackSelected(feedbackItem: FeedbackItem?) { feedbackItem?.let { feedback -> this.feedbackItem = feedback sendFeedback() } }
override fun onFeedbackDismissed() { // do nothing }
override fun onWayNameChanged(wayName: String) { wayNameView.updateWayNameText(wayName) if (summaryBehavior.state == BottomSheetBehavior.STATE_HIDDEN) { hideWayNameView() } else { showWayNameView() } }
@Suppress("DEPRECATION") @SuppressLint("MissingPermission") private fun initViews() { startNavigation.apply { visibility = View.VISIBLE isEnabled = false setOnClickListener { Timber.d("start navigation") if (mapboxNavigation.getRoutes().isNotEmpty()) { updateCameraOnNavigationStateChange(true) navigationMapboxMap?.startCamera(directionRoute!!) mapboxNavigation.startTripSession() // mapboxNavigation.getRerouteController()!!.interrupt() } } }
}
private fun updateViews(tripSessionState: TripSessionState) { when (tripSessionState) { TripSessionState.STARTED -> { startNavigation.visibility = View.GONE
}
private fun showLogoAndAttribution() { summaryBottomSheet.viewTreeObserver.addOnGlobalLayoutListener( object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { navigationMapboxMap?.retrieveMap()?.uiSettings?.apply { val bottomMargin = summaryBottomSheet.measuredHeight setLogoMargins( logoMarginLeft, logoMarginTop, logoMarginRight, bottomMargin ) setAttributionMargins( attributionMarginLeft, attributionMarginTop, attributionMarginRight, bottomMargin ) } summaryBottomSheet.viewTreeObserver.removeOnGlobalLayoutListener(this) } } ) }
private fun initNavigation() { val accessToken = Utils.getMapboxAccessToken(requireContext()) mapboxNavigation = MapboxNavigation( MapboxNavigation.defaultNavigationOptionsBuilder(requireContext(), accessToken) .locationEngine(getLocationEngine()) .build() ) mapboxNavigation.apply { registerTripSessionStateObserver(tripSessionStateObserver) registerRouteProgressObserver(routeProgressObserver) registerBannerInstructionsObserver(bannerInstructionObserver) registerVoiceInstructionsObserver(voiceInstructionsObserver) } }
private fun initializeSpeechPlayer() { val cache = Cache( File( requireContext().cacheDir, InstructionViewActivity.VOICE_INSTRUCTION_CACHE ), 10 1024 1024 ) val voiceInstructionLoader = VoiceInstructionLoader(requireContext(), Mapbox.getAccessToken(), cache) val speechPlayerProvider = SpeechPlayerProvider( requireContext(), Locale.US.language, true, voiceInstructionLoader ) speechPlayer = NavigationSpeechPlayer(speechPlayerProvider) }
private fun showFeedbackBottomSheet() { feedbackItem = null feedbackEncodedScreenShot = null requireFragmentManager().let { mapboxMap?.snapshot(this::encodeSnapshot) FeedbackBottomSheet.newInstance( this, NavigationConstants.FEEDBACK_BOTTOM_SHEET_DURATION ) .show(it, FeedbackBottomSheet.TAG) } }
private fun sendFeedback() { val feedback = feedbackItem val screenShot = feedbackEncodedScreenShot if (feedback != null && !screenShot.isNullOrEmpty()) { mapboxNavigation.postUserFeedback( feedback.feedbackType, feedback.description, UI, screenShot, feedback.feedbackSubType.toTypedArray() ) showFeedbackSentSnackBar( context = requireContext(), view = if (summaryBehavior.state == BottomSheetBehavior.STATE_HIDDEN) { recenterBtn } else { summaryBottomSheet }, setAnchorView = true ) } }
private fun encodeSnapshot(snapshot: Bitmap) { screenshotView.visibility = View.VISIBLE screenshotView.setImageBitmap(snapshot) mapView.visibility = View.INVISIBLE feedbackEncodedScreenShot = ViewUtils.encodeView(ViewUtils.captureView(mapView)) screenshotView.visibility = View.INVISIBLE mapView.visibility = View.VISIBLE
}
private fun showWayNameView() { wayNameView.updateVisibility(!wayNameView.retrieveWayNameText().isNullOrEmpty()) }
private fun hideWayNameView() { wayNameView.updateVisibility(false) }
private fun buildRouteOverviewPadding(): IntArray { val leftRightPadding = resources .getDimension( com.mapbox.navigation.ui.R.dimen.mapbox_route_overview_left_right_padding ) .toInt() val paddingBuffer = resources .getDimension( com.mapbox.navigation.ui.R.dimen.mapbox_route_overview_buffer_padding ) .toInt() val instructionHeight = ( resources .getDimension( com.mapbox.navigation.ui.R.dimen.mapbox_instruction_content_height ) + paddingBuffer ) .toInt() val summaryHeight = resources .getDimension(com.mapbox.navigation.ui.R.dimen.mapbox_summary_bottom_sheet_height) .toInt() return intArrayOf(leftRightPadding, instructionHeight, leftRightPadding, summaryHeight) }
private fun updateCameraOnNavigationStateChange( navigationStarted: Boolean ) { navigationMapboxMap?.apply { if (navigationStarted) { updateCameraTrackingMode(NavigationCamera.NAVIGATION_TRACKING_MODE_GPS) updateLocationLayerRenderMode(RenderMode.GPS) } else { updateCameraTrackingMode(NavigationCamera.NAVIGATION_TRACKING_MODE_NONE) updateLocationLayerRenderMode(RenderMode.COMPASS) } } }
// Callbacks and Observers private val routesReqCallback = object : RoutesRequestCallback { override fun onRoutesReady(routes: List) {
Timber.d("route request success %s", routes.toString())
if (routes.isNotEmpty()) {
directionRoute = routes[0]
var routeJson = Gson().toJson(directionRoute)
}
private fun getSteps(steps: List, geometry: String): MutableList {
val newSteps = ArrayList()
newSteps.addAll(steps)
newSteps.removeLast()
val stepToAlter = steps.get(steps.size - 1)
val alteredStep = LegStep.builder().bannerInstructions(stepToAlter.bannerInstructions()!!).distance(stepToAlter.distance())
.drivingSide(stepToAlter.drivingSide()).geometry(stepToAlter.geometry()).maneuver(
StepManeuver.builder().bearingBefore(stepToAlter.maneuver().bearingBefore())
.bearingAfter(stepToAlter.maneuver().bearingAfter())
.instruction("You will arrive to the dirt road entry point")
.modifier("right")
.rawLocation(arrayOf(stepToAlter.maneuver().location().longitude(), stepToAlter.maneuver().location().latitude()).toDoubleArray())
.type("turn")
.build()
).weight(stepToAlter.weight())
.intersections(stepToAlter.intersections()!!)
.mode(stepToAlter.mode())
.name(stepToAlter.name())
.duration(stepToAlter.duration())
.ref(stepToAlter.ref())
.build()
newSteps.add(alteredStep)
}
private val tripSessionStateObserver = object : TripSessionStateObserver { override fun onSessionStateChanged(tripSessionState: TripSessionState) { when (tripSessionState) { TripSessionState.STARTED -> { updateViews(TripSessionState.STARTED)
}
private val routeProgressObserver = object : RouteProgressObserver { override fun onRouteProgressChanged(routeProgress: RouteProgress) { instructionView.updateDistanceWith(routeProgress) summaryBottomSheet.update(routeProgress) } }
private val bannerInstructionObserver = object : BannerInstructionsObserver { override fun onNewBannerInstructions(bannerInstructions: BannerInstructions) { instructionView.updateBannerInstructionsWith(bannerInstructions) } }
private val voiceInstructionsObserver = object : VoiceInstructionsObserver { override fun onNewVoiceInstructions(voiceInstructions: VoiceInstructions) { speechPlayer.play(voiceInstructions) } }
private val cameraTrackingChangedListener = object : OnCameraTrackingChangedListener { override fun onCameraTrackingChanged(currentMode: Int) { }
}
private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { if (summaryBehavior.state == BottomSheetBehavior.STATE_HIDDEN) { recenterBtn.show() } }
}
private val locationListenerCallback = MyLocationEngineCallback(this)
private class MyLocationEngineCallback(fragment: BasicNavigationFragment) : LocationEngineCallback {
}
// If shouldSimulateRoute is true a ReplayRouteLocationEngine will be used which is intended // for testing else a real location engine is used. private fun getLocationEngine(): LocationEngine { return if (shouldSimulateRoute()) { ReplayLocationEngine(mapboxReplayer) } else { LocationEngineProvider.getBestLocationEngine(activity!!) } }
private fun shouldSimulateRoute(): Boolean { return PreferenceManager.getDefaultSharedPreferences(requireContext()) .getBoolean(this.getString(R.string.simulate_route_key), true) }
override fun onOffRouteStateChanged(offRoute: Boolean) {
} }