mapbox / mapbox/mapbox-navigation-android

routeArrowView not showing; no route progress; camera is incorrectly centered (on fragment)

Open
#4,949 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

jira-sync-complete
Dominant language
Kotlin
Stars
651
Forks
321
PR merge metrics
No merged PRs in 30d

Description

**Android API:** - 28
**Mapbox Navigation SDK version:** - 'com.mapbox.navigation:android:2.0.0-rc.4'

I am implementing navigation on fragment and after migrating from version 1.x to version 2.x. I'm having trouble with the new routeArrowView and routeLineView:

1.routeArrowView not showing on map
2.the route line does not disappear ignoring the parameter: withVanishingRouteLineEnabled (true)
3.camera is incorrectly centered

![Screenshot_1633536126](https://user-images.githubusercontent.com/50403368/136250580-2b9e6d46-714d-4d22-b32e-9e63535bdbb0.png)

I attach all the parts of the code related to the Mapbox configuration.

Click to expand the code
`
class NavigationViewFragment : Fragment() {

private lateinit var maneuverApi: MapboxManeuverApi

// Mapbox Maps components
private lateinit var mapboxMap: MapboxMap

// Mapbox Navigation components
private lateinit var mapboxNavigation: MapboxNavigation

// camera
private lateinit var navigationCamera: NavigationCamera
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
private val pixelDensity = Resources.getSystem().displayMetrics.density
private val overviewPadding: EdgeInsets by lazy {
EdgeInsets(
140.0 * pixelDensity,
40.0 * pixelDensity,
120.0 * pixelDensity,
40.0 * pixelDensity
)
}
private val landscapeOverviewPadding: EdgeInsets by lazy {
EdgeInsets(
30.0 * pixelDensity,
380.0 * pixelDensity,
20.0 * pixelDensity,
20.0 * pixelDensity
)
}
private val followingPadding: EdgeInsets by lazy {
EdgeInsets(
180.0 * pixelDensity,
40.0 * pixelDensity,
150.0 * pixelDensity,
40.0 * pixelDensity
)
}
private val landscapeFollowingPadding: EdgeInsets by lazy {
EdgeInsets(
30.0 * pixelDensity,
380.0 * pixelDensity,
110.0 * pixelDensity,
40.0 * pixelDensity
)
}

// trip progress bottom view
private lateinit var tripProgressApi: MapboxTripProgressApi

// route line
private lateinit var routeLineAPI: MapboxRouteLineApi
private lateinit var routeLineView: MapboxRouteLineView
private lateinit var routeArrowView: MapboxRouteArrowView
private val routeArrowAPI: MapboxRouteArrowApi = MapboxRouteArrowApi()

private val navigationLocationProvider = NavigationLocationProvider()

/* ----- Location and route progress callbacks ----- */
private val locationObserver = object : LocationObserver {
var firstLocationUpdateReceived = false

override fun onNewRawLocation(rawLocation: Location) {
// not handled
}

override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
val enhancedLocation = locationMatcherResult.enhancedLocation

currentPosition = LatLng(enhancedLocation.latitude, enhancedLocation.longitude)
currentBearing = enhancedLocation.bearing

// update location puck's position on the map
navigationLocationProvider.changePosition(
location = enhancedLocation,
keyPoints = locationMatcherResult.keyPoints,
)

// update camera position to account for new location
viewportDataSource.onLocationChanged(enhancedLocation)
viewportDataSource.evaluate()

// if this is the first location update the activity has received,
// it's best to immediately move the camera to the current user location
if (!firstLocationUpdateReceived) {
firstLocationUpdateReceived = true
navigationCamera.requestNavigationCameraToOverview(
stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
.maxDuration(0) // instant transition
.build()
)
}
}
}

private val routeProgressObserver = RouteProgressObserver { routeProgress ->
Log.d("test_nav", "routeProgressObserver: ${routeProgress.currentState.name}")
// update the camera position to account for the progressed fragment of the route
viewportDataSource.onRouteProgressChanged(routeProgress)
viewportDataSource.evaluate()

// draw the upcoming maneuver arrow on the map
val style = mapboxMap.getStyle()
if (style != null) {
val maneuverArrowResult = routeArrowAPI.addUpcomingManeuverArrow(routeProgress)
routeArrowView.renderManeuverUpdate(style, maneuverArrowResult)
}

// update top banner with maneuver instructions
val maneuvers = maneuverApi.getManeuvers(routeProgress)
maneuvers.fold(
{ error ->
Toast.makeText(
activity,
error.errorMessage,
Toast.LENGTH_SHORT
).show()
},
{
maneuverView.visibility = View.VISIBLE
maneuverView.renderManeuvers(maneuvers)
}
)

// update bottom trip progress summary
tripProgressView.render(
tripProgressApi.getTripProgress(routeProgress)
)
}

private val routesObserver = RoutesObserver { routes ->
Log.d("test_nav", "routesObserver: ${routes.size}")
if (routes.isNotEmpty()) {
// generate route geometries asynchronously and render them
val routeLines = routes.map { RouteLine(it, null) }

routeLineAPI.setRoutes(
routeLines
) { value ->
mapboxMap.getStyle()?.apply {
routeLineView.renderRouteDrawData(this, value)
}
}

// update the camera position to account for the new route
viewportDataSource.onRouteChanged(routes.first())
viewportDataSource.evaluate()
} else {
// remove the route line and route arrow from the map
val style = mapboxMap.getStyle()
if (style != null) {
routeLineAPI.clearRouteLine { value ->
routeLineView.renderClearRouteLineValue(
style,
value
)
}
routeArrowView.render(style, routeArrowAPI.clearArrows())
}

// remove the route reference from camera position evaluations
viewportDataSource.clearRouteData()
viewportDataSource.evaluate()
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapboxMap = mapView.getMapboxMap()

activity?.let { activity ->
init(activity)
}
}

private fun initNavigation(context: Context) {
mapboxNavigation = if (MapboxNavigationProvider.isCreated()) {
MapboxNavigationProvider.retrieve()
} else {
MapboxNavigationProvider.create(
NavigationOptions.Builder(context)
.accessToken(getString(R.string.mapbox_access_token))
.build()
)
}
}

private fun moveCameraToCurrentLocationFirst() {
mapboxNavigation.registerLocationObserver(object : LocationObserver {
override fun onNewRawLocation(rawLocation: Location) {
val point = Point.fromLngLat(rawLocation.longitude, rawLocation.latitude)
val cameraOptions = CameraOptions.Builder()
.center(point)
.zoom(INITIAL_ZOOM)
.build()
mapboxMap.setCamera(cameraOptions)
mapboxNavigation.unregisterLocationObserver(this)
}

override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
// not handled
}
})
}

private fun initNavigationCamera() {
viewportDataSource = MapboxNavigationViewportDataSource(mapboxMap)
navigationCamera = NavigationCamera(
mapboxMap,
mapView.camera,
viewportDataSource
)
mapView.camera.addCameraAnimationsLifecycleListener(
NavigationBasicGesturesHandler(navigationCamera)
)
navigationCamera.registerNavigationCameraStateChangeObserver { navigationCameraState ->
// shows/hide the recenter button depending on the camera state
when (navigationCameraState) {
NavigationCameraState.TRANSITION_TO_FOLLOWING,
NavigationCameraState.FOLLOWING -> recenter.visibility = View.INVISIBLE

NavigationCameraState.TRANSITION_TO_OVERVIEW,
NavigationCameraState.OVERVIEW,
NavigationCameraState.IDLE -> recenter.visibility = View.VISIBLE
}
}
if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
viewportDataSource.overviewPadding = landscapeOverviewPadding
} else {
viewportDataSource.overviewPadding = overviewPadding
}
if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
viewportDataSource.followingPadding = landscapeFollowingPadding
} else {
viewportDataSource.followingPadding = followingPadding
}
}

private fun initTopManeuverView(context: Context) {
maneuverApi = MapboxManeuverApi(
MapboxDistanceFormatter(DistanceFormatterOptions.Builder(context).build())
)
}

private fun initBottomProgressView(context: Context) {
tripProgressApi = MapboxTripProgressApi(
TripProgressUpdateFormatter.Builder(context)
.distanceRemainingFormatter(
DistanceRemainingFormatter(
mapboxNavigation.navigationOptions.distanceFormatterOptions
)
)
.timeRemainingFormatter(TimeRemainingFormatter(context))
.percentRouteTraveledFormatter(PercentDistanceTraveledFormatter())
.estimatedTimeToArrivalFormatter(
EstimatedTimeToArrivalFormatter(context, TimeFormat.NONE_SPECIFIED)
)
.build()
)
}

private fun initRouteLine(context: Context) {
val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(context)
.withRouteLineBelowLayerId("road-label")
.withVanishingRouteLineEnabled(true)
.build()
routeLineAPI = MapboxRouteLineApi(mapboxRouteLineOptions)
routeLineView = MapboxRouteLineView(mapboxRouteLineOptions)
val routeArrowOptions = RouteArrowOptions.Builder(context).build()
routeArrowView = MapboxRouteArrowView(routeArrowOptions)
}

@SuppressLint("MissingPermission")
private fun init(context: Context) {
initNavigation(context)
moveCameraToCurrentLocationFirst()
initNavigationCamera()
initTopManeuverView(context)
initBottomProgressView(context)
initRouteLine(context)
loadMapStyle()
initViewInteractions()

// start the trip session to being receiving location updates in free drive
// and later when a route is set also receiving route progress updates
mapboxNavigation.startTripSession()
}

private fun loadMapStyle() {
mapboxMap.loadStyleUri(
Style.MAPBOX_STREETS,
{
Log.d("test_nav", "onStyleLoaded -> ")
getRoute()
},
object : OnMapLoadErrorListener {
override fun onMapLoadError(mapLoadErrorType: MapLoadErrorType, message: String) {
Log.d("test_nav", "onMapLoadError: ${mapLoadErrorType.name}")
}
}
)
}

private fun initViewInteractions() {
stop.setOnClickListener {
clearRouteAndStopNavigation()
}
recenter.setOnClickListener {
navigationCamera.requestNavigationCameraToFollowing()
}
routeOverview.setOnClickListener {
navigationCamera.requestNavigationCameraToOverview()
recenter.showTextAndExtend(BUTTON_ANIMATION_DURATION)
}
}

private fun getRoute() {
if (settingsData.isCustomNavigationEnabled) {
requestWaypoints()
} else {
requestRoute(buildRouteOptions(null), routesReqCallback)
}
}

override fun onStart() {
super.onStart()
subscribeNavigationObservers()
}

override fun onStop() {
super.onStop()
unsubscribeNavigationObservers()
rerouteJob.cancel()
mapboxNavigation.setRerouteController()
}

override fun onDestroyView() {
super.onDestroyView()
routeLineAPI.cancel()
routeLineView.cancel()
MapboxNavigationProvider.destroy()
}

private fun setRouteAndStartNavigation(routes: List) {
// set routes, where the first route in the list is the primary route that
// will be used for active guidance
mapboxNavigation.setRoutes(routes)

// set custom rerouteController
mapboxNavigation.setRerouteController(rerouteController)

// show UI elements
routeOverview.visibility = View.VISIBLE
tripProgressCard.visibility = View.VISIBLE

// move the camera to overview when new route is available
navigationCamera.requestNavigationCameraToOverview()
}

private fun clearRouteAndStopNavigation() {
// clear
mapboxNavigation.setRoutes(listOf())

// hide UI elements
maneuverView.visibility = View.INVISIBLE
routeOverview.visibility = View.INVISIBLE
tripProgressCard.visibility = View.INVISIBLE

stopNavigation()
}

private fun requestWaypoints() {
val responseListener = object : OnResponseListener {
override fun onDataChange(data: ResponseNavigationWaypoints) {
requestRoute(buildRouteOptions(data), routesReqCallback)
}

override fun onCancelled(e: Exception) {
requestRoute(buildRouteOptions(null), routesReqCallback)
}
}

apiClient.getNavigationWaypointsToRideEvent(
currentPosition,
rideEventId,
currentBearing,
responseListener
)
}

private fun buildRouteOptions(responseNavigationWaypoints: ResponseNavigationWaypoints?): RouteOptions? {
val originLocation = navigationLocationProvider.lastLocation

val coordinatesList = getCoordinatesList(responseNavigationWaypoints)
val approachesList = getApproachesList(responseNavigationWaypoints)
val waypointIndicesList =
getWaypointIndicesList(coordinatesList, responseNavigationWaypoints)

return RouteOptions.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(applicationContext)
.coordinatesList(coordinatesList)
// provide the bearing for the origin of the request to ensure
// that the returned route faces in the direction of the current user movement
.bearingsList(
listOf(
Bearing.builder()
.angle(originLocation?.bearing?.toDouble() ?: 120.0)
.degrees(45.0)
.build(),
null
)
)
.approachesList(approachesList)
.waypointIndicesList(waypointIndicesList)
.continueStraight(
responseNavigationWaypoints?.data?.continueStraight
?: !settingsData.isAllowsUTurnAtWaypoint
)
.alternatives(settingsData.isIncludesAlternativeRoutes)
.steps(settingsData.isIncludesSteps)
.build()
}

private fun requestRoute(
routeOptions: RouteOptions?,
routesRequestCallback: RouterCallback
) {
Log.d("test_nav", "requestRoute: ${routeOptions.toString()}")
if (isAdded && routeOptions != null) {
try {
mapboxNavigation.requestRoutes(
routeOptions,
routesRequestCallback
)
} catch (exception: Exception) {
showErrorAlert(settingsData.generalError)
}
}
}

private fun getCoordinatesList(responseNavigationWaypoints: ResponseNavigationWaypoints?): List {
val waypoints = arrayListOf()
responseNavigationWaypoints?.data?.waypoints?.forEach { waypoint ->
waypoints.add(Point.fromLngLat(waypoint.lng, waypoint.lat))
}

val originPoint = Point.fromLngLat(
currentPosition?.longitude ?: 0.0,
currentPosition?.latitude ?: 0.0
)
val destinationPoint = Point.fromLngLat(
directionsPosition?.longitude ?: 0.0,
directionsPosition?.latitude ?: 0.0
)
val routePoints = arrayListOf()
routePoints.add(originPoint)
routePoints.addAll(waypoints)
routePoints.add(destinationPoint)

return routePoints
}

private fun getApproachesList(responseNavigationWaypoints: ResponseNavigationWaypoints?): List {
val approachesList = arrayListOf()
approachesList.add(APPROACH_UNRESTRICTED)
responseNavigationWaypoints?.data?.waypoints?.forEach { waypoint ->
approachesList.add(waypoint.approach)
}
approachesList.add(APPROACH_UNRESTRICTED)
return approachesList
}

private fun getWaypointIndicesList(
coordinates: List,
responseNavigationWaypoints: ResponseNavigationWaypoints?
): List {
val waypointIndicesList = arrayListOf()
waypointIndicesList.add(0)
responseNavigationWaypoints?.data?.waypoints?.forEachIndexed { index, waypoint ->
if (waypoint.separatesLegs) {
waypointIndicesList.add(index + 1)
}
}
waypointIndicesList.add(coordinates.lastIndex)
return waypointIndicesList
}

private fun getWaypointNamesList(coordinates: List): List {
val waypointNamesList = arrayListOf()
coordinates.forEachIndexed { index, _ ->
waypointNamesList.add(if (index == coordinates.lastIndex) settingsData.destinationLabel else "")
}
return waypointNamesList
}

private fun subscribeNavigationObservers() {
mapboxNavigation.registerRoutesObserver(routesObserver)
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
mapboxNavigation.registerLocationObserver(locationObserver)
}

private fun unsubscribeNavigationObservers() {
mapboxNavigation.unregisterRoutesObserver(routesObserver)
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
mapboxNavigation.unregisterLocationObserver(locationObserver)
}

private val routesReqCallback = object : RouterCallback {
override fun onRoutesReady(routes: List, routerOrigin: RouterOrigin) {
Log.d("test_nav", "routesReqCallback: ${routes.size}")
setRouteAndStartNavigation(routes)
}

override fun onFailure(reasons: List, routeOptions: RouteOptions) {
showErrorAlert(settingsData.noRouteError)
}

override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
showErrorAlert(settingsData.noRouteError)
}
}

}
`

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the NavigationViewFragment code, especially routeProgressObserver, routesObserver, initRouteLine, moveCameraToCurrentLocationFirst, and initNavigationCamera. Reproduce the setup with Android API 28 and Navigation SDK 2.0.0-rc.4, then trace route-arrow rendering, vanishing route-line updates, and camera changes. Done means all three reported behaviors work correctly in the fragment.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, kotlin
Domain
mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.