mapbox / mapbox/mapbox-navigation-android

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

Aperta
#4,949 7 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

jira-sync-complete
Lingua principale
Kotlin
Stelle
651
Fork
321
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

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

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<DirectionsRoute>) {
    // 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<ResponseNavigationWaypoints> {
        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<Point> {
    val waypoints = arrayListOf<Point>()
    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<Point>()
    routePoints.add(originPoint)
    routePoints.addAll(waypoints)
    routePoints.add(destinationPoint)

    return routePoints
}

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

private fun getWaypointIndicesList(
    coordinates: List<Point>,
    responseNavigationWaypoints: ResponseNavigationWaypoints?
): List<Int?> {
    val waypointIndicesList = arrayListOf<Int?>()
    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<Point>): List<String?> {
    val waypointNamesList = arrayListOf<String?>()
    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<DirectionsRoute>, routerOrigin: RouterOrigin) {
        Log.d("test_nav", "routesReqCallback: ${routes.size}")
        setRouteAndStartNavigation(routes)
    }

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

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

}
`

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia esaminando il codice di NavigationViewFragment, in particolare routeProgressObserver, routesObserver, initRouteLine, moveCameraToCurrentLocationFirst e initNavigationCamera. Riproduci la configurazione con Android API 28 e Navigation SDK 2.0.0-rc.4, quindi traccia il rendering di route-arrow, gli aggiornamenti di vanishing route-line e le modifiche della fotocamera. Il lavoro è completato quando tutti e tre i comportamenti segnalati funzionano correttamente nel fragment.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
android, kotlin
Ambito
mobile-dev
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.