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
+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 = 103 versionCode = 104
versionName = "0.3.0.103" versionName = "0.3.0.104"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -16,6 +16,7 @@ import androidx.car.app.navigation.model.Trip
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.ViewModelStoreOwner
@@ -60,6 +61,7 @@ import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.maplibre.geojson.FeatureCollection
import java.time.Duration import java.time.Duration
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
@@ -116,6 +118,8 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
var lastAlert : Long = 0 var lastAlert : Long = 0
var navigationManagerStarted = false var navigationManagerStarted = false
var updateLocationIndex = 0
/** /**
* Lifecycle observer for managing session lifecycle events. * Lifecycle observer for managing session lifecycle events.
* Cleans up resources when the session is destroyed. * Cleans up resources when the session is destroyed.
@@ -438,6 +442,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location) checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location)
surfaceRenderer.updateLocation(location, streetName) surfaceRenderer.updateLocation(location, streetName)
} }
updateLocationIndex++
} }
/** /**
@@ -471,7 +476,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
checkArrival() checkArrival()
} }
val endTime = System.currentTimeMillis() - startTime 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 return
} }
val stepData = routeModel.currentStep() // 121 ms val stepData = routeModel.currentStep()
navigationScreen.updateTrip( // 277 ms navigationScreen.updateTrip(
isNavigating = routeModel.isNavigating(), isNavigating = routeModel.isNavigating(),
isRerouting = false, isRerouting = false,
hasArrived = routeModel.isArrival(), hasArrived = routeModel.isArrival(),
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext), // 60 ms destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext),
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext), // 60 ms stepTravelEstimate = routeModel.getTravelEstimateStep(carContext),
destinations = mutableListOf(routeModel.getDestination()), destinations = mutableListOf(routeModel.getDestination()),
steps = routeModel.getSteps(carContext), steps = routeModel.getSteps(carContext),
stepRemainingDistance = routeModel.getDistance(), 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. * Updates the trip information and notifies the listener with a new Trip object.
* This includes destination name, address, travel estimate, and loading status. * 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() navigationScreen.invalidate()
} }
override fun onTrafficMessageReceived(trafficMessage: String) {
}
/** /**
* Loads a route to the specified place and sets it as the destination. * Loads a route to the specified place and sets it as the destination.
*/ */
@@ -748,6 +757,12 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
lastTrafficDate = current lastTrafficDate = current
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation) 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 package com.kouros.navigation.car.map
import android.location.Location import android.location.Location
import android.util.Log
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -28,12 +29,15 @@ 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.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.NavigationColorDark import com.kouros.navigation.data.NavigationColorDark
import com.kouros.navigation.data.NavigationColorLight import com.kouros.navigation.data.NavigationColorLight
import com.kouros.navigation.data.RouteColor import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor import com.kouros.navigation.data.SpeedColor
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.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.isMetricSystem
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import org.maplibre.compose.camera.CameraPosition 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.dsl.zoom
import org.maplibre.compose.expressions.value.ColorValue import org.maplibre.compose.expressions.value.ColorValue
import org.maplibre.compose.expressions.value.DpValue 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.Anchor
import org.maplibre.compose.layers.FillLayer import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer 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.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.LineString
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -131,6 +139,7 @@ fun MapLibre(
fun StartEndLayer(routeData: String?) { fun StartEndLayer(routeData: String?) {
if (!routeData.isNullOrEmpty()) { if (!routeData.isNullOrEmpty()) {
val end = createEndCollection(routeData) val end = createEndCollection(routeData)
Log.d("Map End", " $end")
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,8 +195,48 @@ fun TrafficLayer(trafficData: Map<String, String>) {
color = trafficColor(it.key), color = trafficColor(it.key),
width = routeLineWidth(base = 2.dp, isCasing = false), 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. * 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 val width = base + extra
return interpolate( return interpolate(
type = exponential(1.1f), type = exponential(2f),
input = zoom(), input = zoom(),
5 to const(width + 3.dp), 1 to const(width + 3.dp),
10 to const(width + 8.dp),
15 to const(width + 13.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> { fun trafficColor(key: String): Expression<ColorValue> {
when (key) { when (key) {
"queuing" -> return const(Color(0xFFC46E53)) "queuing" -> return const(Color(0xFFC46E53))
@@ -239,6 +298,8 @@ fun trafficColor(key: String): Expression<ColorValue> {
"stationary" -> return const(Color(0xFF910A0A)) "stationary" -> return const(Color(0xFF910A0A))
"heavy" -> return const(Color(0xFF6B0404)) "heavy" -> return const(Color(0xFF6B0404))
"roadworks" -> return const(Color(0xFFDAA707)) "roadworks" -> return const(Color(0xFFDAA707))
"closed" -> return const(Color(0xFFDA0727))
"lane" -> return const(Color(0xFFEFBAC1))
} }
return const(Color.Blue) return const(Color.Blue)
} }
@@ -29,4 +29,6 @@ interface NavigationObserverCallback {
/** Called to request UI invalidation/refresh */ /** Called to request UI invalidation/refresh */
fun invalidateScreen() fun invalidateScreen()
fun onTrafficMessageReceived(trafficMessage: String)
} }
@@ -14,6 +14,8 @@ class NavigationObserverManager(
val routeObserver = RouteObserver(callback) val routeObserver = RouteObserver(callback)
val trafficObserver = TrafficObserver(callback) val trafficObserver = TrafficObserver(callback)
val trafficMessageObserver = TrafficMessageObserver(callback)
val placeSearchObserver = PlaceSearchObserver(callback) val placeSearchObserver = PlaceSearchObserver(callback)
val speedCameraObserver = SpeedCameraObserver(callback) val speedCameraObserver = SpeedCameraObserver(callback)
val maxSpeedObserver = MaxSpeedObserver(callback) val maxSpeedObserver = MaxSpeedObserver(callback)
@@ -21,6 +23,7 @@ class NavigationObserverManager(
fun attachAllObservers(session: NavigationSession) { fun attachAllObservers(session: NavigationSession) {
viewModel.route.observe(session, routeObserver) viewModel.route.observe(session, routeObserver)
viewModel.traffic.observe(session, trafficObserver) viewModel.traffic.observe(session, trafficObserver)
viewModel.trafficMessage.observe(session, trafficMessageObserver)
viewModel.placeLocation.observe(session, placeSearchObserver) viewModel.placeLocation.observe(session, placeSearchObserver)
viewModel.speedCameras.observe(session, speedCameraObserver) viewModel.speedCameras.observe(session, speedCameraObserver)
viewModel.maxSpeed.observe(session, maxSpeedObserver) 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()
}
}
@@ -0,0 +1,44 @@
package com.kouros.navigation.data
import android.util.Log
import java.io.File
data class ApplicationConfig(
val user: String,
val password: String
) {
companion object {
fun load(): ApplicationConfig {
fun Map<String, String>.envOrLookup(key: String): String {
return System.getenv(key) ?: this[key]!!
}
val envVars: Map<String, String> = envFile().let { envFile ->
if (envFile.exists()) {
envFile.readLines()
.map { it.split("=") }
.filter { it.size == 2 }
.associate { it.first().trim() to it.last().trim() }
} else emptyMap()
}
return ApplicationConfig(
user = envVars.envOrLookup("USER"),
password = envVars.envOrLookup("PASSWORD"),
)
}
}
}
fun envFile(): File {
//val path = context.filesDir.absolutePath
//val config = ApplicationConfig.load(context)
return listOf(".env").map {
Log.d("Overpass", it)
File(it)
}.first { it.exists() }
}
@@ -3,8 +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(0xFF413EAD)
val NavigationColorDark = Color(0xFF07568C)
val RouteColor = Color(0xFF5201B4) val RouteColor = Color(0xFF5201B4)
@@ -146,6 +146,8 @@ object Constants {
const val TRAFFIC_UPDATE = 300 const val TRAFFIC_UPDATE = 300
const val TRAFFIC_MESSAGE_UPDATE = 10
const val SPEED_UPDATE_DISTANCE = 600F const val SPEED_UPDATE_DISTANCE = 600F
const val INSTRUCTION_DISTANCE = 50 const val INSTRUCTION_DISTANCE = 50
@@ -4,9 +4,12 @@ import android.location.Location
import android.util.Log import android.util.Log
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.kouros.data.BuildConfig import com.kouros.data.BuildConfig
import com.kouros.navigation.data.ApplicationConfig
import com.kouros.navigation.utils.GeoUtils.getBoundingBox import com.kouros.navigation.utils.GeoUtils.getBoundingBox
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
import java.net.Authenticator
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.PasswordAuthentication
import java.net.URL import java.net.URL
class Overpass { class Overpass {
@@ -94,7 +97,17 @@ class Overpass {
} }
private fun overpassApi(connection: HttpURLConnection, searchQuery: String): List<Elements> { private fun overpassApi(connection: HttpURLConnection, searchQuery: String): List<Elements> {
return try { return try {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(
"kouros",
"eo7sbjyWpmjSVFyELgbfrryqJ6ddNeq9".toCharArray()
)
}
})
connection.outputStream.use { os -> connection.outputStream.use { os ->
OutputStreamWriter(os).use { writer -> OutputStreamWriter(os).use { writer ->
writer.write(searchQuery) writer.write(searchQuery)
@@ -26,6 +26,7 @@ val useLocal = BuildConfig.DEBUG
val useLocalTraffic = BuildConfig.DEBUG val useLocalTraffic = BuildConfig.DEBUG
class TomTomRepository : NavigationRepository() { class TomTomRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
context: Context, context: Context,
currentLocation: Location, currentLocation: Location,
@@ -102,7 +103,7 @@ class TomTomRepository : NavigationRepository() {
if (!showTraffic) { if (!showTraffic) {
return "" return ""
} }
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0) val bbox = calculateSquareRadius(location.latitude, location.longitude, 10.0)
return if (useLocalTraffic) { return if (useLocalTraffic) {
fetchUrl( fetchUrl(
"http://192.168.1.37/tomtom_traffic.json", "http://192.168.1.37/tomtom_traffic.json",
@@ -3,7 +3,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 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
@@ -11,9 +10,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.kouros.navigation.data.ApplicationConfig
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
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.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Places import com.kouros.navigation.data.Places
@@ -36,6 +37,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.maplibre.geojson.FeatureCollection import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.LineString
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
import kotlin.collections.first import kotlin.collections.first
@@ -59,6 +61,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
MutableLiveData() MutableLiveData()
} }
/** LiveData containing categorized traffic messages map */
val trafficMessage: MutableLiveData<String> by lazy {
MutableLiveData()
}
/** LiveData containing a preview route JSON string for route preview screens */ /** LiveData containing a preview route JSON string for route preview screens */
val previewRoute: MutableLiveData<String> by lazy { val previewRoute: MutableLiveData<String> by lazy {
MutableLiveData() MutableLiveData()
@@ -223,33 +230,6 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
} }
} }
/**
* Categorizes traffic incidents by type (queuing, stationary, slow, heavy, roadworks).
* @param data Raw traffic GeoJSON string
* @return Map of incident type to GeoJSON FeatureCollection
*/
private fun rebuildTraffic(data: String): Map<String, String> {
val featureCollection = FeatureCollection.fromJson(data)
val incidents = mutableMapOf<String, String>()
val queuing = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Queuing traffic") }
incidents["queuing"] = FeatureCollection.fromFeatures(queuing).toJson()
val stationary = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Stationary traffic") }
incidents["stationary"] = FeatureCollection.fromFeatures(stationary).toJson()
val slow = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Slow traffic") }
incidents["slow"] = FeatureCollection.fromFeatures(slow).toJson()
val heavy = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Heavy traffic") }
incidents["heavy"] = FeatureCollection.fromFeatures(heavy).toJson()
val roadworks = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Roadworks") }
incidents["roadworks"] = FeatureCollection.fromFeatures(roadworks).toJson()
return incidents
}
/** /**
* Calculates a preview route for route preview screen. * Calculates a preview route for route preview screen.
* Posts the route JSON to previewRoute LiveData. * Posts the route JSON to previewRoute LiveData.
@@ -278,6 +258,40 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
return previewRoute.value return previewRoute.value
} }
/**
* Categorizes traffic incidents by type (queuing, stationary, slow, heavy, roadworks).
* @param data Raw traffic GeoJSON string
* @return Map of incident type to GeoJSON FeatureCollection
*/
private fun rebuildTraffic(data: String): Map<String, String> {
val featureCollection = FeatureCollection.fromJson(data)
val incidents = mutableMapOf<String, String>()
val queuing = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Queuing traffic") }
incidents["queuing"] = FeatureCollection.fromFeatures(queuing).toJson()
val stationary = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Stationary traffic") }
incidents["stationary"] = FeatureCollection.fromFeatures(stationary).toJson()
val slow = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Slow traffic") }
incidents["slow"] = FeatureCollection.fromFeatures(slow).toJson()
val heavy = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Heavy traffic") }
incidents["heavy"] = FeatureCollection.fromFeatures(heavy).toJson()
val roadworks = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Roadworks") }
incidents["roadworks"] = FeatureCollection.fromFeatures(roadworks).toJson()
val laneClosed = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Lane closed") }
incidents["lane"] = FeatureCollection.fromFeatures(laneClosed).toJson()
val closed = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Closed") }
incidents["closed"] = FeatureCollection.fromFeatures(closed).toJson()
return incidents
}
/** /**
* Loads device contacts with addresses and converts to Place objects. * Loads device contacts with addresses and converts to Place objects.
@@ -463,7 +477,8 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
) )
} }
} }
val result = search.sortedWith(compareBy<ElementSearch> { it.distance }.thenByDescending { it.bearing }) val result =
search.sortedWith(compareBy<ElementSearch> { it.distance }.thenByDescending { it.bearing })
if (result.isNotEmpty()) { if (result.isNotEmpty()) {
element = result.first().element element = result.first().element
speed = if (element.tags.maxspeed == "none" && element.tags.highway == "motorway") { speed = if (element.tags.maxspeed == "none" && element.tags.highway == "motorway") {
@@ -479,7 +494,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
return speed return speed
} }
private fun isBearingValid(element: Elements, streetBearing : Float, routeBearing: Float) : Boolean { private fun isBearingValid(
element: Elements,
streetBearing: Float,
routeBearing: Float
): Boolean {
return if (element.tags.oneway.isNotEmpty()) { return if (element.tags.oneway.isNotEmpty()) {
(streetBearing - routeBearing.absoluteValue) < SPEED_BEARING_DEVIATION (streetBearing - routeBearing.absoluteValue) < SPEED_BEARING_DEVIATION
} else { } else {
@@ -1,7 +1,9 @@
package com.kouros.navigation.model package com.kouros.navigation.model
import android.location.Location import android.location.Location
import android.util.Log
import androidx.car.app.navigation.model.Step import androidx.car.app.navigation.model.Step
import com.kouros.navigation.data.ApplicationConfig
import com.kouros.navigation.data.Constants.MAXIMUM_LOCATION_DISTANCE import com.kouros.navigation.data.Constants.MAXIMUM_LOCATION_DISTANCE
import com.kouros.navigation.data.Constants.NEAREST_LOCATION_DISTANCE import com.kouros.navigation.data.Constants.NEAREST_LOCATION_DISTANCE
import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE
@@ -117,6 +119,7 @@ class RouteCalculator(var routeModel: RouteModel) {
if ((distance > SPEED_UPDATE_DISTANCE * 2) || lastSpeedIndex < routeModel.route.currentStepIndex) { if ((distance > SPEED_UPDATE_DISTANCE * 2) || lastSpeedIndex < routeModel.route.currentStepIndex) {
lastSpeedIndex = routeModel.route.currentStepIndex lastSpeedIndex = routeModel.route.currentStepIndex
lastSpeedLocation = location lastSpeedLocation = location
viewModel.updateSpeedLimit( viewModel.updateSpeedLimit(
location, location,
routeModel.route.currentStep().street, routeModel.route.currentStep().street,
@@ -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="M620,680Q645,680 662.5,662.5Q680,645 680,620Q680,595 662.5,577.5Q645,560 620,560Q595,560 577.5,577.5Q560,595 560,620Q560,645 577.5,662.5Q595,680 620,680ZM260,680Q285,680 302.5,662.5Q320,645 320,620Q320,595 302.5,577.5Q285,560 260,560Q235,560 217.5,577.5Q200,595 200,620Q200,645 217.5,662.5Q235,680 260,680ZM680,480Q597,480 538.5,421.5Q480,363 480,280Q480,198 538,139Q596,80 680,80Q763,80 821.5,138.5Q880,197 880,280Q880,363 821.5,421.5Q763,480 680,480ZM660,320L700,320L700,160L660,160L660,320ZM680,400Q688,400 694,394Q700,388 700,380Q700,372 694,366Q688,360 680,360Q672,360 666,366Q660,372 660,380Q660,388 666,394Q672,400 680,400ZM120,880Q103,880 91.5,868.5Q80,857 80,840L80,520L164,280Q170,262 185.5,251Q201,240 220,240L403,240Q400,260 400,280Q400,300 403,320L234,320L192,440L451,440Q491,497 551,528.5Q611,560 680,560Q711,560 741.5,553.5Q772,547 800,533L800,840Q800,857 788.5,868.5Q777,880 760,880L720,880Q703,880 691.5,868.5Q680,857 680,840L680,800L200,800L200,840Q200,857 188.5,868.5Q177,880 160,880L120,880Z"/>
</vector>
@@ -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="M756,840L537,621L621,537L840,756L756,840ZM204,840L120,756L396,480L328,412L300,440L249,389L249,471L221,499L100,378L128,350L210,350L160,300L302,158Q322,138 345,129Q368,120 392,120Q416,120 439,129Q462,138 482,158L390,250L440,300L412,328L480,396L570,306Q566,295 563.5,283Q561,271 561,259Q561,200 601.5,159.5Q642,119 701,119Q716,119 729.5,122Q743,125 757,131L658,230L730,302L829,203Q836,217 838.5,230.5Q841,244 841,259Q841,318 800.5,358.5Q760,399 701,399Q689,399 677,397Q665,395 654,390L204,840Z"/>
</vector>
@@ -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="M720,520L720,160L800,160L800,520L720,520ZM160,800L160,160L240,160L240,800L160,800ZM440,320L440,160L520,160L520,320L440,320ZM440,560L440,400L520,400L520,560L440,560ZM440,800L440,640L520,640L520,800L440,800ZM617,823L702,738L617,654L674,597L759,682L844,597L900,654L815,739L899,824L844,880L758,795L673,880L617,823Z"/>
</vector>
@@ -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="M80,880Q63,880 51.5,868.5Q40,857 40,840L40,520L125,317Q132,300 147,290Q162,280 180,280L540,280Q558,280 573,290Q588,300 595,317L680,520L680,840Q680,857 668.5,868.5Q657,880 640,880L600,880Q583,880 571.5,868.5Q560,857 560,840L560,800L160,800L160,840Q160,857 148.5,868.5Q137,880 120,880L80,880ZM152,440L567,440L534,360L186,360L152,440ZM262.5,662.5Q280,645 280,620Q280,595 262.5,577.5Q245,560 220,560Q195,560 177.5,577.5Q160,595 160,620Q160,645 177.5,662.5Q195,680 220,680Q245,680 262.5,662.5ZM542.5,662.5Q560,645 560,620Q560,595 542.5,577.5Q525,560 500,560Q475,560 457.5,577.5Q440,595 440,620Q440,645 457.5,662.5Q475,680 500,680Q525,680 542.5,662.5ZM720,760L720,416L647,240L227,240L245,197Q252,180 267,170Q282,160 300,160L660,160Q678,160 693,170Q708,180 715,197L800,400L800,720Q800,737 788.5,748.5Q777,760 760,760L720,760ZM840,640L840,296L767,120L347,120L365,77Q372,60 387,50Q402,40 420,40L780,40Q798,40 813,50Q828,60 835,77L920,280L920,600Q920,617 908.5,628.5Q897,640 880,640L840,640Z"/>
</vector>