feat!: Application outline

This commit is contained in:
Piotr Dec 2024-07-23 00:55:59 +02:00
parent 80dfa6ab4e
commit 01fbf32042
Signed by: stawros
GPG key ID: F89F27AD8F881A91
9 changed files with 1440 additions and 190 deletions

View file

@ -0,0 +1,306 @@
package eu.ztsh.garmin.mapbox
import android.content.Context
import android.view.View
import android.widget.Toast
import com.mapbox.api.directions.v5.models.Bearing
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.geojson.Point
import com.mapbox.maps.plugin.gestures.gestures
import com.mapbox.navigation.base.TimeFormat
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
import com.mapbox.navigation.base.route.NavigationRoute
import com.mapbox.navigation.base.route.NavigationRouterCallback
import com.mapbox.navigation.base.route.RouterFailure
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
import com.mapbox.navigation.tripdata.maneuver.api.MapboxManeuverApi
import com.mapbox.navigation.tripdata.progress.api.MapboxTripProgressApi
import com.mapbox.navigation.tripdata.progress.model.DistanceRemainingFormatter
import com.mapbox.navigation.tripdata.progress.model.EstimatedTimeToArrivalFormatter
import com.mapbox.navigation.tripdata.progress.model.PercentDistanceTraveledFormatter
import com.mapbox.navigation.tripdata.progress.model.TimeRemainingFormatter
import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateFormatter
import com.mapbox.navigation.ui.components.maneuver.view.MapboxManeuverView
import com.mapbox.navigation.ui.components.tripprogress.view.MapboxTripProgressView
import com.mapbox.navigation.ui.maps.NavigationStyles
import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApi
import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowView
import com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptions
import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApi
import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineView
import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineApiOptions
import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineViewOptions
import eu.ztsh.garmin.UI
class RouteControl(private val mapControl: MapControl, ui: UI, private val context: Context) {
/**
* Generates updates for the [MapboxManeuverView] to display the upcoming maneuver instructions
* and remaining distance to the maneuver point.
*/
private lateinit var maneuverApi: MapboxManeuverApi
/**
* Generates updates for the [MapboxTripProgressView] that include remaining time and distance to the destination.
*/
private lateinit var tripProgressApi: MapboxTripProgressApi
/**
* Generates updates for the [routeLineView] with the geometries and properties of the routes that should be drawn on the map.
*/
private lateinit var routeLineApi: MapboxRouteLineApi
/**
* Draws route lines on the map based on the data from the [routeLineApi]
*/
private lateinit var routeLineView: MapboxRouteLineView
/**
* Generates updates for the [routeArrowView] with the geometries and properties of maneuver arrows that should be drawn on the map.
*/
private val routeArrowApi: MapboxRouteArrowApi = MapboxRouteArrowApi()
/**
* Draws maneuver arrows on the map based on the data [routeArrowApi].
*/
private lateinit var routeArrowView: MapboxRouteArrowView
init {
// make sure to use the same DistanceFormatterOptions across different features
val distanceFormatterOptions = DistanceFormatterOptions.Builder(context).build()
maneuverApi = MapboxManeuverApi(
MapboxDistanceFormatter(distanceFormatterOptions)
)
// initialize bottom progress view
tripProgressApi = MapboxTripProgressApi(
TripProgressUpdateFormatter.Builder(context)
.distanceRemainingFormatter(
DistanceRemainingFormatter(distanceFormatterOptions)
)
.timeRemainingFormatter(
TimeRemainingFormatter(context)
)
.percentRouteTraveledFormatter(
PercentDistanceTraveledFormatter()
)
.estimatedTimeToArrivalFormatter(
EstimatedTimeToArrivalFormatter(context, TimeFormat.NONE_SPECIFIED)
)
.build()
)
// initialize route line, the routeLineBelowLayerId is specified to place
// the route line below road labels layer on the map
// the value of this option will depend on the style that you are using
// and under which layer the route line should be placed on the map layers stack
val mapboxRouteLineViewOptions = MapboxRouteLineViewOptions.Builder(context)
.routeLineBelowLayerId("road-label-navigation")
.build()
routeLineApi = MapboxRouteLineApi(MapboxRouteLineApiOptions.Builder().build())
routeLineView = MapboxRouteLineView(mapboxRouteLineViewOptions)
// initialize maneuver arrow view to draw arrows on the map
val routeArrowOptions = RouteArrowOptions.Builder(context).build()
routeArrowView = MapboxRouteArrowView(routeArrowOptions)
// load map style
ui.mapView.mapboxMap.loadStyle(NavigationStyles.NAVIGATION_DAY_STYLE) {
// Ensure that the route line related layers are present before the route arrow
routeLineView.initializeLayers(it)
// add long click listener that search for a route to the clicked destination
ui.mapView.gestures.addOnMapLongClickListener { point ->
findRoute(point)
true
}
}
// initialize view interactions
ui.stop.setOnClickListener {
clearRouteAndStopNavigation()
}
ui.recenter.setOnClickListener {
mapControl.navigationCamera.requestNavigationCameraToFollowing()
ui.routeOverview.showTextAndExtend(UI.BUTTON_ANIMATION_DURATION)
}
ui.routeOverview.setOnClickListener {
mapControl.navigationCamera.requestNavigationCameraToOverview()
ui.recenter.showTextAndExtend(UI.BUTTON_ANIMATION_DURATION)
}
// set initial sounds button state
ui.soundButton.unmute()
}
/**
* Gets notified with progress along the currently active route.
*/
val routeProgressObserver = RouteProgressObserver { routeProgress ->
// update the camera position to account for the progressed fragment of the route
mapControl.viewportDataSource.onRouteProgressChanged(routeProgress)
mapControl.viewportDataSource.evaluate()
// draw the upcoming maneuver arrow on the map
val style = mapControl.ui.mapView.mapboxMap.style
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(
mapControl.context,
error.errorMessage,
Toast.LENGTH_SHORT
).show()
},
{
mapControl.ui.maneuverView.visibility = View.VISIBLE
mapControl.ui.maneuverView.renderManeuvers(maneuvers)
}
)
// update bottom trip progress summary
mapControl.ui.tripProgressView.render(
tripProgressApi.getTripProgress(routeProgress)
)
}
/**
* Gets notified whenever the tracked routes change.
*
* A change can mean:
* - routes get changed with [MapboxNavigation.setNavigationRoutes]
* - routes annotations get refreshed (for example, congestion annotation that indicate the live traffic along the route)
* - driver got off route and a reroute was executed
*/
val routesObserver = RoutesObserver { routeUpdateResult ->
if (routeUpdateResult.navigationRoutes.isNotEmpty()) {
// generate route geometries asynchronously and render them
routeLineApi.setNavigationRoutes(
routeUpdateResult.navigationRoutes
) { value ->
mapControl.ui.mapView.mapboxMap.style?.apply {
routeLineView.renderRouteDrawData(this, value)
}
}
// update the camera position to account for the new route
mapControl.viewportDataSource.onRouteChanged(routeUpdateResult.navigationRoutes.first())
mapControl.viewportDataSource.evaluate()
} else {
// remove the route line and route arrow from the map
val style = mapControl.ui.mapView.mapboxMap.style
if (style != null) {
routeLineApi.clearRouteLine { value ->
routeLineView.renderClearRouteLineValue(
style,
value
)
}
routeArrowView.render(style, routeArrowApi.clearArrows())
}
// remove the route reference from camera position evaluations
mapControl.viewportDataSource.clearRouteData()
mapControl.viewportDataSource.evaluate()
}
}
private fun findRoute(destination: Point) {
val originLocation = mapControl.navigationLocationProvider.lastLocation ?: return
val originPoint = Point.fromLngLat(originLocation.longitude, originLocation.latitude)
// execute a route request
// it's recommended to use the
// applyDefaultNavigationOptions and applyLanguageAndVoiceUnitOptions
// that make sure the route request is optimized
// to allow for support of all of the Navigation SDK features
mapControl.mapboxNavigation().requestRoutes(
RouteOptions.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(context)
.coordinatesList(listOf(originPoint, destination))
.apply {
// provide the bearing for the origin of the request to ensure
// that the returned route faces in the direction of the current user movement
originLocation.bearing?.let { bearing ->
bearingsList(
listOf(
Bearing.builder()
.angle(bearing)
.degrees(45.0)
.build(),
null
)
)
}
}
.layersList(listOf(mapControl.mapboxNavigation().getZLevel(), null))
.build(),
object : NavigationRouterCallback {
override fun onCanceled(routeOptions: RouteOptions, routerOrigin: String) {
// no impl
}
override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
// no impl
}
override fun onRoutesReady(
routes: List<NavigationRoute>,
routerOrigin: String
) {
setRouteAndStartNavigation(routes)
}
}
)
}
private fun setRouteAndStartNavigation(routes: List<NavigationRoute>) {
// set routes, where the first route in the list is the primary route that
// will be used for active guidance
mapControl.mapboxNavigation().setNavigationRoutes(routes)
// show UI elements
mapControl.ui.soundButton.visibility = View.VISIBLE
mapControl.ui.routeOverview.visibility = View.VISIBLE
mapControl.ui.tripProgressCard.visibility = View.VISIBLE
// move the camera to overview when new route is available
mapControl.navigationCamera.requestNavigationCameraToOverview()
// start simulation
mapControl.replay.startSimulation(routes.first().directionsRoute)
}
private fun clearRouteAndStopNavigation() {
// clear
mapControl.mapboxNavigation().setNavigationRoutes(listOf())
// stop simulation
mapControl.replay.stopSimulation()
// hide UI elements
mapControl.ui.soundButton.visibility = View.INVISIBLE
mapControl.ui.maneuverView.visibility = View.INVISIBLE
mapControl.ui.routeOverview.visibility = View.INVISIBLE
mapControl.ui.tripProgressCard.visibility = View.INVISIBLE
}
fun cancel() {
maneuverApi.cancel()
routeLineApi.cancel()
routeLineView.cancel()
}
}