TomTom Routing

This commit is contained in:
Dimitris
2026-02-07 12:56:45 +01:00
parent eac5b56bcb
commit 0d51c6121d
50 changed files with 8923 additions and 5084 deletions

View File

@@ -213,7 +213,7 @@ class SurfaceRenderer(
DrawNavigationImages(
paddingValues,
currentSpeed,
routeModel.maxSpeed,
routeModel,
width,
height
)

View File

@@ -2,7 +2,6 @@ package com.kouros.navigation.car.map
import android.content.Context
import android.location.Location
import androidx.car.app.connection.CarConnection
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
@@ -32,10 +31,9 @@ import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationColor
import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.data.tomtom.TrafficData
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
@@ -92,7 +90,7 @@ fun MapLibre(
cameraState: CameraState,
baseStyle: BaseStyle.Json,
route: String?,
traffic: Map<String, String> ?,
traffic: Map<String, String>?,
viewStyle: ViewStyle,
speedCameras: String? = ""
) {
@@ -112,6 +110,7 @@ fun MapLibre(
AmenityLayer(route)
} else {
RouteLayer(route, traffic!!)
//RouteLayerPoint(route )
}
SpeedCameraLayer(speedCameras)
}
@@ -164,9 +163,9 @@ fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
type = exponential(1.2f),
input = zoom(),
5 to const(0.4.dp),
6 to const(0.8.dp),
7 to const(2.0.dp),
20 to const(24.dp),
6 to const(0.6.dp),
7 to const(1.8.dp),
20 to const(20.dp),
),
)
LineLayer(
@@ -178,18 +177,44 @@ fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
type = exponential(1.2f),
input = zoom(),
5 to const(0.4.dp),
6 to const(0.7.dp),
7 to const(1.75.dp),
20 to const(22.dp),
6 to const(0.5.dp),
7 to const(1.6.dp),
20 to const(18.dp),
),
)
}
}
@Composable
fun RouteLayerPoint(routeData: String?) {
if (routeData != null && routeData.isNotEmpty()) {
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
val img = image(painterResource(R.drawable.ic_favorite_filled_white_24dp), drawAsSdf = true)
SymbolLayer(
id = "point-layer",
source = routes,
iconOpacity = const(2.0f),
iconColor = const(Color.Red),
iconImage = img,
iconSize =
interpolate(
type = exponential(1.2f),
input = zoom(),
5 to const(0.4f),
6 to const(0.6f),
7 to const(0.8f),
20 to const(1.0f),
),
)
}
}
fun trafficColor(key: String): Expression<ColorValue> {
when (key) {
"queuing" -> return const(Color(0xFFD24417))
"queuing" -> return const(Color(0xFFD24417))
"stationary" -> return const(Color(0xFFFF0000))
"heavy" -> return const(Color(0xFF6B0404))
"slow" -> return const(Color(0xFFC41F1F))
@@ -206,7 +231,7 @@ fun AmenityLayer(routeData: String?) {
if (routeData.contains(Constants.CHARGING_STATION)) {
color = const(Color.Green)
img = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true)
} else if (routeData.contains(Constants.FUEL_STATION)){
} else if (routeData.contains(Constants.FUEL_STATION)) {
color = const(Color.Black)
img = image(painterResource(R.drawable.local_gas_station_48px), drawAsSdf = true)
}
@@ -269,17 +294,18 @@ fun BuildingLayer(tiles: Source) {
fun DrawNavigationImages(
padding: PaddingValues,
speed: Float?,
maxSpeed: Int,
routeModel: RouteModel,
width: Int,
height: Int
) {
NavigationImage(padding, width, height)
if (speed != null) {
CurrentSpeed(width, height, speed, maxSpeed)
CurrentSpeed(width, height, speed, routeModel.maxSpeed)
}
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
MaxSpeed(width, height, maxSpeed)
if (speed != null && routeModel.maxSpeed > 0 && (speed * 3.6) > routeModel.maxSpeed) {
MaxSpeed(width, height, routeModel.maxSpeed)
}
//DebugInfo(width, height, routeModel)
}
@Composable
@@ -429,14 +455,52 @@ private fun MaxSpeed(
}
@Composable
fun rememberBaseStyle( baseStyle : BaseStyle.Json): BaseStyle.Json {
fun DebugInfo(
width: Int,
height: Int,
routeModel: RouteModel,
) {
Box(
modifier = Modifier
.padding(
start = 20.dp,
top = 0.dp
),
contentAlignment = Alignment.CenterStart
) {
val textMeasurerLocation = rememberTextMeasurer()
val location = routeModel.location.latitude.toString()
val styleSpeed = TextStyle(
fontSize = 26.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
)
val textLayoutLocation = remember(location) {
textMeasurerLocation.measure(location, styleSpeed)
}
Canvas(modifier = Modifier.fillMaxSize()) {
drawText(
textMeasurer = textMeasurerLocation,
text = location,
style = styleSpeed,
topLeft = Offset(
x = center.x - textLayoutLocation.size.width / 2,
y = center.y - textLayoutLocation.size.height / 2,
)
)
}
}
}
@Composable
fun rememberBaseStyle(baseStyle: BaseStyle.Json): BaseStyle.Json {
val rememberBaseStyle by remember() {
mutableStateOf(baseStyle)
}
return rememberBaseStyle
}
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
return when (viewStyle) {
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(

View File

@@ -16,7 +16,6 @@
package com.kouros.navigation.car.navigation
import android.text.SpannableString
import android.text.Spanned
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.car.app.AppManager
@@ -27,7 +26,6 @@ import androidx.car.app.model.Alert
import androidx.car.app.model.AlertCallback
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarIconSpan
import androidx.car.app.model.CarText
import androidx.car.app.model.DateTimeWithZone
import androidx.car.app.model.Distance
@@ -42,8 +40,9 @@ import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.RouteModel
import org.maplibre.compose.expressions.dsl.step
import com.kouros.navigation.utils.location
import java.util.Collections
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
@@ -54,18 +53,19 @@ class RouteCarModel() : RouteModel() {
fun currentStep(carContext: CarContext): Step {
val stepData = currentStep()
val currentStepCueWithImage: SpannableString =
createString(stepData.instruction)
createString(stepData.instruction)
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon))
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW) {
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
) {
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
}
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
maneuver.build()
maneuver.build()
)
if (destination.street != null) {
step.setRoad(destination.street!!)
@@ -81,16 +81,17 @@ class RouteCarModel() : RouteModel() {
val stepData = nextStep()
val currentStepCueWithImage: SpannableString =
createString(stepData.instruction)
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon))
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW) {
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
) {
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
}
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
maneuver.build()
maneuver.build()
)
.build()
return step
@@ -100,13 +101,13 @@ class RouteCarModel() : RouteModel() {
val timeLeft = travelLeftTime()
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
val leftDistance = travelLeftDistance()
val leftDistance = travelLeftDistance() / 1000
val displayUnit = if (leftDistance > 1.0) {
Distance.UNIT_KILOMETERS
} else {
Distance.UNIT_METERS
}
val arivalTime = DateTimeWithZone.create(
val arrivalTime = DateTimeWithZone.create(
arrivalTime(),
TimeZone.getTimeZone("Europe/Berlin")
)
@@ -115,7 +116,7 @@ class RouteCarModel() : RouteModel() {
leftDistance,
displayUnit
), // Arrival time at the destination with the destination time zone.
arivalTime
arrivalTime
)
.setRemainingTimeSeconds(
TimeUnit.MILLISECONDS.toSeconds(
@@ -132,161 +133,99 @@ class RouteCarModel() : RouteModel() {
return travelBuilder.build()
}
fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) {
var laneImageAdded = false
stepData.lane.forEach {
if (it.indications.isNotEmpty() && it.valid) {
Collections.sort<String>(it.indications)
var direction = ""
it.indications.forEach { it2 ->
direction = if (direction.isEmpty()) {
it2.trim()
} else {
"${direction}_${it2.trim()}"
}
fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) {
var laneImageAdded = false
stepData.lane.forEach {
if (it.indications.isNotEmpty() && it.valid) {
Collections.sort<String>(it.indications)
var direction = ""
it.indications.forEach { it2 ->
direction = if (direction.isEmpty()) {
it2.trim()
} else {
"${direction}_${it2.trim()}"
}
val laneDirection = when (direction) {
"left_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"left" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"left_slight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right_slight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_RIGHT-> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
else -> {
LaneDirection.SHAPE_UNKNOWN
}
}
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
if (!laneImageAdded) {
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
laneImageAdded = true
}
val laneType =
Lane.Builder()
.addDirection(LaneDirection.create(laneDirection, false))
.build()
step.addLane(laneType)
}
val laneDirection = addLanes(direction, stepData)
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
if (!laneImageAdded) {
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
laneImageAdded = true
}
val laneType =
Lane.Builder()
.addDirection(LaneDirection.create(laneDirection, false))
.build()
step.addLane(laneType)
}
}
}
private fun createStringWithIcon(
carContext: CarContext,
text: String,
@DrawableRes iconRes: Int
): SpannableString {
val start = 0
val end = text.length
val span = CarIconSpan.create(createCarIcon(carContext, iconRes), CarIconSpan.ALIGN_CENTER)
val spannableString = SpannableString(text)
spannableString.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannableString
}
fun createString(
text: String
): SpannableString {
val spannableString = SpannableString(text)
return spannableString
}
fun createCarText(carContext: CarContext, @StringRes stringRes: Int): CarText {
return CarText.create(carContext.getString(stringRes))
}
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
fun createCarIcon(iconCompat: IconCompat): CarIcon {
return CarIcon.Builder(iconCompat).build()
}
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(createAlert(carContext, distance, maxSpeed, createCarIcon(carContext, R.drawable.speed_camera_24px)))
}
fun createAlert(
carContext: CarContext,
distance: Double,
maxSpeed: String?,
icon: CarIcon
): Alert {
val title = createCarText(carContext, R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!)
val dismissAction: Action = createToastAction(
carContext,
R.string.exit_action_title, R.string.exit_action_title,
FLAG_DEFAULT
)
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */5000)
.setSubtitle(subtitle)
.setIcon(icon)
.addAction(dismissAction).setCallback(object : AlertCallback {
override fun onCancel(reason: Int) {
}
override fun onDismiss() {
}
}).build()
}
private fun createToastAction(
carContext: CarContext,
@StringRes titleRes: Int, @StringRes toastStringRes: Int,
flags: Int
): Action {
return Action.Builder()
.setOnClickListener { }
.setTitle(createCarText(carContext, titleRes))
.setFlags(flags)
.build()
}
}
fun createString(
text: String
): SpannableString {
val spannableString = SpannableString(text)
return spannableString
}
fun createCarText(carContext: CarContext, @StringRes stringRes: Int): CarText {
return CarText.create(carContext.getString(stringRes))
}
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
// fun createCarIcon(iconCompat: IconCompat): CarIcon {
// return CarIcon.Builder(iconCompat).build()
// }
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(
createAlert(
carContext,
maxSpeed,
createCarIcon(carContext, R.drawable.speed_camera_24px)
)
)
}
fun createAlert(
carContext: CarContext,
maxSpeed: String?,
icon: CarIcon
): Alert {
val title = createCarText(carContext, R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!)
val dismissAction: Action = createToastAction(
carContext,
R.string.exit_action_title, R.string.exit_action_title,
FLAG_DEFAULT
)
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */5000)
.setSubtitle(subtitle)
.setIcon(icon)
.addAction(dismissAction).setCallback(object : AlertCallback {
override fun onCancel(reason: Int) {
}
override fun onDismiss() {
}
}).build()
}
private fun createToastAction(
carContext: CarContext,
@StringRes titleRes: Int, @StringRes toastStringRes: Int,
flags: Int
): Action {
return Action.Builder()
.setOnClickListener { }
.setTitle(createCarText(carContext, titleRes))
.setFlags(flags)
.build()
}
}

View File

@@ -489,7 +489,6 @@ class NavigationScreen(
lastTrafficDate = current
viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
}
//updateTraffic(location)
updateSpeedCamera(location)
with(routeModel) {
updateLocation(location, viewModel)
@@ -524,7 +523,7 @@ class NavigationScreen(
val updatedCameras = mutableListOf<Elements>()
speedCameras.forEach {
val plLocation =
location(longitude = it.lon!!, latitude = it.lat!!)
location(longitude = it.lon, latitude = it.lat)
val distance = plLocation.distanceTo(location)
it.distance = distance.toDouble()
updatedCameras.add(it)
@@ -533,7 +532,11 @@ class NavigationScreen(
val camera = sortedList.first()
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
val bearingSpeedCamera = if (camera.tags.direction != null) {
camera.tags.direction!!.toFloat()
try {
camera.tags.direction!!.toFloat()
} catch ( e: Exception) {
0F
}
} else {
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
}

View File

@@ -34,7 +34,7 @@ class SearchScreen(
var categories: List<Category> = listOf(
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
// Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)),
Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites))
)

View File

@@ -1,11 +1,5 @@
package com.kouros.navigation.car
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.data.SearchFilter
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel
@@ -24,16 +18,16 @@ class ViewModelTest {
@Test
fun routeViewModelTest() {
val fromLocation = Location(LocationManager.GPS_PROVIDER)
fromLocation.isMock = true
fromLocation.latitude = homeLocation.latitude
fromLocation.longitude = homeLocation.longitude
val toLocation = Location(LocationManager.GPS_PROVIDER)
toLocation.isMock = true
toLocation.latitude = home2Location.latitude
toLocation.longitude = home2Location.longitude
val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
// val fromLocation = Location(LocationManager.GPS_PROVIDER)
// fromLocation.isMock = true
// fromLocation.latitude = homeLocation.latitude
// fromLocation.longitude = homeLocation.longitude
// val toLocation = Location(LocationManager.GPS_PROVIDER)
// toLocation.isMock = true
// toLocation.latitude = home2Location.latitude
// toLocation.longitude = home2Location.longitude
//
// val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
//model.startNavigation(route)
}
}