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>

View File

@@ -0,0 +1,255 @@
{
"trip": {
"locations": [
{
"type": "break",
"lat": 48.185749,
"lon": 11.579374,
"side_of_street": "right",
"original_index": 0
},
{
"type": "break",
"lat": 48.116481,
"lon": 11.594322,
"street": "Hohenwaldeckstr. 27",
"side_of_street": "left",
"original_index": 1
}
],
"legs": [
{
"maneuvers": [
{
"type": 2,
"instruction": "Auf Vogelhartstraße Richtung Westen fahren.",
"verbal_succinct_transition_instruction": "Richtung Westen fahren. Dann Rechts auf Silcherstraße abbiegen.",
"verbal_pre_transition_instruction": "Auf Vogelhartstraße Richtung Westen fahren. Dann Rechts auf Silcherstraße abbiegen.",
"verbal_post_transition_instruction": "70 Meter weiter der Route folgen.",
"street_names": [
"Vogelhartstraße"
],
"bearing_after": 273,
"time": 16.965,
"length": 0.07,
"cost": 34.428,
"begin_shape_index": 0,
"end_shape_index": 6,
"verbal_multi_cue": true,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Silcherstraße abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Silcherstraße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Silcherstraße abbiegen.",
"verbal_post_transition_instruction": "200 Meter weiter der Route folgen.",
"street_names": [
"Silcherstraße"
],
"bearing_before": 273,
"bearing_after": 5,
"time": 43.25,
"length": 0.156,
"cost": 89.306,
"begin_shape_index": 6,
"end_shape_index": 13,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Schmalkaldener Straße abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Schmalkaldener Straße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Schmalkaldener Straße abbiegen.",
"verbal_post_transition_instruction": "400 Meter weiter der Route folgen.",
"street_names": [
"Schmalkaldener Straße"
],
"bearing_before": 2,
"bearing_after": 93,
"time": 108.947,
"length": 0.43,
"cost": 217.43,
"begin_shape_index": 13,
"end_shape_index": 29,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Ingolstädter Straße/B 13 abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Ingolstädter Straße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Ingolstädter Straße, B 13 abbiegen.",
"verbal_post_transition_instruction": "einen Kilometer weiter der Route folgen.",
"street_names": [
"B 13"
],
"begin_street_names": [
"Ingolstädter Straße",
"B 13"
],
"bearing_before": 88,
"bearing_after": 178,
"time": 147.528,
"length": 1.064,
"cost": 230.646,
"begin_shape_index": 29,
"end_shape_index": 65,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 19,
"instruction": "Auf die Auffahrt nach links abbiegen.",
"verbal_transition_alert_instruction": "Auf die Auffahrt nach links abbiegen.",
"verbal_pre_transition_instruction": "Auf die Auffahrt nach links abbiegen.",
"street_names": [
"Schenkendorfstraße"
],
"bearing_before": 188,
"bearing_after": 98,
"time": 61.597,
"length": 0.374,
"cost": 117.338,
"begin_shape_index": 65,
"end_shape_index": 84,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 24,
"instruction": "Links halten auf B 2R.",
"verbal_transition_alert_instruction": "Links halten auf B 2R.",
"verbal_pre_transition_instruction": "Links halten auf B 2R.",
"verbal_post_transition_instruction": "6 Kilometer weiter der Route folgen.",
"street_names": [
"B 2R"
],
"bearing_before": 117,
"bearing_after": 118,
"time": 509.658,
"length": 6.37,
"cost": 580.602,
"begin_shape_index": 84,
"end_shape_index": 240,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 20,
"instruction": "An der Ausfahrt rechts abfahren.",
"verbal_transition_alert_instruction": "An der Ausfahrt rechts abfahren.",
"verbal_pre_transition_instruction": "An der Ausfahrt rechts abfahren.",
"verbal_post_transition_instruction": "einen Kilometer weiter der Route folgen.",
"street_names": [
"Ampfingstraße"
],
"bearing_before": 191,
"bearing_after": 206,
"time": 133.661,
"length": 1.031,
"cost": 226.661,
"begin_shape_index": 240,
"end_shape_index": 280,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Anzinger Straße abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Anzinger Straße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Anzinger Straße abbiegen.",
"verbal_post_transition_instruction": "1.5 Kilometer weiter der Route folgen.",
"street_names": [
"Anzinger Straße"
],
"bearing_before": 182,
"bearing_after": 277,
"time": 211.637,
"length": 1.444,
"cost": 450.654,
"begin_shape_index": 280,
"end_shape_index": 334,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 15,
"instruction": "Links auf Hohenwaldeckstraße abbiegen.",
"verbal_transition_alert_instruction": "Links auf Hohenwaldeckstraße abbiegen.",
"verbal_succinct_transition_instruction": "Links abbiegen.",
"verbal_pre_transition_instruction": "Links auf Hohenwaldeckstraße abbiegen.",
"verbal_post_transition_instruction": "200 Meter weiter der Route folgen.",
"street_names": [
"Hohenwaldeckstraße"
],
"bearing_before": 249,
"bearing_after": 170,
"time": 45.365,
"length": 0.183,
"cost": 84.344,
"begin_shape_index": 334,
"end_shape_index": 342,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 6,
"instruction": "Hohenwaldeckstr. 27 befindet sich auf der linken Seite.",
"verbal_transition_alert_instruction": "Hohenwaldeckstr. 27 befindet sich auf der linken Seite.",
"verbal_pre_transition_instruction": "Hohenwaldeckstr. 27 befindet sich auf der linken Seite.",
"bearing_before": 184,
"time": 0,
"length": 0,
"cost": 0,
"begin_shape_index": 342,
"end_shape_index": 342,
"travel_mode": "drive",
"travel_type": "car"
}
],
"summary": {
"level_changes": [
[220, -1]
],
"has_time_restrictions": false,
"has_toll": false,
"has_highway": false,
"has_ferry": false,
"min_lat": 48.116486,
"min_lon": 11.578422,
"max_lat": 48.186957,
"max_lon": 11.616382,
"time": 1278.611,
"length": 11.123,
"cost": 2031.412
},
"shape": "mk_|zA_}vaUA^MzKKhKMrKMrLEbEeLs@kGa@yV}AmIa@cJ]g@AgQc@TgTn@ak@\\}Y`@u~@NiZRss@Ekc@AcGAwE?iB@yN@mH@_L?sAOiVEiGrISbH[|s@kC`KY~Qw@dk@mBdBErH]bIa@pNk@pAGxgA}ErQw@f`@eB`AAhEOjDO~Kg@bh@cCpTcAtEUlBKtFMbk@cBpt@eDfScAlH]hHY`HTdATbBl@rAd@|Bz@xBr@|F`AzD\\l@mHHsCAeDcAkImBiLs@}Ii@wOh@a]vAu[bB{VjCmXjCuUtDoU~A}JjBmIvDmOvDgOfCiJdB}HvAsG|FwSzGaV`IgWdC_K\\cG~Pii@pUcr@dYaz@lEkMdDsPpMm]|Tqj@tQwc@jQsb@nVwm@vEmL`k@suAxHyPzFaKrBaD|AmBxCeDpC}BvDmCnEyBnDuAzFyAhDWdDW|D?~CPjFj@lHrB|QzI~O~Jb]lS`ZbR~NnIhCdBdDtBb`Azk@fhAdr@vN~I~l@|]vr@vVb]jElZw@xG{@jEw@`KiCfLiFrJ_GnPaNjJaJzJoNxMgUtU}g@d]_w@f_@ev@tO_YhRmYbHwIxG_I|NwN~AyAdTiP~b@iZ~J}GxScQ`JoIfGwGtEwFnCqDbEaG~CcFhHgMrGcNxHmR|FaQnCwIpAeEdCiIje@}}AnQil@pGySjCyIvSor@nJ{ZpHwVbQyk@zIkXlHkTrOcc@bPic@rUyk@vKoVnMaWfWed@rT_`@jQcZhBwCzCsEtCcElC}CzCiDrCiCnH{GzLwJlGwDfH_ElGeDhHwClHyBrG{BdK_CzMsBzJgAbIq@jI?nYGbSTfGDvEDnGRfWx@|i@lEvpBbUdRrBpLfAl_@jDr|Ghm@hu@jGrWrBpd@fAxG[raBgUl[{HhM}DzOeGdXiLpCuAzwAij@lEcAbF_AnEc@|DDzE[dAGrIh@|APfe@lLbc@lLpWbIbdAnYnKlBf]|Tzi@rZbl@|ZtSjJpOhG~HvB`AVvBl@hBf@vBl@`AXrJrCtZjHhRvCrKpAjAN|Gb@hLr@dLp@xYbB`CPlDNxBLlOv@n{@jE~F\\lDP|Ov@lSn@rGNrGPlHAxICnJCvJBhFBrQL~E?dC?zFJ[xIcB|_@}Add@kAvi@i@zg@AtGEd_@f@lq@lB`|@jApd@tA`l@XpFn@lHf@bFnAdMjA`HnDpQfEfSvElPfBdOvCrWjP|xANrA|@bH\\pC\\lCNnA~Hhn@dB|MnAtJrAlK`AzH~@hHvBvPj@pEl@zFx@zH^hD~BlQdEbZhF`_@rAbJ|AjK~AtKzBrNt@nFv@lFtB`OdCfQbMb_AlDnUrDvTvF|ZzIxh@jDm@zHgBhF{@lC[`L]jMr@bNj@~_@bB"
}
],
"summary": {
"has_time_restrictions": false,
"has_toll": false,
"has_highway": false,
"has_ferry": false,
"min_lat": 48.116486,
"min_lon": 11.578422,
"max_lat": 48.186957,
"max_lon": 11.616382,
"time": 1278.611,
"length": 11.123,
"cost": 2031.412
},
"status_message": "Found route between points",
"status": 0,
"units": "kilometers",
"language": "de-DE"
},
"id": "my_work_route"
}

View File

@@ -1,6 +1,12 @@
package com.kouros.navigation.car
import com.kouros.navigation.car.navigation.RoutingModel
import android.location.Location
import android.location.LocationManager
import com.kouros.navigation.data.Constants.home2Location
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -14,5 +20,18 @@ class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
val model = RouteModel()
val repo = NavigationRepository()
val viewModel = ViewModel(repo)
val fromLocation = Location(LocationManager.GPS_PROVIDER)
fromLocation.latitude = homeLocation.latitude
fromLocation.longitude = homeLocation.longitude
val toLocation = Location(LocationManager.GPS_PROVIDER)
toLocation.latitude = home2Location.latitude
toLocation.longitude = home2Location.longitude
val route = repo.getRoute(fromLocation, toLocation)
model.startNavigation(route)
println(route)
}
}
}

View File

@@ -21,7 +21,6 @@ import android.location.LocationManager
import android.net.Uri
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.objectbox.annotation.Index
import kotlinx.serialization.Serializable
data class Category(
@@ -52,6 +51,12 @@ data class ContactData(
val avatar: Uri?
)
data class StepData (
var instruction: String,
var leftDistance: Double,
var bearing: Double
)
//val places = mutableListOf<Place>()
/* Place(

View File

@@ -33,7 +33,9 @@ class Contacts(private var context: Context) {
while (moveToNext()) {
val contactId = getLong(getColumnIndex(ContactsContract.Data.CONTACT_ID))
val name = getString(getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
if (name.contains("Jola") || name.contains("Dominic")
if (name.contains("Jola")
|| name.contains("Dominic")
|| name.contains("Martha")
|| name.contains("Μεντή")
|| name.contains("David")) {
val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE))

View File

@@ -3,6 +3,7 @@ package com.kouros.navigation.model
import android.location.Location
import android.location.LocationManager
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.Utils.decodePolyline
import org.json.JSONArray
@@ -10,23 +11,19 @@ import org.json.JSONObject
import kotlin.math.roundToInt
open class RouteModel {
// Source - https://stackoverflow.com/a
// Posted by Dmitrii Bychkov
// Retrieved 2025-11-14, License - CC BY-SA 4.0
open class RouteModel () {
var polylineLocations: List<List<Double>> = emptyList()
lateinit var maneuvers: JSONArray
lateinit var locations: JSONArray
lateinit var summary: JSONObject
lateinit var destination: Place
var navigating = false
var arrived = false
var maneuverIndex = 0
var maneuverType = 0
var currentIndex = 0
var distanceToStepEnd = 0F
@@ -38,13 +35,23 @@ open class RouteModel {
var distanceToRoute = 0F
var geoJson = ""
var route = ""
private fun decodeValhallaRoute(route: String) {
if (route.isEmpty() || route == "[]") {
data class Builder(
var route: String? = null,
var fromLocation: Location? = null,
var toLocation: Location? = null) {
fun route(route: String) = apply { this.route = route }
fun fromLocation(fromLocation: Location) = apply { this.fromLocation = fromLocation }
fun toLocation(toLocation: Location) = apply { this.toLocation = toLocation }
//fun build() = RouteModel(route!!, fromLocation!!, toLocation!!)
}
private fun decodeValhallaRoute(valhallaRoute: String) {
if (valhallaRoute.isEmpty() || valhallaRoute == "[]") {
return;
}
val jObject = JSONObject(route)
val jObject = JSONObject(valhallaRoute)
val trip = jObject.getJSONObject("trip")
locations = trip.getJSONArray("locations")
val legs = trip.getJSONArray("legs")
@@ -54,13 +61,13 @@ open class RouteModel {
polylineLocations = decodePolyline(shape)
}
fun createNavigationRoute(route: String) {
decodeValhallaRoute(route)
fun startNavigation(valhallaRoute: String) {
decodeValhallaRoute(valhallaRoute)
for (i in 0..<maneuvers.length()) {
val maneuver = (maneuvers[i] as JSONObject)
routingManeuvers.add(maneuver)
}
geoJson = createGeoJson(polylineLocations)
route = createGeoJson(polylineLocations)
navigating = true
}
@@ -88,6 +95,31 @@ open class RouteModel {
distanceToRoute = nearestDistance
}
fun currentStep(): StepData {
var bearing = 0
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
var text = ""
if (maneuver.optJSONArray("street_names") != null) {
text = maneuver.getJSONArray("street_names").get(0) as String
}
if (maneuver.has("bearing_after")) {
bearing = maneuver.getInt("bearing_after")
}
val distanceStepLeft = leftStepDistance() * 1000
when (distanceStepLeft) {
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
}
}
}
}
return StepData(text, distanceStepLeft, bearing.toDouble())
}
/** Calculates the index in a maneuver. */
private fun calculateCurrentIndex(
@@ -159,8 +191,6 @@ open class RouteModel {
val maneuver = routingManeuvers[maneuverIndex]
var leftDistance = maneuver.getDouble("length")
if (endIndex > 0) {
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
//leftDistance = leftDistance * percent / 100
leftDistance = (distanceToStepEnd / 1000).toDouble()
}
return leftDistance
@@ -190,11 +220,11 @@ open class RouteModel {
return arrived
}
fun stopNavigating() {
fun stopNavigation() {
navigating = false
polylineLocations = mutableListOf()
routingManeuvers = mutableListOf()
geoJson = ""
route = ""
maneuverIndex = 0
currentIndex = 0
distanceToStepEnd = 0F

View File

@@ -105,4 +105,20 @@ class NavigationUtils() {
//return LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng))
}
}
fun calculateZoom(speed: Double?): Double {
if (speed == null) {
return 18.0
}
val zoom = when (speed.toInt()) {
in 0..10 -> 17.0
in 11..20 -> 17.0
in 21..30 -> 17.0
in 31..40 -> 16.0
in 41..50 -> 15.0
in 51..60 -> 14.0
else -> 11
}
return zoom.toDouble()
}
}