312 lines
No EOL
13 KiB
Kotlin
312 lines
No EOL
13 KiB
Kotlin
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)
|
|
|
|
/*
|
|
dump(maneuvers.value[0])
|
|
garmin.process(maneuvers.value[0])
|
|
*/
|
|
|
|
}
|
|
)
|
|
|
|
// 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()
|
|
}
|
|
|
|
} |