Test, Lanes

This commit is contained in:
Dimitris
2026-03-02 17:11:19 +01:00
parent 378ee8c227
commit e1af3e19fa
30 changed files with 378 additions and 180 deletions

View File

@@ -25,6 +25,7 @@ import org.junit.runner.RunWith
import org.junit.Assert.*
import org.junit.Before
import org.maplibre.compose.expressions.dsl.step
import kotlin.collections.forEach
/**
@@ -65,7 +66,7 @@ class RouteModelTest {
val stepData = routeModel.currentStep()
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT)
assertEquals(stepData.instruction, "Silcherstraße")
assertEquals(stepData.leftStepDistance, 70.0, 1.0)
assertEquals(stepData.leftStepDistance, 30.0, 1.0)
val nextStepData = routeModel.nextStep()
assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT)
assertEquals(nextStepData.instruction, "Schmalkaldener Straße")
@@ -92,12 +93,16 @@ class RouteModelTest {
location.longitude = 11.585125
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep()
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_STRAIGHT)
assertEquals(stepData.instruction, "Ingolstädter Straße")
assertEquals(stepData.leftStepDistance, 350.0, 1.0)
val nextStepData = routeModel.nextStep()
assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
assertEquals(nextStepData.instruction, "Schenkendorfstraße")
if (routeModel.navState.nextStep) {
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_STRAIGHT)
assertEquals(stepData.instruction, "Ingolstädter Straße")
val nextStepData = routeModel.nextStep()
assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
assertEquals(nextStepData.instruction, "Schenkendorfstraße")
} else {
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
}
assertEquals(stepData.leftStepDistance, 300.0, 1.0)
}
@Test
@@ -109,7 +114,7 @@ class RouteModelTest {
val stepData = routeModel.currentStep()
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
assertEquals(stepData.instruction, "Schenkendorfstraße")
assertEquals(stepData.leftStepDistance, 240.0, 1.0)
assertEquals(stepData.leftStepDistance, 170.0, 1.0)
assertEquals(stepData.lane.size, 4)
assertEquals(stepData.lane.first().valid, true)
assertEquals(stepData.lane.last().valid, false)
@@ -127,6 +132,47 @@ class RouteModelTest {
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_DESTINATION_LEFT)
}
@Test
fun checkLanes() {
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
val curLocation = location(waypoint[0], waypoint[1])
if (routeModel.isNavigating()) {
if (index in 42..45) {
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep()
assertEquals(stepData.lane.size, 4)
assertEquals(stepData.lane.first().valid, true)
assertEquals(stepData.lane.first().indications.first(), "SLIGHT_LEFT")
}
if (index in 61..61) {
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep()
assertEquals(stepData.lane.size, 2)
assertEquals(stepData.lane.first().valid, true)
assertEquals(stepData.lane.first().indications.first(), "STRAIGHT")
}
if (index in 74..74) {
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep()
assertEquals(stepData.lane.size, 3)
assertEquals(stepData.lane.first().valid, true)
assertEquals(stepData.lane.first().indications.first(), "SLIGHT_LEFT")
assertEquals(stepData.lane[1].valid, true)
assertEquals(stepData.lane[1].indications.first(), "SLIGHT_LEFT")
}
if (index in 265..265) {
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep()
assertEquals(stepData.lane.size, 2)
assertEquals(stepData.lane.first().valid, false)
assertEquals(stepData.lane.first().indications.first(), "STRAIGHT")
assertEquals(stepData.lane[1].valid, true)
assertEquals(stepData.lane[1].indications.first(), "SLIGHT_RIGHT")
}
}
}
}
@Test
fun simulate() {
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
@@ -136,9 +182,24 @@ class RouteModelTest {
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep()
val nextData = routeModel.nextStep()
println("${stepData.leftStepDistance} : ${stepData.instruction} ${stepData.currentManeuverType} : ${nextData.instruction} ${nextData.currentManeuverType}")
}
}
}
}
@Test
fun `leftStepDistance Inglolstädter `() {
val location: Location = location( 11.584578, 48.183653)
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) )
val step = routeModel.currentStep()
assertEquals(step.leftStepDistance, 650.0, 0.1)
}
@Test
fun `leftStepDistance Vogelhart `() {
val location: Location = location( 11.578911, 48.185565)
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) )
val step = routeModel.currentStep()
assertEquals(step.leftStepDistance , 30.0, 1.0)
}
}

View File

@@ -151,6 +151,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
viewModelStoreOwner = object : ViewModelStoreOwner {
override val viewModelStore = ViewModelStore()
}
lifecycleScope.launch {
try {
awaitCancellation()
@@ -320,8 +321,6 @@ class NavigationSession : Session(), NavigationScreen.Listener {
* Snaps location to route and checks for deviation requiring reroute.
*/
private fun handleNavigationLocation(location: Location) {
routeModel.navState =
routeModel.navState.copy(travelMessage = "${location.latitude} ${location.longitude}")
navigationScreen.updateTrip(location)
if (routeModel.navState.arrived) return
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
@@ -349,7 +348,6 @@ class NavigationSession : Session(), NavigationScreen.Listener {
routeModel.stopNavigation()
}
companion object {
// URI host for deep linking
var uriHost: String = "navigation"

View File

@@ -29,6 +29,7 @@ import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants.TILT
import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.BaseStyleModel
@@ -104,6 +105,9 @@ class SurfaceRenderer(
// Speed limit for current road
val maxSpeed = MutableLiveData(0)
// Current street name
val street = MutableLiveData("")
// Current view mode (navigation, preview, etc.)
var viewStyle = ViewStyle.VIEW
@@ -116,8 +120,8 @@ class SurfaceRenderer(
// Compose view for rendering the map
lateinit var mapView: ComposeView
// Camera tilt angle (default 55 degrees for navigation)
var tilt = 55.0
// Camera tilt angle (default 60 degrees for navigation)
var tilt = TILT
// Map base style (day/night)
val style: MutableLiveData<BaseStyle> by lazy {
@@ -267,7 +271,7 @@ class SurfaceRenderer(
speedCameras,
showBuildings
)
ShowPosition(cameraState, position, paddingValues)
ShowPosition(cameraState, position, paddingValues, darkMode)
}
/**
@@ -278,12 +282,14 @@ class SurfaceRenderer(
fun ShowPosition(
cameraState: CameraState,
position: CameraPosition?,
paddingValues: PaddingValues
paddingValues: PaddingValues,
darkMode: Int
) {
val cameraDuration =
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
val currentSpeed: Float? by speed.observeAsState()
val speed: Int? by maxSpeed.observeAsState()
val streetName: String? by street.observeAsState()
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
DrawNavigationImages(
paddingValues,
@@ -291,7 +297,8 @@ class SurfaceRenderer(
speed!!,
width,
height,
0.0
streetName,
darkMode
)
}
LaunchedEffect(position, viewStyle) {
@@ -345,6 +352,11 @@ class SurfaceRenderer(
*/
fun updateLocation(location: Location) {
synchronized(this) {
if (routeModel.isNavigating()) {
street.value = routeModel.currentStep().street
} else {
street.value = ""
}
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
val bearing = if (carOrientation == 999F) {
if (location.hasBearing()) {

View File

@@ -2,6 +2,7 @@ package com.kouros.navigation.car.map
import android.location.Location
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
@@ -29,7 +30,6 @@ import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationColor
import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.model.RouteModel
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
@@ -292,9 +292,10 @@ fun DrawNavigationImages(
maxSpeed: Int,
width: Int,
height: Int,
lat: Double?
streetName: String?,
darkMode: Int,
) {
NavigationImage(padding, width, height)
NavigationImage(padding, width, height, streetName, darkMode)
if (speed != null) {
CurrentSpeed(width, height, speed, maxSpeed)
}
@@ -305,9 +306,27 @@ fun DrawNavigationImages(
}
@Composable
fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
fun NavigationImage(
padding: PaddingValues,
width: Int,
height: Int,
streetName: String?,
darkMode: Int
) {
val imageSize = (height / 8)
val navigationColor = remember { NavigationColor }
val textMeasurerStreet = rememberTextMeasurer()
val street = streetName.toString()
val styleStreet = TextStyle(
fontSize = 14.sp,
color = if (darkMode == 1) Color.Yellow else Color.Red,
)
val textLayoutStreet = remember(street) {
textMeasurerStreet.measure(street, styleStreet)
}
Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(padding)) {
Canvas(
modifier = Modifier
@@ -325,6 +344,22 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
.size(imageSize.dp, imageSize.dp)
.scale(scaleX = 1f, scaleY = 0.7f),
)
Canvas(
modifier = Modifier
.size(imageSize.dp + 100.dp, imageSize.dp + 80.dp)
) {
if (!streetName.isNullOrEmpty()) {
drawText(
textMeasurer = textMeasurerStreet,
text = streetName,
style = styleStreet,
topLeft = Offset(
x = center.x - textLayoutStreet.size.width / 2,
y = center.y + textLayoutStreet.size.height,
)
)
}
}
}
}

View File

@@ -50,15 +50,19 @@ class RouteCarModel() : RouteModel() {
}
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
maneuver.build()
)
if (navState.destination.street != null) {
step.setRoad(navState.destination.street!!)
}
if (stepData.lane.isNotEmpty()) {
addLanes(carContext, step, stepData)
val lanesAdded = addLanes(carContext, step, stepData)
if (lanesAdded) {
maneuver.setIcon(createCarIcon(carContext, R.drawable.empty))
}
}
step.setManeuver(
maneuver.build()
)
return step.build()
}
@@ -119,10 +123,10 @@ class RouteCarModel() : RouteModel() {
return travelBuilder.build()
}
fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) {
fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) : Boolean {
var laneImageAdded = false
stepData.lane.forEach {
if (it.indications.isNotEmpty() && it.valid) {
if (it.indications.isNotEmpty()) {
val sorted = it.indications.sorted()
var direction = ""
sorted.forEach { it2 ->
@@ -140,12 +144,13 @@ class RouteCarModel() : RouteModel() {
}
val laneType =
Lane.Builder()
.addDirection(LaneDirection.create(laneDirection, false))
.addDirection(LaneDirection.create(laneDirection, true))
.build()
step.addLane(laneType)
}
}
}
return laneImageAdded
}
fun createString(

View File

@@ -13,6 +13,7 @@ import androidx.car.app.model.Template
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) {
@@ -23,9 +24,7 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
init {
lifecycleScope.launch {
settingsViewModel.darkMode.collect {
invalidate()
}
settingsViewModel.darkMode.first()
}
}

View File

@@ -12,6 +12,7 @@ import androidx.car.app.model.Toggle
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
@@ -22,9 +23,7 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
init {
lifecycleScope.launch {
settingsViewModel.show3D.collect {
invalidate()
}
settingsViewModel.show3D.first()
}
}

View File

@@ -77,8 +77,8 @@ class NavigationScreen(
init {
observerManager.attachAllObservers(this)
lifecycleScope.launch {
getSettingsViewModel(carContext).routingEngine.collect {
}
getSettingsViewModel(carContext).routingEngine.first()
getSettingsViewModel(carContext).recentPlaces.first()
}
}
@@ -258,14 +258,17 @@ class NavigationScreen(
} else {
Distance.UNIT_METERS
}
val nextStep = routeModel.nextStep(carContext = carContext)
return RoutingInfo.Builder()
val routingInfo = RoutingInfo.Builder()
.setCurrentStep(
routeModel.currentStep(carContext = carContext),
Distance.create(currentDistance, displayUnit)
)
.setNextStep(nextStep)
.build()
if (routeModel.navState.nextStep) {
val nextStep = routeModel.nextStep(carContext = carContext)
routingInfo.setNextStep(nextStep)
}
return routingInfo.build()
}
private fun createActionStripBuilder(): ActionStrip.Builder {

View File

@@ -13,6 +13,7 @@ import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -32,13 +33,9 @@ class NavigationSettings(
init {
lifecycleScope.launch {
settingsViewModel.avoidTollway.collect {
settingsViewModel.avoidMotorway.collect {
settingsViewModel.carLocation.collect {
invalidate()
}
}
}
settingsViewModel.avoidTollway.first()
settingsViewModel.avoidMotorway.first()
settingsViewModel.carLocation.first()
}
}
@@ -53,7 +50,12 @@ class NavigationSettings(
settingsViewModel.onAvoidMotorway(checked)
motorWayToggleState = !motorWayToggleState
}.setChecked(motorWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_highways_row_title, highwayToggle))
listBuilder.addItem(
buildRowForTemplate(
R.string.avoid_highways_row_title,
highwayToggle
)
)
// Tollway
val tollwayToggle: Toggle =
@@ -92,7 +94,7 @@ class NavigationSettings(
.setSingleList(listBuilder.build())
.setHeader(
Header.Builder()
.setTitle(carContext.getString(R.string.display_settings))
.setTitle(carContext.getString(R.string.navigation_settings))
.setStartHeaderAction(Action.BACK)
.build()
)

View File

@@ -12,6 +12,7 @@ import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class PasswordSettings(
@@ -24,9 +25,7 @@ class PasswordSettings(
init {
lifecycleScope.launch {
settingsViewModel.tomTomApiKey.collect {
invalidate()
}
settingsViewModel.tomTomApiKey.first()
}
}
override fun onGetTemplate(): Template {

View File

@@ -15,6 +15,7 @@ import com.kouros.data.R
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class RoutingSettings(private val carContext: CarContext, private var navigationViewModel: NavigationViewModel) : Screen(carContext) {
@@ -24,9 +25,7 @@ class RoutingSettings(private val carContext: CarContext, private var navigation
init {
lifecycleScope.launch {
settingsViewModel.routingEngine.collect {
invalidate()
}
settingsViewModel.routingEngine.first()
}
}