From cd85180f9981272d20a0a1090c8c303ee4a1c01c Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Mon, 15 Jul 2024 00:16:02 +0200 Subject: [PATCH 01/40] build!: Gradle version & wrapper --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 2084b5d..87ef816 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } } plugins { - id 'com.android.application' version '7.4.1' apply false - id 'com.android.library' version '7.4.1' apply false + id 'com.android.application' version '8.2.0' apply false + id 'com.android.library' version '8.2.0' apply false id 'org.jetbrains.kotlin.android' version '1.9.24' apply false } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 89142c4..b2dda6b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Jun 22 21:00:17 CEST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 9a2b0dd187d1d7da50a47695488db0b50e8e2a01 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Mon, 15 Jul 2024 00:18:55 +0200 Subject: [PATCH 02/40] feat!: Mapbox update 2.20 -> 3.2 (WIP) --- app/build.gradle | 24 +++++--- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 2 +- .../main/java/eu/ztsh/garmin/MainActivity.kt | 55 +++++++++---------- .../java/eu/ztsh/garmin/ManeuverMapper.kt | 2 +- .../NavigationObserver.kt} | 4 +- app/src/main/res/layout/activity_main.xml | 17 +++--- 6 files changed, 53 insertions(+), 51 deletions(-) rename app/src/main/java/eu/ztsh/garmin/{MapboxObserver.kt => mapbox/NavigationObserver.kt} (83%) diff --git a/app/build.gradle b/app/build.gradle index 6f1af48..fc01fb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,6 +7,7 @@ plugins { android { namespace 'eu.ztsh.garmin' compileSdk 34 + ndkVersion "23.2.8568313" defaultConfig { applicationId "eu.ztsh.garmin" @@ -36,18 +37,23 @@ android { viewBinding true buildConfig = true } - buildToolsVersion '35.0.0' +} +ext { + mapboxVersion = '3.2.0' } dependencies { - - implementation "com.mapbox.navigation:ui-dropin:2.20.0" - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation "com.mapbox.navigationcore:navigation:$mapboxVersion" + implementation "com.mapbox.navigationcore:ui-maps:$mapboxVersion" + implementation "com.mapbox.navigationcore:voice:$mapboxVersion" + implementation "com.mapbox.navigationcore:tripdata:$mapboxVersion" + implementation "com.mapbox.navigationcore:ui-components:$mapboxVersion" + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' } diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index b393203..b4da436 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -5,7 +5,7 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import android.util.Log -import com.mapbox.navigation.ui.maneuver.model.Maneuver +import com.mapbox.navigation.tripdata.maneuver.model.Maneuver import java.io.IOException import java.util.* import java.util.concurrent.SynchronousQueue diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index ec61546..4a0dec4 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -5,7 +5,6 @@ import android.annotation.SuppressLint import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager -import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.os.Build @@ -18,14 +17,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import com.mapbox.navigation.base.formatter.DistanceFormatterOptions import com.mapbox.navigation.base.options.NavigationOptions -import com.mapbox.navigation.base.trip.model.RouteProgress -import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp -import com.mapbox.navigation.core.trip.session.RouteProgressObserver -import com.mapbox.navigation.ui.maneuver.api.MapboxManeuverApi import eu.ztsh.garmin.databinding.ActivityMainBinding +import eu.ztsh.garmin.mapbox.NavigationObserver @SuppressLint("MissingPermission") class MainActivity : AppCompatActivity() { @@ -33,18 +28,18 @@ class MainActivity : AppCompatActivity() { lateinit var garmin: Garmin private lateinit var binding: ActivityMainBinding - private val mapboxObserver = MapboxObserver() + private val navigationObserver = NavigationObserver() init { lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { MapboxNavigationApp.attach(owner) - MapboxNavigationApp.registerObserver(mapboxObserver) + MapboxNavigationApp.registerObserver(navigationObserver) } override fun onPause(owner: LifecycleOwner) { MapboxNavigationApp.detach(owner) - MapboxNavigationApp.unregisterObserver(mapboxObserver) + MapboxNavigationApp.unregisterObserver(navigationObserver) } }) } @@ -56,7 +51,7 @@ class MainActivity : AppCompatActivity() { if (!MapboxNavigationApp.isSetup()) { MapboxNavigationApp.setup { NavigationOptions.Builder(applicationContext) - .accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN) +// .accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN) .build() } } @@ -66,37 +61,37 @@ class MainActivity : AppCompatActivity() { override fun onStart() { super.onStart() - MapboxNavigationApp.current()?.registerRouteProgressObserver(routeProgressObserver) +// MapboxNavigationApp.current()?.registerRouteProgressObserver(routeProgressObserver) } override fun onStop() { super.onStop() - MapboxNavigationApp.current()?.unregisterRouteProgressObserver(routeProgressObserver) +// MapboxNavigationApp.current()?.unregisterRouteProgressObserver(routeProgressObserver) } override fun onDestroy() { super.onDestroy() MapboxNavigationApp.current()?.stopTripSession() - maneuverApi.cancel() +// maneuverApi.cancel() } - // 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] - ) - } - } +// // 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) diff --git a/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt b/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt index 3ecef12..1699870 100644 --- a/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt @@ -1,6 +1,6 @@ package eu.ztsh.garmin -import com.mapbox.navigation.ui.maneuver.model.Maneuver +import com.mapbox.navigation.tripdata.maneuver.model.Maneuver class ManeuverMapper { diff --git a/app/src/main/java/eu/ztsh/garmin/MapboxObserver.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt similarity index 83% rename from app/src/main/java/eu/ztsh/garmin/MapboxObserver.kt rename to app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt index 35bdabd..663818a 100644 --- a/app/src/main/java/eu/ztsh/garmin/MapboxObserver.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt @@ -1,10 +1,10 @@ -package eu.ztsh.garmin +package eu.ztsh.garmin.mapbox import android.util.Log import com.mapbox.navigation.core.MapboxNavigation import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver -class MapboxObserver : MapboxNavigationObserver { +class NavigationObserver : MapboxNavigationObserver { override fun onAttached(mapboxNavigation: MapboxNavigation) { Log.d(TAG, "Attached") diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8177dab..ae8744c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,9 +1,10 @@ - - - + From c923f8825dc4c26cf21c8c1f31108e3e65346e66 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Mon, 15 Jul 2024 00:44:49 +0200 Subject: [PATCH 03/40] feat: Location permissions check --- .../main/java/eu/ztsh/garmin/MainActivity.kt | 17 ++++--- .../eu/ztsh/garmin/util/PermissionsHelper.kt | 50 +++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/eu/ztsh/garmin/util/PermissionsHelper.kt diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index 4a0dec4..5a41afa 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -21,6 +21,8 @@ import com.mapbox.navigation.base.options.NavigationOptions import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp import eu.ztsh.garmin.databinding.ActivityMainBinding import eu.ztsh.garmin.mapbox.NavigationObserver +import eu.ztsh.garmin.util.PermissionsHelper +import java.lang.ref.WeakReference @SuppressLint("MissingPermission") class MainActivity : AppCompatActivity() { @@ -29,6 +31,7 @@ class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val navigationObserver = NavigationObserver() + val permissionsHelper = PermissionsHelper(WeakReference(this)) init { lifecycle.addObserver(object : DefaultLifecycleObserver { @@ -48,14 +51,16 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - if (!MapboxNavigationApp.isSetup()) { - MapboxNavigationApp.setup { - NavigationOptions.Builder(applicationContext) -// .accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN) - .build() + permissionsHelper.checkPermissions { + if (!MapboxNavigationApp.isSetup()) { + MapboxNavigationApp.setup { + NavigationOptions.Builder(applicationContext) + // .accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN) + .build() + } } + MapboxNavigationApp.current()?.startTripSession() } - MapboxNavigationApp.current()?.startTripSession() bluetoothInit() } diff --git a/app/src/main/java/eu/ztsh/garmin/util/PermissionsHelper.kt b/app/src/main/java/eu/ztsh/garmin/util/PermissionsHelper.kt new file mode 100644 index 0000000..573e457 --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/util/PermissionsHelper.kt @@ -0,0 +1,50 @@ +package eu.ztsh.garmin.util + +import android.app.Activity +import android.widget.Toast +import com.mapbox.android.core.permissions.PermissionsListener +import com.mapbox.android.core.permissions.PermissionsManager +import java.lang.ref.WeakReference + +class PermissionsHelper(val activityRef: WeakReference) { + private lateinit var permissionsManager: PermissionsManager + + fun checkPermissions(onMapReady: () -> Unit) { + activityRef.get()?.let { activity: Activity -> + if (PermissionsManager.areLocationPermissionsGranted(activity)) { + onMapReady() + } else { + permissionsManager = PermissionsManager(object : PermissionsListener { + + override fun onExplanationNeeded(permissionsToExplain: List) { + activityRef.get()?.let { + Toast.makeText( + it, "You need to accept location permissions.", + Toast.LENGTH_SHORT + ).show() + } + } + + override fun onPermissionResult(granted: Boolean) { + activityRef.get()?.let { + if (granted) { + onMapReady() + } else { + it.finish() + } + } + } + }) + permissionsManager.requestLocationPermissions(activity) + } + } + } + + fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults) + } +} \ No newline at end of file From 756b760d5c9f734f25bae0230d46f1e67e2ce81c Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Mon, 15 Jul 2024 01:05:50 +0200 Subject: [PATCH 04/40] feat: Map controller basics --- .../main/java/eu/ztsh/garmin/MainActivity.kt | 5 +++ .../java/eu/ztsh/garmin/mapbox/MapControl.kt | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index 5a41afa..c5f4911 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.LifecycleOwner import com.mapbox.navigation.base.options.NavigationOptions import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp 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 @@ -30,6 +31,7 @@ class MainActivity : AppCompatActivity() { lateinit var garmin: Garmin private lateinit var binding: ActivityMainBinding + private lateinit var mapControl: MapControl private val navigationObserver = NavigationObserver() val permissionsHelper = PermissionsHelper(WeakReference(this)) @@ -51,6 +53,7 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + binding.mapView permissionsHelper.checkPermissions { if (!MapboxNavigationApp.isSetup()) { MapboxNavigationApp.setup { @@ -60,6 +63,8 @@ class MainActivity : AppCompatActivity() { } } MapboxNavigationApp.current()?.startTripSession() + mapControl = MapControl(binding.mapView, resources) + mapControl.follow() } bluetoothInit() } diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt new file mode 100644 index 0000000..4b919b6 --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt @@ -0,0 +1,32 @@ +package eu.ztsh.garmin.mapbox + +import android.content.res.Resources +import android.util.Log +import com.mapbox.maps.EdgeInsets +import com.mapbox.maps.MapView +import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateBearing +import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateOptions +import com.mapbox.maps.plugin.viewport.state.FollowPuckViewportState +import com.mapbox.maps.plugin.viewport.viewport + +class MapControl(private val mapView: MapView, private val resources: Resources) { + + fun follow() { + val viewportPlugin = mapView.viewport + // transition to followPuckViewportState with default transition + val followPuckViewportState: FollowPuckViewportState = viewportPlugin.makeFollowPuckViewportState( + FollowPuckViewportStateOptions.Builder() +// .bearing(FollowPuckViewportStateBearing.Constant(0.0)) + .padding(EdgeInsets(200.0 * resources.displayMetrics.density, 0.0, 0.0, 0.0)) + .build() + ) + val immediateTransition = viewportPlugin.makeImmediateViewportTransition() + viewportPlugin.transitionTo(followPuckViewportState, immediateTransition) { success -> + Log.d(TAG, "follow: $success") + } + } + + companion object { + const val TAG = "MAPCTRL" + } +} \ No newline at end of file From 9db0e3611c5d3bf14e25548db36556ff857c3a81 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Mon, 15 Jul 2024 01:52:16 +0200 Subject: [PATCH 05/40] feat: More options to map initialization --- .../main/java/eu/ztsh/garmin/MainActivity.kt | 4 +- .../java/eu/ztsh/garmin/mapbox/MapControl.kt | 57 ++++++++++++++----- app/src/main/res/layout/activity_main.xml | 2 +- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index c5f4911..a958c08 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -63,8 +63,8 @@ class MainActivity : AppCompatActivity() { } } MapboxNavigationApp.current()?.startTripSession() - mapControl = MapControl(binding.mapView, resources) - mapControl.follow() + mapControl = MapControl(this, binding.mapView, resources) + mapControl.init() } bluetoothInit() } diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt index 4b919b6..255f95a 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt @@ -4,29 +4,60 @@ import android.content.res.Resources import android.util.Log import com.mapbox.maps.EdgeInsets import com.mapbox.maps.MapView -import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateBearing +import com.mapbox.maps.Style +import com.mapbox.maps.plugin.gestures.gestures +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 eu.ztsh.garmin.MainActivity -class MapControl(private val mapView: MapView, private val resources: Resources) { +class MapControl(private val context: MainActivity, private val mapView: MapView, private val resources: Resources) { - fun follow() { - val viewportPlugin = mapView.viewport - // transition to followPuckViewportState with default transition - val followPuckViewportState: FollowPuckViewportState = viewportPlugin.makeFollowPuckViewportState( - FollowPuckViewportStateOptions.Builder() + fun init() { + mapView.mapboxMap.loadStyle(Style.TRAFFIC_DAY) // TODO: base on sun position + follow(true) + setGestures(mapView) + } + + 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() - ) - val immediateTransition = viewportPlugin.makeImmediateViewportTransition() - viewportPlugin.transitionTo(followPuckViewportState, immediateTransition) { success -> - Log.d(TAG, "follow: $success") + .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) { + mapView.gestures.apply { + addOnMapClickListener { point -> + mapView.location.isLocatedAt(point) { isPuckLocatedAtPoint -> + if (isPuckLocatedAtPoint) { + follow() + } + } + true + } } } companion object { + const val TAG = "MAPCTRL" + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ae8744c..8bcf23b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,5 +6,5 @@ android:layout_width="match_parent" android:layout_height="match_parent" mapbox:mapbox_locationComponentEnabled = "true" - mapbox:mapbox_locationComponentPuckBearing = "heading" + mapbox:mapbox_locationComponentPuckBearingEnabled = "true" tools:context=".MainActivity" /> From 0a1c908a9da5125f34c6db1e51f8a12e236ddcf1 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 19 Jul 2024 22:15:32 +0200 Subject: [PATCH 06/40] feat: Navigation observer --- .../main/java/eu/ztsh/garmin/MainActivity.kt | 21 ++++++-- .../java/eu/ztsh/garmin/mapbox/MapControl.kt | 48 ++++++++++++++++++- .../ztsh/garmin/mapbox/NavigationObserver.kt | 27 ++++++++++- app/src/main/res/layout/activity_main.xml | 2 - 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index a958c08..1e39f57 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -32,7 +32,8 @@ class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var mapControl: MapControl - private val navigationObserver = NavigationObserver() + private lateinit var navigationObserver: NavigationObserver + private lateinit var initThread: Thread val permissionsHelper = PermissionsHelper(WeakReference(this)) init { @@ -62,13 +63,27 @@ class MainActivity : AppCompatActivity() { .build() } } - MapboxNavigationApp.current()?.startTripSession() - mapControl = MapControl(this, binding.mapView, resources) + mapControl = MapControl(binding.mapView, resources) mapControl.init() + navigationObserver = NavigationObserver(mapControl) } + 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) diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt index 255f95a..e00814f 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt @@ -2,6 +2,7 @@ package eu.ztsh.garmin.mapbox 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 @@ -10,12 +11,45 @@ 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 eu.ztsh.garmin.MainActivity +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.ui.maps.location.NavigationLocationProvider +import com.mapbox.navigation.utils.internal.toPoint + +class MapControl(val mapView: MapView, private val resources: Resources) { + + val navigationLocationProvider = NavigationLocationProvider() + + val routesRequestCallback = object : NavigationRouterCallback { + override fun onRoutesReady(routes: List, @RouterOrigin routerOrigin: String) { + MapboxNavigationApp.current()?.setNavigationRoutes(routes) + } + + override fun onFailure(reasons: List, routeOptions: RouteOptions) { + Log.e(TAG, "onFailure: ") + } + + override fun onCanceled(routeOptions: RouteOptions, @RouterOrigin routerOrigin: String) { + Log.w(TAG, "onCanceled: ") + } + } + -class MapControl(private val context: MainActivity, private val mapView: MapView, private val resources: Resources) { fun init() { mapView.mapboxMap.loadStyle(Style.TRAFFIC_DAY) // TODO: base on sun position + + mapView.location.apply { +// locationProvider = this.getLocationProvider() +// setLocationProvider(navigationLocationProvider) + puckBearingEnabled = true + enabled = true + } + follow(true) setGestures(mapView) } @@ -52,6 +86,16 @@ class MapControl(private val context: MainActivity, private val mapView: MapView } true } + addOnMapLongClickListener { point -> + MapboxNavigationApp.current()?.requestRoutes( + RouteOptions.builder() + .applyDefaultNavigationOptions() + .coordinatesList(mutableListOf(navigationLocationProvider.lastLocation!!.toPoint(), point)) + .build(), + routesRequestCallback + ) + true + } } } diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt index 663818a..8d892ab 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt @@ -1,16 +1,41 @@ 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 : MapboxNavigationObserver { +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") } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8bcf23b..7b7292a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,6 +5,4 @@ android:id="@+id/mapView" android:layout_width="match_parent" android:layout_height="match_parent" - mapbox:mapbox_locationComponentEnabled = "true" - mapbox:mapbox_locationComponentPuckBearingEnabled = "true" tools:context=".MainActivity" /> From 80dfa6ab4e9dae67cd38f478749328b40c7396c3 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 19 Jul 2024 22:30:29 +0200 Subject: [PATCH 07/40] feat: New activity layout --- app/src/main/res/layout/activity_main.xml | 86 +++++++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 7b7292a..cb6c22d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,8 +1,80 @@ - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 01fbf32042b208e33a1a13b0ecb008b9d7d0cfe6 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 23 Jul 2024 00:55:59 +0200 Subject: [PATCH 08/40] feat!: Application outline --- .../java/eu/ztsh/garmin/ExampleActivity.kt | 692 ++++++++++++++++++ .../main/java/eu/ztsh/garmin/MainActivity.kt | 94 +-- app/src/main/java/eu/ztsh/garmin/UI.kt | 59 ++ .../eu/ztsh/garmin/mapbox/LocationObserver.kt | 46 ++ .../java/eu/ztsh/garmin/mapbox/MapControl.kt | 203 +++-- .../ztsh/garmin/mapbox/NavigationObserver.kt | 46 -- .../eu/ztsh/garmin/mapbox/RouteControl.kt | 306 ++++++++ .../eu/ztsh/garmin/mapbox/VoiceControl.kt | 111 +++ .../eu/ztsh/garmin/mock/ReplayResources.kt | 73 ++ 9 files changed, 1440 insertions(+), 190 deletions(-) create mode 100644 app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt create mode 100644 app/src/main/java/eu/ztsh/garmin/UI.kt create mode 100644 app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt delete mode 100644 app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt create mode 100644 app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt create mode 100644 app/src/main/java/eu/ztsh/garmin/mapbox/VoiceControl.kt create mode 100644 app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt diff --git a/app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt b/app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt new file mode 100644 index 0000000..91a77f4 --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt @@ -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 +// * +// * +// * +// * +// * +// * +// * 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 -> +// 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 { 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, routeOptions: RouteOptions) { +// // no impl +// } +// +// override fun onRoutesReady( +// routes: List, +// routerOrigin: String +// ) { +// setRouteAndStartNavigation(routes) +// } +// } +// ) +// } +// +// private fun setRouteAndStartNavigation(routes: List) { +// // set routes, where the first route in the list is the primary route that +// // will be used for active guidance +// 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() +// } +//} \ No newline at end of file diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index 1e39f57..540888d 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -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 diff --git a/app/src/main/java/eu/ztsh/garmin/UI.kt b/app/src/main/java/eu/ztsh/garmin/UI.kt new file mode 100644 index 0000000..c27faff --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/UI.kt @@ -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 + +} \ No newline at end of file diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt new file mode 100644 index 0000000..998eebe --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt @@ -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() + ) + } + } + +} diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt index e00814f..5ae5688 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt @@ -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, @RouterOrigin routerOrigin: String) { - MapboxNavigationApp.current()?.setNavigationRoutes(routes) - } - - override fun onFailure(reasons: List, 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 { diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt deleted file mode 100644 index 8d892ab..0000000 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/NavigationObserver.kt +++ /dev/null @@ -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" - } - -} diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt new file mode 100644 index 0000000..cf59579 --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt @@ -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, routeOptions: RouteOptions) { + // no impl + } + + override fun onRoutesReady( + routes: List, + routerOrigin: String + ) { + setRouteAndStartNavigation(routes) + } + } + ) + } + + private fun setRouteAndStartNavigation(routes: List) { + // set routes, where the first route in the list is the primary route that + // will be used for active guidance + mapControl.mapboxNavigation().setNavigationRoutes(routes) + + // show UI elements + mapControl.ui.soundButton.visibility = View.VISIBLE + mapControl.ui.routeOverview.visibility = View.VISIBLE + mapControl.ui.tripProgressCard.visibility = View.VISIBLE + + // move the camera to overview when new route is available + mapControl.navigationCamera.requestNavigationCameraToOverview() + + // start simulation + mapControl.replay.startSimulation(routes.first().directionsRoute) + } + + private fun clearRouteAndStopNavigation() { + // clear + mapControl.mapboxNavigation().setNavigationRoutes(listOf()) + + // stop simulation + mapControl.replay.stopSimulation() + + // hide UI elements + mapControl.ui.soundButton.visibility = View.INVISIBLE + mapControl.ui.maneuverView.visibility = View.INVISIBLE + mapControl.ui.routeOverview.visibility = View.INVISIBLE + mapControl.ui.tripProgressCard.visibility = View.INVISIBLE + } + + fun cancel() { + maneuverApi.cancel() + routeLineApi.cancel() + routeLineView.cancel() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/VoiceControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/VoiceControl.kt new file mode 100644 index 0000000..aa9578f --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/VoiceControl.kt @@ -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 -> + 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 { value -> + // remove already consumed file to free-up space + speechApi.clean(value) + } + + fun cancel() { + speechApi.cancel() + voiceInstructionsPlayer.shutdown() + + } + +} diff --git a/app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt b/app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt new file mode 100644 index 0000000..25c4e40 --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt @@ -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() + } + +} \ No newline at end of file From 73a57cb742b44ee52c05d56921237ab00a5e8d8e Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 26 Jul 2024 00:38:38 +0200 Subject: [PATCH 09/40] chore: Python cleanup --- {app => python}/main.py | 0 {app => python}/test.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {app => python}/main.py (100%) rename {app => python}/test.py (100%) diff --git a/app/main.py b/python/main.py similarity index 100% rename from app/main.py rename to python/main.py diff --git a/app/test.py b/python/test.py similarity index 100% rename from app/test.py rename to python/test.py From 7e9b2dbc34be54bee82ba1164da5663e50916d81 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 26 Jul 2024 00:58:55 +0200 Subject: [PATCH 10/40] fix: Distance changed to Double --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 16 +++++++++++----- .../main/java/eu/ztsh/garmin/ManeuverMapper.kt | 3 +-- app/src/main/java/eu/ztsh/garmin/Model.kt | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index b4da436..e4ce7ea 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -55,8 +55,13 @@ class Garmin( private fun setDistance(state: eu.ztsh.garmin.State) { connection.enqueue(intArrayOf( - 0x03, asDigit(state.distance / 1000), asDigit(state.distance / 100), asDigit(state.distance / 10), - 0x00, asDigit(state.distance), state.unit.data + 0x03, + asDigit(state.distance / 1000), // position 1 + asDigit(state.distance / 100), // position 2 + asDigit(state.distance / 10), // position 3 + if ((state.distance * 10).toInt() == (state.distance.toInt() * 10)) 0x00 else 0xff, // comma + asDigit(state.distance), // position 4 + state.unit.data // unit )) } @@ -76,11 +81,12 @@ class Garmin( connection.enqueue(intArrayOf(0x01, param1, param2, param3)) } - private fun asDigit(n: Int): Int { - if (n == 0) { + private fun asDigit(input: Double): Int { + val number = input.toInt() + if (number == 0) { return 0 } - val m = n % 10 + val m = number % 10 return if (m == 0) 10 else m } } diff --git a/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt b/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt index 1699870..5fddf2c 100644 --- a/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/ManeuverMapper.kt @@ -48,8 +48,7 @@ class ManeuverMapper { this.stepDistance.apply { this.distanceRemaining?.apply { distanceFormatter.formatDistance(distanceRemaining!!).split(" ").apply { - // TODO: Send double - state.distance = this[0].replace(',', '.').toDouble().toInt() + state.distance = this[0].replace(',', '.').toDouble() state.unit = when (this[1]) { "m" -> Unit.Metres "km" -> Unit.Kilometres diff --git a/app/src/main/java/eu/ztsh/garmin/Model.kt b/app/src/main/java/eu/ztsh/garmin/Model.kt index 15e77bf..965d1b7 100644 --- a/app/src/main/java/eu/ztsh/garmin/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/Model.kt @@ -57,7 +57,7 @@ class State { var lineArrows: Int = 0 var lineOutlines: Int = 0 var direction = Direction() - var distance: Int = 0 + var distance: Double = 0.0 var unit: Unit = Unit.Any var speed: Int = 0 var limit: Int = 0 From bc608e9f2f8259963e381c43b80644bc354ec930 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 17:25:41 +0200 Subject: [PATCH 11/40] chore: Processing notes --- app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt index cf59579..2e3237c 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt @@ -166,6 +166,12 @@ class RouteControl(private val mapControl: MapControl, ui: UI, private val conte { mapControl.ui.maneuverView.visibility = View.VISIBLE mapControl.ui.maneuverView.renderManeuvers(maneuvers) + + /* + dump(maneuvers.value[0]) + garmin.process(maneuvers.value[0]) + */ + } ) From 7209f3657c46431f816ab27b37ce03f29bb7b047 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 18:12:39 +0200 Subject: [PATCH 12/40] chore: gradlew crlf --- gradlew.bat | 178 ++++++++++++++++++++++++++-------------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..107acd3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From b724d5041d8d6dedb4bee793b4ab240d1222b150 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 18:40:43 +0200 Subject: [PATCH 13/40] chore: float support in python --- python/main.py | 17 +++++++++++++---- python/test.py | 11 ++++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/python/main.py b/python/main.py index 73a55ab..4d002dd 100644 --- a/python/main.py +++ b/python/main.py @@ -63,7 +63,8 @@ class Controller: def clear(self): self.send_hud([0x03, 0, 0, 0, 0x00, 0, 0]) - def _as_dgt(self, n: int) -> int: + def _as_dgt(self, number: float) -> int: + n = int(number) if n == 0: return 0 n %= 10 @@ -89,9 +90,17 @@ class Controller: arr = [0x01, param_1, param_2, param_3] self.send_hud(arr) - def set_distance(self, distance, unit: Unit = Unit.Any): - arr = [0x03, self._as_dgt(distance // 1000), self._as_dgt(distance // 100), self._as_dgt(distance // 10), 0x00, - self._as_dgt(distance), unit.value] + def set_distance(self, distance: float, unit: Unit = Unit.Any): + is_float = int(distance * 10) == int(distance) * 10 + if not is_float: + distance = distance * 10 + arr = [0x03, + self._as_dgt(distance // 1000), + self._as_dgt(distance // 100), + self._as_dgt(distance // 10), + 0x00 if is_float else 0xff, + self._as_dgt(distance), + unit.value] self.send_hud(arr) def set_speed(self, speed: int, limit: int = 0, acc: bool = False): diff --git a/python/test.py b/python/test.py index 68addc9..53ea305 100644 --- a/python/test.py +++ b/python/test.py @@ -62,8 +62,10 @@ def direction(controller: Controller): def distance(controller: Controller): print("Distance") - controller.set_distance(999) - controller.set_distance(999, Unit.Kilometres) + controller.set_distance(555.5) + controller.set_distance(6666) + controller.set_distance(777.7) + controller.set_distance(888, Unit.Kilometres) controller.set_distance(999, Unit.Metres) controller.set_distance(999, Unit.Foot) controller.set_distance(999, Unit.Miles) @@ -108,4 +110,7 @@ def compass(controller: Controller): if __name__ == '__main__': - instance = Controller('/dev/rfcomm0') + import os + name = '/dev/rfcomm0' if os.name != 'nt' else 'COM8' + instance = Controller(name) + suite(instance) From fc05e543e6fd43ae2632f7e412798194456c89b9 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 19:33:13 +0200 Subject: [PATCH 14/40] chore: directions order in python --- python/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/test.py b/python/test.py index 53ea305..ae5a23d 100644 --- a/python/test.py +++ b/python/test.py @@ -32,7 +32,6 @@ def lines(controller: Controller): def direction(controller: Controller): print("Direction") - controller.set_direction(OutAngle.RightDown) controller.set_direction(OutAngle.SharpRight) controller.set_direction(OutAngle.Right) controller.set_direction(OutAngle.EasyRight) @@ -41,6 +40,7 @@ def direction(controller: Controller): controller.set_direction(OutAngle.Left) controller.set_direction(OutAngle.SharpLeft) controller.set_direction(OutAngle.LeftDown) + controller.set_direction(OutAngle.RightDown) controller.set_direction(OutAngle.Down) controller.set_direction(OutAngle.SharpRight, OutType.LongerLane) controller.set_direction(OutAngle.Right, OutType.LongerLane) From 6b6cffc87f47d45225614a55dbe87b161b98fb70 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 19:43:02 +0200 Subject: [PATCH 15/40] chore: ExampleActivity removed --- .../java/eu/ztsh/garmin/ExampleActivity.kt | 692 ------------------ 1 file changed, 692 deletions(-) delete mode 100644 app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt diff --git a/app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt b/app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt deleted file mode 100644 index 91a77f4..0000000 --- a/app/src/main/java/eu/ztsh/garmin/ExampleActivity.kt +++ /dev/null @@ -1,692 +0,0 @@ -//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 -// * -// * -// * -// * -// * -// * -// * 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 -> -// 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 { 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, routeOptions: RouteOptions) { -// // no impl -// } -// -// override fun onRoutesReady( -// routes: List, -// routerOrigin: String -// ) { -// setRouteAndStartNavigation(routes) -// } -// } -// ) -// } -// -// private fun setRouteAndStartNavigation(routes: List) { -// // set routes, where the first route in the list is the primary route that -// // will be used for active guidance -// 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() -// } -//} \ No newline at end of file From 5f854b883f94df013a5415daa26deb2d27900c87 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 19:44:15 +0200 Subject: [PATCH 16/40] chore: MapboxToolbox removed MapControl took most Toolbox functionalities --- .../main/java/eu/ztsh/garmin/MainActivity.kt | 12 --- .../main/java/eu/ztsh/garmin/MapboxToolbox.kt | 96 ------------------- 2 files changed, 108 deletions(-) delete mode 100644 app/src/main/java/eu/ztsh/garmin/MapboxToolbox.kt diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index 27f86ba..01e8cd2 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -51,7 +51,6 @@ class MainActivity : AppCompatActivity() { mapControl.initNavigation() } ) - private val mapboxToolbox = MapboxToolbox(lifecycle, this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -67,26 +66,15 @@ class MainActivity : AppCompatActivity() { .build() ) } - mapboxToolbox.onCreate() bluetoothInit() window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } - override fun onStart() { - super.onStart() - mapboxToolbox.onStart() - } - - override fun onStop() { - super.onStop() - mapboxToolbox.onStop() - } override fun onDestroy() { super.onDestroy() mapControl.onDestroy() window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - mapboxToolbox.onDestroy() } private fun bluetoothInit() { diff --git a/app/src/main/java/eu/ztsh/garmin/MapboxToolbox.kt b/app/src/main/java/eu/ztsh/garmin/MapboxToolbox.kt deleted file mode 100644 index 5dabf1e..0000000 --- a/app/src/main/java/eu/ztsh/garmin/MapboxToolbox.kt +++ /dev/null @@ -1,96 +0,0 @@ -package eu.ztsh.garmin - -import android.annotation.SuppressLint -import android.location.Location -import android.util.Log -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import com.mapbox.navigation.base.formatter.DistanceFormatterOptions -import com.mapbox.navigation.base.options.NavigationOptions -import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter -import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp -import com.mapbox.navigation.core.trip.session.LocationMatcherResult -import com.mapbox.navigation.core.trip.session.LocationObserver -import com.mapbox.navigation.core.trip.session.NavigationSessionState -import com.mapbox.navigation.core.trip.session.NavigationSessionStateObserver -import com.mapbox.navigation.core.trip.session.RouteProgressObserver -import com.mapbox.navigation.ui.maneuver.api.MapboxManeuverApi - -@SuppressLint("MissingPermission") -class MapboxToolbox(lifecycle: Lifecycle, private val context: MainActivity) { - - private val mapboxObserver = MapboxObserver() - - init { - lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onResume(owner: LifecycleOwner) { - MapboxNavigationApp.attach(owner) - MapboxNavigationApp.registerObserver(mapboxObserver) - } - - override fun onPause(owner: LifecycleOwner) { - MapboxNavigationApp.detach(owner) - MapboxNavigationApp.unregisterObserver(mapboxObserver) - } - }) - } - - fun onCreate() { - if (!MapboxNavigationApp.isSetup()) { - MapboxNavigationApp.setup { - NavigationOptions.Builder(context) - .accessToken(BuildConfig.MAPBOX_DOWNLOADS_TOKEN) - .build() - } - } - MapboxNavigationApp.current()?.startTripSession() - } - - fun onStart() { - MapboxNavigationApp.current()?.registerRouteProgressObserver(routeProgressObserver) - MapboxNavigationApp.current()?.registerLocationObserver(locationObserver) - MapboxNavigationApp.current()?.registerNavigationSessionStateObserver(navigationStateObserver) - } - - fun onStop() { - MapboxNavigationApp.current()?.unregisterRouteProgressObserver(routeProgressObserver) - MapboxNavigationApp.current()?.unregisterLocationObserver(locationObserver) - MapboxNavigationApp.current()?.unregisterNavigationSessionStateObserver(navigationStateObserver) - } - - fun onDestroy() { - MapboxNavigationApp.current()?.stopTripSession() - maneuverApi.cancel() - } - - // Define distance formatter options - private val distanceFormatter: DistanceFormatterOptions by lazy { - DistanceFormatterOptions.Builder(context).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 { - context.garmin.process( - this[0] - ) - } - } - - private val locationObserver = object : LocationObserver { - override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) { - context.garmin.process(locationMatcherResult) - } - - override fun onNewRawLocation(rawLocation: Location) { - } - } - - private val navigationStateObserver = NavigationSessionStateObserver { context.garmin.process(it) } - -} \ No newline at end of file From af2b3cce5cc6997ce85ff9af79c93141bf664c21 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 19:45:51 +0200 Subject: [PATCH 17/40] chore: DataCache SDKv3 --- app/src/main/java/eu/ztsh/garmin/data/DataCache.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt index 6b5622b..734f6e3 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt @@ -2,7 +2,7 @@ package eu.ztsh.garmin.data import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.core.trip.session.NavigationSessionState -import com.mapbox.navigation.ui.maneuver.model.Maneuver +import com.mapbox.navigation.tripdata.maneuver.model.Maneuver class DataCache { From 6f3643a2b47847120d15fc8a82aa1ffcac6cbe60 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 19:50:24 +0200 Subject: [PATCH 18/40] fix: DataCache & State domain fixes --- .../java/eu/ztsh/garmin/data/DataCache.kt | 19 +---------------- .../main/java/eu/ztsh/garmin/data/Model.kt | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt index 734f6e3..a7f8b8b 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt @@ -38,24 +38,7 @@ class DataCache { // Merge states fun update(state: State) { - if (state.lineArrows != null) { - stateCache.lineArrows = state.lineArrows - } - if (state.lineOutlines != null) { - state.lineOutlines = state.lineOutlines - } - if (state.direction != null) { - stateCache.direction = state.direction - } - if (state.distance != null) { - stateCache.distance = state.distance - } - if (state.speed != null) { - stateCache.speed = state.speed - } - if (state.arrival != null) { - stateCache.arrival = state.arrival - } + stateCache.merge(state) } // maneuver diff --git a/app/src/main/java/eu/ztsh/garmin/data/Model.kt b/app/src/main/java/eu/ztsh/garmin/data/Model.kt index 146d4ed..a11b2a3 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/Model.kt @@ -111,6 +111,27 @@ class State { var traffic: Boolean? = null var flag: Boolean? = null var control: Boolean? = null + + fun merge(other: State) { + if (other.lineArrows != null) { + this.lineArrows = other.lineArrows + } + if (other.lineOutlines != null) { + this.lineOutlines = other.lineOutlines + } + if (other.direction != null) { + this.direction = other.direction + } + if (other.distance != null) { + this.distance = other.distance + } + if (other.speed != null) { + this.speed = other.speed + } + if (other.arrival != null) { + this.arrival = other.arrival + } + } } From 35e24dcc8ec540d8e8e4365043e94eabd1599a7b Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 20:03:47 +0200 Subject: [PATCH 19/40] fix: isDigit & setDistance --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 22 ++----------------- .../java/eu/ztsh/garmin/data/GarminMapper.kt | 17 +++++++++++--- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index 1f1c572..0c08154 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -5,9 +5,9 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import android.util.Log -import com.mapbox.navigation.tripdata.maneuver.model.Maneuver import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.core.trip.session.NavigationSessionState +import com.mapbox.navigation.tripdata.maneuver.model.Maneuver import eu.ztsh.garmin.data.DataCache import eu.ztsh.garmin.data.GarminMapper import eu.ztsh.garmin.data.MapboxMapper @@ -72,17 +72,6 @@ class Garmin( } } - private fun setDistance(state: eu.ztsh.garmin.State) { - connection.enqueue(intArrayOf( - 0x03, - asDigit(state.distance / 1000), // position 1 - asDigit(state.distance / 100), // position 2 - asDigit(state.distance / 10), // position 3 - if ((state.distance * 10).toInt() == (state.distance.toInt() * 10)) 0x00 else 0xff, // comma - asDigit(state.distance), // position 4 - state.unit.data // unit - )) - } private open inner class ProcessingThread : Thread() { @@ -96,14 +85,6 @@ class Garmin( cache.update(incoming) } - private fun asDigit(input: Double): Int { - val number = input.toInt() - if (number == 0) { - return 0 - } - val m = number % 10 - return if (m == 0) 10 else m - } } private inner class ConnectThread : Thread() { @@ -183,6 +164,7 @@ class Garmin( } companion object { + fun prepareData(input: IntArray): IntArray { val n = input.size var crc = (0xeb + n + n).toUInt() diff --git a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt index 44182c8..9045fff 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt @@ -89,10 +89,17 @@ class GarminMapper { return intArrayOf(0x01, param1, param2, param3) } - private fun setDistance(distance: Int, unit: Unit = Unit.Any): IntArray { + private fun setDistance(distance: Double, unit: Unit = Unit.Any): IntArray { + val isDecimal = (distance * 10).toInt() == (distance.toInt() * 10) + val distanceValue = if (isDecimal) distance * 10 else distance return intArrayOf( - 0x03, asDigit(distance / 1000), asDigit(distance / 100), asDigit(distance / 10), - 0x00, asDigit(distance), unit.value + 0x03, + asDigit(distanceValue / 1000), // position 1 + asDigit(distanceValue / 100), // position 2 + asDigit(distanceValue / 10), // position 3 + if (isDecimal) 0x00 else 0xff, // comma + asDigit(distanceValue), // position 4 + unit.value // unit ) } @@ -134,6 +141,10 @@ class GarminMapper { } } + private fun asDigit(n: Double): Int { + return asDigit(n.toInt()) + } + private fun asDigit(n: Int): Int { if (n == 0) { return 0 From 116b13adcb15e57b6f27b943fda2d9d956cbc49f Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Tue, 30 Jul 2024 22:47:57 +0200 Subject: [PATCH 20/40] test: Python route test --- python/test.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/python/test.py b/python/test.py index ae5a23d..0ff701f 100644 --- a/python/test.py +++ b/python/test.py @@ -109,6 +109,30 @@ def compass(controller: Controller): pass +def route(controller: Controller): + print("Route") + controller.set_direction(OutAngle.Left) + controller.set_gps(True) + controller.set_speed(50, 50) + controller.set_distance(1.2, Unit.Kilometres) + sleep(1) + controller.set_distance(1.1, Unit.Kilometres) + sleep(1) + controller.set_distance(1, Unit.Kilometres) + sleep(1) + remaining = 900 + while remaining > 0: + controller.set_distance(remaining, Unit.Metres) + sleep(1) + remaining -= 100 + controller.set_direction(OutAngle.Right, OutType.Flag) + remaining = 900 + while remaining > 0: + controller.set_distance(remaining, Unit.Metres) + sleep(1) + remaining -= 100 + + if __name__ == '__main__': import os name = '/dev/rfcomm0' if os.name != 'nt' else 'COM8' From 75e10c1579b1fb77466d15c36a23b5f6dc7d418b Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Wed, 31 Jul 2024 18:38:42 +0200 Subject: [PATCH 21/40] fix: Proces threading fix --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 21 ++++++++++++------- .../main/java/eu/ztsh/garmin/MainActivity.kt | 6 +++--- .../eu/ztsh/garmin/mapbox/RouteControl.kt | 9 +++----- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index 0c08154..46a702b 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -23,7 +23,7 @@ class Garmin( ) { private lateinit var connection: ConnectThread - private lateinit var processing: ProcessingThread + private val observer = ProcessingThreadObserver() private val cache = DataCache() fun start() { @@ -36,15 +36,11 @@ class Garmin( } fun process(maneuver: Maneuver) { - processing = ManeuverProcessingThread(maneuver) - processing.start() - processing.join() + ManeuverProcessingThread(maneuver).start() } fun process(location: LocationMatcherResult) { - processing = LocationProcessingThread(location) - processing.start() - processing.join() + LocationProcessingThread(location).start() } fun process(navigationSessionState: NavigationSessionState) { @@ -83,6 +79,15 @@ class Garmin( connection.enqueue(GarminMapper.setDirection(incoming)) } cache.update(incoming) + observer.join(this) + } + + } + + private inner class ProcessingThreadObserver { + + fun join(thread: ProcessingThread) { + thread.join() } } @@ -165,6 +170,8 @@ class Garmin( companion object { + lateinit var instance: Garmin + fun prepareData(input: IntArray): IntArray { val n = input.size var crc = (0xeb + n + n).toUInt() diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index 01e8cd2..b7ae97f 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -30,7 +30,7 @@ import java.lang.ref.WeakReference @SuppressLint("MissingPermission") class MainActivity : AppCompatActivity() { - lateinit var garmin: Garmin +// lateinit var garmin: Garmin private lateinit var binding: ActivityMainBinding private lateinit var mapControl: MapControl @@ -95,8 +95,8 @@ class MainActivity : AppCompatActivity() { Log.d(TAG, device.name) device.name.equals("GARMIN HUD") }?.apply { - garmin = Garmin(context, this, bluetoothAdapter) - garmin.start() + Garmin.instance = Garmin(context, this, bluetoothAdapter) + Garmin.instance.start() } } diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt index 2e3237c..37bd0d6 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt @@ -35,6 +35,7 @@ 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.Garmin import eu.ztsh.garmin.UI class RouteControl(private val mapControl: MapControl, ui: UI, private val context: Context) { @@ -166,12 +167,8 @@ class RouteControl(private val mapControl: MapControl, ui: UI, private val conte { mapControl.ui.maneuverView.visibility = View.VISIBLE mapControl.ui.maneuverView.renderManeuvers(maneuvers) - - /* - dump(maneuvers.value[0]) - garmin.process(maneuvers.value[0]) - */ - + Garmin.instance.process(it[0]) + // dump(maneuvers.value[0]) } ) From 785a35473e2df35b80a5ac731919fb6635ae08ff Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Wed, 31 Jul 2024 20:16:44 +0200 Subject: [PATCH 22/40] fix: Model rebuilt --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 75 +++++++++------ .../java/eu/ztsh/garmin/data/DataCache.kt | 41 ++++----- .../java/eu/ztsh/garmin/data/GarminMapper.kt | 31 +++---- .../java/eu/ztsh/garmin/data/MapboxMapper.kt | 22 ++--- .../main/java/eu/ztsh/garmin/data/Model.kt | 92 +++++++++---------- .../eu/ztsh/garmin/data/GarminMapperTest.kt | 12 +-- 6 files changed, 136 insertions(+), 137 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index 46a702b..e9731cc 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -5,11 +5,11 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import android.util.Log -import com.mapbox.navigation.core.trip.session.LocationMatcherResult -import com.mapbox.navigation.core.trip.session.NavigationSessionState import com.mapbox.navigation.tripdata.maneuver.model.Maneuver import eu.ztsh.garmin.data.DataCache +import eu.ztsh.garmin.data.GarminManeuver import eu.ztsh.garmin.data.GarminMapper +import eu.ztsh.garmin.data.GarminModelItem import eu.ztsh.garmin.data.MapboxMapper import java.io.IOException import java.util.* @@ -39,46 +39,61 @@ class Garmin( ManeuverProcessingThread(maneuver).start() } - fun process(location: LocationMatcherResult) { - LocationProcessingThread(location).start() - } +// fun process(location: LocationMatcherResult) { +// LocationProcessingThread(location).start() +// } - fun process(navigationSessionState: NavigationSessionState) { - cache.update(navigationSessionState) - } +// fun process(navigationSessionState: NavigationSessionState) { +// cache.update(navigationSessionState) +// } - private inner class ManeuverProcessingThread(val maneuver: Maneuver) : ProcessingThread() { + private inner class ManeuverProcessingThread(val maneuver: Maneuver) : ProcessingThread() { - override fun run() { + override fun process(): GarminManeuver? { if (cache.hasChanged(maneuver)) { cache.update(maneuver) - send(MapboxMapper.apply(maneuver)) + return MapboxMapper.map(maneuver) } + return null + } + + override fun enqueue(item: GarminManeuver) { + if (cache.hasChanged(item.lanes)) { + connection.enqueue(GarminMapper.map(item.lanes)) + } + if (cache.hasChanged(item.direction)) { + connection.enqueue(GarminMapper.map(item.direction)) + } + if (cache.hasChanged(item.distance)) { + connection.enqueue(GarminMapper.map(item.distance)) + } + // flag? } } - private inner class LocationProcessingThread(val location: LocationMatcherResult) : ProcessingThread() { +// private inner class LocationProcessingThread(val location: LocationMatcherResult) : ProcessingThread() { +// +// override fun process() { +// if (cache.hasChanged(location)) { +// cache.update(location) +// send(MapboxMapper.apply(location)) +// } +// } +// } + + private abstract inner class ProcessingThread : Thread() { + + abstract fun process(): T? + + abstract fun enqueue(item: T) override fun run() { - if (cache.hasChanged(location)) { - cache.update(location) - send(MapboxMapper.apply(location)) + val processing = process() + if (processing != null) { + enqueue(processing) + cache.update(processing) } - } - - } - - private open inner class ProcessingThread : Thread() { - - fun send(incoming: eu.ztsh.garmin.data.State) { - if (cache.hasChanged(incoming.distance)) { - connection.enqueue(GarminMapper.setDistance(incoming)) - } - if (cache.hasChanged(incoming.direction)) { - connection.enqueue(GarminMapper.setDirection(incoming)) - } - cache.update(incoming) observer.join(this) } @@ -86,7 +101,7 @@ class Garmin( private inner class ProcessingThreadObserver { - fun join(thread: ProcessingThread) { + fun join(thread: ProcessingThread) { thread.join() } diff --git a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt index a7f8b8b..2abeaa8 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt @@ -6,39 +6,38 @@ import com.mapbox.navigation.tripdata.maneuver.model.Maneuver class DataCache { - private val stateCache: State = State() + private val garminManeuver: GarminManeuver = GarminManeuver.empty() private var maneuverCache: Maneuver? = null private var locationCache: LocationMatcherResult? = null private var session: NavigationSessionState? = null // state - fun hasChanged(lanes: Lanes?): Boolean { - return stateCache.lineArrows == null || stateCache.lineArrows != lanes + fun hasChanged(lanes: Lanes): Boolean { + return garminManeuver.lanes != lanes } - fun hasChanged(outlines: Outlines?): Boolean { - return stateCache.lineOutlines == null || stateCache.lineOutlines != outlines + fun hasChanged(distance: Distance): Boolean { + return garminManeuver.distance != distance } - fun hasChanged(distance: Distance?): Boolean { - return stateCache.distance == null || stateCache.distance != distance - } - - fun hasChanged(direction: Direction?): Boolean { - return stateCache.direction == null || stateCache.direction != direction - } - - fun hasChanged(speed: Speed?): Boolean { - return stateCache.speed == null || stateCache.speed != speed - } - - fun hasChanged(arrival: Arrival?): Boolean { - return stateCache.arrival == null || stateCache.arrival != arrival + fun hasChanged(direction: Direction): Boolean { + return garminManeuver.direction != direction } +// +// fun hasChanged(speed: Speed?): Boolean { +// return stateCache.speed == null || stateCache.speed != speed +// } +// +// fun hasChanged(arrival: Arrival?): Boolean { +// return stateCache.arrival == null || stateCache.arrival != arrival +// } // Merge states - fun update(state: State) { - stateCache.merge(state) + fun update(item: GarminModelItem) { + when(item) { + is GarminManeuver -> garminManeuver.merge(item) + } + } // maneuver diff --git a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt index 9045fff..11757f5 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt @@ -4,24 +4,20 @@ class GarminMapper { companion object { - fun setLines(state: State): IntArray { - return intArrayOf(0x02, state.lineOutlines.sumOf { it.value }, state.lineArrows.sumOf { it.value }) + fun map(lanes: Lanes): IntArray { + return intArrayOf(0x02, lanes.lanes.lanes.sumOf { it.value }, lanes.outlines.lanes.sumOf { it.value }) } - fun setDirection(state: State): IntArray { - return setDirection(state.direction.angle, state.direction.out, state.direction.roundabout) + fun map(direction: Direction): IntArray { + return toDirectionArray(direction.angle, direction.out, direction.roundabout) } - fun setDistance(state: State): IntArray { - return setDistance(state.distance, state.unit) + fun map(distance: Distance): IntArray { + return setDistance(distance.distance, distance.unit) } - fun setSpeed(state: State): IntArray { - return setSpeed(state.speed, state.limit, state.speed > state.limit) - } - - fun setSpeedFreeRide(state: State): Pair { - return Pair(setDistance(state.speed), setSpeed(state.limit, limitWarning = state.speed > state.limit)) + fun map(speed: Speed): IntArray { + return setSpeed(speed.speed, speed.limit, speed.speed > speed.limit) } fun setTime(hours: Int, minutes: Int, traffic: Boolean = false, flag: Boolean = false): IntArray { @@ -54,20 +50,15 @@ class GarminMapper { } } - fun setSpeedControl(state: State): IntArray { - return intArrayOf(0x04, if (state.control) 0x01 else 0x02) - } - - fun setCompass(state: State): IntArray { - // TODO: Implement - return setDirection(OutAngle.Straight, OutType.ArrowOnly) + fun speedControl(state: Boolean): IntArray { + return intArrayOf(0x04, if (state) 0x01 else 0x02) } fun cleanDistance(): IntArray { return intArrayOf(0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) } - private fun setDirection( + private fun toDirectionArray( angle: OutAngle, out: OutType = OutType.Lane, roundabout: OutAngle = OutAngle.AsDirection diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index db40c45..eada8e1 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -7,14 +7,14 @@ class MapboxMapper { companion object { - fun apply(maneuver: Maneuver): State { - val state = State() + fun map(maneuver: Maneuver): GarminManeuver { + val state = GarminManeuver() maneuver.apply { this.primary.apply { state.direction = Direction() when (this.type) { "roundabout" -> { - state.direction!!.out = OutType.RightRoundabout + state.direction.out = OutType.RightRoundabout } "arrive" -> { @@ -24,11 +24,11 @@ class MapboxMapper { when (this.modifier) { "right" -> { when (this.type) { - "turn" -> state.direction!!.angle = OutAngle.Right + "turn" -> state.direction.angle = OutAngle.Right "roundabout" -> { when (this.degrees) { - 137.0 -> state.direction!!.angle = OutAngle.EasyRight - 180.0 -> state.direction!!.angle = OutAngle.Straight + 137.0 -> state.direction.angle = OutAngle.EasyRight + 180.0 -> state.direction.angle = OutAngle.Straight } } } @@ -36,16 +36,16 @@ class MapboxMapper { "left" -> { when (this.type) { - "turn" -> state.direction!!.angle = OutAngle.Left + "turn" -> state.direction.angle = OutAngle.Left } } } } this.stepDistance.apply { this.distanceRemaining?.apply { - distanceFormatter.formatDistance(distanceRemaining!!).split(" ").apply { + distanceFormatter.formatDistance(this).split(" ").apply { state.distance = Distance( - this[0].replace(',', '.').toDouble().toInt(), + this[0].replace(',', '.').toDouble(), when (this[1]) { "m" -> Unit.Metres "km" -> Unit.Kilometres @@ -65,8 +65,8 @@ class MapboxMapper { return state } - fun apply(locationMatcherResult: LocationMatcherResult): State { - val state = State() + fun map(locationMatcherResult: LocationMatcherResult): GarminLocation { + val state = GarminLocation() // TODO: speed, limit, location?, bearing return state } diff --git a/app/src/main/java/eu/ztsh/garmin/data/Model.kt b/app/src/main/java/eu/ztsh/garmin/data/Model.kt index a11b2a3..82bffe6 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/Model.kt @@ -12,7 +12,6 @@ enum class OutType(val value: Int) { } - enum class OutAngle(val value: Int) { Down(0x01), @@ -52,7 +51,7 @@ enum class Lane(val value: Int) { } -open class Arrows(val lanes: List) { +class Arrows(val lanes: List) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -68,11 +67,9 @@ open class Arrows(val lanes: List) { } } -class Lanes(lanes: List) : Arrows(lanes) +class Lanes(val outlines: Arrows, val lanes: Arrows) -class Outlines(lanes: List) : Arrows(lanes) - -class Distance(val distance: Int, val unit: Unit) { +class Distance(val distance: Double, val unit: Unit) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -87,7 +84,7 @@ class Distance(val distance: Int, val unit: Unit) { } override fun hashCode(): Int { - var result = distance + var result = distance.hashCode() result = 31 * result + unit.hashCode() return result } @@ -98,47 +95,12 @@ class Speed(val speed: Int, val limit: Int) class Arrival(val hours: Int, val minutes: Int) -class State { - - var lineArrows: Lanes? = null - var lineOutlines: Outlines? = null - var direction : Direction? = null - var distance: Distance? = null - var speed: Speed? = null - var arrival: Arrival? = null - // TODO: Bearing - // TODO: support - var traffic: Boolean? = null - var flag: Boolean? = null - var control: Boolean? = null - - fun merge(other: State) { - if (other.lineArrows != null) { - this.lineArrows = other.lineArrows - } - if (other.lineOutlines != null) { - this.lineOutlines = other.lineOutlines - } - if (other.direction != null) { - this.direction = other.direction - } - if (other.distance != null) { - this.distance = other.distance - } - if (other.speed != null) { - this.speed = other.speed - } - if (other.arrival != null) { - this.arrival = other.arrival - } - } - -} - -class Direction { - var angle: OutAngle = OutAngle.AsDirection - var out: OutType = OutType.Lane +class Direction( + var angle: OutAngle = OutAngle.AsDirection, + var out: OutType = OutType.Lane, var roundabout: OutAngle = OutAngle.AsDirection +) { + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -160,3 +122,39 @@ class Direction { } } + +interface GarminModelItem { + + fun merge(item: GarminModelItem) +} + +class GarminManeuver : GarminModelItem { + + lateinit var lanes: Lanes + lateinit var direction: Direction + lateinit var distance: Distance + var flag: Boolean = false // WTF? + + override fun merge(item: GarminModelItem) { + TODO("Not yet implemented") + } + + companion object { + + val empty: () -> GarminManeuver = { + val manouver = GarminManeuver() + manouver.lanes = Lanes(Arrows(listOf()), Arrows(listOf())) + manouver.direction = Direction(out = OutType.Off) + manouver.distance = Distance(0.0, Unit.Any) + manouver + } + } +} + +class GarminLocation : GarminModelItem { + + override fun merge(item: GarminModelItem) { + TODO("Not yet implemented") + } + +} diff --git a/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt b/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt index f0f1269..82be573 100644 --- a/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt +++ b/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt @@ -253,10 +253,8 @@ class GarminMapperTest { } private fun linesTest(outlines: List, arrows: List, expectedRaw: IntArray, expectedBoxed: IntArray) { - val state = State() - state.lineOutlines = outlines - state.lineArrows = arrows - makeAssertions(GarminMapper.setLines(state), expectedRaw, expectedBoxed) + val lanes = Lanes(Arrows(arrows), Arrows(outlines)) + makeAssertions(GarminMapper.map(lanes), expectedRaw, expectedBoxed) } private fun directionTest(outAngle: OutAngle, expectedRaw: IntArray, expectedBoxed: IntArray) { @@ -264,10 +262,8 @@ class GarminMapperTest { } private fun directionTest(outAngle: OutAngle, outType: OutType, expectedRaw: IntArray, expectedBoxed: IntArray) { - val state = State() - state.direction.angle = outAngle - state.direction.out = outType - makeAssertions(GarminMapper.setDirection(state), expectedRaw, expectedBoxed) + val direction = Direction(outAngle, outType) + makeAssertions(GarminMapper.map(direction), expectedRaw, expectedBoxed) } private fun makeAssertions(resultRaw: IntArray, expectedRaw: IntArray, expectedBoxed: IntArray) { From bf878dc2c6b05b8c4311cf8dbd915ec4f1a2e588 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Wed, 31 Jul 2024 20:28:45 +0200 Subject: [PATCH 23/40] fix: Application crash prevention --- app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt | 2 ++ app/src/main/java/eu/ztsh/garmin/data/Model.kt | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index eada8e1..58f7887 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -56,6 +56,8 @@ class MapboxMapper { } } } + // TODO: implement + state.lanes = Lanes(Arrows(listOf()), Arrows(listOf())) this.laneGuidance?.apply { this.allLanes.apply { println() diff --git a/app/src/main/java/eu/ztsh/garmin/data/Model.kt b/app/src/main/java/eu/ztsh/garmin/data/Model.kt index 82bffe6..ef30ede 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/Model.kt @@ -136,7 +136,11 @@ class GarminManeuver : GarminModelItem { var flag: Boolean = false // WTF? override fun merge(item: GarminModelItem) { - TODO("Not yet implemented") + val maneuver = item as GarminManeuver + this.lanes = maneuver.lanes + this.direction = maneuver.direction + this.distance = maneuver.distance + this.flag = maneuver.flag } companion object { From bbfeabc0e4a636473e5792bcc59603c649296b92 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 1 Aug 2024 00:15:57 +0200 Subject: [PATCH 24/40] feat: LongerLane support --- app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index 58f7887..7e2be69 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -16,7 +16,7 @@ class MapboxMapper { "roundabout" -> { state.direction.out = OutType.RightRoundabout } - + "fork" -> state.direction.out = OutType.LongerLane "arrive" -> { state.flag = true } From 0e22c02da3ca476cf542ceaa9192e1353bc90ceb Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 1 Aug 2024 01:14:16 +0200 Subject: [PATCH 25/40] fix: Distance comma fixed --- app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt index 11757f5..7367a25 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt @@ -81,14 +81,14 @@ class GarminMapper { } private fun setDistance(distance: Double, unit: Unit = Unit.Any): IntArray { - val isDecimal = (distance * 10).toInt() == (distance.toInt() * 10) + val isDecimal = (distance * 10).toInt() != (distance.toInt() * 10) val distanceValue = if (isDecimal) distance * 10 else distance return intArrayOf( 0x03, asDigit(distanceValue / 1000), // position 1 asDigit(distanceValue / 100), // position 2 asDigit(distanceValue / 10), // position 3 - if (isDecimal) 0x00 else 0xff, // comma + if (isDecimal) 0xff else 0x00, // comma asDigit(distanceValue), // position 4 unit.value // unit ) From 6d71245d9fb99d649e1ad08a5757de52b1761496 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 1 Aug 2024 19:34:48 +0200 Subject: [PATCH 26/40] feat: new MapboxMapper cases --- app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index 7e2be69..f771068 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -20,6 +20,11 @@ class MapboxMapper { "arrive" -> { state.flag = true } + "turn" -> { + when (this.type) { + "straight" -> state.direction.angle = OutAngle.Straight + } + } } when (this.modifier) { "right" -> { @@ -31,6 +36,10 @@ class MapboxMapper { 180.0 -> state.direction.angle = OutAngle.Straight } } + "off ramp" -> { + state.direction.angle = OutAngle.EasyRight + state.direction.out = OutType.LongerLane + } } } From f5ec5003431d14aac988a0283808da53ad15a4fe Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 1 Aug 2024 22:52:26 +0200 Subject: [PATCH 27/40] fix: Threading & optimizations --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 116 +++++++++-------- .../java/eu/ztsh/garmin/data/DataCache.kt | 44 ++----- .../java/eu/ztsh/garmin/data/MapboxMapper.kt | 120 +++++++++--------- .../main/java/eu/ztsh/garmin/data/Model.kt | 4 + 4 files changed, 142 insertions(+), 142 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index e9731cc..587e68f 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -7,12 +7,11 @@ import android.bluetooth.BluetoothSocket import android.util.Log import com.mapbox.navigation.tripdata.maneuver.model.Maneuver import eu.ztsh.garmin.data.DataCache -import eu.ztsh.garmin.data.GarminManeuver import eu.ztsh.garmin.data.GarminMapper -import eu.ztsh.garmin.data.GarminModelItem import eu.ztsh.garmin.data.MapboxMapper import java.io.IOException import java.util.* +import java.util.concurrent.Executors import java.util.concurrent.SynchronousQueue @SuppressLint("MissingPermission") @@ -23,88 +22,98 @@ class Garmin( ) { private lateinit var connection: ConnectThread - private val observer = ProcessingThreadObserver() + private lateinit var maneuvers: ManeuverProcessingThread private val cache = DataCache() + private val maneuversPool = Executors.newFixedThreadPool(4) + fun start() { connection = ConnectThread() connection.start() + + maneuvers = ManeuverProcessingThread() + maneuvers.start() } fun close() { connection.close() + + maneuvers.interrupt() + maneuvers.join(0) + + maneuversPool.shutdown() } fun process(maneuver: Maneuver) { - ManeuverProcessingThread(maneuver).start() + maneuversPool.submit{maneuvers.enqueue(maneuver)} } -// fun process(location: LocationMatcherResult) { -// LocationProcessingThread(location).start() -// } + private inner class ManeuverProcessingThread : ProcessingThread() { -// fun process(navigationSessionState: NavigationSessionState) { -// cache.update(navigationSessionState) -// } - - private inner class ManeuverProcessingThread(val maneuver: Maneuver) : ProcessingThread() { - - override fun process(): GarminManeuver? { - if (cache.hasChanged(maneuver)) { - cache.update(maneuver) - return MapboxMapper.map(maneuver) + override fun mapAndSend(maybeItem: Maneuver?): Maneuver? { + if (maybeItem != null) { + Log.d(TAG, "mapAndSend (${currentThread().name}): got new") + var changed = false + if (cache.hasChanged(maybeItem.laneGuidance)) { + changed = true + Log.d(TAG, "mapAndSend: lanes") + send(GarminMapper.map(MapboxMapper.asLanes(maybeItem))) + } + if (cache.hasChanged(maybeItem.stepDistance)) { + changed = true + Log.d(TAG, "mapAndSend: stepDistance") + send(GarminMapper.map(MapboxMapper.asDistance(maybeItem))) + } + if (cache.hasChanged(maybeItem.primary)) { + changed = true + Log.d(TAG, "mapAndSend: primary") + send(GarminMapper.map(MapboxMapper.asDirection(maybeItem))) + } + if (changed) { + return maybeItem + } } return null } - override fun enqueue(item: GarminManeuver) { - if (cache.hasChanged(item.lanes)) { - connection.enqueue(GarminMapper.map(item.lanes)) - } - if (cache.hasChanged(item.direction)) { - connection.enqueue(GarminMapper.map(item.direction)) - } - if (cache.hasChanged(item.distance)) { - connection.enqueue(GarminMapper.map(item.distance)) - } - // flag? + override fun updateCache(item: Maneuver) { + cache.update(item) } } -// private inner class LocationProcessingThread(val location: LocationMatcherResult) : ProcessingThread() { -// -// override fun process() { -// if (cache.hasChanged(location)) { -// cache.update(location) -// send(MapboxMapper.apply(location)) -// } -// } -// } + private abstract inner class ProcessingThread : Thread() { - private abstract inner class ProcessingThread : Thread() { + private val queue: SynchronousQueue = SynchronousQueue() + private var stop: Boolean = false - abstract fun process(): T? + abstract fun mapAndSend(maybeItem: T?): T? - abstract fun enqueue(item: T) + abstract fun updateCache(item: T) + + fun send(data: IntArray) { + connection.enqueue(data) + } + + fun enqueue(item: T) { + queue.put(item) + } override fun run() { - val processing = process() - if (processing != null) { - enqueue(processing) - cache.update(processing) + while (!stop) { + val maybeItem = queue.poll() + val item = mapAndSend(maybeItem) + if (item != null) { + Log.d(TAG, "run: Cache updated") + updateCache(item) + } } - observer.join(this) } - } - - private inner class ProcessingThreadObserver { - - fun join(thread: ProcessingThread) { - thread.join() + override fun interrupt() { + stop = true + super.interrupt() } - } private inner class ConnectThread : Thread() { @@ -126,9 +135,11 @@ class Garmin( context.setConnectionStatus(true) sleep(3000) readAll() + send(intArrayOf(0x07, 0x01)) // Set GPS to true while (true) { val newCurrent = queue.poll() if (newCurrent == null) { + Log.d(TAG, "run (${currentThread().name}): Sleep...") sleep(500) } else { current = newCurrent @@ -177,7 +188,6 @@ class Garmin( private fun sendRaw(buff: IntArray) { buff.forEach { socket!!.outputStream.write(it) } socket!!.outputStream.flush() - sleep(2000) readAll() } diff --git a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt index 2abeaa8..51fb02e 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt @@ -2,7 +2,10 @@ package eu.ztsh.garmin.data import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.core.trip.session.NavigationSessionState +import com.mapbox.navigation.tripdata.maneuver.model.Lane import com.mapbox.navigation.tripdata.maneuver.model.Maneuver +import com.mapbox.navigation.tripdata.maneuver.model.PrimaryManeuver +import com.mapbox.navigation.tripdata.maneuver.model.StepDistance class DataCache { @@ -11,38 +14,17 @@ class DataCache { private var locationCache: LocationMatcherResult? = null private var session: NavigationSessionState? = null - // state - fun hasChanged(lanes: Lanes): Boolean { - return garminManeuver.lanes != lanes - } - - fun hasChanged(distance: Distance): Boolean { - return garminManeuver.distance != distance - } - - fun hasChanged(direction: Direction): Boolean { - return garminManeuver.direction != direction - } -// -// fun hasChanged(speed: Speed?): Boolean { -// return stateCache.speed == null || stateCache.speed != speed -// } -// -// fun hasChanged(arrival: Arrival?): Boolean { -// return stateCache.arrival == null || stateCache.arrival != arrival -// } - - // Merge states - fun update(item: GarminModelItem) { - when(item) { - is GarminManeuver -> garminManeuver.merge(item) - } - - } - // maneuver - fun hasChanged(maneuver: Maneuver): Boolean { - return maneuverCache == null || maneuverCache!! != maneuver + fun hasChanged(guidance: Lane?): Boolean { + return guidance != null && maneuverCache.let { it == null || it.laneGuidance != guidance } + } + + fun hasChanged(distance: StepDistance): Boolean { + return maneuverCache.let { it == null || it.stepDistance != distance } + } + + fun hasChanged(primaryManeuver: PrimaryManeuver): Boolean { + return maneuverCache.let { it == null || it.primary != primaryManeuver } } fun update(maneuver: Maneuver) { diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index f771068..d9161c5 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -6,74 +6,78 @@ import com.mapbox.navigation.core.trip.session.LocationMatcherResult class MapboxMapper { companion object { - - fun map(maneuver: Maneuver): GarminManeuver { - val state = GarminManeuver() - maneuver.apply { - this.primary.apply { - state.direction = Direction() - when (this.type) { - "roundabout" -> { - state.direction.out = OutType.RightRoundabout - } - "fork" -> state.direction.out = OutType.LongerLane - "arrive" -> { - state.flag = true - } - "turn" -> { - when (this.type) { - "straight" -> state.direction.angle = OutAngle.Straight - } - } + + fun asDirection(maneuver: Maneuver): Direction { + val direction = Direction() + maneuver.primary.apply { + when (this.type) { + "roundabout" -> { + direction.out = OutType.RightRoundabout } - when (this.modifier) { - "right" -> { - when (this.type) { - "turn" -> state.direction.angle = OutAngle.Right - "roundabout" -> { - when (this.degrees) { - 137.0 -> state.direction.angle = OutAngle.EasyRight - 180.0 -> state.direction.angle = OutAngle.Straight - } - } - "off ramp" -> { - state.direction.angle = OutAngle.EasyRight - state.direction.out = OutType.LongerLane - } - } - } - - "left" -> { - when (this.type) { - "turn" -> state.direction.angle = OutAngle.Left - } + "fork" -> direction.out = OutType.LongerLane + "arrive" -> { +// flag = true + } + "turn" -> { + when (this.type) { + "straight" -> direction.angle = OutAngle.Straight } } } - this.stepDistance.apply { - this.distanceRemaining?.apply { - distanceFormatter.formatDistance(this).split(" ").apply { - state.distance = Distance( - this[0].replace(',', '.').toDouble(), - when (this[1]) { - "m" -> Unit.Metres - "km" -> Unit.Kilometres - else -> Unit.Any + when (this.modifier) { + "right" -> { + when (this.type) { + "turn" -> direction.angle = OutAngle.Right + "roundabout" -> { + when (this.degrees) { + 137.0 -> direction.angle = OutAngle.EasyRight + 180.0 -> direction.angle = OutAngle.Straight } - ) - + } + "off ramp" -> { + direction.angle = OutAngle.EasyRight + direction.out = OutType.LongerLane + } } } - } - // TODO: implement - state.lanes = Lanes(Arrows(listOf()), Arrows(listOf())) - this.laneGuidance?.apply { - this.allLanes.apply { - println() + + "left" -> { + when (this.type) { + "turn" -> direction.angle = OutAngle.Left + "off ramp" -> { + direction.angle = OutAngle.EasyLeft + direction.out = OutType.LongerLane + } + } } } } - return state + return direction + } + + fun asDistance(maneuver: Maneuver): Distance { + return maneuver.stepDistance.let { step -> + step.distanceFormatter.formatDistance(step.distanceRemaining!!).split(" ").let { + Distance( + it[0].replace(',', '.').toDouble(), + when (it[1]) { + "m" -> Unit.Metres + "km" -> Unit.Kilometres + else -> Unit.Any + } + ) + } + } + } + + fun asLanes(maneuver: Maneuver): Lanes { + // TODO: implement + maneuver.laneGuidance?.apply { + this.allLanes.apply { + println() + } + } + return Lanes(Arrows(listOf()), Arrows(listOf())) } fun map(locationMatcherResult: LocationMatcherResult): GarminLocation { diff --git a/app/src/main/java/eu/ztsh/garmin/data/Model.kt b/app/src/main/java/eu/ztsh/garmin/data/Model.kt index ef30ede..ad6b205 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/Model.kt @@ -89,6 +89,10 @@ class Distance(val distance: Double, val unit: Unit) { return result } + override fun toString(): String { + return "Distance($distance$unit)" + } + } class Speed(val speed: Int, val limit: Int) From 386bb506215447598891fb0a56f0265049d34d28 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 1 Aug 2024 23:28:49 +0200 Subject: [PATCH 28/40] feat: Lane assist --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 15 +++++++++----- .../java/eu/ztsh/garmin/data/MapboxMapper.kt | 20 +++++++++++++++---- .../main/java/eu/ztsh/garmin/data/Model.kt | 10 +++++++--- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index 587e68f..2974097 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -6,8 +6,10 @@ import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import android.util.Log import com.mapbox.navigation.tripdata.maneuver.model.Maneuver +import eu.ztsh.garmin.data.Arrows import eu.ztsh.garmin.data.DataCache import eu.ztsh.garmin.data.GarminMapper +import eu.ztsh.garmin.data.Lanes import eu.ztsh.garmin.data.MapboxMapper import java.io.IOException import java.util.* @@ -54,6 +56,14 @@ class Garmin( if (maybeItem != null) { Log.d(TAG, "mapAndSend (${currentThread().name}): got new") var changed = false + if (cache.hasChanged(maybeItem.primary)) { + changed = true + Log.d(TAG, "mapAndSend: primary") + send(GarminMapper.map(MapboxMapper.asDirection(maybeItem))) + if (maybeItem.laneGuidance == null) { + send(GarminMapper.map(Lanes(Arrows(setOf()), Arrows(setOf())))) + } + } if (cache.hasChanged(maybeItem.laneGuidance)) { changed = true Log.d(TAG, "mapAndSend: lanes") @@ -64,11 +74,6 @@ class Garmin( Log.d(TAG, "mapAndSend: stepDistance") send(GarminMapper.map(MapboxMapper.asDistance(maybeItem))) } - if (cache.hasChanged(maybeItem.primary)) { - changed = true - Log.d(TAG, "mapAndSend: primary") - send(GarminMapper.map(MapboxMapper.asDirection(maybeItem))) - } if (changed) { return maybeItem } diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index d9161c5..777b5a2 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -71,13 +71,25 @@ class MapboxMapper { } fun asLanes(maneuver: Maneuver): Lanes { - // TODO: implement + val laneIterator = Lane.iterator() + val outlines = mutableSetOf() + val lanes = mutableSetOf() maneuver.laneGuidance?.apply { - this.allLanes.apply { - println() + this.allLanes.reversed().let { + it.forEach{ indicator -> + val lane = if (laneIterator.hasNext()) laneIterator.next() else Lane.DotsLeft + if (lane == Lane.DotsLeft) { + outlines.add(Lane.DotsLeft) + } else { + outlines.add(lane) + if (indicator.isActive) { + lanes.add(lane) + } + } + } } } - return Lanes(Arrows(listOf()), Arrows(listOf())) + return Lanes(Arrows(lanes), Arrows(outlines)) } fun map(locationMatcherResult: LocationMatcherResult): GarminLocation { diff --git a/app/src/main/java/eu/ztsh/garmin/data/Model.kt b/app/src/main/java/eu/ztsh/garmin/data/Model.kt index ad6b205..9bfdbe7 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/Model.kt @@ -47,11 +47,15 @@ enum class Lane(val value: Int) { InnerLeft(0x10), MiddleLeft(0x20), OuterLeft(0x40), - DotsLeft(0x80) + DotsLeft(0x80); + + companion object { + val iterator = {sortedSetOf(OuterRight, MiddleRight, InnerRight, InnerLeft, MiddleLeft, OuterLeft).iterator()} + } } -class Arrows(val lanes: List) { +class Arrows(val lanes: Set) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -151,7 +155,7 @@ class GarminManeuver : GarminModelItem { val empty: () -> GarminManeuver = { val manouver = GarminManeuver() - manouver.lanes = Lanes(Arrows(listOf()), Arrows(listOf())) + manouver.lanes = Lanes(Arrows(setOf()), Arrows(setOf())) manouver.direction = Direction(out = OutType.Off) manouver.distance = Distance(0.0, Unit.Any) manouver From ac24c6ed2f6fc830de95d0a6cd6f7f59ba1da796 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 00:24:35 +0200 Subject: [PATCH 29/40] chore: small python suite fix --- python/test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/test.py b/python/test.py index 0ff701f..e51a2ae 100644 --- a/python/test.py +++ b/python/test.py @@ -49,6 +49,12 @@ def direction(controller: Controller): controller.set_direction(OutAngle.EasyLeft, OutType.LongerLane) controller.set_direction(OutAngle.Left, OutType.LongerLane) controller.set_direction(OutAngle.SharpLeft, OutType.LongerLane) + roundabout(controller) + controller.set_direction(OutAngle.Left, OutType.Flag) + controller.set_direction(OutAngle.Right, OutType.Flag) + + +def roundabout(controller: Controller): controller.set_direction(OutAngle.SharpRight, OutType.RightRoundabout) controller.set_direction(OutAngle.Right, OutType.RightRoundabout) controller.set_direction(OutAngle.EasyRight, OutType.RightRoundabout) @@ -56,8 +62,7 @@ def direction(controller: Controller): controller.set_direction(OutAngle.EasyLeft, OutType.RightRoundabout) controller.set_direction(OutAngle.Left, OutType.RightRoundabout) controller.set_direction(OutAngle.SharpLeft, OutType.RightRoundabout) - controller.set_direction(OutAngle.Left, OutType.Flag) - controller.set_direction(OutAngle.Right, OutType.Flag) + controller.set_direction(OutAngle.Down, OutType.RightRoundabout) def distance(controller: Controller): From c3d553ece065f1fceed77e13daaf3710681dcf32 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 00:55:05 +0200 Subject: [PATCH 30/40] feat: More Mapbox mappings --- .../java/eu/ztsh/garmin/data/MapboxMapper.kt | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index 777b5a2..e2228d9 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -14,27 +14,38 @@ class MapboxMapper { "roundabout" -> { direction.out = OutType.RightRoundabout } - "fork" -> direction.out = OutType.LongerLane "arrive" -> { -// flag = true + direction.out = OutType.Flag + direction.angle = OutAngle.Right } "turn" -> { - when (this.type) { + when (this.modifier) { "straight" -> direction.angle = OutAngle.Straight + "slight right" -> direction.angle = OutAngle.EasyRight + "slight left" -> direction.angle = OutAngle.EasyLeft + "sharp right" -> direction.angle = OutAngle.SharpRight + "sharp left" -> direction.angle = OutAngle.SharpLeft + "uturn" -> direction.angle = OutAngle.LeftDown } } } when (this.modifier) { "right" -> { + direction.angle = OutAngle.Right when (this.type) { - "turn" -> direction.angle = OutAngle.Right "roundabout" -> { - when (this.degrees) { - 137.0 -> direction.angle = OutAngle.EasyRight - 180.0 -> direction.angle = OutAngle.Straight + when (this.degrees!!.toInt()) { + in 22..66 -> direction.angle = OutAngle.SharpRight + in 67..111 -> direction.angle = OutAngle.Right + in 112..156 -> direction.angle = OutAngle.EasyRight + in 157..203 -> direction.angle = OutAngle.Straight + in 204..248 -> direction.angle = OutAngle.EasyLeft + in 249..293 -> direction.angle = OutAngle.Left + in 294..338 -> direction.angle = OutAngle.SharpLeft + else -> direction.angle = OutAngle.Down } } - "off ramp" -> { + "fork", "off ramp" -> { direction.angle = OutAngle.EasyRight direction.out = OutType.LongerLane } @@ -42,9 +53,9 @@ class MapboxMapper { } "left" -> { + direction.angle = OutAngle.Left when (this.type) { - "turn" -> direction.angle = OutAngle.Left - "off ramp" -> { + "fork", "off ramp" -> { direction.angle = OutAngle.EasyLeft direction.out = OutType.LongerLane } From 2e67a6f09f2b502073617ae7f81f6e3d427b3213 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 00:55:34 +0200 Subject: [PATCH 31/40] fix: Shortened queue interval --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index 2974097..1eedfb5 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -145,7 +145,7 @@ class Garmin( val newCurrent = queue.poll() if (newCurrent == null) { Log.d(TAG, "run (${currentThread().name}): Sleep...") - sleep(500) + sleep(250) } else { current = newCurrent } From f600303b5ffe8d11eabce25cd7ec244f090493d1 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 00:56:01 +0200 Subject: [PATCH 32/40] fix: SanFran -> Rybnik in replay --- app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt b/app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt index 25c4e40..b25e579 100644 --- a/app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt +++ b/app/src/main/java/eu/ztsh/garmin/mock/ReplayResources.kt @@ -28,7 +28,7 @@ class ReplayResources(private val mapControl: MapControl) { listOf( ReplayRouteMapper.mapToUpdateLocation( Date().time.toDouble(), - com.mapbox.geojson.Point.fromLngLat(-122.39726512303575, 37.785128345296805) + com.mapbox.geojson.Point.fromLngLat(18.531478, 50.088155) ) ) ) From 85e4fdb85813419771665c698bd85a599a8a2929 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 01:02:15 +0200 Subject: [PATCH 33/40] fix: Model cleanup --- .../java/eu/ztsh/garmin/data/DataCache.kt | 1 - .../main/java/eu/ztsh/garmin/data/Model.kt | 40 ------------------- 2 files changed, 41 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt index 51fb02e..5235100 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt @@ -9,7 +9,6 @@ import com.mapbox.navigation.tripdata.maneuver.model.StepDistance class DataCache { - private val garminManeuver: GarminManeuver = GarminManeuver.empty() private var maneuverCache: Maneuver? = null private var locationCache: LocationMatcherResult? = null private var session: NavigationSessionState? = null diff --git a/app/src/main/java/eu/ztsh/garmin/data/Model.kt b/app/src/main/java/eu/ztsh/garmin/data/Model.kt index 9bfdbe7..0bea440 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/Model.kt @@ -130,43 +130,3 @@ class Direction( } } - -interface GarminModelItem { - - fun merge(item: GarminModelItem) -} - -class GarminManeuver : GarminModelItem { - - lateinit var lanes: Lanes - lateinit var direction: Direction - lateinit var distance: Distance - var flag: Boolean = false // WTF? - - override fun merge(item: GarminModelItem) { - val maneuver = item as GarminManeuver - this.lanes = maneuver.lanes - this.direction = maneuver.direction - this.distance = maneuver.distance - this.flag = maneuver.flag - } - - companion object { - - val empty: () -> GarminManeuver = { - val manouver = GarminManeuver() - manouver.lanes = Lanes(Arrows(setOf()), Arrows(setOf())) - manouver.direction = Direction(out = OutType.Off) - manouver.distance = Distance(0.0, Unit.Any) - manouver - } - } -} - -class GarminLocation : GarminModelItem { - - override fun merge(item: GarminModelItem) { - TODO("Not yet implemented") - } - -} From 30dc5c623ed546319cc9a1e0428e5bcd60e16757 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 01:53:32 +0200 Subject: [PATCH 34/40] feat: Speed mapping --- app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index e2228d9..5658fc8 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -103,10 +103,12 @@ class MapboxMapper { return Lanes(Arrows(lanes), Arrows(outlines)) } - fun map(locationMatcherResult: LocationMatcherResult): GarminLocation { - val state = GarminLocation() - // TODO: speed, limit, location?, bearing - return state + fun asSpeed(locationMatcherResult: LocationMatcherResult): Speed { + return Speed( + locationMatcherResult.enhancedLocation.speed.let { it?.toInt() ?: 0 }, + locationMatcherResult.speedLimitInfo.speed.let { it ?: 0 }, + ) + } } From 028f1b6082224d3056275b8c1e0ade1b04de1793 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 18:59:38 +0200 Subject: [PATCH 35/40] fix: GarminMapperTest --- .../java/eu/ztsh/garmin/data/GarminMapperTest.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt b/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt index 82be573..8ae4482 100644 --- a/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt +++ b/app/src/test/java/eu/ztsh/garmin/data/GarminMapperTest.kt @@ -9,8 +9,8 @@ class GarminMapperTest { @Test fun linesTest() { linesTest( - listOf(Lane.DotsLeft), - listOf(), + setOf(Lane.DotsLeft), + setOf(), intArrayOf(2, 128, 0), intArrayOf(16, 123, 9, 3, 0, 0, 0, 85, 21, 2, 128, 0, 141, 16, 3) ) @@ -51,14 +51,14 @@ class GarminMapperTest { intArrayOf(16, 123, 9, 3, 0, 0, 0, 85, 21, 2, 64, 2, 203, 16, 3) ) linesTest( - listOf(Lane.DotsRight), - listOf(Lane.OuterRight, Lane.MiddleRight, Lane.InnerRight, Lane.InnerLeft, Lane.MiddleLeft, Lane.OuterLeft), + setOf(Lane.DotsRight), + setOf(Lane.OuterRight, Lane.MiddleRight, Lane.InnerRight, Lane.InnerLeft, Lane.MiddleLeft, Lane.OuterLeft), intArrayOf(2, 1, 126), intArrayOf(16, 123, 9, 3, 0, 0, 0, 85, 21, 2, 1, 126, 142, 16, 3) ) linesTest( - listOf(), - listOf(), + setOf(), + setOf(), intArrayOf(2, 0, 0), intArrayOf(16, 123, 9, 3, 0, 0, 0, 85, 21, 2, 0, 0, 13, 16, 3) ) @@ -249,10 +249,10 @@ class GarminMapperTest { } private fun linesTest(outlines: Lane, arrows: Lane, expectedRaw: IntArray, expectedBoxed: IntArray) { - linesTest(listOf(outlines), listOf(arrows), expectedRaw, expectedBoxed) + linesTest(setOf(outlines), setOf(arrows), expectedRaw, expectedBoxed) } - private fun linesTest(outlines: List, arrows: List, expectedRaw: IntArray, expectedBoxed: IntArray) { + private fun linesTest(outlines: Set, arrows: Set, expectedRaw: IntArray, expectedBoxed: IntArray) { val lanes = Lanes(Arrows(arrows), Arrows(outlines)) makeAssertions(GarminMapper.map(lanes), expectedRaw, expectedBoxed) } From c329cffeb71e7a9fb49d88b5ab87cff4c2ce42cf Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Fri, 2 Aug 2024 23:28:56 +0200 Subject: [PATCH 36/40] feat: ETA --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 35 +++++++++++++++++++ .../java/eu/ztsh/garmin/data/DataCache.kt | 10 ++++++ .../java/eu/ztsh/garmin/data/MapboxMapper.kt | 8 +++++ .../main/java/eu/ztsh/garmin/data/Model.kt | 19 +++++++++- .../eu/ztsh/garmin/mapbox/RouteControl.kt | 11 +++--- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index 1eedfb5..dc70096 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -6,6 +6,7 @@ import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import android.util.Log import com.mapbox.navigation.tripdata.maneuver.model.Maneuver +import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateValue import eu.ztsh.garmin.data.Arrows import eu.ztsh.garmin.data.DataCache import eu.ztsh.garmin.data.GarminMapper @@ -25,9 +26,11 @@ class Garmin( private lateinit var connection: ConnectThread private lateinit var maneuvers: ManeuverProcessingThread + private lateinit var trips: TripProgressProcessingThread private val cache = DataCache() private val maneuversPool = Executors.newFixedThreadPool(4) + private val tripPool = Executors.newFixedThreadPool(4) fun start() { connection = ConnectThread() @@ -35,6 +38,9 @@ class Garmin( maneuvers = ManeuverProcessingThread() maneuvers.start() + + trips = TripProgressProcessingThread() + trips.start() } fun close() { @@ -43,13 +49,42 @@ class Garmin( maneuvers.interrupt() maneuvers.join(0) + trips.interrupt() + trips.join(0) + maneuversPool.shutdown() + tripPool.shutdown() } fun process(maneuver: Maneuver) { maneuversPool.submit{maneuvers.enqueue(maneuver)} } + fun process(tripProgressUpdateValue: TripProgressUpdateValue) { + maneuversPool.submit{trips.enqueue(tripProgressUpdateValue)} + } + + private inner class TripProgressProcessingThread : ProcessingThread() { + + override fun mapAndSend(maybeItem: TripProgressUpdateValue?): TripProgressUpdateValue? { + if (maybeItem != null) { + // it is much simplier to parse and compare model object + val value = MapboxMapper.asEta(maybeItem) + if (cache.hasChanged(value)) { + // TODO: traffic + send(GarminMapper.setTime(value.hours, value.minutes)) + cache.update(value) + } + } + return null + } + + override fun updateCache(item: TripProgressUpdateValue) { + // won't be used + } + + } + private inner class ManeuverProcessingThread : ProcessingThread() { override fun mapAndSend(maybeItem: Maneuver?): Maneuver? { diff --git a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt index 5235100..61bedfb 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt @@ -12,6 +12,7 @@ class DataCache { private var maneuverCache: Maneuver? = null private var locationCache: LocationMatcherResult? = null private var session: NavigationSessionState? = null + private var eta: Arrival? = null // maneuver fun hasChanged(guidance: Lane?): Boolean { @@ -30,6 +31,15 @@ class DataCache { maneuverCache = maneuver } + // eta + fun hasChanged(eta: Arrival): Boolean { + return this.eta.let { it == null || it != eta } + } + + fun update(eta: Arrival) { + this.eta = eta + } + // location fun hasChanged(locationMatcherResult: LocationMatcherResult): Boolean { return locationCache == null || locationCache!! != locationMatcherResult diff --git a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt index 5658fc8..445cb40 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/MapboxMapper.kt @@ -2,6 +2,7 @@ package eu.ztsh.garmin.data import com.mapbox.navigation.tripdata.maneuver.model.Maneuver import com.mapbox.navigation.core.trip.session.LocationMatcherResult +import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateValue class MapboxMapper { @@ -111,6 +112,13 @@ class MapboxMapper { } + fun asEta(trip: TripProgressUpdateValue): Arrival { + val eta = trip.formatter + .getEstimatedTimeToArrival(trip.estimatedTimeToArrival) + .toString().split(":") + return Arrival(eta[0].toInt(), eta[1].toInt()) + } + } } diff --git a/app/src/main/java/eu/ztsh/garmin/data/Model.kt b/app/src/main/java/eu/ztsh/garmin/data/Model.kt index 0bea440..5b76a83 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/Model.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/Model.kt @@ -101,7 +101,24 @@ class Distance(val distance: Double, val unit: Unit) { class Speed(val speed: Int, val limit: Int) -class Arrival(val hours: Int, val minutes: Int) +class Arrival(val hours: Int, val minutes: Int) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Arrival + + if (hours != other.hours) return false + if (minutes != other.minutes) return false + + return true + } + + override fun hashCode(): Int { + return 61 * hours + minutes + } +} class Direction( var angle: OutAngle = OutAngle.AsDirection, diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt index 37bd0d6..419fdfb 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt @@ -91,7 +91,7 @@ class RouteControl(private val mapControl: MapControl, ui: UI, private val conte PercentDistanceTraveledFormatter() ) .estimatedTimeToArrivalFormatter( - EstimatedTimeToArrivalFormatter(context, TimeFormat.NONE_SPECIFIED) + EstimatedTimeToArrivalFormatter(context, TimeFormat.TWENTY_FOUR_HOURS) ) .build() ) @@ -172,10 +172,11 @@ class RouteControl(private val mapControl: MapControl, ui: UI, private val conte } ) - // update bottom trip progress summary - mapControl.ui.tripProgressView.render( - tripProgressApi.getTripProgress(routeProgress) - ) + // update bottom trip progress summary and send to HUD + tripProgressApi.getTripProgress(routeProgress).let { + mapControl.ui.tripProgressView.render(it) + Garmin.instance.process(it) + } } /** From a015ab2c5f3851ce241fa072e382709195e5b481 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 3 Aug 2024 00:20:16 +0200 Subject: [PATCH 37/40] feat: speed --- app/src/main/java/eu/ztsh/garmin/Garmin.kt | 38 ++++++++++++++++--- .../java/eu/ztsh/garmin/data/DataCache.kt | 4 +- .../eu/ztsh/garmin/mapbox/LocationObserver.kt | 3 ++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index dc70096..aaf8f0c 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -5,6 +5,7 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import android.util.Log +import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.tripdata.maneuver.model.Maneuver import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateValue import eu.ztsh.garmin.data.Arrows @@ -27,10 +28,10 @@ class Garmin( private lateinit var connection: ConnectThread private lateinit var maneuvers: ManeuverProcessingThread private lateinit var trips: TripProgressProcessingThread + private lateinit var locations: LocationMatcherProcessingThread private val cache = DataCache() - private val maneuversPool = Executors.newFixedThreadPool(4) - private val tripPool = Executors.newFixedThreadPool(4) + private val processingPool = Executors.newFixedThreadPool(8) fun start() { connection = ConnectThread() @@ -41,6 +42,9 @@ class Garmin( trips = TripProgressProcessingThread() trips.start() + + locations = LocationMatcherProcessingThread() + locations.start() } fun close() { @@ -52,16 +56,38 @@ class Garmin( trips.interrupt() trips.join(0) - maneuversPool.shutdown() - tripPool.shutdown() + locations.interrupt() + locations.join(0) + + processingPool.shutdown() } fun process(maneuver: Maneuver) { - maneuversPool.submit{maneuvers.enqueue(maneuver)} + processingPool.submit{maneuvers.enqueue(maneuver)} } fun process(tripProgressUpdateValue: TripProgressUpdateValue) { - maneuversPool.submit{trips.enqueue(tripProgressUpdateValue)} + processingPool.submit{trips.enqueue(tripProgressUpdateValue)} + } + + fun process(locationMatcherResult: LocationMatcherResult) { + processingPool.submit{locations.enqueue(locationMatcherResult)} + } + + private inner class LocationMatcherProcessingThread: ProcessingThread() { + + override fun mapAndSend(maybeItem: LocationMatcherResult?): LocationMatcherResult? { + if (maybeItem != null && cache.hasChanged(maybeItem)) { + send(GarminMapper.map(MapboxMapper.asSpeed(maybeItem))) + return maybeItem + } + return null + } + + override fun updateCache(item: LocationMatcherResult) { + cache.update(item) + } + } private inner class TripProgressProcessingThread : ProcessingThread() { diff --git a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt index 61bedfb..3e61688 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/DataCache.kt @@ -42,7 +42,9 @@ class DataCache { // location fun hasChanged(locationMatcherResult: LocationMatcherResult): Boolean { - return locationCache == null || locationCache!! != locationMatcherResult + return locationCache.let { it == null + || it.enhancedLocation.speed != locationMatcherResult.enhancedLocation.speed + || it.speedLimitInfo.speed != locationMatcherResult.speedLimitInfo.speed } } fun update(locationMatcherResult: LocationMatcherResult) { diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt index 998eebe..8dfefde 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/LocationObserver.kt @@ -4,6 +4,7 @@ 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 +import eu.ztsh.garmin.Garmin class LocationObserver(private val mapControl: MapControl) : LocationObserver { @@ -31,6 +32,8 @@ class LocationObserver(private val mapControl: MapControl) : LocationObserver { mapControl.viewportDataSource.onLocationChanged(enhancedLocation) mapControl.viewportDataSource.evaluate() + Garmin.instance.process(locationMatcherResult) + // 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) { From 711c79dd6158dc33576b25c9af877b4f0a783c79 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 3 Aug 2024 00:22:00 +0200 Subject: [PATCH 38/40] fix: speed warning --- app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt index 7367a25..06fcecb 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt @@ -17,7 +17,7 @@ class GarminMapper { } fun map(speed: Speed): IntArray { - return setSpeed(speed.speed, speed.limit, speed.speed > speed.limit) + return setSpeed(speed.speed, speed.limit, speed.limit > 0 && speed.speed > speed.limit) } fun setTime(hours: Int, minutes: Int, traffic: Boolean = false, flag: Boolean = false): IntArray { From 36644a1af20e8f1d41b53669e6ff49d1b509000a Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 3 Aug 2024 00:32:41 +0200 Subject: [PATCH 39/40] fix: trailing zeros, speed warning --- .../main/java/eu/ztsh/garmin/data/GarminMapper.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt index 06fcecb..b398381 100644 --- a/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt +++ b/app/src/main/java/eu/ztsh/garmin/data/GarminMapper.kt @@ -17,12 +17,13 @@ class GarminMapper { } fun map(speed: Speed): IntArray { - return setSpeed(speed.speed, speed.limit, speed.limit > 0 && speed.speed > speed.limit) + return setSpeed(speed.speed, speed.limit) } fun setTime(hours: Int, minutes: Int, traffic: Boolean = false, flag: Boolean = false): IntArray { val trafficChar = asChar(traffic) val flagChar = asChar(flag) + // TODO: needed? Displaying ETA, not remaining return if (hours > 99) { intArrayOf( 0x05, @@ -40,7 +41,7 @@ class GarminMapper { 0x05, trafficChar, asDigit(hours / 10), - asDigit(hours), + asDigit(if (hours == 0) 10 else hours), 0xff, asDigit(minutes / 10), asDigit(minutes), @@ -97,23 +98,21 @@ class GarminMapper { private fun setSpeed( speed: Int, limit: Int = 0, - limitWarning: Boolean = false, acc: Boolean = false ): IntArray { // TODO: car connection val accChar = asChar(acc) - val limitWarningChar = asChar(limitWarning) return if (limit > 0) { intArrayOf( 0x06, asDigit(speed / 100), asDigit(speed / 10), - asDigit(speed), + asDigit(if (speed == 0) 10 else speed), 0xff, asDigit(limit / 100), asDigit(limit / 10), asDigit(limit), - limitWarningChar, + asChar(speed > limit), accChar ) } else { @@ -126,7 +125,7 @@ class GarminMapper { asDigit(speed / 100), asDigit(speed / 10), asDigit(speed), - limitWarningChar, + asChar(false), accChar ) } From 3a67ed31b24d9b3219506e6f1fdcd3acaa17e691 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 3 Aug 2024 01:29:39 +0200 Subject: [PATCH 40/40] chore: SonarLint fixes --- app/src/main/java/eu/ztsh/garmin/MainActivity.kt | 2 +- app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt | 1 - python/test.py | 6 +----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index b7ae97f..bb84358 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -93,7 +93,7 @@ class MainActivity : AppCompatActivity() { val context = this pairedDevices?.firstOrNull { device -> Log.d(TAG, device.name) - device.name.equals("GARMIN HUD") + device.name == "GARMIN HUD" }?.apply { Garmin.instance = Garmin(context, this, bluetoothAdapter) Garmin.instance.start() diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt index 419fdfb..a59a899 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/RouteControl.kt @@ -168,7 +168,6 @@ class RouteControl(private val mapControl: MapControl, ui: UI, private val conte mapControl.ui.maneuverView.visibility = View.VISIBLE mapControl.ui.maneuverView.renderManeuvers(maneuvers) Garmin.instance.process(it[0]) - // dump(maneuvers.value[0]) } ) diff --git a/python/test.py b/python/test.py index e51a2ae..c21a9e0 100644 --- a/python/test.py +++ b/python/test.py @@ -1,4 +1,4 @@ -from main import * +from main import Controller, Lane, OutType, OutAngle, Unit from time import sleep interval = 0.2 @@ -74,7 +74,6 @@ def distance(controller: Controller): controller.set_distance(999, Unit.Metres) controller.set_distance(999, Unit.Foot) controller.set_distance(999, Unit.Miles) - pass def speed(controller: Controller): @@ -84,7 +83,6 @@ def speed(controller: Controller): controller.set_speed(50, 100) controller.set_speed(150, 100) controller.set_speed(50, 100, True) - pass def time(controller: Controller): @@ -92,7 +90,6 @@ def time(controller: Controller): controller.set_time(22, 22) controller.set_time(22, 22, traffic=True) controller.set_time(22, 22, flag=True) - pass def control(controller: Controller): @@ -111,7 +108,6 @@ def compass(controller: Controller): controller.set_compass(247.5) controller.set_compass(292.5) controller.set_compass(337.5) - pass def route(controller: Controller):