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.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.WindowManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultCallback
|
import androidx.activity.result.ActivityResultCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import com.mapbox.navigation.base.options.NavigationOptions
|
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.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.databinding.ActivityMainBinding
|
||||||
import eu.ztsh.garmin.mapbox.MapControl
|
import eu.ztsh.garmin.mapbox.MapControl
|
||||||
import eu.ztsh.garmin.mapbox.NavigationObserver
|
|
||||||
import eu.ztsh.garmin.util.PermissionsHelper
|
import eu.ztsh.garmin.util.PermissionsHelper
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
@ -32,23 +34,23 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var mapControl: MapControl
|
private lateinit var mapControl: MapControl
|
||||||
private lateinit var navigationObserver: NavigationObserver
|
|
||||||
private lateinit var initThread: Thread
|
|
||||||
val permissionsHelper = PermissionsHelper(WeakReference(this))
|
|
||||||
|
|
||||||
init {
|
private val permissionsHelper = PermissionsHelper(WeakReference(this))
|
||||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
val mapboxNavigation: MapboxNavigation by requireMapboxNavigation(
|
||||||
MapboxNavigationApp.attach(owner)
|
onResumedObserver = object : DefaultLifecycleObserver, MapboxNavigationObserver {
|
||||||
MapboxNavigationApp.registerObserver(navigationObserver)
|
override fun onAttached(mapboxNavigation: MapboxNavigation) {
|
||||||
|
mapControl.onAttached(mapboxNavigation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause(owner: LifecycleOwner) {
|
override fun onDetached(mapboxNavigation: MapboxNavigation) {
|
||||||
MapboxNavigationApp.detach(owner)
|
mapControl.onDetached(mapboxNavigation)
|
||||||
MapboxNavigationApp.unregisterObserver(navigationObserver)
|
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
}
|
onInitialize = fun() {
|
||||||
|
mapControl.initNavigation()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -56,68 +58,24 @@ class MainActivity : AppCompatActivity() {
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
binding.mapView
|
binding.mapView
|
||||||
permissionsHelper.checkPermissions {
|
permissionsHelper.checkPermissions {
|
||||||
if (!MapboxNavigationApp.isSetup()) {
|
mapControl = MapControl(this, UI(binding), resources)
|
||||||
MapboxNavigationApp.setup {
|
|
||||||
NavigationOptions.Builder(applicationContext)
|
|
||||||
// .accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mapControl = MapControl(binding.mapView, resources)
|
|
||||||
mapControl.init()
|
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()
|
bluetoothInit()
|
||||||
}
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
private fun threadCallback() {
|
|
||||||
initThread.join()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
// MapboxNavigationApp.current()?.registerRouteProgressObserver(routeProgressObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
// MapboxNavigationApp.current()?.unregisterRouteProgressObserver(routeProgressObserver)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
MapboxNavigationApp.current()?.stopTripSession()
|
mapControl.onDestroy()
|
||||||
// maneuverApi.cancel()
|
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() {
|
private fun bluetoothInit() {
|
||||||
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
|
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
|
||||||
val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
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
|
package eu.ztsh.garmin.mapbox
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.util.Log
|
import android.view.View
|
||||||
import com.mapbox.api.directions.v5.models.RouteOptions
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.mapbox.maps.EdgeInsets
|
import com.mapbox.maps.ImageHolder
|
||||||
import com.mapbox.maps.MapView
|
import com.mapbox.maps.plugin.LocationPuck2D
|
||||||
import com.mapbox.maps.Style
|
import com.mapbox.maps.plugin.animation.camera
|
||||||
import com.mapbox.maps.plugin.gestures.gestures
|
|
||||||
import com.mapbox.maps.plugin.locationcomponent.location
|
import com.mapbox.maps.plugin.locationcomponent.location
|
||||||
import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateOptions
|
import com.mapbox.navigation.base.options.NavigationOptions
|
||||||
import com.mapbox.maps.plugin.viewport.state.FollowPuckViewportState
|
import com.mapbox.navigation.core.MapboxNavigation
|
||||||
import com.mapbox.maps.plugin.viewport.viewport
|
import com.mapbox.navigation.core.directions.session.RoutesObserver
|
||||||
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.core.lifecycle.MapboxNavigationApp
|
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.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 navigationLocationProvider = NavigationLocationProvider()
|
||||||
|
|
||||||
val routesRequestCallback = object : NavigationRouterCallback {
|
val replay = ReplayResources(this)
|
||||||
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: ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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() {
|
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 {
|
navigationCamera = NavigationCamera(
|
||||||
// locationProvider = this.getLocationProvider()
|
ui.mapView.mapboxMap,
|
||||||
// setLocationProvider(navigationLocationProvider)
|
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
|
puckBearingEnabled = true
|
||||||
enabled = true
|
enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
follow(true)
|
replay.replayOriginLocation()
|
||||||
setGestures(mapView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun follow(immediately: Boolean = false) {
|
fun mapboxNavigation(): MapboxNavigation {
|
||||||
mapView.viewport.apply {
|
return (context as MainActivity).mapboxNavigation
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setGestures(mapView: MapView) {
|
override fun onAttached(mapboxNavigation: MapboxNavigation) {
|
||||||
mapView.gestures.apply {
|
mapboxNavigation.registerRoutesObserver(routesObserver)
|
||||||
addOnMapClickListener { point ->
|
mapboxNavigation.registerLocationObserver(locationObserver)
|
||||||
mapView.location.isLocatedAt(point) { isPuckLocatedAtPoint ->
|
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
|
||||||
if (isPuckLocatedAtPoint) {
|
mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
|
||||||
follow()
|
|
||||||
}
|
replay.onAttached(mapboxNavigation)
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
override fun onDetached(mapboxNavigation: MapboxNavigation) {
|
||||||
addOnMapLongClickListener { point ->
|
mapboxNavigation.unregisterRoutesObserver(routesObserver)
|
||||||
MapboxNavigationApp.current()?.requestRoutes(
|
mapboxNavigation.unregisterLocationObserver(locationObserver)
|
||||||
RouteOptions.builder()
|
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
|
||||||
.applyDefaultNavigationOptions()
|
mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
|
||||||
.coordinatesList(mutableListOf(navigationLocationProvider.lastLocation!!.toPoint(), point))
|
replay.onDetached(mapboxNavigation)
|
||||||
.build(),
|
}
|
||||||
routesRequestCallback
|
|
||||||
)
|
fun onDestroy() {
|
||||||
true
|
routeControl.cancel()
|
||||||
}
|
voiceControl.cancel()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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