Skip to content

Commit

Permalink
Route Polyline Configuration (#439)
Browse files Browse the repository at this point in the history
* Add Test Implementation

* Add Basic NavigationPathBuilder Implementation

* Add currentStepGeometryIndex to NavigationUIState

* Update Doc Strings

* Formatting

* Rename Builder and add Docstring
  • Loading branch information
ben-burwood authored Feb 4, 2025
1 parent 4397dee commit 8825d2a
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.stadiamaps.ferrostar.core.annotation.AnnotationPublisher
import com.stadiamaps.ferrostar.core.annotation.AnnotationWrapper
import com.stadiamaps.ferrostar.core.annotation.NoOpAnnotationPublisher
import com.stadiamaps.ferrostar.core.extensions.currentRoadName
import com.stadiamaps.ferrostar.core.extensions.currentStepGeometryIndex
import com.stadiamaps.ferrostar.core.extensions.deviation
import com.stadiamaps.ferrostar.core.extensions.progress
import com.stadiamaps.ferrostar.core.extensions.remainingSteps
Expand Down Expand Up @@ -57,6 +58,11 @@ data class NavigationUiState(
val isMuted: Boolean?,
/** The name of the road which the current route step is traversing. */
val currentStepRoadName: String?,
/**
* The index of the closest coordinate to the user's snapped location. The index is Relative to
* the *current* (i.e. first in remainingSteps) RouteStep Geometry
*/
val currentStepGeometryIndex: Int?,
/** The remaining steps in the trip (including the current step). */
val remainingSteps: List<RouteStep>?,
/** The route annotation object at the current location. */
Expand All @@ -83,6 +89,7 @@ data class NavigationUiState(
routeDeviation = coreState.tripState.deviation(),
isMuted = isMuted,
currentStepRoadName = coreState.tripState.currentRoadName(),
currentStepGeometryIndex = coreState.tripState.currentStepGeometryIndex(),
remainingSteps = coreState.tripState.remainingSteps(),
currentAnnotation = annotation)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ fun TripState.currentRoadName() =
TripState.Idle -> null
}

/**
* Get the current step geometry index - closest coordinate to the user's snapped location This
* index is relative to the *current* [`RouteStep`]'s geometry.
*
* @return The current step geometry index (if available and navigating).
*/
fun TripState.currentStepGeometryIndex() =
when (this) {
is TripState.Navigating -> this.currentStepGeometryIndex?.toInt()
is TripState.Complete,
TripState.Idle -> null
}

/**
* Get the remaining steps (including the current) in the current trip.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class DemoNavigationViewModel(
null,
null,
null,
null,
null))

override fun toggleMute() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.mapbox.mapboxsdk.geometry.LatLng
import com.mapbox.mapboxsdk.maps.Style
import com.maplibre.compose.MapView
import com.maplibre.compose.StaticLocationEngine
Expand All @@ -20,6 +19,7 @@ import com.maplibre.compose.settings.MapControls
import com.stadiamaps.ferrostar.core.NavigationUiState
import com.stadiamaps.ferrostar.core.toAndroidLocation
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.routeline.RouteOverlayBuilder
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera

/**
Expand All @@ -36,6 +36,8 @@ import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param routeOverlayBuilder The route overlay builder to use for rendering the route line on the
* MapView.
* @param onMapReadyCallback A callback that is invoked when the map is ready to be interacted with.
* If unspecified, the camera will change to `navigationCamera` if navigation is in progress.
* @param content Any additional composable map symbol content to render.
Expand All @@ -50,6 +52,7 @@ fun NavigationMapView(
locationRequestProperties: LocationRequestProperties =
LocationRequestProperties.NavigationDefault(),
snapUserLocationToRoute: Boolean = true,
routeOverlayBuilder: RouteOverlayBuilder = RouteOverlayBuilder.Default(),
onMapReadyCallback: ((Style) -> Unit)? = null,
content: @Composable @MapLibreComposable ((NavigationUiState) -> Unit)? = null
) {
Expand Down Expand Up @@ -80,9 +83,7 @@ fun NavigationMapView(
onMapReadyCallback =
onMapReadyCallback ?: { if (isNavigating) camera.value = navigationCamera },
) {
val geometry = uiState.routeGeometry
if (geometry != null)
BorderedPolyline(points = geometry.map { LatLng(it.lat, it.lng) }, zIndex = 0)
routeOverlayBuilder.navigationPath(uiState)

if (content != null) {
content(uiState)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.stadiamaps.ferrostar.maplibreui
package com.stadiamaps.ferrostar.maplibreui.routeline

import androidx.compose.runtime.Composable
import com.mapbox.mapboxsdk.geometry.LatLng
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.stadiamaps.ferrostar.maplibreui.routeline

import androidx.compose.runtime.Composable
import com.mapbox.mapboxsdk.geometry.LatLng
import com.maplibre.compose.ramani.MapLibreComposable
import com.stadiamaps.ferrostar.core.NavigationUiState

/**
* A Route Overlay (Polyline) Builder with sensible defaults - showing the full Navigation Route
* Geometry.
*
* This banner view includes the default [BorderedPolyline] to display the full supplied Route
* Geometry. Custom implementations to the appearance/functionality of the Route Overlay can be
* achieved by passing a custom implementation of the [navigationPath] parameter, using the
* [NavigationUiState] to access the Route Geometry.
*/
data class RouteOverlayBuilder(
internal val navigationPath:
@Composable
@MapLibreComposable
(uiState: NavigationUiState) -> Unit
) {
companion object {
fun Default() =
RouteOverlayBuilder(
navigationPath = { uiState ->
uiState.routeGeometry?.let { geometry ->
BorderedPolyline(points = geometry.map { LatLng(it.lat, it.lng) }, zIndex = 0)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
Expand Down Expand Up @@ -36,6 +35,7 @@ import com.stadiamaps.ferrostar.core.boundingBox
import com.stadiamaps.ferrostar.maplibreui.NavigationMapView
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.extensions.cameraControlState
import com.stadiamaps.ferrostar.maplibreui.routeline.RouteOverlayBuilder
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import com.stadiamaps.ferrostar.maplibreui.runtime.rememberMapControlsForProgressViewHeight

Expand All @@ -53,6 +53,8 @@ import com.stadiamaps.ferrostar.maplibreui.runtime.rememberMapControlsForProgres
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param routeOverlayBuilder The route overlay builder to use for rendering the route line on the
* MapView.
* @param theme The navigation UI theme to use for the view.
* @param config The configuration for the navigation view.
* @param views The navigation view component builder to use for the view.
Expand All @@ -74,6 +76,7 @@ fun DynamicallyOrientingNavigationView(
config: VisualNavigationViewConfig = VisualNavigationViewConfig.Default(),
views: NavigationViewComponentBuilder = NavigationViewComponentBuilder.Default(theme),
mapViewInsets: MutableState<PaddingValues> = remember { mutableStateOf(PaddingValues(0.dp)) },
routeOverlayBuilder: RouteOverlayBuilder = RouteOverlayBuilder.Default(),
onTapExit: (() -> Unit)? = null,
mapContent: @Composable @MapLibreComposable ((NavigationUiState) -> Unit)? = null,
) {
Expand All @@ -99,6 +102,7 @@ fun DynamicallyOrientingNavigationView(
mapControls = mapControls,
locationRequestProperties = locationRequestProperties,
snapUserLocationToRoute = snapUserLocationToRoute,
routeOverlayBuilder = routeOverlayBuilder,
content = mapContent)

if (uiState.isNavigating()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.stadiamaps.ferrostar.core.mock.pedestrianExample
import com.stadiamaps.ferrostar.maplibreui.NavigationMapView
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.extensions.cameraControlState
import com.stadiamaps.ferrostar.maplibreui.routeline.RouteOverlayBuilder
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import com.stadiamaps.ferrostar.maplibreui.runtime.rememberMapControlsForProgressViewHeight
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -54,6 +55,8 @@ import kotlinx.coroutines.flow.asStateFlow
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param routeOverlayBuilder The route overlay builder to use for rendering the route line on the
* MapView.
* @param theme The navigation UI theme to use for the view.
* @param config The configuration for the navigation view.
* @param views The navigation view component builder to use for the view.
Expand All @@ -75,6 +78,7 @@ fun LandscapeNavigationView(
config: VisualNavigationViewConfig = VisualNavigationViewConfig.Default(),
views: NavigationViewComponentBuilder = NavigationViewComponentBuilder.Default(theme),
mapViewInsets: MutableState<PaddingValues> = remember { mutableStateOf(PaddingValues(0.dp)) },
routeOverlayBuilder: RouteOverlayBuilder = RouteOverlayBuilder.Default(),
onTapExit: (() -> Unit)? = null,
mapContent: @Composable @MapLibreComposable() ((NavigationUiState) -> Unit)? = null,
) {
Expand All @@ -94,6 +98,7 @@ fun LandscapeNavigationView(
mapControls,
locationRequestProperties,
snapUserLocationToRoute,
routeOverlayBuilder,
onMapReadyCallback = { camera.value = navigationCamera },
mapContent)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.stadiamaps.ferrostar.core.mock.pedestrianExample
import com.stadiamaps.ferrostar.maplibreui.NavigationMapView
import com.stadiamaps.ferrostar.maplibreui.extensions.NavigationDefault
import com.stadiamaps.ferrostar.maplibreui.extensions.cameraControlState
import com.stadiamaps.ferrostar.maplibreui.routeline.RouteOverlayBuilder
import com.stadiamaps.ferrostar.maplibreui.runtime.navigationMapViewCamera
import com.stadiamaps.ferrostar.maplibreui.runtime.rememberMapControlsForProgressViewHeight
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -57,6 +58,8 @@ import kotlinx.coroutines.flow.asStateFlow
* engine.
* @param snapUserLocationToRoute If true, the user's displayed location will be snapped to the
* route line.
* @param routeOverlayBuilder The route overlay builder to use for rendering the route line on the
* MapView.
* @param theme The navigation UI theme to use for the view.
* @param config The configuration for the navigation view.
* @param views The navigation view component builder to use for the view.
Expand All @@ -78,6 +81,7 @@ fun PortraitNavigationView(
config: VisualNavigationViewConfig = VisualNavigationViewConfig.Default(),
views: NavigationViewComponentBuilder = NavigationViewComponentBuilder.Default(theme),
mapViewInsets: MutableState<PaddingValues> = remember { mutableStateOf(PaddingValues(0.dp)) },
routeOverlayBuilder: RouteOverlayBuilder = RouteOverlayBuilder.Default(),
onTapExit: (() -> Unit)? = null,
mapContent: @Composable @MapLibreComposable() ((NavigationUiState) -> Unit)? = null,
) {
Expand All @@ -104,6 +108,7 @@ fun PortraitNavigationView(
mapControls,
locationRequestProperties,
snapUserLocationToRoute,
routeOverlayBuilder,
onMapReadyCallback = { camera.value = navigationCamera },
mapContent)

Expand Down

0 comments on commit 8825d2a

Please sign in to comment.