Before TomTom Routing

This commit is contained in:
Dimitris
2026-01-29 12:13:37 +01:00
parent 7db7cba4fb
commit eac5b56bcb
51 changed files with 5825 additions and 212 deletions

View File

@@ -30,21 +30,18 @@ import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.car.screen.RequestPermissionScreen
import com.kouros.navigation.car.screen.SearchScreen
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.CAR_LOCATION
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.tomtom.TomTomRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getViewModel
import org.maplibre.compose.style.BaseStyle
class NavigationSession : Session(), NavigationScreen.Listener {
@@ -93,8 +90,6 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var navigationViewModel: ViewModel
lateinit var baseStyle: BaseStyle.Json
val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
OnCarDataAvailableListener { data ->
if (data.location.status == CarValue.STATUS_SUCCESS) {
@@ -129,21 +124,21 @@ class NavigationSession : Session(), NavigationScreen.Listener {
fun onRoutingEngineStateUpdated(routeEngine : Int) {
navigationViewModel = when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
else -> ViewModel(OsrmRepository())
RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository())
else -> ViewModel(TomTomRepository())
}
}
override fun onCreateScreen(intent: Intent): Screen {
navigationViewModel = getViewModel(carContext)
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
routeModel = RouteCarModel()
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, baseStyle)
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
navigationScreen =
NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)

View File

@@ -29,10 +29,13 @@ import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues
import com.kouros.navigation.car.map.rememberBaseStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.tomtom.TrafficData
import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing
@@ -49,8 +52,7 @@ import org.maplibre.spatialk.geojson.Position
class SurfaceRenderer(
private var carContext: CarContext, lifecycle: Lifecycle,
private var routeModel: RouteCarModel,
private var baseStyle: BaseStyle.Json
private var routeModel: RouteCarModel
) : DefaultLifecycleObserver {
var lastLocation = location(0.0, 0.0)
@@ -70,14 +72,20 @@ class SurfaceRenderer(
var height = 0
var lastBearing = 0.0
val routeData = MutableLiveData("")
val trafficData = MutableLiveData(emptyMap<String, String>())
val speedCamerasData = MutableLiveData("")
val speed = MutableLiveData(0F)
lateinit var centerLocation: Location
var viewStyle = ViewStyle.VIEW
lateinit var centerLocation: Location
var previewDistance = 0.0
lateinit var mapView: ComposeView
var tilt = 55.0
var countDownTimerActive = false
val style: MutableLiveData<BaseStyle> by lazy {
MutableLiveData()
}
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
lateinit var lifecycleOwner: CustomLifecycleOwner
@@ -160,6 +168,7 @@ class SurfaceRenderer(
init {
lifecycle.addObserver(this)
speed.value = 0F
}
fun onConnectionStateUpdated(connectionState: Int) {
@@ -170,16 +179,24 @@ class SurfaceRenderer(
}
}
fun onBaseStyleStateUpdated(style: BaseStyle) {
}
@Composable
fun MapView() {
//println("DarkMode ${carContext.isDarkMode}")
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState()
val traffic: Map<String, String> ? by trafficData.observeAsState()
val speedCameras: String? by speedCamerasData.observeAsState()
val paddingValues = getPaddingValues(height, viewStyle)
val cameraState = cameraState(paddingValues, position, tilt)
val rememberBaseStyle = rememberBaseStyle(baseStyle)
MapLibre(carContext, cameraState, rememberBaseStyle, route, viewStyle, speedCameras)
MapLibre(carContext, cameraState, rememberBaseStyle, route, traffic, viewStyle, speedCameras)
ShowPosition(cameraState, position, paddingValues)
}
@@ -217,6 +234,7 @@ class SurfaceRenderer(
override fun onCreate(owner: LifecycleOwner) {
CarConnection(carContext).type.observe(owner, ::onConnectionStateUpdated)
style.observe(owner, :: onBaseStyleStateUpdated)
Log.i(TAG, "SurfaceRenderer created")
carContext.getCarService(AppManager::class.java)
.setSurfaceCallback(mSurfaceCallback)
@@ -288,6 +306,10 @@ class SurfaceRenderer(
viewStyle = ViewStyle.VIEW
}
fun setTrafficData(traffic: Map<String, String> ) {
trafficData.value = traffic as MutableMap<String, String>?
}
fun setPreviewRouteData(routeModel: RouteModel) {
viewStyle = ViewStyle.PREVIEW
with(routeModel) {

View File

@@ -2,6 +2,7 @@ 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
@@ -10,18 +11,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
@@ -34,19 +32,21 @@ 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.model.BaseStyleModel
import com.kouros.navigation.data.tomtom.TrafficData
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.ast.Expression
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.expressions.dsl.exponential
import org.maplibre.compose.expressions.dsl.image
import org.maplibre.compose.expressions.dsl.interpolate
import org.maplibre.compose.expressions.dsl.zoom
import org.maplibre.compose.expressions.value.ColorValue
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
@@ -92,10 +92,10 @@ fun MapLibre(
cameraState: CameraState,
baseStyle: BaseStyle.Json,
route: String?,
traffic: Map<String, String> ?,
viewStyle: ViewStyle,
speedCameras: String? = ""
) {
MaplibreMap(
options = MapOptions(
ornamentOptions =
@@ -111,7 +111,7 @@ fun MapLibre(
if (viewStyle == ViewStyle.AMENITY_VIEW) {
AmenityLayer(route)
} else {
RouteLayer(route)
RouteLayer(route, traffic!!)
}
SpeedCameraLayer(speedCameras)
}
@@ -121,7 +121,7 @@ fun MapLibre(
}
@Composable
fun RouteLayer(routeData: String?) {
fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
if (routeData != null && routeData.isNotEmpty()) {
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
LineLayer(
@@ -153,6 +153,49 @@ fun RouteLayer(routeData: String?) {
),
)
}
trafficData.forEach {
val traffic = rememberGeoJsonSource(GeoJsonData.JsonString(it.value))
LineLayer(
id = "traffic-${it.key}-casing",
source = traffic,
color = const(Color.White),
width =
interpolate(
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),
),
)
LineLayer(
id = "traffic-${it.key}",
source = traffic,
color = trafficColor(it.key),
width =
interpolate(
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),
),
)
}
}
fun trafficColor(key: String): Expression<ColorValue> {
when (key) {
"queuing" -> return const(Color(0xFFD24417))
"stationary" -> return const(Color(0xFFFF0000))
"heavy" -> return const(Color(0xFF6B0404))
"slow" -> return const(Color(0xFFC41F1F))
"roadworks" -> return const(Color(0xFF7A631A))
}
return const(Color.Blue)
}
@Composable
@@ -270,7 +313,7 @@ private fun CurrentSpeed(
curSpeed: Float,
maxSpeed: Int
) {
val radius = 32
val radius = 34
Box(
modifier = Modifier
.padding(
@@ -336,7 +379,7 @@ private fun MaxSpeed(
height: Int,
maxSpeed: Int,
) {
val radius = 20
val radius = 24
Box(
modifier = Modifier
.padding(

View File

@@ -16,6 +16,7 @@
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
@@ -26,22 +27,25 @@ 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
import androidx.car.app.navigation.model.Lane
import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
import androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
import androidx.car.app.navigation.model.Step
import androidx.car.app.navigation.model.TravelEstimate
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 java.util.Collections
import java.util.TimeZone
import java.util.concurrent.TimeUnit
import kotlin.text.trim
/** A class that provides models for the routing demos. */
class RouteCarModel() : RouteModel() {
@@ -50,13 +54,18 @@ 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) {
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
}
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon))
.build()
maneuver.build()
)
if (destination.street != null) {
step.setRoad(destination.street!!)
@@ -72,12 +81,16 @@ class RouteCarModel() : RouteModel() {
val stepData = nextStep()
val currentStepCueWithImage: SpannableString =
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) {
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
}
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon))
.build()
maneuver.build()
)
.build()
return step
@@ -109,8 +122,8 @@ class RouteCarModel() : RouteModel() {
timeToDestinationMillis
)
)
.setRemainingTimeColor(CarColor.YELLOW)
.setRemainingDistanceColor(CarColor.RED)
.setRemainingTimeColor(CarColor.GREEN)
.setRemainingDistanceColor(CarColor.BLUE)
if (travelMessage.isNotEmpty()) {
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
@@ -203,6 +216,18 @@ class RouteCarModel() : RouteModel() {
}
}
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 {
@@ -222,7 +247,7 @@ class RouteCarModel() : RouteModel() {
return CarIcon.Builder(iconCompat).build()
}
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
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)))
}

View File

@@ -31,10 +31,16 @@ import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.tomtom.Features
import com.kouros.navigation.data.tomtom.Geometry
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.location
import java.time.LocalDateTime
import java.time.Period
import java.time.ZoneOffset
import kotlin.math.absoluteValue
import kotlin.time.Duration
class NavigationScreen(
carContext: CarContext,
@@ -55,6 +61,7 @@ class NavigationScreen(
var recentPlace = Place()
var navigationType = NavigationType.VIEW
var lastTrafficDate = LocalDateTime.of(1960, 6, 21, 0, 0)
val observer = Observer<String> { route ->
if (route.isNotEmpty()) {
navigationType = NavigationType.NAVIGATION
@@ -71,6 +78,10 @@ class NavigationScreen(
invalidate()
}
}
val trafficObserver = Observer<Map<String, String> > { traffic ->
surfaceRenderer.setTrafficData(traffic)
invalidate()
}
val placeObserver = Observer<SearchResult> { searchResult ->
val place = Place(
@@ -101,6 +112,7 @@ class NavigationScreen(
init {
viewModel.route.observe(this, observer)
viewModel.traffic.observe(this, trafficObserver);
viewModel.recentPlace.observe(this, recentObserver)
viewModel.placeLocation.observe(this, placeObserver)
viewModel.speedCameras.observe(this, speedObserver)
@@ -471,7 +483,14 @@ class NavigationScreen(
}
fun updateTrip(location: Location) {
updateSpeedCamera(surfaceRenderer.lastLocation)
val current = LocalDateTime.now(ZoneOffset.UTC)
val duration = java.time.Duration.between(current, lastTrafficDate)
if (duration.abs().seconds > 360) {
lastTrafficDate = current
viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
}
//updateTraffic(location)
updateSpeedCamera(location)
with(routeModel) {
updateLocation(location, viewModel)
if ((maneuverType == Maneuver.TYPE_DESTINATION
@@ -512,11 +531,14 @@ class NavigationScreen(
}
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
val camera = sortedList.first()
val bearingSpeedCamera = location.bearingTo(location(camera.lon!!, camera.lat!!))
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
val bearingSpeedCamera = if (camera.tags.direction != null) {
camera.tags.direction!!.toFloat()
} else {
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
}
if (camera.distance < 80) {
if ((bearingSpeedCamera.absoluteValue - bearingRoute.absoluteValue).absoluteValue < 20.0) {
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
}
}

View File

@@ -190,7 +190,6 @@ class RoutePreviewScreen(
private fun createRouteText(index: Int): CarText {
val time = routeModel.route.routes[index].summary.duration
println("Duration $time")
val length =
BigDecimal(routeModel.route.routes[index].summary.distance).setScale(
1,

View File

@@ -23,7 +23,7 @@ import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
class RoutingSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
private var routingEngine = RouteEngine.VALHALLA.ordinal
private var routingEngine = RouteEngine.OSRM.ordinal
init {
@@ -45,6 +45,11 @@ class RoutingSettings(private val carContext: CarContext, private var viewModel:
R.string.osrm,
)
)
.addItem(
buildRowForTemplate(
R.string.tomtom,
)
)
.setOnSelectedListener { index: Int ->
this.onSelected(index)
}