CheckRoute, Simulation, Closed Streets

This commit is contained in:
Dimitris
2026-05-12 09:51:58 +02:00
parent 357a9af4f1
commit 9b4e730dcf
11 changed files with 177 additions and 95 deletions
+2 -2
View File
@@ -17,8 +17,8 @@ android {
applicationId = "com.kouros.navigation" applicationId = "com.kouros.navigation"
minSdk = 33 minSdk = 33
targetSdk = 37 targetSdk = 37
versionCode = 110 versionCode = 111
versionName = "0.3.0.110" versionName = "0.3.0.111"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -111,7 +111,8 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
var speedCameras = listOf<Elements>() var speedCameras = listOf<Elements>()
var lastRouteDate: LocalDateTime = LocalDateTime.now()
private var lastRouteCheckLocation = location(0.0, 0.0)
var navigationManagerStarted = false var navigationManagerStarted = false
@@ -461,6 +462,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
* Snaps location to route and checks for deviation requiring reroute. * Snaps location to route and checks for deviation requiring reroute.
*/ */
private fun handleNavigationLocation(location: Location) { private fun handleNavigationLocation(location: Location) {
val start = System.currentTimeMillis()
routeModel.updateLocation(location, navigationViewModel) routeModel.updateLocation(location, navigationViewModel)
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations()) val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
val streetName = routeModel.currentStep().street val streetName = routeModel.currentStep().street
@@ -472,16 +474,22 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
val currentDate = LocalDateTime.now(ZoneOffset.UTC) val currentDate = LocalDateTime.now(ZoneOffset.UTC)
checkTraffic(currentDate, snappedLocation) checkTraffic(currentDate, snappedLocation)
updateSpeedCamera(snappedLocation) updateSpeedCamera(snappedLocation)
checkRoute(currentDate, snappedLocation) checkRoute(snappedLocation)
updateNavigationScreen() updateNavigationScreen()
checkArrival() checkArrival()
} }
val end = System.currentTimeMillis()
// Log.d(TAG, "UpdateLoction ${end-start} ms")
} }
/** /**
* Checks if the location deviation is acceptable. * Checks if the location deviation is acceptable.
*/ */
private fun checkLocationDeviation(location: Location, snappedLocation: Location, streetName: String): Boolean { private fun checkLocationDeviation(
location: Location,
snappedLocation: Location,
streetName: String
): Boolean {
val distance = location.distanceTo(snappedLocation) val distance = location.distanceTo(snappedLocation)
when { when {
distance > MAXIMAL_ROUTE_DEVIATION -> { distance > MAXIMAL_ROUTE_DEVIATION -> {
@@ -493,6 +501,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
distance < MAXIMAL_SNAP_CORRECTION -> { distance < MAXIMAL_SNAP_CORRECTION -> {
surfaceRenderer.updateLocation(snappedLocation, streetName) surfaceRenderer.updateLocation(snappedLocation, streetName)
} }
else -> { else -> {
surfaceRenderer.updateLocation(location, streetName) surfaceRenderer.updateLocation(location, streetName)
} }
@@ -657,9 +666,11 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
val newRouteModel = RouteModel() val newRouteModel = RouteModel()
newRouteModel.navState = routeModel.navState.copy(routingEngine = routingEngine) newRouteModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
newRouteModel.startNavigation(route) newRouteModel.startNavigation(route)
routeModel.curRoute.summary.trafficDelay = newRouteModel.curRoute.summary.trafficDelay if ((routeModel.curRoute.summary.trafficDelay - newRouteModel.curRoute.summary.trafficDelay).absoluteValue > 300) {
routeModel.startNavigation(route)
updateNavigationScreen() updateNavigationScreen()
} }
}
override fun isNavigating(): Boolean = routeModel.isNavigating() override fun isNavigating(): Boolean = routeModel.isNavigating()
@@ -767,7 +778,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
* Periodically requests speed camera information near the current location. * Periodically requests speed camera information near the current location.
*/ */
private fun updateSpeedCamera(location: Location) { private fun updateSpeedCamera(location: Location) {
if (lastCameraSearch % 100 == 0) { if (lastCameraSearch % 200 == 0) {
navigationViewModel.getSpeedCameras(location, 5.0) navigationViewModel.getSpeedCameras(location, 5.0)
} }
if (speedCameras.isNotEmpty() && lastCameraSearch % 15 == 0) { if (speedCameras.isNotEmpty() && lastCameraSearch % 15 == 0) {
@@ -811,11 +822,11 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
/** /**
* Checks if a new route is needed based on the time since the last update. * Checks if a new route is needed based on the time since the last update.
*/ */
private fun checkRoute(currentDate: LocalDateTime, location: Location) { private fun checkRoute(location: Location) {
val duration = Duration.between(currentDate, lastRouteDate) val distance = location.distanceTo(lastRouteCheckLocation)
val routeUpdate = routeModel.curRoute.summary.duration / 4 if (distance > checkDistance(routeModel)) {
if (duration.abs().seconds > routeUpdate) { Log.d(TAG, "CheckRoute $distance ${checkDistance(routeModel)}")
lastRouteDate = currentDate if (lastRouteCheckLocation.latitude != 0.0) {
val destination = location( val destination = location(
routeModel.navState.destination.longitude, routeModel.navState.destination.longitude,
routeModel.navState.destination.latitude routeModel.navState.destination.latitude
@@ -827,6 +838,18 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
) )
} }
lastRouteCheckLocation = location
}
}
private fun checkDistance(routeModel: RouteModel): Double {
val factor = when (routeModel.curRoute.summary.distance) {
in 0.0..20000.0 -> 4
in 20000.0..100000.0 -> 5
in 10000.0..200000.0 -> 6
else -> 7
}
return (routeModel.curRoute.summary.distance / factor)
} }
override fun invalidateNavigationScreen() { override fun invalidateNavigationScreen() {
@@ -28,9 +28,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.ClosedColor
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.HeavyColor import com.kouros.navigation.data.HeavyColor
import com.kouros.navigation.data.LaneColor import com.kouros.navigation.data.LaneColor
import com.kouros.navigation.data.NavigationColorDark import com.kouros.navigation.data.NavigationColorDark
@@ -43,7 +41,6 @@ import com.kouros.navigation.data.SlowColor
import com.kouros.navigation.data.SpeedColor import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.data.StationaryColor import com.kouros.navigation.data.StationaryColor
import com.kouros.navigation.data.ViewStyle import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.utils.GeoUtils.createEndCollection
import com.kouros.navigation.utils.GeoUtils.createPointCollection import com.kouros.navigation.utils.GeoUtils.createPointCollection
import com.kouros.navigation.utils.isMetricSystem import com.kouros.navigation.utils.isMetricSystem
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
@@ -143,7 +140,7 @@ fun MapLibre(
@Composable @Composable
fun StartEndLayer(routeData: String?) { fun StartEndLayer(routeData: String?) {
if (!routeData.isNullOrEmpty()) { if (!routeData.isNullOrEmpty()) {
val end = createEndCollection(routeData) val end = createPointCollection(routeData)
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(end)) val routes = rememberGeoJsonSource(GeoJsonData.JsonString(end))
val img = image(painterResource(R.drawable.sports_score_48px), drawAsSdf = true) val img = image(painterResource(R.drawable.sports_score_48px), drawAsSdf = true)
SymbolLayer( SymbolLayer(
@@ -186,6 +183,9 @@ fun RouteLayer(routeData: String?) {
@Composable @Composable
fun TrafficLayer(trafficData: Map<String, String>) { fun TrafficLayer(trafficData: Map<String, String>) {
trafficData.forEach { trafficData.forEach {
if (it.key == "closed") {
ClosedLayer(it)
} else {
val traffic = rememberGeoJsonSource(GeoJsonData.JsonString(it.value)) val traffic = rememberGeoJsonSource(GeoJsonData.JsonString(it.value))
LineLayer( LineLayer(
id = "traffic-${it.key}-casing", id = "traffic-${it.key}-casing",
@@ -199,10 +199,32 @@ fun TrafficLayer(trafficData: Map<String, String>) {
color = trafficColor(it.key), color = trafficColor(it.key),
width = routeLineWidth(base = 1.dp, isCasing = false), width = routeLineWidth(base = 1.dp, isCasing = false),
) )
}
// TrafficIcons(it) // TrafficIcons(it)
} }
} }
@Composable
private fun ClosedLayer(entry: Map.Entry<String, String>) {
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(entry.value))
val img = image(painterResource(R.drawable.do_not_disturb_on_24px), drawAsSdf = true)
SymbolLayer(
id = "closed-layer",
source = routes,
iconColor = const(Color.Red),
iconImage = img,
iconSize =
interpolate(
type = exponential(2.0f),
input = zoom(),
5 to const(2.0f),
10 to const(2.0f),
15 to const(3.0f),
20 to const(4.0f),
),
)
}
@Composable @Composable
private fun TrafficIcons(entry: Map.Entry<String, String>) { private fun TrafficIcons(entry: Map.Entry<String, String>) {
val featureCollection = FeatureCollection.fromJson(entry.value) val featureCollection = FeatureCollection.fromJson(entry.value)
@@ -307,7 +329,6 @@ fun trafficColor(key: String): Expression<ColorValue> {
"stationary" -> return const(StationaryColor) "stationary" -> return const(StationaryColor)
"heavy" -> return const(HeavyColor) "heavy" -> return const(HeavyColor)
"roadworks" -> return const(RoadworksColor) "roadworks" -> return const(RoadworksColor)
"closed" -> return const(ClosedColor)
"lane" -> return const(LaneColor) "lane" -> return const(LaneColor)
} }
return const(Color.Blue) return const(Color.Blue)
@@ -56,14 +56,14 @@ class Simulation {
longitude = point[0] longitude = point[0]
bearing = curBearing bearing = curBearing
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
speed = 9.0f speed = 10.0f
time = System.currentTimeMillis() time = System.currentTimeMillis()
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos() elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
} }
// Update your app's state as if a real GPS update occurred // Update your app's state as if a real GPS update occurred
updateLocation(fakeLocation) updateLocation(fakeLocation)
// Wait before moving to the next point (e.g., every 1 second) // Wait before moving to the next point (e.g., every 1 second)
delay(500) delay(1500)
lastLocation = fakeLocation lastLocation = fakeLocation
} }
} }
@@ -3,7 +3,7 @@ package com.kouros.navigation.data
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val NavigationColorLight = Color(0xFF17A119) val NavigationColorLight = Color(0xFF17A119)
val NavigationColorDark = Color(0xFF1055DE) val NavigationColorDark = Color(0xFF2B007A)
val RouteColor = Color(0xFF5201B4) val RouteColor = Color(0xFF5201B4)
@@ -18,7 +18,6 @@ val SlowColor = Color(0xFFC43E3E)
val StationaryColor = Color(0xFF910A0A) val StationaryColor = Color(0xFF910A0A)
val HeavyColor = Color(0xFF6B0404) val HeavyColor = Color(0xFF6B0404)
val RoadworksColor = Color(0xFFDAA707) val RoadworksColor = Color(0xFFDAA707)
val ClosedColor = Color(0xFF280C10)
val LaneColor = Color(0xFFEFBAC1) val LaneColor = Color(0xFFEFBAC1)
val PharmacyColor = Color(0xFF054603) val PharmacyColor = Color(0xFF054603)
@@ -166,6 +166,8 @@ object Constants {
const val TILT = 60.0 const val TILT = 60.0
const val TANKER_KOENIG_DELAY = 300000 const val TANKER_KOENIG_DELAY = 300000
const val LAST_CHECK_ROUTE_DISTANCE = 5000
} }
/** /**
@@ -2,7 +2,6 @@ package com.kouros.navigation.model
import android.content.Context import android.content.Context
import android.location.Location import android.location.Location
import android.util.Log
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@@ -14,7 +13,6 @@ import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.FAVORITES import com.kouros.navigation.data.Constants.FAVORITES
import com.kouros.navigation.data.Constants.SPEED_BEARING_DEVIATION import com.kouros.navigation.data.Constants.SPEED_BEARING_DEVIATION
import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.Constants.TANKER_KOENIG_DELAY import com.kouros.navigation.data.Constants.TANKER_KOENIG_DELAY
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
@@ -30,7 +28,8 @@ import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.overpass.Overpass import com.kouros.navigation.data.overpass.Overpass
import com.kouros.navigation.repository.SettingsRepository import com.kouros.navigation.repository.SettingsRepository
import com.kouros.navigation.utils.GeoUtils.snapLocation import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.GeoUtils.transferCoordinates import com.kouros.navigation.utils.GeoUtils.buildLineStrings
import com.kouros.navigation.utils.GeoUtils.buildPointCollection
import com.kouros.navigation.utils.bearingPositive import com.kouros.navigation.utils.bearingPositive
import com.kouros.navigation.utils.countryCodeSpeedLimit import com.kouros.navigation.utils.countryCodeSpeedLimit
import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsRepository
@@ -290,25 +289,25 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
val incidents = mutableMapOf<String, String>() val incidents = mutableMapOf<String, String>()
val queuing = featureCollection.features()!! val queuing = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Queuing traffic") } .filter { it.properties()!!.get("events").toString().contains("Queuing traffic") }
incidents["queuing"] = transferCoordinates(queuing) incidents["queuing"] = buildLineStrings(queuing)
val stationary = featureCollection.features()!! val stationary = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Stationary traffic") } .filter { it.properties()!!.get("events").toString().contains("Stationary traffic") }
incidents["stationary"] = transferCoordinates(stationary) incidents["stationary"] = buildLineStrings(stationary)
val slow = featureCollection.features()!! val slow = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Slow traffic") } .filter { it.properties()!!.get("events").toString().contains("Slow traffic") }
incidents["slow"] = transferCoordinates(slow) incidents["slow"] = buildLineStrings(slow)
val heavy = featureCollection.features()!! val heavy = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Heavy traffic") } .filter { it.properties()!!.get("events").toString().contains("Heavy traffic") }
incidents["heavy"] = transferCoordinates(heavy) incidents["heavy"] = buildLineStrings(heavy)
val roadworks = featureCollection.features()!! val roadworks = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Roadworks") } .filter { it.properties()!!.get("events").toString().contains("Roadworks") }
incidents["roadworks"] = transferCoordinates(roadworks) incidents["roadworks"] = buildLineStrings(roadworks)
val laneClosed = featureCollection.features()!! val laneClosed = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Lane closed") } .filter { it.properties()!!.get("events").toString().contains("Lane closed") }
incidents["lane"] = transferCoordinates(laneClosed) incidents["lane"] = buildLineStrings(laneClosed)
val closed = featureCollection.features()!! val closed = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Closed") } .filter { it.properties()!!.get("events").toString().contains("Closed") }
incidents["closed"] = transferCoordinates(closed) incidents["closed"] = buildPointCollection(closed)
return incidents return incidents
} }
@@ -10,6 +10,7 @@ import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.dsl.addFeature import org.maplibre.spatialk.geojson.dsl.addFeature
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
import org.maplibre.spatialk.geojson.dsl.buildLineString import org.maplibre.spatialk.geojson.dsl.buildLineString
import org.maplibre.spatialk.geojson.dsl.buildMultiPoint
import org.maplibre.spatialk.geojson.toJson import org.maplibre.spatialk.geojson.toJson
import org.maplibre.turf.TurfMeasurement import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMisc import org.maplibre.turf.TurfMisc
@@ -136,7 +137,7 @@ object GeoUtils {
return points return points
} }
fun createEndCollection(geoJson: String): String { fun createPointCollection(geoJson: String): String {
val featureCollection = FeatureCollection.fromJson(geoJson) val featureCollection = FeatureCollection.fromJson(geoJson)
val geometry = featureCollection.features()!!.first().geometry() val geometry = featureCollection.features()!!.first().geometry()
val coordinates = (geometry as LineString) val coordinates = (geometry as LineString)
@@ -187,7 +188,7 @@ object GeoUtils {
return isInside return isInside
} }
fun transferCoordinates(features: List<org.maplibre.geojson.Feature>): String { fun buildLineStrings(features: List<org.maplibre.geojson.Feature>): String {
return buildFeatureCollection { return buildFeatureCollection {
features.forEach { features.forEach {
val movedCoordinates = arrayListOf<org.maplibre.spatialk.geojson.Point>() val movedCoordinates = arrayListOf<org.maplibre.spatialk.geojson.Point>()
@@ -225,6 +226,31 @@ object GeoUtils {
}.toJson() }.toJson()
} }
fun buildPointCollection(features: List<org.maplibre.geojson.Feature>): String {
return buildFeatureCollection {
features.forEach {
val movedCoordinates = arrayListOf<org.maplibre.spatialk.geojson.Point>()
val coordinates = it.geometry() as LineString
val geo = coordinates.coordinates()
var point = org.maplibre.spatialk.geojson.Point(geo.first().longitude(), geo.first().latitude())
movedCoordinates.add(point)
point = org.maplibre.spatialk.geojson.Point(geo.last().longitude(), geo.last().latitude())
movedCoordinates.add(point)
val multiPoint = buildMultiPoint {
movedCoordinates.forEach { ft ->
add(
ft
)
}
}
addFeature {
geometry = multiPoint
properties = buildJsonObject { null }
}
}
}.toJson()
}
fun movePoint( fun movePoint(
latitude: Double, latitude: Double,
longitude: Double, longitude: Double,
@@ -49,18 +49,20 @@ object NavigationUtils {
} }
fun calculateZoom(speed: Double?): Double { fun calculateZoom(speed: Double?): Double {
val zoom = 16.5
if (speed == null) { if (speed == null) {
return 17.0 return zoom
} }
val speedKmh = (speed * 3.6).toInt() val speedKmh = (speed * 3.6).toInt()
val zoom = when (speedKmh) { return when (speedKmh) {
in 0..10 -> 17.0 in 0..10 -> zoom + 1.0
in 11..30 -> 17.5 in 11..30 -> zoom + 0.5
in 31..65 -> 17.0 in 31..65 -> zoom
in 66..70 -> 16.5 in 66..70 -> zoom - 0.5
else -> 16.0 in 71..90 -> zoom - 1.0
in 91..100 -> zoom - 2.0
else -> zoom - 3.0
} }
return zoom
} }
fun previewZoom(centerLocation: Location, previewDistance: Double): Double { fun previewZoom(centerLocation: Location, previewDistance: Double): Double {
@@ -0,0 +1,10 @@
<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="M280,520L680,520L680,440L280,440L280,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z"/>
</vector>