Skip to content

Commit

Permalink
When a new route needs to be drawn the process is done in two steps. …
Browse files Browse the repository at this point in the history
…First the route line is cleared from the map and the MapRouteLine class reset then on the next route progress update the line is drawn on the map
  • Loading branch information
Seth Bourget authored and cafesilencio committed Jul 7, 2020
1 parent 589c5a4 commit 2476bab
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 57 deletions.
3 changes: 3 additions & 0 deletions libnavigation-ui/api/current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,9 @@ package com.mapbox.navigation.ui.route {
method public void onInitialized(com.mapbox.navigation.ui.route.RouteLineLayerIds routeLineLayerIds);
}

public final class MapRouteProgressChangeListenerKt {
}

public class NavigationMapRoute implements androidx.lifecycle.LifecycleObserver {
method public void addProgressChangeListener(com.mapbox.navigation.core.MapboxNavigation!);
method public void addProgressChangeListener(com.mapbox.navigation.core.MapboxNavigation!, boolean);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,11 @@ internal class MapRouteLine(
* @param directionsRoutes the routes to be represented on the map.
*/
fun draw(directionsRoutes: List<DirectionsRoute>) {
reinitializeWithRoutes(directionsRoutes)
drawRoutes(routeFeatureData)
}

internal fun reinitializeWithRoutes(directionsRoutes: List<DirectionsRoute>) {
if (directionsRoutes.isNotEmpty()) {
clearRouteData()
this.directionsRoutes.addAll(directionsRoutes)
Expand All @@ -431,15 +436,23 @@ internal class MapRouteLine(
ThreadController.getMainScopeAndRootJob().scope
)
routeFeatureData.addAll(newRouteFeatureData)
drawRoutes(newRouteFeatureData)
hideRouteLineAtOffset(0f)
hideShieldLineAtOffset(0f)
if (newRouteFeatureData.isNotEmpty()) {
updateRouteTrafficSegments(newRouteFeatureData.first())
}
drawWayPoints()
updateAlternativeLayersVisibility(alternativesVisible, routeLayerIds)
updateAllLayersVisibility(allLayersAreVisible)
}
}

internal fun reinitializePrimaryRoute() {
this.routeFeatureData.firstOrNull { it.route == primaryRoute }?.let {
drawPrimaryRoute(it)
hideRouteLineAtOffset(vanishPointOffset)
hideShieldLineAtOffset(vanishPointOffset)
}
}

/**
* Updates which route is identified as the primary route.
*
Expand Down Expand Up @@ -652,24 +665,36 @@ internal class MapRouteLine(
private fun drawRoutes(routeData: List<RouteFeatureData>) {
val partitionedRoutes = routeData.partition { it.route == primaryRoute }
partitionedRoutes.first.firstOrNull()?.let {
setPrimaryRoutesSource(it.featureCollection)
val lineString: LineString = getLineStringForRoute(it.route)
val segments = calculateRouteLineSegments(
it.route,
lineString,
true,
::getRouteColorForCongestion
)
routeLineExpressionData.clear()
routeLineExpressionData.addAll(segments)
drawPrimaryRoute(it)
hideRouteLineAtOffset(vanishPointOffset)
hideShieldLineAtOffset(vanishPointOffset)
}
drawAlternativeRoutes(partitionedRoutes.second)
}

if (style.isFullyLoaded) {
val expression = getExpressionAtOffset(0f)
style.getLayer(PRIMARY_ROUTE_TRAFFIC_LAYER_ID)?.setProperties(lineGradient(expression))
}
private fun drawPrimaryRoute(routeData: RouteFeatureData) {
setPrimaryRoutesSource(routeData.featureCollection)
updateRouteTrafficSegments(routeData)
if (style.isFullyLoaded) {
val expression = getExpressionAtOffset(0f)
style.getLayer(PRIMARY_ROUTE_TRAFFIC_LAYER_ID)?.setProperties(lineGradient(expression))
}
}

partitionedRoutes.second.mapNotNull {
private fun updateRouteTrafficSegments(routeData: RouteFeatureData) {
val lineString: LineString = getLineStringForRoute(routeData.route)
val segments = calculateRouteLineSegments(
routeData.route,
lineString,
true,
::getRouteColorForCongestion
)
routeLineExpressionData.clear()
routeLineExpressionData.addAll(segments)
}

private fun drawAlternativeRoutes(routeData: List<RouteFeatureData>) {
routeData.mapNotNull {
it.featureCollection.features()
}.flatten().let {
setAlternativeRoutesSource(FeatureCollection.fromFeatures(it))
Expand All @@ -689,7 +714,10 @@ internal class MapRouteLine(
vanishPointOffset = distanceOffset
val filteredItems = routeLineExpressionData.filter { it.offset > distanceOffset }
val trafficExpressions = when (filteredItems.isEmpty()) {
true -> listOf(routeLineExpressionData.last().copy(offset = distanceOffset))
true -> when (routeLineExpressionData.isEmpty()) {
true -> listOf(RouteLineExpressionData(distanceOffset, routeUnknownColor))
false -> listOf(routeLineExpressionData.last().copy(offset = distanceOffset))
}
false -> {
val firstItemIndex = routeLineExpressionData.indexOf(filteredItems.first())
val fillerItem = if (firstItemIndex == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal class MapRouteProgressChangeListener(

private val jobControl by lazy { ThreadController.getMainScopeAndRootJob() }
private var lastDistanceValue = 0f

private var restoreRouteArrowVisibilityFun: DeferredRouteUpdateFun? = null
private var shouldReInitializePrimaryRoute = false
/**
* Returns the percentage of the distance traveled that was last calculated. This is only
* calculated if the @param vanishingLineAnimator passed into the constructor was non-null.
Expand Down Expand Up @@ -75,19 +76,33 @@ internal class MapRouteProgressChangeListener(
}

private fun updateRoute(directionsRoute: DirectionsRoute?, routeProgress: RouteProgress) {
if (routeArrow.routeArrowIsVisible()) {
routeArrow.addUpcomingManeuverArrow(routeProgress)
}
vanishingLineAnimator?.cancel()
val currentRoute = routeProgress.route
val hasGeometry = currentRoute.geometry()?.isNotEmpty() ?: false
if (hasGeometry && currentRoute != directionsRoute) {
vanishingLineAnimator?.cancel()
routeLine.draw(currentRoute)
routeLine.reinitializeWithRoutes(listOf(currentRoute))
shouldReInitializePrimaryRoute = true

restoreRouteArrowVisibilityFun = getRestoreArrowVisibilityFun(routeArrow.routeArrowIsVisible())
routeArrow.updateVisibilityTo(false)

this.lastDistanceValue = routeLine.vanishPointOffset
} else {
restoreRouteArrowVisibilityFun?.invoke()
restoreRouteArrowVisibilityFun = null

if (routeArrow.routeArrowIsVisible()) {
routeArrow.addUpcomingManeuverArrow(routeProgress)
}
// if there is no geometry then the session is in free drive and the vanishing
// route line code should not execute.
if (vanishingLineAnimator != null && hasGeometry) {
if (hasGeometry) {
if (shouldReInitializePrimaryRoute) {
routeLine.reinitializePrimaryRoute()
shouldReInitializePrimaryRoute = false
}

if (vanishingLineAnimator != null)
jobControl.scope.launch {
val percentDistanceTraveled = getPercentDistanceTraveled(routeProgress)
if (percentDistanceTraveled > 0) {
Expand Down Expand Up @@ -116,4 +131,9 @@ internal class MapRouteProgressChangeListener(
(routeProgress.distanceRemaining + routeProgress.distanceTraveled)
return routeProgress.distanceTraveled / totalDist
}

private fun getRestoreArrowVisibilityFun(isVisible: Boolean): DeferredRouteUpdateFun = {
routeArrow.updateVisibilityTo(isVisible)
}
}
internal typealias DeferredRouteUpdateFun = () -> Unit
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,86 @@ class MapRouteLineTest {
assertEquals(-7957339, result)
}

@Test
fun reinitializeWithRoutes() {
every { style.layers } returns listOf(primaryRouteLayer)
val route = getDirectionsRoute(true)
val mapRouteLine = MapRouteLine(
ctx,
style,
styleRes,
null,
layerProvider,
mapRouteSourceProvider,
null
)

mapRouteLine.reinitializeWithRoutes(listOf(route))

assertEquals(route, mapRouteLine.getPrimaryRoute())
}

@Test
fun reinitializePrimaryRoute() {
every { style.layers } returns listOf(primaryRouteLayer)
every { style.isFullyLoaded } returns true
every { style.getLayer(PRIMARY_ROUTE_TRAFFIC_LAYER_ID) } returns primaryRouteLayer
every { primaryRouteLayer.setFilter(any()) } returns Unit
every { primaryRouteShieldLayer.setFilter(any()) } returns Unit
every { alternativeRouteLayer.setFilter(any()) } returns Unit
every { alternativeRouteShieldLayer.setFilter(any()) } returns Unit
every { primaryRouteTrafficLayer.setFilter(any()) } returns Unit
every { waypointLayer.setFilter(any()) } returns Unit
every { primaryRouteLayer.setProperties(any()) } returns Unit
every { primaryRouteShieldLayer.setProperties(any()) } returns Unit
every { alternativeRouteLayer.setProperties(any()) } returns Unit
every { alternativeRouteShieldLayer.setProperties(any()) } returns Unit
every { primaryRouteTrafficLayer.setProperties(any()) } returns Unit
every { waypointLayer.setProperties(any()) } returns Unit
every { style.getLayerAs<LineLayer>("mapbox-navigation-route-shield-layer") } returns primaryRouteShieldLayer

val route = getDirectionsRoute(true)
val mapRouteLine = MapRouteLine(
ctx,
style,
styleRes,
null,
layerProvider,
mapRouteSourceProvider,
null
)

mapRouteLine.reinitializeWithRoutes(listOf(route))
mapRouteLine.reinitializePrimaryRoute()

verify { primaryRouteLayer.setProperties(any()) }
}

@Test
fun getExpressionAtOffsetWhenExpressionDataEmpty() {
every { style.layers } returns listOf(primaryRouteLayer)
val expectedExpression = "[\"step\", [\"line-progress\"], [\"rgba\", 0.0, 0.0, 0.0, 0.0], 0.2, [\"rgba\", 86.0, 168.0, 251.0, 1.0]]"
val route = getDirectionsRoute(true)
val mapRouteLine = MapRouteLine(
ctx,
style,
styleRes,
null,
layerProvider,
listOf<RouteFeatureData>(),
listOf<RouteLineExpressionData>(),
true,
false,
mapRouteSourceProvider,
0f,
null
)

val expression = mapRouteLine.getExpressionAtOffset(.2f)

assertEquals(expectedExpression, expression.toString())
}

private fun getDirectionsRoute(includeCongestion: Boolean): DirectionsRoute {
val congestion = when (includeCongestion) {
true -> "\"unknown\",\"heavy\",\"low\""
Expand Down

0 comments on commit 2476bab

Please sign in to comment.