This commit is contained in:
Dimitris
2026-04-28 16:52:48 +02:00
parent 42b3eabe47
commit ae4d3f321e
17 changed files with 268 additions and 50 deletions
@@ -16,6 +16,7 @@ import androidx.car.app.navigation.model.Trip
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
@@ -60,6 +61,7 @@ import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch
import org.maplibre.geojson.FeatureCollection
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneOffset
@@ -116,6 +118,8 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
var lastAlert : Long = 0
var navigationManagerStarted = false
var updateLocationIndex = 0
/**
* Lifecycle observer for managing session lifecycle events.
* Cleans up resources when the session is destroyed.
@@ -438,6 +442,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location)
surfaceRenderer.updateLocation(location, streetName)
}
updateLocationIndex++
}
/**
@@ -471,7 +476,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
checkArrival()
}
val endTime = System.currentTimeMillis() - startTime
//Log.d(TAG, "handleNavigationLocation: $endTime")
Log.d(TAG, "handleNavigationLocation: $endTime")
}
/**
@@ -507,14 +512,14 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
return
}
val stepData = routeModel.currentStep() // 121 ms
val stepData = routeModel.currentStep()
navigationScreen.updateTrip( // 277 ms
navigationScreen.updateTrip(
isNavigating = routeModel.isNavigating(),
isRerouting = false,
hasArrived = routeModel.isArrival(),
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext), // 60 ms
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext), // 60 ms
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext),
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext),
destinations = mutableListOf(routeModel.getDestination()),
steps = routeModel.getSteps(carContext),
stepRemainingDistance = routeModel.getDistance(),
@@ -529,7 +534,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
* Updates the trip information and notifies the listener with a new Trip object.
* This includes destination name, address, travel estimate, and loading status.
*/
updateTrip(routeModel.getTrip(carContext)) // 230 ms
updateTrip(routeModel.getTrip(carContext))
}
/**
@@ -703,6 +708,10 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
navigationScreen.invalidate()
}
override fun onTrafficMessageReceived(trafficMessage: String) {
}
/**
* Loads a route to the specified place and sets it as the destination.
*/
@@ -748,6 +757,12 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
lastTrafficDate = current
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
}
if (updateLocationIndex % 4 == 0) {
// navigationViewModel.updateTrafficMessages(
// surfaceRenderer.lastLocation,
// surfaceRenderer.trafficData.value!!
// )
}
}
/**
@@ -1,6 +1,7 @@
package com.kouros.navigation.car.map
import android.location.Location
import android.util.Log
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
@@ -28,12 +29,15 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.kouros.data.R
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.NavigationColorDark
import com.kouros.navigation.data.NavigationColorLight
import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.utils.GeoUtils.createEndCollection
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
import com.kouros.navigation.utils.GeoUtils.createPointCollection
import com.kouros.navigation.utils.isMetricSystem
import com.kouros.navigation.utils.location
import org.maplibre.compose.camera.CameraPosition
@@ -47,6 +51,8 @@ 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.expressions.value.DpValue
import org.maplibre.compose.expressions.value.ExpressionValue
import org.maplibre.compose.expressions.value.ImageValue
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
@@ -61,6 +67,8 @@ import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.LineString
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
@@ -131,6 +139,7 @@ fun MapLibre(
fun StartEndLayer(routeData: String?) {
if (!routeData.isNullOrEmpty()) {
val end = createEndCollection(routeData)
Log.d("Map End", " $end")
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(end))
val img = image(painterResource(R.drawable.sports_score_48px), drawAsSdf = true)
SymbolLayer(
@@ -186,9 +195,49 @@ fun TrafficLayer(trafficData: Map<String, String>) {
color = trafficColor(it.key),
width = routeLineWidth(base = 2.dp, isCasing = false),
)
val featureCollection = FeatureCollection.fromJson(it.value)
if (featureCollection.features()!!.isNotEmpty()) {
val points = mutableListOf<List<Double>>()
featureCollection.features()!!.forEach { geo ->
val coordinates = (geo.geometry() as LineString).coordinates()
if (coordinates.size > 5) {
val first = coordinates.first()
val second = coordinates[1]
points.add(listOf(first.coordinates()[0], second.coordinates()[1]))
}
}
val collection = createPointCollection(
points, it.key
)
TrafficPoint(it.key, "${it.key}-point-layer", collection)
}
}
}
@Composable
fun TrafficPoint(id: String, layer: String, routeData: String?) {
if (!routeData.isNullOrEmpty()) {
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
val img = trafficImage(id)
SymbolLayer(
id = layer,
source = routes,
iconColor = const(Color.Red),
iconImage = img,
iconSize =
interpolate(
type = exponential(2f),
input = zoom(),
5 to const(0.4f),
6 to const(1.6f),
7 to const(2.0f),
20 to const(5.0f),
),
)
}
}
/**
* Reusable helper for consistent line widths across all route layers.
*/
@@ -198,12 +247,12 @@ private fun routeLineWidth(base: Dp, isCasing: Boolean = false): Expression<DpVa
val width = base + extra
return interpolate(
type = exponential(1.1f),
type = exponential(2f),
input = zoom(),
5 to const(width + 3.dp),
10 to const(width + 8.dp),
1 to const(width + 3.dp),
15 to const(width + 13.dp),
20 to const(width + 18.dp),
18 to const(width + 15.dp),
24 to const(width + 24.dp),
)
}
@@ -232,6 +281,16 @@ fun RouteLayerPoint(routeData: String?) {
}
@Composable
fun trafficImage(key: String): Expression<ImageValue> {
when (key) {
"heavy", "queuing", "slow", "stationary" -> return image(painterResource(R.drawable.traffic_jam_24px), drawAsSdf = true)
"roadworks" -> return image(painterResource(R.drawable.construction_24px), drawAsSdf = true)
"closed", "lane" -> return image(painterResource(R.drawable.remove_road_24px), drawAsSdf = true)
}
return image(painterResource(R.drawable.traffic_jam_24px), drawAsSdf = true)
}
fun trafficColor(key: String): Expression<ColorValue> {
when (key) {
"queuing" -> return const(Color(0xFFC46E53))
@@ -239,6 +298,8 @@ fun trafficColor(key: String): Expression<ColorValue> {
"stationary" -> return const(Color(0xFF910A0A))
"heavy" -> return const(Color(0xFF6B0404))
"roadworks" -> return const(Color(0xFFDAA707))
"closed" -> return const(Color(0xFFDA0727))
"lane" -> return const(Color(0xFFEFBAC1))
}
return const(Color.Blue)
}
@@ -29,4 +29,6 @@ interface NavigationObserverCallback {
/** Called to request UI invalidation/refresh */
fun invalidateScreen()
fun onTrafficMessageReceived(trafficMessage: String)
}
@@ -14,6 +14,8 @@ class NavigationObserverManager(
val routeObserver = RouteObserver(callback)
val trafficObserver = TrafficObserver(callback)
val trafficMessageObserver = TrafficMessageObserver(callback)
val placeSearchObserver = PlaceSearchObserver(callback)
val speedCameraObserver = SpeedCameraObserver(callback)
val maxSpeedObserver = MaxSpeedObserver(callback)
@@ -21,6 +23,7 @@ class NavigationObserverManager(
fun attachAllObservers(session: NavigationSession) {
viewModel.route.observe(session, routeObserver)
viewModel.traffic.observe(session, trafficObserver)
viewModel.trafficMessage.observe(session, trafficMessageObserver)
viewModel.placeLocation.observe(session, placeSearchObserver)
viewModel.speedCameras.observe(session, speedCameraObserver)
viewModel.maxSpeed.observe(session, maxSpeedObserver)
@@ -0,0 +1,16 @@
package com.kouros.navigation.car.screen.observers
import androidx.lifecycle.Observer
/**
* Observer for traffic data updates.
*/
class TrafficMessageObserver(
private val callback: NavigationObserverCallback
) : Observer<String> {
override fun onChanged(value: String) {
callback.onTrafficMessageReceived(value)
callback.invalidateScreen()
}
}