LocationPuck

This commit is contained in:
Dimitris
2025-11-15 12:38:40 +01:00
parent d63747e811
commit 1773ec2244
15 changed files with 568 additions and 213 deletions

View File

@@ -12,7 +12,6 @@ import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.ScreenManager
import androidx.car.app.Session
import androidx.car.app.navigation.model.Maneuver
import androidx.core.location.LocationListenerCompat
import androidx.core.net.toUri
import androidx.lifecycle.DefaultLifecycleObserver
@@ -25,19 +24,18 @@ import com.kouros.navigation.car.screen.RequestPermissionScreen
import com.kouros.navigation.car.screen.SearchScreen
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.model.RouteModel
class NavigationSession : Session() {
val uriScheme = "samples";
val uriHost = "navigation";
lateinit var route: RouteCarModel;
lateinit var routeModel: RouteCarModel;
lateinit var navigationScreen: NavigationScreen
lateinit var surfaceRenderer: SurfaceRenderer
var locationIndex = 0
val test = true
val test = false
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
updateLocation(location)
@@ -75,12 +73,12 @@ class NavigationSession : Session() {
}
override fun onCreateScreen(intent: Intent): Screen {
route = RouteCarModel()
routeModel = RouteCarModel()
ObjectBox.init(carContext);
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, route)
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
navigationScreen = NavigationScreen(carContext, surfaceRenderer, route)
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel)
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED
@@ -152,7 +150,7 @@ class NavigationSession : Session() {
updateLocation(location)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
/* minTimeMs= */ 100,
/* minTimeMs= */ 1000,
/* minDistanceM= */ 0f,
mLocationListener
)
@@ -169,14 +167,14 @@ class NavigationSession : Session() {
}
fun test(location: Location?) {
if (route.isNavigating() && locationIndex < route.polylineLocations.size) {
val loc = route.polylineLocations[locationIndex]
if (routeModel.isNavigating() && locationIndex < routeModel.polylineLocations.size) {
val loc = routeModel.polylineLocations[locationIndex]
val curLocation = Location(LocationManager.GPS_PROVIDER)
curLocation.longitude = loc[0]
curLocation.latitude = loc[1]
update(curLocation)
locationIndex += 1
if (locationIndex > route.polylineLocations.size) {
if (locationIndex > routeModel.polylineLocations.size) {
val locationManager =
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.removeUpdates(mLocationListener)
@@ -188,12 +186,12 @@ class NavigationSession : Session() {
fun update(location: Location) {
surfaceRenderer.updateLocation(location)
if (route.isNavigating()) {
route.findManeuver(location)
// if (routingModel.distanceToRoute > 50) {
// routingModel.stopNavigating()
if (routeModel.isNavigating()) {
routeModel.findManeuver(location)
// if (routeModel.distanceToRoute > 50) {
// routeModel.stopNavigation()
// locationIndex = 0
// surfaceRenderer.setGeoJson()
// surfaceRenderer.setRouteData()
// navigationScreen.reRoute()
// }
navigationScreen.updateTrip()

View File

@@ -15,6 +15,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.graphics.Color
@@ -27,11 +28,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils
import kotlinx.coroutines.flow.onSubscription
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.CircleLayer
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationPuck
@@ -45,13 +46,13 @@ import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
import androidx.compose.runtime.collectAsState
class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
private var routeModel: RouteCarModel) : DefaultLifecycleObserver {
var mVisibleArea: Rect? = null
var mStableArea: Rect? = null
class SurfaceRenderer(
carContext: CarContext, lifecycle: Lifecycle,
private var routeModel: RouteCarModel
) : DefaultLifecycleObserver {
private val mCarContext: CarContext = carContext
var lastLocation = Location(LocationManager.GPS_PROVIDER)
val cameraPosition = MutableLiveData(
@@ -60,15 +61,15 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
target = Position(latitude = 48.1857475, longitude = 11.5793627)
)
)
val geojson = MutableLiveData("")
val routeData = MutableLiveData("")
val previewGeojson = MutableLiveData("")
val previewRouteData = MutableLiveData("")
var preview = false
lateinit var mapView: ComposeView
val tilt = 60.0
val tilt = 55.0
val padding = PaddingValues(start = 150.dp, top = 250.dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 300.dp)
@@ -114,15 +115,14 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
presentation.show()
}
}
override fun onVisibleAreaChanged(visibleArea: Rect) {
synchronized(this@SurfaceRenderer) {
mVisibleArea = visibleArea
}
}
override fun onStableAreaChanged(stableArea: Rect) {
synchronized(this@SurfaceRenderer) {
mStableArea = stableArea
}
}
@@ -155,10 +155,9 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
@Composable
fun Map() {
val position: CameraPosition? by cameraPosition.observeAsState()
val geoJsonData: String? by geojson.observeAsState()
val previewGeoJsonData: String? by previewGeojson.observeAsState()
val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState()
val cameraState =
rememberCameraState(
firstPosition =
@@ -172,15 +171,17 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
padding = getPaddingValues()
)
)
val variant = if (isSystemInDarkTheme()) "dark" else "light"
val locationProvider = rememberDefaultLocationProvider()
val locationState = rememberUserLocationState(locationProvider)
MaplibreMap(
cameraState = cameraState,
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
baseStyle = BaseStyle.Uri("https://kouros-online.de/liberty"),
) {
getBaseSource(id = "openmaptiles")?.let { tiles ->
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
RouteLayer(route, previewRoute)
}
LocationPuck(
idPrefix = "user-location",
locationState = locationState,
@@ -188,18 +189,6 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
accuracyThreshold = 10f,
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
)
getBaseSource(id = "openmaptiles")?.let { tiles ->
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
val coordinates = mutableListOf<List<Double>>()
coordinates.add(listOf(position!!.target.longitude, position!!.target.latitude))
coordinates.add(
listOf(
position!!.target.longitude + 0.00001,
position!!.target.latitude + 0.00001
)
)
RouteLayer(geoJsonData, previewGeoJsonData)
}
}
LaunchedEffect(position) {
@@ -217,26 +206,26 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
}
@Composable
fun RouteLayer(geoJsonData: String?, previewGeoJsonData: String?) {
if (geoJsonData!!.isNotEmpty()) {
fun RouteLayer(routeData: String?, previewRoute: String?) {
if (routeData!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(geoJsonData!!))
rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
LineLayer(
id = "routes-casing",
source = routes,
color = const(Color.White),
width = const(12.dp),
width = const(16.dp),
)
LineLayer(
id = "routes",
source = routes,
color = const(Color.Blue),
width = const(10.dp),
width = const(14.dp),
)
}
if (previewGeoJsonData!!.isNotEmpty()) {
if (previewRoute!!.isNotEmpty()) {
val routes =
rememberGeoJsonSource(GeoJsonData.JsonString(previewGeoJsonData!!))
rememberGeoJsonSource(GeoJsonData.JsonString(previewRoute))
LineLayer(
id = "routes-casing-pre",
source = routes,
@@ -251,6 +240,7 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
)
}
}
override fun onCreate(owner: LifecycleOwner) {
Log.i(TAG, "SurfaceRenderer created")
mCarContext.getCarService(AppManager::class.java)
@@ -276,16 +266,22 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
fun updateLocation(location: Location) {
synchronized(this) {
var bearing = cameraPosition.value!!.bearing
if (lastLocation.latitude != location.latitude) {
if (lastLocation.distanceTo(location) > 10) {
bearing = lastLocation.bearingTo(location).toDouble()
var bearing: Double
if (routeModel.isNavigating()) {
bearing = routeModel.currentStep().bearing
} else {
bearing = cameraPosition.value!!.bearing
if (lastLocation.latitude != location.latitude) {
if (lastLocation.distanceTo(location) > 5) {
bearing = lastLocation.bearingTo(location).toDouble()
}
}
}
var zoom = NavigationUtils().calculateZoom(location.speed.toDouble())
if (preview) {
bearing = 0.0
zoom = 11.0
}
val zoom = calculateZoom(location)
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
@@ -298,30 +294,13 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
}
}
private fun calculateZoom(location: Location): Double {
if (preview) {
return 11.0
}
//var zoom = cameraPosition.value!!.zoom
val zoom = when (location.speed.toInt()) {
in 0..10 -> 17.0
in 11..20 -> 16.0
in 21..30 -> 15.0
in 31..40 -> 14.0
in 41..50 -> 13.0
in 51..60 -> 12.0
else -> 11
}
return zoom.toDouble()
}
fun setGeoJson() {
geojson.value = routeModel.geoJson
fun setRouteData() {
routeData.value = routeModel.route
preview = false
}
fun setPreviewGeoJson(geoRoute: String) {
previewGeojson.value = geoRoute
fun setPreviewRouteData(route: String) {
previewRouteData.value = route
preview = true
}
@@ -332,6 +311,7 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
padding
}
}
companion
object {
private const val TAG = "MapRenderer"

View File

@@ -15,7 +15,6 @@
*/
package com.kouros.navigation.car.navigation
import android.net.Uri
import android.text.SpannableString
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
@@ -43,34 +42,27 @@ class RouteCarModel() : RouteModel() {
fun currentStep(carContext: CarContext): Step {
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
val maneuverType = maneuver.getInt("type")
val distanceStepLeft = leftStepDistance() * 1000
var text = ""
val stepData = currentStep()
var routing: (Pair<Int, CarIcon>)
routing = if (hasArrived(maneuverType)) {
routingData(maneuverType, carContext)
} else {
routingData(ManeuverType.None.value, carContext)
}
when (distanceStepLeft) {
when (stepData.leftDistance) {
in 0.0..100.0 -> {
if (maneuverIndex < maneuvers.length()) {
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
if (maneuver.optJSONArray("street_names") != null) {
text = maneuver.getJSONArray("street_names").get(0) as String
}
val maneuverType = maneuver.getInt("type")
routing = routingData(maneuverType, carContext)
}
}
else -> {
if (maneuver.optJSONArray("street_names") != null) {
text = maneuver.getJSONArray("street_names").get(0) as String
}
}
}
val currentStepCueWithImage: SpannableString =
createString(text)
createString(stepData.instruction)
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
@@ -88,8 +80,8 @@ class RouteCarModel() : RouteModel() {
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
val maneuverType = maneuver.getInt("type")
val routing = routingData(maneuverType, carContext)
val distanceLeft = leftStepDistance() * 1000
var text = ""
val distanceLeft = leftStepDistance() * 1000
when (distanceLeft) {
in 0.0..100.0 -> {

View File

@@ -24,7 +24,6 @@ import androidx.lifecycle.Observer
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.car.NavigationCarAppService
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
@@ -41,8 +40,8 @@ class NavigationScreen(
val vieModel = ViewModel(NavigationRepository())
val observer = Observer<String> { route ->
if (route.isNotEmpty()) {
routeModel.createNavigationRoute(route)
surfaceRenderer.setGeoJson()
routeModel.startNavigation(route)
surfaceRenderer.setRouteData()
invalidate()
}
}
@@ -84,8 +83,8 @@ class NavigationScreen(
.build()
)
.setOnClickListener {
surfaceRenderer.geojson.postValue("")
routeModel.stopNavigating()
surfaceRenderer.routeData.postValue("")
routeModel.stopNavigation()
invalidate()
}
.build()
@@ -241,7 +240,7 @@ class NavigationScreen(
fun updateTrip() {
if (routeModel.maneuverType == Maneuver.TYPE_DESTINATION && routeModel.leftStepDistance() * 1000 < 25.0) {
routeModel.arrived = true
routeModel.stopNavigating()
routeModel.stopNavigation()
}
invalidate()
}

View File

@@ -67,8 +67,8 @@ class RoutePreviewScreen(
val navigationMessage = NavigationMessage(carContext)
val observer = Observer<String> { route ->
if (route.isNotEmpty()) {
routeModel.createNavigationRoute(route)
surfaceRenderer.setPreviewGeoJson(routeModel.geoJson)
routeModel.startNavigation(route)
surfaceRenderer.setPreviewRouteData(routeModel.route)
val geocoder = Geocoder(carContext)
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
for (address in it) {

View File

@@ -47,26 +47,17 @@
<string name="no_action_title" msgid="1452124604210014010">"Nein"</string>
<string name="disable_all_rows" msgid="3003225080532928046">"Alle Zeilen deaktivieren"</string>
<string name="enable_all_rows" msgid="7274285275711872091">"Alle Zeilen aktivieren"</string>
<string name="bug_reported_toast_msg" msgid="2487119172744644317">"Fehler wurde gemeldet!"</string>
<string name="zoomed_in_toast_msg" msgid="8915301497303842649">"Herangezoomt"</string>
<string name="zoomed_out_toast_msg" msgid="6260981223227212493">"Herausgezoomt"</string>
<string name="triggered_toast_msg" msgid="3396166539208366382">"Ausgelöst"</string>
<string name="primary_toast_msg" msgid="7153771322662005447">"Primäre Schaltfläche gedrückt"</string>
<string name="search_toast_msg" msgid="7826530065407699347">"Schaltfläche „Suchen“ gedrückt"</string>
<string name="options_toast_msg" msgid="2146223786877557730">"Optionsschaltfläche gedrückt"</string>
<string name="favorite_toast_msg" msgid="522064494016370117">"Favorit!"</string>
<string name="not_favorite_toast_msg" msgid="6831181108681007428">"Kein Favorit!"</string>
<string name="nav_requested_toast_msg" msgid="6696525973145493908">"Navigation angefragt"</string>
<string name="selected_route_toast_msg" msgid="3149189677200086656">"Ausgewählte Route"</string>
<string name="visible_routes_toast_msg" msgid="7065558153736024203">"Sichtbare Routen"</string>
<string name="second_item_toast_msg" msgid="7210054709419608215">"Zweiter Eintrag angeklickt"</string>
<string name="third_item_checked_toast_msg" msgid="3022450599567347361">"Dritter Eintrag aktiviert"</string>
<string name="fifth_item_checked_toast_msg" msgid="1627599668504718594">"Fünfter Eintrag aktiviert"</string>
<string name="sixth_item_toast_msg" msgid="6117028866385793707">"Sechster Eintrag angeklickt"</string>
<string name="settings_toast_msg" msgid="7697794473002342727">"Einstellungen angeklickt"</string>
<string name="parked_toast_msg" msgid="2532422265890824446">"Aktion „Geparkt“"</string>
<string name="more_toast_msg" msgid="5938288138225509885">"„Mehr“ angeklickt"</string>
<string name="commute_toast_msg" msgid="4112684360647638688">"Schaltfläche für Arbeitsweg gedrückt"</string>
<string name="grant_location_permission_toast_msg" msgid="268046297444808010">"Standortermittlung erlauben, um aktuellen Standort anzuzeigen"</string>
<string name="sign_in_with_google_toast_msg" msgid="5720947549233124775">"Über Google anmelden beginnt hier"</string>
<string name="changes_selection_to_index_toast_msg_prefix" msgid="957766225794389167">"Auswahl auf Index geändert"</string>

View File

@@ -15,7 +15,7 @@
limitations under the License.
-->
<resources>
<string name="app_name" translatable="false">Showcase</string>
<string name="app_name" translatable="false">Navigation</string>
<!-- Action Titles -->
<string name="back_caps_action_title">BACK</string>
@@ -50,27 +50,18 @@
<string name="enable_all_rows">Enable All Rows</string>
<!-- Toast Messages -->
<string name="bug_reported_toast_msg">Bug reported!</string>
<string name="zoomed_in_toast_msg">Zoomed in</string>
<string name="zoomed_in_toast_msg">Zoomed in</string>
<string name="zoomed_out_toast_msg">Zoomed out</string>
<string name="triggered_toast_msg">Triggered</string>
<string name="primary_toast_msg">Primary button pressed</string>
<string name="search_toast_msg">Search button pressed</string>
<string name="options_toast_msg">Options button pressed</string>
<string name="favorite_toast_msg">Favorite!</string>
<string name="favorite_toast_msg">Favorite!</string>
<string name="not_favorite_toast_msg">Not a favorite!</string>
<string name="nav_requested_toast_msg">Navigation Requested</string>
<string name="selected_route_toast_msg">Selected route</string>
<string name="visible_routes_toast_msg">Visible routes</string>
<string name="second_item_toast_msg">Clicked second item</string>
<string name="third_item_checked_toast_msg">Third item checked</string>
<string name="fifth_item_checked_toast_msg">Fifth item checked</string>
<string name="sixth_item_toast_msg">Clicked sixth item</string>
<string name="settings_toast_msg">Clicked Settings</string>
<string name="settings_toast_msg">Clicked Settings</string>
<string name="parked_toast_msg">Parked action</string>
<string name="more_toast_msg">Clicked More</string>
<string name="commute_toast_msg">Commute button pressed</string>
<string name="grant_location_permission_toast_msg">Grant location Permission to see current location</string>
<string name="grant_location_permission_toast_msg">Grant location Permission to see current location</string>
<string name="sign_in_with_google_toast_msg">Sign-in with Google starts here</string>
<string name="changes_selection_to_index_toast_msg_prefix">Changed selection to index</string>
<string name="yes_action_toast_msg">Yes button pressed!</string>