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, routeOptions: RouteOptions) { // no impl } override fun onRoutesReady( routes: List, routerOrigin: String ) { setRouteAndStartNavigation(routes) } } ) } private fun setRouteAndStartNavigation(routes: List) { // 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() } }