feat!: Application outline
This commit is contained in:
parent
80dfa6ab4e
commit
01fbf32042
9 changed files with 1440 additions and 190 deletions
692
app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt
Normal file
692
app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt
Normal file
|
@ -0,0 +1,692 @@
|
|||
//package eu.ztsh.garmin
|
||||
//
|
||||
//import android.annotation.SuppressLint
|
||||
//import android.content.res.Configuration
|
||||
//import android.content.res.Resources
|
||||
//import android.os.Bundle
|
||||
//import android.view.View
|
||||
//import android.widget.Toast
|
||||
//import androidx.appcompat.app.AppCompatActivity
|
||||
//import com.mapbox.api.directions.v5.models.Bearing
|
||||
//import com.mapbox.api.directions.v5.models.DirectionsRoute
|
||||
//import com.mapbox.api.directions.v5.models.RouteOptions
|
||||
//import com.mapbox.bindgen.Expected
|
||||
//import com.mapbox.common.location.Location
|
||||
//import com.mapbox.geojson.Point
|
||||
//import com.mapbox.maps.EdgeInsets
|
||||
//import com.mapbox.maps.ImageHolder
|
||||
//import com.mapbox.maps.plugin.LocationPuck2D
|
||||
//import com.mapbox.maps.plugin.animation.camera
|
||||
//import com.mapbox.maps.plugin.gestures.gestures
|
||||
//import com.mapbox.maps.plugin.locationcomponent.location
|
||||
//import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
|
||||
//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.options.NavigationOptions
|
||||
//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.lifecycle.MapboxNavigationApp
|
||||
//import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
|
||||
//import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation
|
||||
//import com.mapbox.navigation.core.replay.route.ReplayProgressObserver
|
||||
//import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
|
||||
//import com.mapbox.navigation.core.trip.session.LocationMatcherResult
|
||||
//import com.mapbox.navigation.core.trip.session.LocationObserver
|
||||
//import com.mapbox.navigation.core.trip.session.RouteProgressObserver
|
||||
//import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
|
||||
//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.base.util.MapboxNavigationConsumer
|
||||
//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.camera.NavigationCamera
|
||||
//import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
|
||||
//import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler
|
||||
//import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState
|
||||
//import com.mapbox.navigation.ui.maps.camera.transition.NavigationCameraTransitionOptions
|
||||
//import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
|
||||
//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 com.mapbox.navigation.voice.api.MapboxSpeechApi
|
||||
//import com.mapbox.navigation.voice.api.MapboxVoiceInstructionsPlayer
|
||||
//import com.mapbox.navigation.voice.model.SpeechAnnouncement
|
||||
//import com.mapbox.navigation.voice.model.SpeechError
|
||||
//import com.mapbox.navigation.voice.model.SpeechValue
|
||||
//import com.mapbox.navigation.voice.model.SpeechVolume
|
||||
//import eu.ztsh.garmin.databinding.ActivityMainBinding
|
||||
//import java.util.Date
|
||||
//import java.util.Locale
|
||||
//
|
||||
///**
|
||||
// * This example demonstrates a basic turn-by-turn navigation experience by putting together some UI elements to showcase
|
||||
// * navigation camera transitions, guidance instructions banners and playback, and progress along the route.
|
||||
// *
|
||||
// * Before running the example make sure you have put your access_token in the correct place
|
||||
// * inside [app/src/main/res/values/mapbox_access_token.xml]. If not present then add this file
|
||||
// * at the location mentioned above and add the following content to it
|
||||
// *
|
||||
// * <?xml version="1.0" encoding="utf-8"?>
|
||||
// * <resources xmlns:tools="http://schemas.android.com/tools">
|
||||
// * <string name="mapbox_access_token"><PUT_YOUR_ACCESS_TOKEN_HERE></string>
|
||||
// * </resources>
|
||||
// *
|
||||
// * The example assumes that you have granted location permissions and does not enforce it. However,
|
||||
// * the permission is essential for proper functioning of this example. The example also uses replay
|
||||
// * location engine to facilitate navigation without actually physically moving.
|
||||
// *
|
||||
// * How to use this example:
|
||||
// * - You can long-click the map to select a destination.
|
||||
// * - The guidance will start to the selected destination while simulating location updates.
|
||||
// * You can disable simulation by commenting out the [replayLocationEngine] setter in [NavigationOptions].
|
||||
// * Then, the device's real location will be used.
|
||||
// * - At any point in time you can finish guidance or select a new destination.
|
||||
// * - You can use buttons to mute/unmute voice instructions, recenter the camera, or show the route overview.
|
||||
// */
|
||||
//@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
|
||||
//class ExampleActivity {
|
||||
//
|
||||
// private companion object {
|
||||
// private const val BUTTON_ANIMATION_DURATION = 1500L
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Debug observer that makes sure the replayer has always an up-to-date information to generate mock updates.
|
||||
// */
|
||||
// private lateinit var replayProgressObserver: ReplayProgressObserver
|
||||
//
|
||||
// /**
|
||||
// * Debug object that converts a route into events that can be replayed to navigate a route.
|
||||
// */
|
||||
// private val replayRouteMapper = ReplayRouteMapper()
|
||||
//
|
||||
// /**
|
||||
// * Bindings to the example layout.
|
||||
// */
|
||||
// private lateinit var binding: ActivityMainBinding
|
||||
//
|
||||
// /**
|
||||
// * Used to execute camera transitions based on the data generated by the [viewportDataSource].
|
||||
// * This includes transitions from route overview to route following and continuously updating the camera as the location changes.
|
||||
// */
|
||||
// private lateinit var navigationCamera: NavigationCamera
|
||||
//
|
||||
// /**
|
||||
// * Produces the camera frames based on the location and routing data for the [navigationCamera] to execute.
|
||||
// */
|
||||
// private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
|
||||
//
|
||||
// /*
|
||||
// * Below are generated camera padding values to ensure that the route fits well on screen while
|
||||
// * other elements are overlaid on top of the map (including instruction view, buttons, etc.)
|
||||
// */
|
||||
// private val pixelDensity = Resources.getSystem().displayMetrics.density
|
||||
// private val overviewPadding: EdgeInsets by lazy {
|
||||
// EdgeInsets(
|
||||
// 140.0 * pixelDensity,
|
||||
// 40.0 * pixelDensity,
|
||||
// 120.0 * pixelDensity,
|
||||
// 40.0 * pixelDensity
|
||||
// )
|
||||
// }
|
||||
// private val landscapeOverviewPadding: EdgeInsets by lazy {
|
||||
// EdgeInsets(
|
||||
// 30.0 * pixelDensity,
|
||||
// 380.0 * pixelDensity,
|
||||
// 110.0 * pixelDensity,
|
||||
// 20.0 * pixelDensity
|
||||
// )
|
||||
// }
|
||||
// private val followingPadding: EdgeInsets by lazy {
|
||||
// EdgeInsets(
|
||||
// 180.0 * pixelDensity,
|
||||
// 40.0 * pixelDensity,
|
||||
// 150.0 * pixelDensity,
|
||||
// 40.0 * pixelDensity
|
||||
// )
|
||||
// }
|
||||
// private val landscapeFollowingPadding: EdgeInsets by lazy {
|
||||
// EdgeInsets(
|
||||
// 30.0 * pixelDensity,
|
||||
// 380.0 * pixelDensity,
|
||||
// 110.0 * pixelDensity,
|
||||
// 40.0 * pixelDensity
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 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
|
||||
//
|
||||
// /**
|
||||
// * Stores and updates the state of whether the voice instructions should be played as they come or muted.
|
||||
// */
|
||||
// private var isVoiceInstructionsMuted = false
|
||||
// set(value) {
|
||||
// field = value
|
||||
// if (value) {
|
||||
// binding.soundButton.muteAndExtend(BUTTON_ANIMATION_DURATION)
|
||||
// voiceInstructionsPlayer.volume(SpeechVolume(0f))
|
||||
// } else {
|
||||
// binding.soundButton.unmuteAndExtend(BUTTON_ANIMATION_DURATION)
|
||||
// voiceInstructionsPlayer.volume(SpeechVolume(1f))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Extracts message that should be communicated to the driver about the upcoming maneuver.
|
||||
// * When possible, downloads a synthesized audio file that can be played back to the driver.
|
||||
// */
|
||||
// private lateinit var speechApi: MapboxSpeechApi
|
||||
//
|
||||
// /**
|
||||
// * Plays the synthesized audio files with upcoming maneuver instructions
|
||||
// * or uses an on-device Text-To-Speech engine to communicate the message to the driver.
|
||||
// * NOTE: do not use lazy initialization for this class since it takes some time to initialize
|
||||
// * the system services required for on-device speech synthesis. With lazy initialization
|
||||
// * there is a high risk that said services will not be available when the first instruction
|
||||
// * has to be played. [MapboxVoiceInstructionsPlayer] should be instantiated in
|
||||
// * `Activity#onCreate`.
|
||||
// */
|
||||
// private lateinit var voiceInstructionsPlayer: MapboxVoiceInstructionsPlayer
|
||||
//
|
||||
// /**
|
||||
// * Observes when a new voice instruction should be played.
|
||||
// */
|
||||
// private val voiceInstructionsObserver = VoiceInstructionsObserver { voiceInstructions ->
|
||||
// speechApi.generate(voiceInstructions, speechCallback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Based on whether the synthesized audio file is available, the callback plays the file
|
||||
// * or uses the fall back which is played back using the on-device Text-To-Speech engine.
|
||||
// */
|
||||
// private val speechCallback =
|
||||
// MapboxNavigationConsumer<Expected<SpeechError, SpeechValue>> { expected ->
|
||||
// expected.fold(
|
||||
// { error ->
|
||||
// // play the instruction via fallback text-to-speech engine
|
||||
// voiceInstructionsPlayer.play(
|
||||
// error.fallback,
|
||||
// voiceInstructionsPlayerCallback
|
||||
// )
|
||||
// },
|
||||
// { value ->
|
||||
// // play the sound file from the external generator
|
||||
// voiceInstructionsPlayer.play(
|
||||
// value.announcement,
|
||||
// voiceInstructionsPlayerCallback
|
||||
// )
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * When a synthesized audio file was downloaded, this callback cleans up the disk after it was played.
|
||||
// */
|
||||
// private val voiceInstructionsPlayerCallback =
|
||||
// MapboxNavigationConsumer<SpeechAnnouncement> { value ->
|
||||
// // remove already consumed file to free-up space
|
||||
// speechApi.clean(value)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * [NavigationLocationProvider] is a utility class that helps to provide location updates generated by the Navigation SDK
|
||||
// * to the Maps SDK in order to update the user location indicator on the map.
|
||||
// */
|
||||
// private val navigationLocationProvider = NavigationLocationProvider()
|
||||
//
|
||||
// /**
|
||||
// * Gets notified with location updates.
|
||||
// *
|
||||
// * Exposes raw updates coming directly from the location services
|
||||
// * and the updates enhanced by the Navigation SDK (cleaned up and matched to the road).
|
||||
// */
|
||||
// private val locationObserver = object : LocationObserver {
|
||||
// var firstLocationUpdateReceived = false
|
||||
//
|
||||
// override fun onNewRawLocation(rawLocation: Location) {
|
||||
// // not handled
|
||||
// }
|
||||
//
|
||||
// override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
|
||||
// val enhancedLocation = locationMatcherResult.enhancedLocation
|
||||
// // update location puck's position on the map
|
||||
// navigationLocationProvider.changePosition(
|
||||
// location = enhancedLocation,
|
||||
// keyPoints = locationMatcherResult.keyPoints,
|
||||
// )
|
||||
//
|
||||
// // update camera position to account for new location
|
||||
// viewportDataSource.onLocationChanged(enhancedLocation)
|
||||
// viewportDataSource.evaluate()
|
||||
//
|
||||
// // if this is the first location update the activity has received,
|
||||
// // it's best to immediately move the camera to the current user location
|
||||
// if (!firstLocationUpdateReceived) {
|
||||
// firstLocationUpdateReceived = true
|
||||
// navigationCamera.requestNavigationCameraToOverview(
|
||||
// stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
|
||||
// .maxDuration(0) // instant transition
|
||||
// .build()
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Gets notified with progress along the currently active route.
|
||||
// */
|
||||
// private val routeProgressObserver = RouteProgressObserver { routeProgress ->
|
||||
// // update the camera position to account for the progressed fragment of the route
|
||||
// viewportDataSource.onRouteProgressChanged(routeProgress)
|
||||
// viewportDataSource.evaluate()
|
||||
//
|
||||
// // draw the upcoming maneuver arrow on the map
|
||||
// val style = binding.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(
|
||||
// this@ExampleActivity,
|
||||
// error.errorMessage,
|
||||
// Toast.LENGTH_SHORT
|
||||
// ).show()
|
||||
// },
|
||||
// {
|
||||
// binding.maneuverView.visibility = View.VISIBLE
|
||||
// binding.maneuverView.renderManeuvers(maneuvers)
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// // update bottom trip progress summary
|
||||
// binding.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
|
||||
// */
|
||||
// private val routesObserver = RoutesObserver { routeUpdateResult ->
|
||||
// if (routeUpdateResult.navigationRoutes.isNotEmpty()) {
|
||||
// // generate route geometries asynchronously and render them
|
||||
// routeLineApi.setNavigationRoutes(
|
||||
// routeUpdateResult.navigationRoutes
|
||||
// ) { value ->
|
||||
// binding.mapView.mapboxMap.style?.apply {
|
||||
// routeLineView.renderRouteDrawData(this, value)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // update the camera position to account for the new route
|
||||
// viewportDataSource.onRouteChanged(routeUpdateResult.navigationRoutes.first())
|
||||
// viewportDataSource.evaluate()
|
||||
// } else {
|
||||
// // remove the route line and route arrow from the map
|
||||
// val style = binding.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
|
||||
// viewportDataSource.clearRouteData()
|
||||
// viewportDataSource.evaluate()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private val mapboxNavigation: MapboxNavigation by requireMapboxNavigation(
|
||||
// onResumedObserver = object : MapboxNavigationObserver {
|
||||
// @SuppressLint("MissingPermission")
|
||||
// override fun onAttached(mapboxNavigation: MapboxNavigation) {
|
||||
// mapboxNavigation.registerRoutesObserver(routesObserver)
|
||||
// mapboxNavigation.registerLocationObserver(locationObserver)
|
||||
// mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
|
||||
// mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
|
||||
//
|
||||
// replayProgressObserver = ReplayProgressObserver(mapboxNavigation.mapboxReplayer)
|
||||
// mapboxNavigation.registerRouteProgressObserver(replayProgressObserver)
|
||||
//
|
||||
// // Start the trip session to being receiving location updates in free drive
|
||||
// // and later when a route is set also receiving route progress updates.
|
||||
// // In case of `startReplayTripSession`,
|
||||
// // location events are emitted by the `MapboxReplayer`
|
||||
// mapboxNavigation.startReplayTripSession()
|
||||
// }
|
||||
//
|
||||
// override fun onDetached(mapboxNavigation: MapboxNavigation) {
|
||||
// mapboxNavigation.unregisterRoutesObserver(routesObserver)
|
||||
// mapboxNavigation.unregisterLocationObserver(locationObserver)
|
||||
// mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
|
||||
// mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver)
|
||||
// mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
|
||||
// mapboxNavigation.mapboxReplayer.finish()
|
||||
// }
|
||||
// },
|
||||
// onInitialize = this::initNavigation
|
||||
// )
|
||||
//
|
||||
// @SuppressLint("MissingPermission")
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
// setContentView(binding.root)
|
||||
//
|
||||
// // initialize Navigation Camera
|
||||
// viewportDataSource = MapboxNavigationViewportDataSource(binding.mapView.mapboxMap)
|
||||
// navigationCamera = NavigationCamera(
|
||||
// binding.mapView.mapboxMap,
|
||||
// binding.mapView.camera,
|
||||
// viewportDataSource
|
||||
// )
|
||||
// // set the animations lifecycle listener to ensure the NavigationCamera stops
|
||||
// // automatically following the user location when the map is interacted with
|
||||
// binding.mapView.camera.addCameraAnimationsLifecycleListener(
|
||||
// NavigationBasicGesturesHandler(navigationCamera)
|
||||
// )
|
||||
// navigationCamera.registerNavigationCameraStateChangeObserver { navigationCameraState ->
|
||||
// // shows/hide the recenter button depending on the camera state
|
||||
// when (navigationCameraState) {
|
||||
// NavigationCameraState.TRANSITION_TO_FOLLOWING,
|
||||
// NavigationCameraState.FOLLOWING -> binding.recenter.visibility = View.INVISIBLE
|
||||
// NavigationCameraState.TRANSITION_TO_OVERVIEW,
|
||||
// NavigationCameraState.OVERVIEW,
|
||||
// NavigationCameraState.IDLE -> binding.recenter.visibility = View.VISIBLE
|
||||
// }
|
||||
// }
|
||||
// // set the padding values depending on screen orientation and visible view layout
|
||||
// if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
// viewportDataSource.overviewPadding = landscapeOverviewPadding
|
||||
// } else {
|
||||
// viewportDataSource.overviewPadding = overviewPadding
|
||||
// }
|
||||
// if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
// viewportDataSource.followingPadding = landscapeFollowingPadding
|
||||
// } else {
|
||||
// viewportDataSource.followingPadding = followingPadding
|
||||
// }
|
||||
//
|
||||
// // make sure to use the same DistanceFormatterOptions across different features
|
||||
// val distanceFormatterOptions = DistanceFormatterOptions.Builder(this).build()
|
||||
//
|
||||
// // initialize maneuver api that feeds the data to the top banner maneuver view
|
||||
// maneuverApi = MapboxManeuverApi(
|
||||
// MapboxDistanceFormatter(distanceFormatterOptions)
|
||||
// )
|
||||
//
|
||||
// // initialize bottom progress view
|
||||
// tripProgressApi = MapboxTripProgressApi(
|
||||
// TripProgressUpdateFormatter.Builder(this)
|
||||
// .distanceRemainingFormatter(
|
||||
// DistanceRemainingFormatter(distanceFormatterOptions)
|
||||
// )
|
||||
// .timeRemainingFormatter(
|
||||
// TimeRemainingFormatter(this)
|
||||
// )
|
||||
// .percentRouteTraveledFormatter(
|
||||
// PercentDistanceTraveledFormatter()
|
||||
// )
|
||||
// .estimatedTimeToArrivalFormatter(
|
||||
// EstimatedTimeToArrivalFormatter(this, TimeFormat.NONE_SPECIFIED)
|
||||
// )
|
||||
// .build()
|
||||
// )
|
||||
//
|
||||
// // initialize voice instructions api and the voice instruction player
|
||||
// speechApi = MapboxSpeechApi(
|
||||
// this,
|
||||
// Locale("pl").language
|
||||
// )
|
||||
// voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
|
||||
// this,
|
||||
// Locale("pl").language
|
||||
// )
|
||||
//
|
||||
// // 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(this)
|
||||
// .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(this).build()
|
||||
// routeArrowView = MapboxRouteArrowView(routeArrowOptions)
|
||||
//
|
||||
// // load map style
|
||||
// binding.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
|
||||
// binding.mapView.gestures.addOnMapLongClickListener { point ->
|
||||
// findRoute(point)
|
||||
// true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // initialize view interactions
|
||||
// binding.stop.setOnClickListener {
|
||||
// clearRouteAndStopNavigation()
|
||||
// }
|
||||
// binding.recenter.setOnClickListener {
|
||||
// navigationCamera.requestNavigationCameraToFollowing()
|
||||
// binding.routeOverview.showTextAndExtend(BUTTON_ANIMATION_DURATION)
|
||||
// }
|
||||
// binding.routeOverview.setOnClickListener {
|
||||
// navigationCamera.requestNavigationCameraToOverview()
|
||||
// binding.recenter.showTextAndExtend(BUTTON_ANIMATION_DURATION)
|
||||
// }
|
||||
// binding.soundButton.setOnClickListener {
|
||||
// // mute/unmute voice instructions
|
||||
// isVoiceInstructionsMuted = !isVoiceInstructionsMuted
|
||||
// }
|
||||
//
|
||||
// // set initial sounds button state
|
||||
// binding.soundButton.unmute()
|
||||
// }
|
||||
//
|
||||
// override fun onDestroy() {
|
||||
// super.onDestroy()
|
||||
// maneuverApi.cancel()
|
||||
// routeLineApi.cancel()
|
||||
// routeLineView.cancel()
|
||||
// speechApi.cancel()
|
||||
// voiceInstructionsPlayer.shutdown()
|
||||
// }
|
||||
//
|
||||
// private fun initNavigation() {
|
||||
// MapboxNavigationApp.setup(
|
||||
// NavigationOptions.Builder(this)
|
||||
// .build()
|
||||
// )
|
||||
//
|
||||
// // initialize location puck
|
||||
// binding.mapView.location.apply {
|
||||
// setLocationProvider(navigationLocationProvider)
|
||||
// this.locationPuck = LocationPuck2D(
|
||||
// bearingImage = ImageHolder.Companion.from(
|
||||
// com.mapbox.navigation.ui.maps.R.drawable.mapbox_navigation_puck_icon
|
||||
// )
|
||||
// )
|
||||
// puckBearingEnabled = true
|
||||
// enabled = true
|
||||
// }
|
||||
//
|
||||
// replayOriginLocation()
|
||||
// }
|
||||
//
|
||||
// private fun replayOriginLocation() {
|
||||
// with(mapboxNavigation.mapboxReplayer) {
|
||||
// play()
|
||||
// pushEvents(
|
||||
// listOf(
|
||||
// ReplayRouteMapper.mapToUpdateLocation(
|
||||
// Date().time.toDouble(),
|
||||
// Point.fromLngLat(-122.39726512303575, 37.785128345296805)
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// playFirstLocation()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun findRoute(destination: Point) {
|
||||
// val originLocation = 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
|
||||
// mapboxNavigation.requestRoutes(
|
||||
// RouteOptions.builder()
|
||||
// .applyDefaultNavigationOptions()
|
||||
// .applyLanguageAndVoiceUnitOptions(this)
|
||||
// .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(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
|
||||
// mapboxNavigation.setNavigationRoutes(routes)
|
||||
//
|
||||
// // show UI elements
|
||||
// binding.soundButton.visibility = View.VISIBLE
|
||||
// binding.routeOverview.visibility = View.VISIBLE
|
||||
// binding.tripProgressCard.visibility = View.VISIBLE
|
||||
//
|
||||
// // move the camera to overview when new route is available
|
||||
// navigationCamera.requestNavigationCameraToOverview()
|
||||
//
|
||||
// // start simulation
|
||||
// startSimulation(routes.first().directionsRoute)
|
||||
// }
|
||||
//
|
||||
// private fun clearRouteAndStopNavigation() {
|
||||
// // clear
|
||||
// mapboxNavigation.setNavigationRoutes(listOf())
|
||||
//
|
||||
// // stop simulation
|
||||
// stopSimulation()
|
||||
//
|
||||
// // hide UI elements
|
||||
// binding.soundButton.visibility = View.INVISIBLE
|
||||
// binding.maneuverView.visibility = View.INVISIBLE
|
||||
// binding.routeOverview.visibility = View.INVISIBLE
|
||||
// binding.tripProgressCard.visibility = View.INVISIBLE
|
||||
// }
|
||||
//
|
||||
// private fun startSimulation(route: DirectionsRoute) {
|
||||
// mapboxNavigation.mapboxReplayer.stop()
|
||||
// mapboxNavigation.mapboxReplayer.clearEvents()
|
||||
// val replayData = replayRouteMapper.mapDirectionsRouteGeometry(route)
|
||||
// mapboxNavigation.mapboxReplayer.pushEvents(replayData)
|
||||
// mapboxNavigation.mapboxReplayer.seekTo(replayData[0])
|
||||
// mapboxNavigation.mapboxReplayer.play()
|
||||
// }
|
||||
//
|
||||
// private fun stopSimulation() {
|
||||
// mapboxNavigation.mapboxReplayer.stop()
|
||||
// mapboxNavigation.mapboxReplayer.clearEvents()
|
||||
// }
|
||||
//}
|
|
@ -10,18 +10,20 @@ import android.content.pm.PackageManager
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.mapbox.navigation.base.options.NavigationOptions
|
||||
import com.mapbox.navigation.core.MapboxNavigation
|
||||
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
|
||||
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
|
||||
import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation
|
||||
import eu.ztsh.garmin.databinding.ActivityMainBinding
|
||||
import eu.ztsh.garmin.mapbox.MapControl
|
||||
import eu.ztsh.garmin.mapbox.NavigationObserver
|
||||
import eu.ztsh.garmin.util.PermissionsHelper
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
|
@ -32,23 +34,23 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var mapControl: MapControl
|
||||
private lateinit var navigationObserver: NavigationObserver
|
||||
private lateinit var initThread: Thread
|
||||
val permissionsHelper = PermissionsHelper(WeakReference(this))
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
MapboxNavigationApp.attach(owner)
|
||||
MapboxNavigationApp.registerObserver(navigationObserver)
|
||||
private val permissionsHelper = PermissionsHelper(WeakReference(this))
|
||||
|
||||
val mapboxNavigation: MapboxNavigation by requireMapboxNavigation(
|
||||
onResumedObserver = object : DefaultLifecycleObserver, MapboxNavigationObserver {
|
||||
override fun onAttached(mapboxNavigation: MapboxNavigation) {
|
||||
mapControl.onAttached(mapboxNavigation)
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
MapboxNavigationApp.detach(owner)
|
||||
MapboxNavigationApp.unregisterObserver(navigationObserver)
|
||||
override fun onDetached(mapboxNavigation: MapboxNavigation) {
|
||||
mapControl.onDetached(mapboxNavigation)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onInitialize = fun() {
|
||||
mapControl.initNavigation()
|
||||
}
|
||||
)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -56,68 +58,24 @@ class MainActivity : AppCompatActivity() {
|
|||
setContentView(binding.root)
|
||||
binding.mapView
|
||||
permissionsHelper.checkPermissions {
|
||||
if (!MapboxNavigationApp.isSetup()) {
|
||||
MapboxNavigationApp.setup {
|
||||
NavigationOptions.Builder(applicationContext)
|
||||
// .accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
mapControl = MapControl(binding.mapView, resources)
|
||||
mapControl = MapControl(this, UI(binding), resources)
|
||||
mapControl.init()
|
||||
navigationObserver = NavigationObserver(mapControl)
|
||||
|
||||
MapboxNavigationApp.setup(
|
||||
NavigationOptions.Builder(applicationContext)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
initThread = Thread {
|
||||
while (true) {
|
||||
if (MapboxNavigationApp.current() != null) {
|
||||
MapboxNavigationApp.current()!!.startTripSession()
|
||||
threadCallback()
|
||||
}
|
||||
Thread.sleep(100)
|
||||
}
|
||||
}
|
||||
initThread.start()
|
||||
bluetoothInit()
|
||||
}
|
||||
|
||||
private fun threadCallback() {
|
||||
initThread.join()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// MapboxNavigationApp.current()?.registerRouteProgressObserver(routeProgressObserver)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
// MapboxNavigationApp.current()?.unregisterRouteProgressObserver(routeProgressObserver)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
MapboxNavigationApp.current()?.stopTripSession()
|
||||
// maneuverApi.cancel()
|
||||
mapControl.onDestroy()
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
// // Define distance formatter options
|
||||
// private val distanceFormatter: DistanceFormatterOptions by lazy {
|
||||
// DistanceFormatterOptions.Builder(this).build()
|
||||
// }
|
||||
// // Create an instance of the Maneuver API
|
||||
// private val maneuverApi: MapboxManeuverApi by lazy {
|
||||
// MapboxManeuverApi(MapboxDistanceFormatter(distanceFormatter))
|
||||
// }
|
||||
//
|
||||
// private val routeProgressObserver =
|
||||
// RouteProgressObserver { routeProgress ->
|
||||
// maneuverApi.getManeuvers(routeProgress).value?.apply {
|
||||
// garmin.process(
|
||||
// this[0]
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun bluetoothInit() {
|
||||
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
|
||||
val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
||||
|
|
59
app/src/main/java/eu/ztsh/garmin/UI.kt
Normal file
59
app/src/main/java/eu/ztsh/garmin/UI.kt
Normal file
|
@ -0,0 +1,59 @@
|
|||
package eu.ztsh.garmin
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.mapbox.maps.EdgeInsets
|
||||
import eu.ztsh.garmin.databinding.ActivityMainBinding
|
||||
|
||||
class UI(b: ActivityMainBinding) {
|
||||
|
||||
companion object {
|
||||
|
||||
const val BUTTON_ANIMATION_DURATION = 1500L
|
||||
|
||||
private val pixelDensity = Resources.getSystem().displayMetrics.density
|
||||
val overviewPadding: EdgeInsets by lazy {
|
||||
EdgeInsets(
|
||||
140.0 * pixelDensity,
|
||||
40.0 * pixelDensity,
|
||||
120.0 * pixelDensity,
|
||||
40.0 * pixelDensity
|
||||
)
|
||||
}
|
||||
val landscapeOverviewPadding: EdgeInsets by lazy {
|
||||
EdgeInsets(
|
||||
30.0 * pixelDensity,
|
||||
380.0 * pixelDensity,
|
||||
110.0 * pixelDensity,
|
||||
20.0 * pixelDensity
|
||||
)
|
||||
}
|
||||
val followingPadding: EdgeInsets by lazy {
|
||||
EdgeInsets(
|
||||
180.0 * pixelDensity,
|
||||
40.0 * pixelDensity,
|
||||
150.0 * pixelDensity,
|
||||
40.0 * pixelDensity
|
||||
)
|
||||
}
|
||||
val landscapeFollowingPadding: EdgeInsets by lazy {
|
||||
EdgeInsets(
|
||||
30.0 * pixelDensity,
|
||||
380.0 * pixelDensity,
|
||||
110.0 * pixelDensity,
|
||||
40.0 * pixelDensity
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val mapView = b.mapView
|
||||
val maneuverView = b.maneuverView
|
||||
val tripProgressView = b.tripProgressView
|
||||
val tripProgressCard = b.tripProgressCard
|
||||
|
||||
val recenter = b.recenter
|
||||
val soundButton = b.soundButton
|
||||
val routeOverview = b.routeOverview
|
||||
val stop = b.stop
|
||||
|
||||
}
|
46
app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt
Normal file
46
app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt
Normal file
|
@ -0,0 +1,46 @@
|
|||
package eu.ztsh.garmin.mapbox
|
||||
|
||||
import com.mapbox.common.location.Location
|
||||
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
|
||||
import com.mapbox.navigation.core.trip.session.LocationObserver
|
||||
import com.mapbox.navigation.ui.maps.camera.transition.NavigationCameraTransitionOptions
|
||||
|
||||
class LocationObserver(private val mapControl: MapControl) : LocationObserver {
|
||||
|
||||
/**
|
||||
* Gets notified with location updates.
|
||||
*
|
||||
* Exposes raw updates coming directly from the location services
|
||||
* and the updates enhanced by the Navigation SDK (cleaned up and matched to the road).
|
||||
*/
|
||||
private var firstLocationUpdateReceived = false
|
||||
|
||||
override fun onNewRawLocation(rawLocation: Location) {
|
||||
// not handled
|
||||
}
|
||||
|
||||
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
|
||||
val enhancedLocation = locationMatcherResult.enhancedLocation
|
||||
// update location puck's position on the map
|
||||
mapControl.navigationLocationProvider.changePosition(
|
||||
location = enhancedLocation,
|
||||
keyPoints = locationMatcherResult.keyPoints,
|
||||
)
|
||||
|
||||
// update camera position to account for new location
|
||||
mapControl.viewportDataSource.onLocationChanged(enhancedLocation)
|
||||
mapControl.viewportDataSource.evaluate()
|
||||
|
||||
// if this is the first location update the activity has received,
|
||||
// it's best to immediately move the camera to the current user location
|
||||
if (!firstLocationUpdateReceived) {
|
||||
firstLocationUpdateReceived = true
|
||||
mapControl.navigationCamera.requestNavigationCameraToOverview(
|
||||
stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
|
||||
.maxDuration(0) // instant transition
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,102 +1,153 @@
|
|||
package eu.ztsh.garmin.mapbox
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.util.Log
|
||||
import com.mapbox.api.directions.v5.models.RouteOptions
|
||||
import com.mapbox.maps.EdgeInsets
|
||||
import com.mapbox.maps.MapView
|
||||
import com.mapbox.maps.Style
|
||||
import com.mapbox.maps.plugin.gestures.gestures
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.mapbox.maps.ImageHolder
|
||||
import com.mapbox.maps.plugin.LocationPuck2D
|
||||
import com.mapbox.maps.plugin.animation.camera
|
||||
import com.mapbox.maps.plugin.locationcomponent.location
|
||||
import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateOptions
|
||||
import com.mapbox.maps.plugin.viewport.state.FollowPuckViewportState
|
||||
import com.mapbox.maps.plugin.viewport.viewport
|
||||
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
|
||||
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.base.route.RouterOrigin
|
||||
import com.mapbox.navigation.base.options.NavigationOptions
|
||||
import com.mapbox.navigation.core.MapboxNavigation
|
||||
import com.mapbox.navigation.core.directions.session.RoutesObserver
|
||||
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
|
||||
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
|
||||
import com.mapbox.navigation.core.trip.session.LocationObserver
|
||||
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
|
||||
import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
|
||||
import com.mapbox.navigation.ui.maps.camera.NavigationCamera
|
||||
import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
|
||||
import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler
|
||||
import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState
|
||||
import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
|
||||
import com.mapbox.navigation.utils.internal.toPoint
|
||||
import eu.ztsh.garmin.MainActivity
|
||||
import eu.ztsh.garmin.UI
|
||||
import eu.ztsh.garmin.mock.ReplayResources
|
||||
|
||||
class MapControl(val mapView: MapView, private val resources: Resources) {
|
||||
class MapControl(
|
||||
val context: AppCompatActivity,
|
||||
val ui: UI,
|
||||
private val resources: Resources
|
||||
) : MapboxNavigationObserver {
|
||||
|
||||
/**
|
||||
* Used to execute camera transitions based on the data generated by the [viewportDataSource].
|
||||
* This includes transitions from route overview to route following and continuously updating the camera as the location changes.
|
||||
*/
|
||||
lateinit var navigationCamera: NavigationCamera
|
||||
|
||||
/**
|
||||
* Produces the camera frames based on the location and routing data for the [navigationCamera] to execute.
|
||||
*/
|
||||
lateinit var viewportDataSource: MapboxNavigationViewportDataSource
|
||||
|
||||
/**
|
||||
* [NavigationLocationProvider] is a utility class that helps to provide location updates generated by the Navigation SDK
|
||||
* to the Maps SDK in order to update the user location indicator on the map.
|
||||
*/
|
||||
val navigationLocationProvider = NavigationLocationProvider()
|
||||
|
||||
val routesRequestCallback = object : NavigationRouterCallback {
|
||||
override fun onRoutesReady(routes: List<NavigationRoute>, @RouterOrigin routerOrigin: String) {
|
||||
MapboxNavigationApp.current()?.setNavigationRoutes(routes)
|
||||
}
|
||||
|
||||
override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
|
||||
Log.e(TAG, "onFailure: ")
|
||||
}
|
||||
|
||||
override fun onCanceled(routeOptions: RouteOptions, @RouterOrigin routerOrigin: String) {
|
||||
Log.w(TAG, "onCanceled: ")
|
||||
}
|
||||
}
|
||||
val replay = ReplayResources(this)
|
||||
|
||||
// Observers
|
||||
private lateinit var routeControl: RouteControl
|
||||
private lateinit var voiceControl: VoiceControl
|
||||
|
||||
private lateinit var routesObserver: RoutesObserver
|
||||
private lateinit var locationObserver: LocationObserver
|
||||
private lateinit var routeProgressObserver: RouteProgressObserver
|
||||
private lateinit var voiceInstructionsObserver: VoiceInstructionsObserver
|
||||
|
||||
fun init() {
|
||||
mapView.mapboxMap.loadStyle(Style.TRAFFIC_DAY) // TODO: base on sun position
|
||||
viewportDataSource = MapboxNavigationViewportDataSource(ui.mapView.mapboxMap)
|
||||
if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
viewportDataSource.overviewPadding = UI.landscapeOverviewPadding
|
||||
} else {
|
||||
viewportDataSource.overviewPadding = UI.overviewPadding
|
||||
}
|
||||
if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
viewportDataSource.followingPadding = UI.landscapeFollowingPadding
|
||||
} else {
|
||||
viewportDataSource.followingPadding = UI.followingPadding
|
||||
}
|
||||
|
||||
mapView.location.apply {
|
||||
// locationProvider = this.getLocationProvider()
|
||||
// setLocationProvider(navigationLocationProvider)
|
||||
navigationCamera = NavigationCamera(
|
||||
ui.mapView.mapboxMap,
|
||||
ui.mapView.camera,
|
||||
viewportDataSource
|
||||
)
|
||||
// set the animations lifecycle listener to ensure the NavigationCamera stops
|
||||
// automatically following the user location when the map is interacted with
|
||||
ui.mapView.camera.addCameraAnimationsLifecycleListener(
|
||||
NavigationBasicGesturesHandler(navigationCamera)
|
||||
)
|
||||
navigationCamera.registerNavigationCameraStateChangeObserver { navigationCameraState ->
|
||||
// shows/hide the recenter button depending on the camera state
|
||||
when (navigationCameraState) {
|
||||
NavigationCameraState.TRANSITION_TO_FOLLOWING,
|
||||
NavigationCameraState.FOLLOWING -> ui.recenter.visibility = View.INVISIBLE
|
||||
|
||||
NavigationCameraState.TRANSITION_TO_OVERVIEW,
|
||||
NavigationCameraState.OVERVIEW,
|
||||
NavigationCameraState.IDLE -> ui.recenter.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
routeControl = RouteControl(this, ui, context)
|
||||
voiceControl = VoiceControl(ui, context)
|
||||
|
||||
routesObserver = routeControl.routesObserver
|
||||
locationObserver = LocationObserver(this)
|
||||
routeProgressObserver = routeControl.routeProgressObserver
|
||||
voiceInstructionsObserver = voiceControl.voiceInstructionsObserver
|
||||
}
|
||||
|
||||
fun initNavigation() {
|
||||
MapboxNavigationApp.setup(
|
||||
NavigationOptions.Builder(context)
|
||||
.build()
|
||||
)
|
||||
|
||||
// initialize location puck
|
||||
ui.mapView.location.apply {
|
||||
setLocationProvider(navigationLocationProvider)
|
||||
this.locationPuck = LocationPuck2D(
|
||||
bearingImage = ImageHolder.Companion.from(
|
||||
com.mapbox.navigation.ui.maps.R.drawable.mapbox_navigation_puck_icon
|
||||
)
|
||||
)
|
||||
puckBearingEnabled = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
follow(true)
|
||||
setGestures(mapView)
|
||||
replay.replayOriginLocation()
|
||||
}
|
||||
|
||||
fun follow(immediately: Boolean = false) {
|
||||
mapView.viewport.apply {
|
||||
// transition to followPuckViewportState with default transition
|
||||
val followPuckViewportState: FollowPuckViewportState = this.makeFollowPuckViewportState(
|
||||
FollowPuckViewportStateOptions.Builder()
|
||||
// .bearing(FollowPuckViewportStateBearing.Constant(0.0))
|
||||
.padding(EdgeInsets(200.0 * resources.displayMetrics.density, 0.0, 0.0, 0.0))
|
||||
.build()
|
||||
)
|
||||
if (immediately) {
|
||||
val immediateTransition = this.makeImmediateViewportTransition()
|
||||
this.transitionTo(followPuckViewportState, immediateTransition) { success ->
|
||||
Log.d(TAG, "follow: $success")
|
||||
}
|
||||
} else {
|
||||
this.transitionTo(followPuckViewportState) { success ->
|
||||
Log.d(TAG, "follow: $success")
|
||||
}
|
||||
}
|
||||
}
|
||||
fun mapboxNavigation(): MapboxNavigation {
|
||||
return (context as MainActivity).mapboxNavigation
|
||||
}
|
||||
|
||||
private fun setGestures(mapView: MapView) {
|
||||
mapView.gestures.apply {
|
||||
addOnMapClickListener { point ->
|
||||
mapView.location.isLocatedAt(point) { isPuckLocatedAtPoint ->
|
||||
if (isPuckLocatedAtPoint) {
|
||||
follow()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
addOnMapLongClickListener { point ->
|
||||
MapboxNavigationApp.current()?.requestRoutes(
|
||||
RouteOptions.builder()
|
||||
.applyDefaultNavigationOptions()
|
||||
.coordinatesList(mutableListOf(navigationLocationProvider.lastLocation!!.toPoint(), point))
|
||||
.build(),
|
||||
routesRequestCallback
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
override fun onAttached(mapboxNavigation: MapboxNavigation) {
|
||||
mapboxNavigation.registerRoutesObserver(routesObserver)
|
||||
mapboxNavigation.registerLocationObserver(locationObserver)
|
||||
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
|
||||
mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
|
||||
|
||||
replay.onAttached(mapboxNavigation)
|
||||
}
|
||||
|
||||
override fun onDetached(mapboxNavigation: MapboxNavigation) {
|
||||
mapboxNavigation.unregisterRoutesObserver(routesObserver)
|
||||
mapboxNavigation.unregisterLocationObserver(locationObserver)
|
||||
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
|
||||
mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
|
||||
replay.onDetached(mapboxNavigation)
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
routeControl.cancel()
|
||||
voiceControl.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
package eu.ztsh.garmin.mapbox
|
||||
|
||||
import android.util.Log
|
||||
import com.mapbox.common.location.Location
|
||||
import com.mapbox.navigation.core.MapboxNavigation
|
||||
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
|
||||
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
|
||||
import com.mapbox.navigation.core.trip.session.LocationObserver
|
||||
import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
|
||||
|
||||
class NavigationObserver(private val mapControl: MapControl) : MapboxNavigationObserver {
|
||||
|
||||
private val viewportDataSource = MapboxNavigationViewportDataSource(mapControl.mapView.mapboxMap)
|
||||
|
||||
private val locationObserver = object : LocationObserver {
|
||||
|
||||
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
|
||||
mapControl.navigationLocationProvider.changePosition(
|
||||
location = locationMatcherResult.enhancedLocation,
|
||||
keyPoints = locationMatcherResult.keyPoints
|
||||
)
|
||||
viewportDataSource.onLocationChanged(locationMatcherResult.enhancedLocation)
|
||||
viewportDataSource.evaluate()
|
||||
}
|
||||
|
||||
override fun onNewRawLocation(rawLocation: Location) {
|
||||
println()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onAttached(mapboxNavigation: MapboxNavigation) {
|
||||
mapboxNavigation.registerLocationObserver(locationObserver)
|
||||
Log.d(TAG, "Attached")
|
||||
}
|
||||
|
||||
override fun onDetached(mapboxNavigation: MapboxNavigation) {
|
||||
mapboxNavigation.unregisterLocationObserver(locationObserver)
|
||||
Log.d(TAG, "Detached")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "MBOXOBS"
|
||||
}
|
||||
|
||||
}
|
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()
|
||||
}
|
||||
|
||||
}
|
111
app/src/main/java/eu/ztsh/garmin/mapbox/VoiceControl.kt
Normal file
111
app/src/main/java/eu/ztsh/garmin/mapbox/VoiceControl.kt
Normal file
|
@ -0,0 +1,111 @@
|
|||
package eu.ztsh.garmin.mapbox
|
||||
|
||||
import android.content.Context
|
||||
import com.mapbox.bindgen.Expected
|
||||
import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
|
||||
import com.mapbox.navigation.ui.base.util.MapboxNavigationConsumer
|
||||
import com.mapbox.navigation.voice.api.MapboxSpeechApi
|
||||
import com.mapbox.navigation.voice.api.MapboxVoiceInstructionsPlayer
|
||||
import com.mapbox.navigation.voice.model.SpeechAnnouncement
|
||||
import com.mapbox.navigation.voice.model.SpeechError
|
||||
import com.mapbox.navigation.voice.model.SpeechValue
|
||||
import com.mapbox.navigation.voice.model.SpeechVolume
|
||||
import eu.ztsh.garmin.UI
|
||||
import java.util.*
|
||||
|
||||
class VoiceControl(private val ui: UI, context: Context) {
|
||||
|
||||
/**
|
||||
* Extracts message that should be communicated to the driver about the upcoming maneuver.
|
||||
* When possible, downloads a synthesized audio file that can be played back to the driver.
|
||||
*/
|
||||
private lateinit var speechApi: MapboxSpeechApi
|
||||
|
||||
/**
|
||||
* Plays the synthesized audio files with upcoming maneuver instructions
|
||||
* or uses an on-device Text-To-Speech engine to communicate the message to the driver.
|
||||
* NOTE: do not use lazy initialization for this class since it takes some time to initialize
|
||||
* the system services required for on-device speech synthesis. With lazy initialization
|
||||
* there is a high risk that said services will not be available when the first instruction
|
||||
* has to be played. [MapboxVoiceInstructionsPlayer] should be instantiated in
|
||||
* `Activity#onCreate`.
|
||||
*/
|
||||
private lateinit var voiceInstructionsPlayer: MapboxVoiceInstructionsPlayer
|
||||
|
||||
init {
|
||||
speechApi = MapboxSpeechApi(
|
||||
context,
|
||||
Locale("pl").language
|
||||
)
|
||||
voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
|
||||
context,
|
||||
Locale("pl").language
|
||||
)
|
||||
ui.soundButton.setOnClickListener {
|
||||
// mute/unmute voice instructions
|
||||
isVoiceInstructionsMuted = !isVoiceInstructionsMuted
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores and updates the state of whether the voice instructions should be played as they come or muted.
|
||||
*/
|
||||
private var isVoiceInstructionsMuted = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value) {
|
||||
ui.soundButton.muteAndExtend(UI.BUTTON_ANIMATION_DURATION)
|
||||
voiceInstructionsPlayer.volume(SpeechVolume(0f))
|
||||
} else {
|
||||
ui.soundButton.unmuteAndExtend(UI.BUTTON_ANIMATION_DURATION)
|
||||
voiceInstructionsPlayer.volume(SpeechVolume(1f))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes when a new voice instruction should be played.
|
||||
*/
|
||||
val voiceInstructionsObserver = VoiceInstructionsObserver { voiceInstructions ->
|
||||
speechApi.generate(voiceInstructions, speechCallback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on whether the synthesized audio file is available, the callback plays the file
|
||||
* or uses the fall back which is played back using the on-device Text-To-Speech engine.
|
||||
*/
|
||||
private val speechCallback =
|
||||
MapboxNavigationConsumer<Expected<SpeechError, SpeechValue>> { expected ->
|
||||
expected.fold(
|
||||
{ error ->
|
||||
// play the instruction via fallback text-to-speech engine
|
||||
voiceInstructionsPlayer.play(
|
||||
error.fallback,
|
||||
voiceInstructionsPlayerCallback
|
||||
)
|
||||
},
|
||||
{ value ->
|
||||
// play the sound file from the external generator
|
||||
voiceInstructionsPlayer.play(
|
||||
value.announcement,
|
||||
voiceInstructionsPlayerCallback
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When a synthesized audio file was downloaded, this callback cleans up the disk after it was played.
|
||||
*/
|
||||
private val voiceInstructionsPlayerCallback =
|
||||
MapboxNavigationConsumer<SpeechAnnouncement> { value ->
|
||||
// remove already consumed file to free-up space
|
||||
speechApi.clean(value)
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
speechApi.cancel()
|
||||
voiceInstructionsPlayer.shutdown()
|
||||
|
||||
}
|
||||
|
||||
}
|
73
app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt
Normal file
73
app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt
Normal file
|
@ -0,0 +1,73 @@
|
|||
package eu.ztsh.garmin.mock
|
||||
|
||||
import com.mapbox.api.directions.v5.models.DirectionsRoute
|
||||
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
|
||||
import com.mapbox.navigation.core.MapboxNavigation
|
||||
import com.mapbox.navigation.core.replay.route.ReplayProgressObserver
|
||||
import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
|
||||
import eu.ztsh.garmin.mapbox.MapControl
|
||||
import java.util.*
|
||||
|
||||
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
|
||||
class ReplayResources(private val mapControl: MapControl) {
|
||||
|
||||
/**
|
||||
* Debug observer that makes sure the replayer has always an up-to-date information to generate mock updates.
|
||||
*/
|
||||
private lateinit var replayProgressObserver: ReplayProgressObserver
|
||||
|
||||
/**
|
||||
* Debug object that converts a route into events that can be replayed to navigate a route.
|
||||
*/
|
||||
private val replayRouteMapper = ReplayRouteMapper()
|
||||
|
||||
fun replayOriginLocation() {
|
||||
with(mapControl.mapboxNavigation().mapboxReplayer) {
|
||||
play()
|
||||
pushEvents(
|
||||
listOf(
|
||||
ReplayRouteMapper.mapToUpdateLocation(
|
||||
Date().time.toDouble(),
|
||||
com.mapbox.geojson.Point.fromLngLat(-122.39726512303575, 37.785128345296805)
|
||||
)
|
||||
)
|
||||
)
|
||||
playFirstLocation()
|
||||
}
|
||||
}
|
||||
|
||||
fun startSimulation(route: DirectionsRoute) {
|
||||
with(mapControl.mapboxNavigation()) {
|
||||
mapboxReplayer.stop()
|
||||
mapboxReplayer.clearEvents()
|
||||
val replayData = replayRouteMapper.mapDirectionsRouteGeometry(route)
|
||||
mapboxReplayer.pushEvents(replayData)
|
||||
mapboxReplayer.seekTo(replayData[0])
|
||||
mapboxReplayer.play()
|
||||
}
|
||||
}
|
||||
|
||||
fun stopSimulation() {
|
||||
with(mapControl.mapboxNavigation()) {
|
||||
mapboxReplayer.stop()
|
||||
mapboxReplayer.clearEvents()
|
||||
}
|
||||
}
|
||||
|
||||
fun onAttached(mapboxNavigation: MapboxNavigation) {
|
||||
replayProgressObserver = ReplayProgressObserver(mapboxNavigation.mapboxReplayer)
|
||||
mapboxNavigation.registerRouteProgressObserver(replayProgressObserver)
|
||||
|
||||
// Start the trip session to being receiving location updates in free drive
|
||||
// and later when a route is set also receiving route progress updates.
|
||||
// In case of `startReplayTripSession`,
|
||||
// location events are emitted by the `MapboxReplayer`
|
||||
mapboxNavigation.startReplayTripSession()
|
||||
}
|
||||
|
||||
fun onDetached(mapboxNavigation: MapboxNavigation) {
|
||||
mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver)
|
||||
mapboxNavigation.mapboxReplayer.finish()
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue