This commit is contained in:
Dimitris
2026-01-12 13:12:22 +01:00
parent e274011080
commit e22865bd73
24 changed files with 3670 additions and 918 deletions

View File

@@ -43,6 +43,7 @@ import com.kouros.data.R
import com.kouros.navigation.MainApplication.Companion.navigationViewModel import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.home2Location
import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.BaseStyleModel
@@ -61,6 +62,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.joda.time.DateTime import org.joda.time.DateTime
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.Location import org.maplibre.compose.location.Location
@@ -76,7 +78,7 @@ class MainActivity : ComponentActivity() {
val routeModel = RouteModel() val routeModel = RouteModel()
var tilt = 50.0 var tilt = 50.0
val useMock = true val useMock = false
val stepData: MutableLiveData<StepData> by lazy { val stepData: MutableLiveData<StepData> by lazy {
MutableLiveData<StepData>() MutableLiveData<StepData>()
} }
@@ -90,7 +92,7 @@ class MainActivity : ComponentActivity() {
routeData.value = routeModel.route.routeGeoJson routeData.value = routeModel.route.routeGeoJson
simulate() simulate()
//test() //test()
//gpx(applicationContext) ///gpx(applicationContext)
} }
} }
val cameraPosition = MutableLiveData( val cameraPosition = MutableLiveData(
@@ -130,8 +132,8 @@ class MainActivity : ComponentActivity() {
if (useMock) { if (useMock) {
mock = MockLocation(locationManager) mock = MockLocation(locationManager)
mock.setMockLocation( mock.setMockLocation(
location?.latitude ?: homeLocation.latitude, home2Location.latitude,
location?.longitude ?: homeLocation.longitude home2Location.longitude
) )
} }
} }
@@ -153,6 +155,10 @@ class MainActivity : ComponentActivity() {
requiredPermissions = listOf(permissions.first()), requiredPermissions = listOf(permissions.first()),
onGranted = { onGranted = {
Content() Content()
// auto navigate
if (useMock) {
//navigationViewModel.loadRoute(applicationContext, homeLocation, home2Location, 0F)
}
}, },
) )
} }
@@ -271,7 +277,7 @@ class MainActivity : ComponentActivity() {
) )
lastLocation = currentLocation lastLocation = currentLocation
if (!loadRecentPlaces) { if (!loadRecentPlaces) {
navigationViewModel.loadRecentPlaces(applicationContext, lastLocation) navigationViewModel.loadRecentPlaces(applicationContext, lastLocation, 0F)
loadRecentPlaces = true loadRecentPlaces = true
} }
} }
@@ -333,21 +339,34 @@ class MainActivity : ComponentActivity() {
fun test() { fun test() {
for ((index, step) in routeModel.legs.steps.withIndex()) { for ((index, step) in routeModel.legs.steps.withIndex()) {
if (index in 3..3) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
routeModel.updateLocation(location(waypoint[0], waypoint[1]), navigationViewModel) routeModel.updateLocation(
location(waypoint[0], waypoint[1]),
navigationViewModel
)
val step = routeModel.currentStep() val step = routeModel.currentStep()
println("Street: ${step.instruction} Dist: ${step.leftStepDistance} ${step.currentManeuverType}") if (step.leftStepDistance == 70.0) {
println("")
}
if (index + 1 <= routeModel.legs.steps.size) { if (index + 1 <= routeModel.legs.steps.size) {
//nextStepData.value = routeModel.nextStep() //nextStepData.value = routeModel.nextStep()
} }
} }
} }
} }
}
fun test2() {
CoroutineScope(Dispatchers.IO).launch {
// Balanstr.
mock.setMockLocation( 48.119357, 11.599130)
}
}
fun gpx(context: Context) { fun gpx(context: Context) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val parser = GPXParser() val parser = GPXParser()
val input = context.resources.openRawResource(R.raw.vh) val input = context.resources.openRawResource(R.raw.hv)
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
parsedGpx?.let { parsedGpx?.let {
val tracks = parsedGpx.tracks val tracks = parsedGpx.tracks

View File

@@ -96,7 +96,7 @@ fun Home(
Button(onClick = { Button(onClick = {
val places = viewModel.loadRecentPlace() val places = viewModel.loadRecentPlace()
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude) val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
viewModel.loadRoute(applicationContext, location, toLocation) viewModel.loadRoute(applicationContext, location, toLocation, 0F)
closeSheet() closeSheet()
}) { }) {
Icon( Icon(
@@ -207,7 +207,7 @@ private fun SearchPlaces(
viewModel.saveRecent(pl) viewModel.saveRecent(pl)
val toLocation = val toLocation =
location(place.lon.toDouble(), place.lat.toDouble()) location(place.lon.toDouble(), place.lat.toDouble())
viewModel.loadRoute(context, location, toLocation) viewModel.loadRoute(context, location, toLocation, 0F)
closeSheet() closeSheet()
} }
.fillMaxWidth() .fillMaxWidth()
@@ -245,7 +245,7 @@ private fun RecentPlaces(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
val toLocation = location(place.longitude, place.latitude) val toLocation = location(place.longitude, place.latitude)
viewModel.loadRoute(context, location, toLocation) viewModel.loadRoute(context, location, toLocation, 0F)
closeSheet() closeSheet()
} }
.fillMaxWidth() .fillMaxWidth()

View File

@@ -18,6 +18,7 @@ import androidx.car.app.hardware.common.CarValue
import androidx.car.app.hardware.common.OnCarDataAvailableListener import androidx.car.app.hardware.common.OnCarDataAvailableListener
import androidx.car.app.hardware.info.CarHardwareLocation import androidx.car.app.hardware.info.CarHardwareLocation
import androidx.car.app.hardware.info.CarSensors import androidx.car.app.hardware.info.CarSensors
import androidx.car.app.hardware.info.Compass
import androidx.car.app.hardware.info.Speed import androidx.car.app.hardware.info.Speed
import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationListenerCompat
import androidx.core.net.toUri import androidx.core.net.toUri
@@ -104,6 +105,17 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
} }
val carCompassListener: OnCarDataAvailableListener<Compass?> =
OnCarDataAvailableListener { data ->
if (data.orientations.status == CarValue.STATUS_SUCCESS) {
val orientation = data.orientations.value
if (orientation != null) {
surfaceRenderer.carOrientation = orientation[0]
}
}
}
val carSpeedListener = OnCarDataAvailableListener<Speed> { data -> val carSpeedListener = OnCarDataAvailableListener<Speed> { data ->
if (data.displaySpeedMetersPerSecond.status == CarValue.STATUS_SUCCESS) { if (data.displaySpeedMetersPerSecond.status == CarValue.STATUS_SUCCESS) {
val speed = data.displaySpeedMetersPerSecond.value val speed = data.displaySpeedMetersPerSecond.value
@@ -172,6 +184,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION) val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION)
if (useCarLocation) { if (useCarLocation) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
carSensors.addCompassListener(CarSensors.UPDATE_RATE_FASTEST,
carContext.mainExecutor,
carCompassListener)
carSensors.addCarHardwareLocationListener( carSensors.addCarHardwareLocationListener(
CarSensors.UPDATE_RATE_FASTEST, CarSensors.UPDATE_RATE_FASTEST,
carContext.mainExecutor, carContext.mainExecutor,
@@ -191,8 +206,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
SearchScreen( SearchScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location, navigationViewModel
navigationViewModel,
// TODO: Uri // TODO: Uri
) )
) { obj: Any? -> ) { obj: Any? ->
@@ -229,7 +243,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if (location != null) { if (location != null) {
navigationViewModel.loadRecentPlace(location = location, carContext) navigationViewModel.loadRecentPlace(location = location, surfaceRenderer.carOrientation, carContext)
updateLocation(location) updateLocation(location)
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, LocationManager.GPS_PROVIDER,

View File

@@ -54,6 +54,8 @@ class SurfaceRenderer(
) : DefaultLifecycleObserver { ) : DefaultLifecycleObserver {
var lastLocation = location(0.0, 0.0) var lastLocation = location(0.0, 0.0)
var carOrientation = 0F
private val cameraPosition = MutableLiveData( private val cameraPosition = MutableLiveData(
CameraPosition( CameraPosition(
zoom = 15.0, zoom = 15.0,
@@ -303,7 +305,7 @@ class SurfaceRenderer(
routeData.value = route routeData.value = route
updateCameraPosition( updateCameraPosition(
0.0, 0.0,
12.0, 14.0,
target = Position(location.longitude, location.latitude) target = Position(location.longitude, location.latitude)
) )
} }

View File

@@ -160,8 +160,10 @@ fun AmenityLayer(routeData: String?) {
if (routeData != null && routeData.isNotEmpty()) { if (routeData != null && routeData.isNotEmpty()) {
val color = if (routeData.contains(Constants.PHARMACY)) { val color = if (routeData.contains(Constants.PHARMACY)) {
const(Color.Red) const(Color.Red)
} else if (routeData.contains(Constants.CHARGING_STATION)) {
const(Color.Blue)
} else { } else {
const(Color.Green) const(Color.Black)
} }
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData)) val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
SymbolLayer( SymbolLayer(
@@ -169,6 +171,7 @@ fun AmenityLayer(routeData: String?) {
source = routes, source = routes,
iconImage = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true), iconImage = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true),
iconColor = color, iconColor = color,
iconOpacity = const(2.0f),
iconSize = const(3.0f), iconSize = const(3.0f),
) )
} }
@@ -188,10 +191,10 @@ fun SpeedCameraLayer(speedCameras: String?) {
interpolate( interpolate(
type = exponential(1.2f), type = exponential(1.2f),
input = zoom(), input = zoom(),
5 to const(0.4f), 5 to const(0.7f),
6 to const(0.7f), 6 to const(1.0f),
7 to const(1.75f), 7 to const(2.0f),
20 to const(3f), 20 to const(4f),
), ),
) )
} }

View File

@@ -58,7 +58,9 @@ class RouteCarModel() : RouteModel() {
.setIcon(createCarIcon(carContext, stepData.icon)) .setIcon(createCarIcon(carContext, stepData.icon))
.build() .build()
) )
.setRoad(destination.street!!) if (destination.street != null) {
step.setRoad(destination.street!!)
}
if (stepData.lane.isNotEmpty()) { if (stepData.lane.isNotEmpty()) {
addLanes(carContext, step, stepData) addLanes(carContext, step, stepData)
} }
@@ -222,13 +224,17 @@ class RouteCarModel() : RouteModel() {
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) { fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
carContext.getCarService<AppManager?>(AppManager::class.java) carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(createAlert(carContext, distance, maxSpeed)) .showAlert(createAlert(carContext, distance, maxSpeed, createCarIcon(carContext, R.drawable.speed_camera_24px)))
} }
fun createAlert(carContext: CarContext, distance: Double, maxSpeed: String?): Alert { fun createAlert(
carContext: CarContext,
distance: Double,
maxSpeed: String?,
icon: CarIcon
): Alert {
val title = createCarText(carContext, R.string.speed_camera) val title = createCarText(carContext, R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!) val subtitle = CarText.create(maxSpeed!!)
val icon = CarIcon.ALERT
val dismissAction: Action = createToastAction( val dismissAction: Action = createToastAction(
carContext, carContext,

View File

@@ -14,6 +14,7 @@ import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Category import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants.CHARGING_STATION import com.kouros.navigation.data.Constants.CHARGING_STATION
import com.kouros.navigation.data.Constants.FUEL_STATION import com.kouros.navigation.data.Constants.FUEL_STATION
@@ -23,8 +24,7 @@ import com.kouros.navigation.model.ViewModel
class CategoriesScreen( class CategoriesScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
private val location: Location, private val viewModel: ViewModel,
private val viewModel: ViewModel
) : Screen(carContext) { ) : Screen(carContext) {
var categories: List<Category> = listOf( var categories: List<Category> = listOf(
@@ -47,7 +47,6 @@ class CategoriesScreen(
CategoryScreen( CategoryScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location,
it.id, it.id,
viewModel viewModel
) )

View File

@@ -19,8 +19,9 @@ import androidx.lifecycle.Observer
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.Place
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils.createPointCollection import com.kouros.navigation.utils.GeoUtils.createPointCollection
@@ -31,9 +32,8 @@ import kotlin.math.min
class CategoryScreen( class CategoryScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
location: Location,
private val category: String, private val category: String,
private val viewModel: ViewModel private val viewModel: ViewModel,
) : Screen(carContext) { ) : Screen(carContext) {
var elements = listOf<Elements>() var elements = listOf<Elements>()
@@ -58,7 +58,7 @@ class CategoryScreen(
init { init {
viewModel.elements.observe(this, observer) viewModel.elements.observe(this, observer)
viewModel.getAmenities(category, location) viewModel.getAmenities(category, surfaceRenderer.lastLocation)
} }
@@ -126,6 +126,28 @@ class CategoryScreen(
} else { } else {
row.addText(carText("${it.tags.openingHours}")) row.addText(carText("${it.tags.openingHours}"))
} }
val navigationMessage = NavigationMessage(carContext)
row.addAction(
Action.Builder()
.setOnClickListener {
viewModel.loadRoute(
carContext,
currentLocation = surfaceRenderer.lastLocation,
location(it.lon!!, it.lat!!),
surfaceRenderer.carOrientation
)
setResult(
Place(
name = name,
category = Constants.CHARGING_STATION,
latitude = it.lat!!,
longitude = it.lon!!
)
)
finish()
}
.setIcon(navigationMessage.createCarIcon(R.drawable.navigation_48px))
.build())
return row.build() return row.build()
} }

View File

@@ -98,6 +98,7 @@ class NavigationScreen(
surfaceRenderer.speedCamerasData.value = speedData surfaceRenderer.speedCamerasData.value = speedData
} }
init { init {
viewModel.route.observe(this, observer) viewModel.route.observe(this, observer)
viewModel.recentPlace.observe(this, recentObserver) viewModel.recentPlace.observe(this, recentObserver)
@@ -297,7 +298,12 @@ class NavigationScreen(
) )
.setOnClickListener { .setOnClickListener {
val navigateTo = location(recentPlace.longitude, recentPlace.latitude) val navigateTo = location(recentPlace.longitude, recentPlace.latitude)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo) viewModel.loadRoute(
carContext,
surfaceRenderer.lastLocation,
navigateTo,
surfaceRenderer.carOrientation
)
routeModel.destination = recentPlace routeModel.destination = recentPlace
} }
.build() .build()
@@ -394,7 +400,11 @@ class NavigationScreen(
private fun startSearchScreen() { private fun startSearchScreen() {
screenManager screenManager
.pushForResult( .pushForResult(
SearchScreen(carContext, surfaceRenderer, surfaceRenderer.lastLocation, viewModel) SearchScreen(
carContext,
surfaceRenderer,
viewModel
)
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {
val place = obj as Place val place = obj as Place
@@ -416,7 +426,12 @@ class NavigationScreen(
val location = location(place.longitude, place.latitude) val location = location(place.longitude, place.latitude)
viewModel.saveRecent(place) viewModel.saveRecent(place)
currentNavigationLocation = location currentNavigationLocation = location
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location) viewModel.loadRoute(
carContext,
surfaceRenderer.lastLocation,
location,
surfaceRenderer.carOrientation
)
routeModel.destination = place routeModel.destination = place
invalidate() invalidate()
} }
@@ -447,7 +462,12 @@ class NavigationScreen(
fun reRoute(destination: Place) { fun reRoute(destination: Place) {
val dest = location(destination.longitude, destination.latitude) val dest = location(destination.longitude, destination.latitude)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, dest) viewModel.loadRoute(
carContext,
surfaceRenderer.lastLocation,
dest,
surfaceRenderer.carOrientation
)
} }
fun updateTrip(location: Location) { fun updateTrip(location: Location) {
@@ -495,13 +515,13 @@ class NavigationScreen(
val bearingSpeedCamera = location.bearingTo(location(camera.lon!!, camera.lat!!)) val bearingSpeedCamera = location.bearingTo(location(camera.lon!!, camera.lat!!))
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location) val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
if (camera.distance < 80 if (camera.distance < 80) {
&& (bearingSpeedCamera.absoluteValue - bearingRoute.absoluteValue).absoluteValue < 15.0 if ((bearingSpeedCamera.absoluteValue - bearingRoute.absoluteValue).absoluteValue < 20.0) {
) {
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed) routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
} }
} }
} }
}
enum class NavigationType { enum class NavigationType {
VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL

View File

@@ -31,7 +31,6 @@ import com.kouros.navigation.model.ViewModel
class PlaceListScreen( class PlaceListScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
private val location: Location,
private val category: String, private val category: String,
private val viewModel: ViewModel private val viewModel: ViewModel
) : Screen(carContext) { ) : Screen(carContext) {
@@ -63,13 +62,21 @@ class PlaceListScreen(
fun loadPlaces() { fun loadPlaces() {
if (category == RECENT) { if (category == RECENT) {
viewModel.loadRecentPlaces(carContext, location) viewModel.loadRecentPlaces(
carContext,
surfaceRenderer.lastLocation,
surfaceRenderer.carOrientation
)
} }
if (category == CONTACTS) { if (category == CONTACTS) {
viewModel.loadContacts(carContext) viewModel.loadContacts(carContext)
} }
if (category == FAVORITES) { if (category == FAVORITES) {
viewModel.loadFavorites(carContext, location) viewModel.loadFavorites(
carContext,
surfaceRenderer.lastLocation,
surfaceRenderer.carOrientation
)
} }
} }
@@ -77,9 +84,14 @@ class PlaceListScreen(
val itemListBuilder = ItemList.Builder() val itemListBuilder = ItemList.Builder()
.setNoItemsMessage(carContext.getString(R.string.no_places)) .setNoItemsMessage(carContext.getString(R.string.no_places))
places.forEach { places.forEach {
val street = if (it.street != null) {
it.street
} else {
""
}
val row = Row.Builder() val row = Row.Builder()
.setImage(contactIcon(it.avatar, it.category)) .setImage(contactIcon(it.avatar, it.category))
.setTitle("${it.street!!} ${it.city}") .setTitle("$street ${it.city}")
.setOnClickListener { .setOnClickListener {
val place = Place( val place = Place(
0, 0,

View File

@@ -61,7 +61,12 @@ class RoutePreviewScreen(
init { init {
viewModel.previewRoute.observe(this, observer) viewModel.previewRoute.observe(this, observer)
val location = location(destination.longitude, destination.latitude) val location = location(destination.longitude, destination.latitude)
viewModel.loadPreviewRoute(carContext, surfaceRenderer.lastLocation, location) viewModel.loadPreviewRoute(
carContext,
surfaceRenderer.lastLocation,
location,
surfaceRenderer.carOrientation
)
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
@@ -173,7 +178,8 @@ class RoutePreviewScreen(
private fun createRouteText(): CarText { private fun createRouteText(): CarText {
val time = routeModel.route.summary!!.duration val time = routeModel.route.summary!!.duration
val length = BigDecimal(routeModel.route.summary!!.distance).setScale(1, RoundingMode.HALF_EVEN) val length =
BigDecimal(routeModel.route.summary!!.distance).setScale(1, RoundingMode.HALF_EVEN)
val firstRoute = SpannableString(" \u00b7 $length km") val firstRoute = SpannableString(" \u00b7 $length km")
firstRoute.setSpan( firstRoute.setSpan(
DurationSpan.create(time.toLong()), 0, 1, 0 DurationSpan.create(time.toLong()), 0, 1, 0

View File

@@ -2,7 +2,6 @@ package com.kouros.navigation.car.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.location.Location import android.location.Location
import android.net.Uri
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
@@ -17,9 +16,9 @@ import androidx.lifecycle.Observer
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Category import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
@@ -28,8 +27,7 @@ import com.kouros.navigation.model.ViewModel
class SearchScreen( class SearchScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var location: Location, private val viewModel: ViewModel,
private val viewModel: ViewModel
) : Screen(carContext) { ) : Screen(carContext) {
var isSearchComplete: Boolean = false var isSearchComplete: Boolean = false
@@ -73,7 +71,6 @@ class SearchScreen(
CategoriesScreen( CategoriesScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location,
viewModel viewModel
) )
) { obj: Any? -> ) { obj: Any? ->
@@ -89,7 +86,6 @@ class SearchScreen(
PlaceListScreen( PlaceListScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location,
it.id, it.id,
viewModel viewModel
) )
@@ -119,7 +115,7 @@ class SearchScreen(
object : SearchCallback { object : SearchCallback {
override fun onSearchSubmitted(searchTerm: String) { override fun onSearchSubmitted(searchTerm: String) {
isSearchComplete = true isSearchComplete = true
viewModel.searchPlaces(searchTerm, location) viewModel.searchPlaces(searchTerm, surfaceRenderer.lastLocation)
} }
}) })
.setHeaderAction(Action.BACK) .setHeaderAction(Action.BACK)

View File

@@ -37,10 +37,10 @@ abstract class NavigationRepository {
private val nominatimUrl = "https://kouros-online.de/nominatim/" private val nominatimUrl = "https://kouros-online.de/nominatim/"
abstract fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String abstract fun getRoute(currentLocation: Location, location: Location, carOrientation: Float, searchFilter: SearchFilter): String
fun getRouteDistance(currentLocation: Location, location: Location, searchFilter: SearchFilter, context: Context): Double { fun getRouteDistance(currentLocation: Location, location: Location, carOrientation : Float, searchFilter: SearchFilter, context: Context): Double {
val route = getRoute(currentLocation, location, searchFilter) val route = getRoute(currentLocation, location, carOrientation, searchFilter)
val routeModel = RouteModel() val routeModel = RouteModel()
routeModel.startNavigation(route, context) routeModel.startNavigation(route, context)
return routeModel.route.summary!!.distance return routeModel.route.summary!!.distance

View File

@@ -46,7 +46,6 @@ data class Route(
this.routeGeoJson = routeGeoJson this.routeGeoJson = routeGeoJson
centerLocation = createCenterLocation(routeGeoJson) centerLocation = createCenterLocation(routeGeoJson)
} }
fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine } fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine }
fun waypoints(waypoints: List<List<Double>>) = apply { this.waypoints = waypoints } fun waypoints(waypoints: List<List<Double>>) = apply { this.waypoints = waypoints }
fun route(route: String) = apply { fun route(route: String) = apply {

View File

@@ -10,6 +10,7 @@ class OsrmRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
currentLocation: Location, currentLocation: Location,
location: Location, location: Location,
carOrientation: Float,
searchFilter: SearchFilter searchFilter: SearchFilter
): String { ): String {
@@ -20,7 +21,7 @@ class OsrmRepository : NavigationRepository() {
if (searchFilter.avoidTollway) { if (searchFilter.avoidTollway) {
exclude = "$exclude&exclude=toll" exclude = "$exclude&exclude=toll"
} }
val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true" val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=2"
return fetchUrl(routeUrl + routeLocation + exclude, true) return fetchUrl(routeUrl + routeLocation + exclude, true)
} }
} }

View File

@@ -62,6 +62,7 @@ class OsrmRoute {
} }
} }
val leg = Leg(steps) val leg = Leg(steps)
builder builder
.routeType(1) .routeType(1)
.summary(summary) .summary(summary)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
package com.kouros.navigation.data.route
class Routes (
var legs : List<Leg> = arrayListOf()
)

View File

@@ -12,7 +12,12 @@ private const val routeUrl = "https://kouros-online.de/valhalla/route?json="
class ValhallaRepository : NavigationRepository() { class ValhallaRepository : NavigationRepository() {
override fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String { override fun getRoute(
currentLocation: Location,
location: Location,
carOrientation: Float,
searchFilter: SearchFilter
): String {
var exclude = "" var exclude = ""
if (searchFilter.avoidMotorway) { if (searchFilter.avoidMotorway) {
@@ -23,7 +28,11 @@ class ValhallaRepository : NavigationRepository() {
} }
val vLocation = listOf( val vLocation = listOf(
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude, search_filter = exclude), Locations(
lat = currentLocation.latitude,
lon = currentLocation.longitude,
search_filter = exclude
),
Locations(lat = location.latitude, lon = location.longitude, search_filter = exclude) Locations(lat = location.latitude, lon = location.longitude, search_filter = exclude)
) )
val valhallaLocation = ValhallaLocation( val valhallaLocation = ValhallaLocation(

View File

@@ -114,8 +114,8 @@ open class RouteModel() {
val interBearing = location.bearingTo(location(it.location[0], it.location[1])) val interBearing = location.bearingTo(location(it.location[0], it.location[1]))
if (distance < nearestDistance) { if (distance < nearestDistance) {
nearestDistance = distance nearestDistance = distance
if (distance <= NEXT_STEP_THRESHOLD) { if (distance <= NEXT_STEP_THRESHOLD * 3) {
if ((interBearing.absoluteValue - route.currentStep().maneuver.bearingAfter.absoluteValue).absoluteValue < 10) { if ((interBearing.absoluteValue - route.currentStep().maneuver.bearingAfter.absoluteValue).absoluteValue < 20) {
inter = it inter = it
} }
} }
@@ -171,11 +171,11 @@ open class RouteModel() {
val maneuverIcon = maneuverIcon(curManeuverType) val maneuverIcon = maneuverIcon(curManeuverType)
maneuverType = curManeuverType maneuverType = curManeuverType
val lanes = if (shouldAdvance) { val lanes = currentLanes(location)
currentLanes(location)
} else { if (lanes.isNotEmpty())
emptyList() println("Street: $streetName Dist: $distanceToNextStep Lane: ${lanes.size}")
}
// Construct and return the final StepData object // Construct and return the final StepData object
return StepData( return StepData(
streetName, streetName,
@@ -186,6 +186,7 @@ open class RouteModel() {
travelLeftDistance(), travelLeftDistance(),
lanes lanes
) )
} }
@@ -334,7 +335,7 @@ open class RouteModel() {
fun createLaneIcon(context: Context, stepData: StepData): IconCompat { fun createLaneIcon(context: Context, stepData: StepData): IconCompat {
val bitmaps = mutableListOf<Bitmap>() val bitmaps = mutableListOf<Bitmap>()
stepData.lane.forEach { stepData.lane.forEach {
if (it.indications.isNotEmpty()) { //&& it.valid) { if (it.indications.isNotEmpty()) {
Collections.sort<String>(it.indications) Collections.sort<String>(it.indications)
val resource = laneToResource(it.indications, stepData) val resource = laneToResource(it.indications, stepData)
if (resource.isNotEmpty()) { if (resource.isNotEmpty()) {
@@ -357,7 +358,7 @@ open class RouteModel() {
return bitmaps.first() return bitmaps.first()
} }
val bmOverlay = createBitmap( val bmOverlay = createBitmap(
bitmaps.first().getWidth() * (bitmaps.size), bitmaps.first().getWidth() * (bitmaps.size * 1.5).toInt(),
bitmaps.first().getHeight(), bitmaps.first().getHeight(),
bitmaps.first().getConfig()!! bitmaps.first().getConfig()!!
) )

View File

@@ -73,7 +73,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData() MutableLiveData()
} }
fun loadRecentPlace(location: Location, context: Context) { fun loadRecentPlace(location: Location, carOrientation: Float, context: Context) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val placeBox = boxStore.boxFor(Place::class) val placeBox = boxStore.boxFor(Place::class)
@@ -85,7 +85,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
query.close() query.close()
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = repository.getRouteDistance(location, plLocation, SearchFilter(), context) val distance = repository.getRouteDistance(location, plLocation, carOrientation, SearchFilter(), context)
place.distance = distance.toFloat() place.distance = distance.toFloat()
if (place.distance > 1F) { if (place.distance > 1F) {
recentPlace.postValue(place) recentPlace.postValue(place)
@@ -98,7 +98,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadRecentPlaces(context: Context, location: Location) { fun loadRecentPlaces(context: Context, location: Location, carOrientation : Float) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val placeBox = boxStore.boxFor(Place::class) val placeBox = boxStore.boxFor(Place::class)
@@ -115,6 +115,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
repository.getRouteDistance( repository.getRouteDistance(
location, location,
plLocation, plLocation,
carOrientation,
getSearchFilter(context), context getSearchFilter(context), context
) )
place.distance = distance.toFloat() place.distance = distance.toFloat()
@@ -127,7 +128,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadFavorites(context: Context, location: Location) { fun loadFavorites(context: Context, location: Location, carOrientation: Float) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val placeBox = boxStore.boxFor(Place::class) val placeBox = boxStore.boxFor(Place::class)
@@ -140,7 +141,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = val distance =
repository.getRouteDistance(location, plLocation, getSearchFilter(context), context) repository.getRouteDistance(location, plLocation, carOrientation, getSearchFilter(context), context)
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
favorites.postValue(results) favorites.postValue(results)
@@ -150,13 +151,14 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadRoute(context: Context, currentLocation: Location, location: Location) { fun loadRoute(context: Context, currentLocation: Location, location: Location, carOrientation : Float) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
route.postValue( route.postValue(
repository.getRoute( repository.getRoute(
currentLocation, currentLocation,
location, location,
carOrientation,
getSearchFilter(context) getSearchFilter(context)
) )
) )
@@ -166,13 +168,14 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadPreviewRoute(context: Context, currentLocation: Location, location: Location) { fun loadPreviewRoute(context: Context, currentLocation: Location, location: Location, carOrientation: Float) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
previewRoute.postValue( previewRoute.postValue(
repository.getRoute( repository.getRoute(
currentLocation, currentLocation,
location, location,
carOrientation,
getSearchFilter(context) getSearchFilter(context)
) )
) )
@@ -369,7 +372,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
fun loadPlaces2(context: Context, location: Location): SnapshotStateList<Place?> { fun loadPlaces2(context: Context, location: Location, carOrientation: Float): SnapshotStateList<Place?> {
val results = listOf<Place>() val results = listOf<Place>()
try { try {
val placeBox = boxStore.boxFor(Place::class) val placeBox = boxStore.boxFor(Place::class)
@@ -382,7 +385,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = val distance =
repository.getRouteDistance(location, plLocation, getSearchFilter(context), context) repository.getRouteDistance(location, plLocation, carOrientation, getSearchFilter(context), context)
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M340,760L440,600L380,600L380,480L280,640L340,640L340,760ZM240,400L480,400L480,200Q480,200 480,200Q480,200 480,200L240,200Q240,200 240,200Q240,200 240,200L240,400ZM240,760L480,760L480,480L240,480L240,760ZM160,840L160,200Q160,167 183.5,143.5Q207,120 240,120L480,120Q513,120 536.5,143.5Q560,167 560,200L560,480L610,480Q639,480 659.5,500.5Q680,521 680,550L680,735Q680,752 694,766Q708,780 725,780Q743,780 756.5,766Q770,752 770,735L770,360L760,360Q743,360 731.5,348.5Q720,337 720,320L720,240L740,240L740,180L780,180L780,240L820,240L820,180L860,180L860,240L880,240L880,320Q880,337 868.5,348.5Q857,360 840,360L830,360L830,735Q830,777 799.5,808.5Q769,840 725,840Q682,840 651,808.5Q620,777 620,735L620,550Q620,545 617.5,542.5Q615,540 610,540L560,540L560,840L160,840ZM480,760L240,760L240,760L480,760Z"/>
</vector>

View File

@@ -6,5 +6,5 @@
android:tint="?attr/colorControlNormal"> android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M337,746L425,606L372,606L372,501L285,641L337,641L337,746ZM220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM220,780L489,780L489,468L220,468L220,780ZM160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.71,468 649.36,482.64Q664,497.29 664,518L664,737Q664,759 681.5,773.5Q699,788 722,788Q745,788 765,773.5Q785,759 785,737L785,350L770,350Q757.25,350 748.63,341.37Q740,332.75 740,320L740,230L760,230L760,180L790,180L790,230L830,230L830,180L860,180L860,230L880,230L880,320Q880,332.75 871.38,341.37Q862.75,350 850,350L835,350L835,736.69Q835,780 801,810Q767,840 721.82,840Q677.66,840 645.83,810Q614,780 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM489,780L220,780L220,780L489,780Z"/> android:pathData="M220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.71,468 649.36,482.64Q664,497.29 664,518L664,737Q664,759 681.5,773.5Q699,788 722,788Q745,788 765,773.5Q785,759 785,737L785,350L770,350Q757.25,350 748.63,341.37Q740,332.75 740,320L740,230L760,230L760,180L790,180L790,230L830,230L830,180L860,180L860,230L880,230L880,320Q880,332.75 871.38,341.37Q862.75,350 850,350L835,350L835,736.69Q835,780 801,810Q767,840 721.82,840Q677.66,840 645.83,810Q614,780 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM337,746L425,606L372,606L372,501L285,641L337,641L337,746Z"/>
</vector> </vector>