This commit is contained in:
Dimitris
2025-11-17 10:11:15 +01:00
parent 1773ec2244
commit 3f3bdeb96d
12 changed files with 153 additions and 88 deletions

View File

@@ -187,7 +187,7 @@ class NavigationSession : Session() {
fun update(location: Location) {
surfaceRenderer.updateLocation(location)
if (routeModel.isNavigating()) {
routeModel.findManeuver(location)
routeModel.updateLocation(location)
// if (routeModel.distanceToRoute > 50) {
// routeModel.stopNavigation()
// locationIndex = 0

View File

@@ -11,11 +11,9 @@ import androidx.car.app.AppManager
import androidx.car.app.CarContext
import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer
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
@@ -28,8 +26,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.model.RouteModel
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
@@ -65,6 +63,7 @@ class SurfaceRenderer(
val previewRouteData = MutableLiveData("")
lateinit var centerLocation : Location
var preview = false
lateinit var mapView: ComposeView
@@ -72,7 +71,7 @@ class SurfaceRenderer(
val tilt = 55.0
val padding = PaddingValues(start = 150.dp, top = 250.dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 300.dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
@@ -107,7 +106,7 @@ class SurfaceRenderer(
this.setViewTreeLifecycleOwner(lifecycleOwner)
this.setViewTreeSavedStateRegistryOwner(lifecycleOwner)
setContent {
Map()
MapView()
}
}
presentation = Presentation(carContext, virtualDisplay.display)
@@ -154,7 +153,9 @@ class SurfaceRenderer(
}
@Composable
fun Map() {
fun MapView() {
val locationProvider = rememberDefaultLocationProvider()
val locationState = rememberUserLocationState(locationProvider)
val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState()
@@ -171,8 +172,6 @@ class SurfaceRenderer(
padding = getPaddingValues()
)
)
val locationProvider = rememberDefaultLocationProvider()
val locationState = rememberUserLocationState(locationProvider)
MaplibreMap(
cameraState = cameraState,
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
@@ -191,17 +190,32 @@ class SurfaceRenderer(
)
}
LaunchedEffect(position) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = position!!.bearing,
zoom = position!!.zoom,
target = position!!.target,
tilt = tilt,
padding = getPaddingValues()
),
duration = 3.seconds
)
if (!preview) {
LaunchedEffect(position) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = position!!.bearing,
zoom = position!!.zoom,
target = position!!.target,
tilt = tilt,
padding = getPaddingValues()
),
duration = 3.seconds
)
}
} else {
LaunchedEffect(position) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = 0.0,
zoom = 9.0,
target = Position(centerLocation.longitude, centerLocation.latitude),
tilt = 0.0,
padding = getPaddingValues()
),
duration = 3.seconds
)
}
}
}
@@ -266,41 +280,52 @@ class SurfaceRenderer(
fun updateLocation(location: Location) {
synchronized(this) {
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()
if (!preview) {
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
}
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
padding = getPaddingValues(),
target = Position(location.longitude, location.latitude),
val zoom = NavigationUtils().calculateZoom(location.speed.toDouble())
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
padding = getPaddingValues(),
target = Position(location.longitude, location.latitude),
)
)
)
lastLocation = location
lastLocation = location
} else {
val bearing = 0.0
val zoom = 11.0
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
tilt = 0.0,
target = Position(centerLocation.longitude, centerLocation.latitude)
)
)
}
}
}
fun setRouteData() {
routeData.value = routeModel.route
preview = false
}
fun setPreviewRouteData(route: String) {
previewRouteData.value = route
fun setPreviewRouteData(routeModel: RouteModel) {
previewRouteData.value = routeModel.route
centerLocation = routeModel.centerLocation
preview = true
}

View File

@@ -134,7 +134,7 @@ class RouteCarModel() : RouteModel() {
ManeuverType.Left.value -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_slight_left)
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_left)
}
ManeuverType.RampLeft.value -> {
@@ -149,7 +149,7 @@ class RouteCarModel() : RouteModel() {
ManeuverType.StayLeft.value -> {
type = Maneuver.TYPE_KEEP_LEFT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_slight_left)
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
}
}
maneuverType = type

View File

@@ -53,7 +53,7 @@ class PlaceListScreen(
}
if (category == Constants.CONTACTS) {
viewModel.contactAddress.observe(this, observerAddress)
viewModel.loadContacts(carContext)
viewModel.loadContacts(carContext, location)
}
}

View File

@@ -47,6 +47,9 @@ import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel
import java.math.BigDecimal
import java.math.RoundingMode
import kotlin.math.roundToInt
/** Creates a screen using the new [androidx.car.app.navigation.model.MapWithContentTemplate] */
class RoutePreviewScreen(
@@ -68,7 +71,7 @@ class RoutePreviewScreen(
val observer = Observer<String> { route ->
if (route.isNotEmpty()) {
routeModel.startNavigation(route)
surfaceRenderer.setPreviewRouteData(routeModel.route)
surfaceRenderer.setPreviewRouteData(routeModel)
val geocoder = Geocoder(carContext)
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
for (address in it) {
@@ -198,8 +201,9 @@ class RoutePreviewScreen(
private fun createRouteText(index: Int): CarText {
val time = routeModel.summary.getInt("time")
val length = routeModel.summary.getInt("length")
val time = routeModel.routeTime
val length = BigDecimal(routeModel.routeDistance).setScale(1, RoundingMode.HALF_EVEN)
val firstRoute = SpannableString(" \u00b7 $length km")
firstRoute.setSpan(
DurationSpan.create(time.toLong()), 0, 1,0
@@ -208,7 +212,6 @@ class RoutePreviewScreen(
.build()
}
private fun onNavigate() {
setResult(destination)
finish()

View File

@@ -9,6 +9,7 @@ import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Example local unit test, which will execute on the development machine (host).

View File

@@ -17,6 +17,7 @@
package com.kouros.navigation.data
import android.location.Location
import com.kouros.navigation.model.RouteModel
import org.json.JSONArray
import java.net.Authenticator
import java.net.HttpURLConnection
@@ -47,6 +48,13 @@ class NavigationRepository {
return fetchUrl(routeUrl + routeLocation)
}
fun getRouteDistance(currentLocation : Location, location: Location): Double {
val route = getRoute(currentLocation, location)
val routeModel = RouteModel()
routeModel.startNavigation(route)
return routeModel.routeDistance
}
fun getPlaces(): List<Place> {
val places: MutableList<Place> = ArrayList()
val placesStr = fetchUrl(placesUrl)

View File

@@ -36,7 +36,7 @@ class Contacts(private var context: Context) {
if (name.contains("Jola")
|| name.contains("Dominic")
|| name.contains("Martha")
|| name.contains("Μεντή")
|| name.contains("Μεντη")
|| name.contains("David")) {
val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE))
if (mimeType == ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) {

View File

@@ -4,22 +4,26 @@ 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
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.Utils.decodePolyline
import org.json.JSONArray
import org.json.JSONObject
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
// 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
private lateinit var summary: JSONObject
var routeDistance = 0.0
var routeTime = 0.0
lateinit var centerLocation: Location
lateinit var destination: Place
var navigating = false
var arrived = false
var maneuverIndex = 0
@@ -28,12 +32,13 @@ open class RouteModel () {
var distanceToStepEnd = 0F
var bearing = 0F
var beginIndex = 0
var endIndex = 0
var routingManeuvers = mutableListOf<JSONObject>()
var distanceToRoute = 0F
var routingManeuvers = mutableListOf<JSONObject>()
var route = ""
@@ -56,11 +61,20 @@ open class RouteModel () {
locations = trip.getJSONArray("locations")
val legs = trip.getJSONArray("legs")
summary = trip.getJSONObject("summary")
routeTime = summary.getDouble("time")
routeDistance = summary.getDouble("length")
centerLocation = createCenterLocation()
maneuvers = legs.getJSONObject(0).getJSONArray("maneuvers")
val shape = legs.getJSONObject(0).getString("shape")
polylineLocations = decodePolyline(shape)
}
private fun createCenterLocation() : Location {
val latitude = summary.getDouble("max_lat") - (summary.getDouble("max_lat") - summary.getDouble("min_lat"))
val longitude = summary.getDouble("max_lon") - (summary.getDouble("max_lon") - summary.getDouble("min_lon"))
return NavigationUtils().location(latitude, longitude)
}
fun startNavigation(valhallaRoute: String) {
decodeValhallaRoute(valhallaRoute)
for (i in 0..<maneuvers.length()) {
@@ -77,7 +91,7 @@ open class RouteModel () {
return ((leftStepDistance() * 1000).roundToInt().toDouble() / 10.0).roundToInt() * 10.0
}
fun findManeuver(location: Location) {
fun updateLocation(location: Location) {
var nearestDistance = 100000.0f
maneuverIndex = -1
// find maneuver
@@ -92,18 +106,18 @@ open class RouteModel () {
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
}
}
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")
if (bearing == 0F) {
if (maneuver.has("bearing_after")) {
bearing = maneuver.getInt("bearing_after").toFloat()
}
}
val distanceStepLeft = leftStepDistance() * 1000
when (distanceStepLeft) {
@@ -120,7 +134,6 @@ open class RouteModel () {
}
/** Calculates the index in a maneuver. */
private fun calculateCurrentIndex(
beginShapeIndex: Int,
@@ -139,9 +152,14 @@ open class RouteModel () {
beginIndex = beginShapeIndex
endIndex = endShapeIndex
distanceToStepEnd = 0F
val loc1 = Location(LocationManager.GPS_PROVIDER)
val loc2 = Location(LocationManager.GPS_PROVIDER)
loc1.longitude = polylineLocations[i][0]
loc1.latitude = polylineLocations[i][1]
loc2.longitude = polylineLocations[i+1][0]
loc2.latitude = polylineLocations[i+1][1]
bearing = loc1.bearingTo(loc2).absoluteValue
for (j in i + 1..endShapeIndex) {
val loc1 = Location(LocationManager.GPS_PROVIDER)
val loc2 = Location(LocationManager.GPS_PROVIDER)
loc1.longitude = polylineLocations[j - 1][0]
loc1.latitude = polylineLocations[j - 1][1]
loc2.longitude = polylineLocations[j][0]
@@ -228,7 +246,6 @@ open class RouteModel () {
maneuverIndex = 0
currentIndex = 0
distanceToStepEnd = 0F
distanceToRoute = 0F
beginIndex = 0
endIndex = 0

View File

@@ -12,6 +12,7 @@ import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_
import com.kouros.navigation.utils.NavigationUtils
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -42,11 +43,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val placeBox = boxStore.boxFor(Place::class)
pl.addAll(placeBox.all)
for (place in pl) {
val plLocation = Location(LocationManager.GPS_PROVIDER)
plLocation.longitude = place.longitude
plLocation.latitude = place.latitude
val distance: Float = location.distanceTo(plLocation)
place.distance = distance / 1000
val plLocation = NavigationUtils().location(place.latitude, place.longitude)
val distance = repository.getRouteDistance(location, plLocation)
place.distance = distance.toFloat()
}
places.postValue(pl)
} catch (e: Exception) {
@@ -75,7 +74,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
}
}
fun loadContacts(context: Context) {
fun loadContacts(context: Context, currentLocation: Location) {
viewModelScope.launch(Dispatchers.IO) {
try {
val geocoder = Geocoder(context)
@@ -83,22 +82,28 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val contacts = Contacts(context = context)
val addresses = contacts.retrieveContacts()
for (address in addresses) {
val lines = address.address.split("\n")
val addressLines = address.address.split("\n")
geocoder.getFromLocationName(
address.address, 5) {
for (adr in it) {
contactList.add(
Place(
id = address.contactId,
name = address.name + " " + lines[0] + " " + lines[1],
Constants.CONTACTS,
street = lines[0],
city = lines[1],
latitude = adr.latitude,
longitude = adr.longitude,
avatar = address.avatar
if (addressLines.size > 1) {
val plLocation = NavigationUtils().location(adr.latitude, adr.longitude)
val distance = repository.getRouteDistance(currentLocation, plLocation)
contactList.add(
Place(
id = address.contactId,
name = address.name + " " + addressLines[0] + " " + addressLines[1],
Constants.CONTACTS,
street = addressLines[0],
city = addressLines[1],
latitude = adr.latitude,
longitude = adr.longitude,
avatar = address.avatar,
distance = distance.toFloat()
)
)
)
}
}
contactAddress.postValue(contactList)
}

View File

@@ -121,4 +121,11 @@ class NavigationUtils() {
}
return zoom.toDouble()
}
fun location(latitude: Double, longitude: Double): Location {
val location = Location(LocationManager.GPS_PROVIDER)
location.longitude = longitude
location.latitude = latitude
return location
}
}