Traffic
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -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)
|
||||||
|
|||||||
+16
@@ -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>
|
||||||
Reference in New Issue
Block a user