feat!: Application outline
This commit is contained in:
parent
80dfa6ab4e
commit
01fbf32042
9 changed files with 1440 additions and 190 deletions
306
app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt
Normal file
306
app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt
Normal 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()
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue