Overpass
This commit is contained in:
@@ -11,14 +11,14 @@ val properties = Properties().apply {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.kouros.navigation"
|
namespace = "com.kouros.navigation"
|
||||||
compileSdk = 36
|
compileSdk = 37
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 37
|
||||||
versionCode = 94
|
versionCode = 97
|
||||||
versionName = "0.2.3.94"
|
versionName = "0.2.3.97"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
|
import android.location.Location
|
||||||
|
import android.location.LocationManager
|
||||||
|
import com.kouros.navigation.data.overpass.Overpass
|
||||||
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class OverpassTest {
|
||||||
|
|
||||||
|
val location = Location(LocationManager.GPS_PROVIDER)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `maxSpeed Schmalkaldener 30 `() {
|
||||||
|
val curLocation = location(11.582495, 48.186863)
|
||||||
|
executeSpeedTest( curLocation, "Schmalkaldener Straße", emptyList(), 90, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `maxSpeed Ingolstädter 50 `() {
|
||||||
|
val curLocation = location(11.584384, 48.186338)
|
||||||
|
executeSpeedTest( curLocation, "Ingolstädter Straße", listOf("B13"), 180, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `maxSpeed Isarring 50 `() {
|
||||||
|
val curLocation = location(11.5989114, 48.1694783)
|
||||||
|
executeSpeedTest( curLocation, "Isarring", listOf("B2R"), 190, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `maxSpeed A94 `() {
|
||||||
|
val curLocation = location(11.88117, 48.16595)
|
||||||
|
executeSpeedTest( curLocation, "", listOf("A94", "E552"), 90, 130)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `maxSpeed Fendsbach `() {
|
||||||
|
val curLocation = location(11.94989, 48.21522)
|
||||||
|
executeSpeedTest( curLocation, "Fendsbach", listOf("St 2331"), 0, 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `maxSpeed Leopoldstraße `() {
|
||||||
|
val locations = listOf(
|
||||||
|
location(11.5854771, 48.1778470),
|
||||||
|
location(11.5855582, 48.1756081),
|
||||||
|
location(11.5854672, 48.1753093),
|
||||||
|
location(11.5850147, 48.1774400)
|
||||||
|
)
|
||||||
|
|
||||||
|
executeSpeedTest( locations[0], "Leopoldstraße", emptyList(), 0, 50)
|
||||||
|
executeSpeedTest( locations[1], "Leopoldstraße", emptyList(), 180, 30)
|
||||||
|
executeSpeedTest( locations[2], "Leopoldstraße", emptyList(), 180, 30)
|
||||||
|
executeSpeedTest( locations[3], "Leopoldstraße", emptyList(), 180, 50)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `maxSpeed Egnatia `() {
|
||||||
|
val locations = listOf(
|
||||||
|
location(20.645487, 39.552875),
|
||||||
|
location(20.686672, 39.838547),
|
||||||
|
)
|
||||||
|
|
||||||
|
executeSpeedTest( locations[0], "", listOf("E90", "E92"), 100, 120)
|
||||||
|
executeSpeedTest( locations[1], "", listOf("E853"), 320, 90)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun executeSpeedTest(
|
||||||
|
curLocation: Location,
|
||||||
|
street: String,
|
||||||
|
roadNumbers: List<String>,
|
||||||
|
routeBearing: Int,
|
||||||
|
result: Int
|
||||||
|
) {
|
||||||
|
val viewModel = NavigationViewModel(TomTomRepository())
|
||||||
|
val lineString = "${curLocation.latitude},${curLocation.longitude}"
|
||||||
|
val elements = Overpass().getSpeedLimit(600F, lineString, street, roadNumbers)
|
||||||
|
viewModel.speedElements.addAll(elements)
|
||||||
|
assertNotEquals(0, viewModel.speedElements.size)
|
||||||
|
|
||||||
|
val speed = viewModel.calculateSpeedLimit(
|
||||||
|
curLocation,
|
||||||
|
routeBearing.toFloat(),
|
||||||
|
"DEU",
|
||||||
|
)
|
||||||
|
assertEquals(result, speed)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,14 @@ import androidx.test.platform.app.InstrumentationRegistry
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
|
import com.kouros.navigation.data.overpass.Overpass
|
||||||
import com.kouros.navigation.data.route.ManeuverType
|
import com.kouros.navigation.data.route.ManeuverType
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -69,7 +71,7 @@ class RouteModelTest {
|
|||||||
val repository = getSettingsRepository(appContext)
|
val repository = getSettingsRepository(appContext)
|
||||||
runBlocking { repository.setRoutingEngine(RouteEngine.TOMTOM.ordinal) }
|
runBlocking { repository.setRoutingEngine(RouteEngine.TOMTOM.ordinal) }
|
||||||
val routeJsonString = TomTomRepository().fetchUrl(
|
val routeJsonString = TomTomRepository().fetchUrl(
|
||||||
"https://kouros-online.de/tomtom_routing.json",
|
"http://192.168.1.37/tomtom_routing.json",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
assertNotEquals("", routeJsonString)
|
assertNotEquals("", routeJsonString)
|
||||||
@@ -201,6 +203,7 @@ class RouteModelTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun simulate() {
|
fun simulate() {
|
||||||
|
val viewModel = NavigationViewModel(TomTomRepository())
|
||||||
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
val curLocation = location(waypoint[0], waypoint[1])
|
val curLocation = location(waypoint[0], waypoint[1])
|
||||||
@@ -244,9 +247,12 @@ class RouteModelTest {
|
|||||||
val curLocation = location(waypoint[0], waypoint[1])
|
val curLocation = location(waypoint[0], waypoint[1])
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
if (index in 16..43) {
|
if (index in 16..43) {
|
||||||
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(
|
||||||
|
curLocation,
|
||||||
|
NavigationViewModel(TomTomRepository())
|
||||||
|
)
|
||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
assertEquals(stepData.leftStepDistance, distance[index-16], 1.0)
|
assertEquals(stepData.leftStepDistance, distance[index - 16], 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.kouros.data.BuildConfig
|
||||||
|
import com.kouros.navigation.data.Constants.a9
|
||||||
|
import com.kouros.navigation.data.Constants.a94
|
||||||
|
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||||
|
import com.kouros.navigation.data.Constants.ioannina
|
||||||
|
import com.kouros.navigation.data.Constants.subislawa
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
@@ -92,21 +98,29 @@ class DeviceLocationManager(
|
|||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun startLocationUpdates(minTimeMs: Long = 1000, minDistanceM: Float = 5f) {
|
fun startLocationUpdates(minTimeMs: Long = 1000, minDistanceM: Float = 5f) {
|
||||||
if (isListening) return
|
if (isListening) return
|
||||||
|
val setIndividualLocation = BuildConfig.DEBUG
|
||||||
|
|
||||||
// Get and deliver last known location first
|
// Get and deliver last known location first
|
||||||
val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||||
if (lastLocation != null) {
|
if (lastLocation != null) {
|
||||||
onInitialLocation(lastLocation)
|
if (setIndividualLocation) {
|
||||||
onLocationUpdate(lastLocation)
|
onInitialLocation(homeVogelhart)
|
||||||
|
onLocationUpdate(homeVogelhart)
|
||||||
|
} else {
|
||||||
|
onInitialLocation(lastLocation)
|
||||||
|
onLocationUpdate(lastLocation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start continuous location updates
|
// Start continuous location updates
|
||||||
locationManager.requestLocationUpdates(
|
if (!setIndividualLocation) {
|
||||||
LocationManager.GPS_PROVIDER,
|
locationManager.requestLocationUpdates(
|
||||||
minTimeMs,
|
LocationManager.GPS_PROVIDER,
|
||||||
minDistanceM,
|
minTimeMs,
|
||||||
locationListener
|
minDistanceM,
|
||||||
)
|
locationListener
|
||||||
|
)
|
||||||
|
}
|
||||||
isListening = true
|
isListening = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
|
|
||||||
var lastRouteDate: LocalDateTime = LocalDateTime.now()
|
var lastRouteDate: LocalDateTime = LocalDateTime.now()
|
||||||
|
|
||||||
|
var lastAlert : Long = 0
|
||||||
var navigationManagerStarted = false
|
var navigationManagerStarted = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -462,29 +463,30 @@ 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 startTime = System.currentTimeMillis()
|
||||||
routeModel.updateLocation(location, navigationViewModel)
|
routeModel.updateLocation(location, navigationViewModel)
|
||||||
if (routeModel.navState.arrived) return
|
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||||
if (guidanceAudio == 1) {
|
|
||||||
handleGuidanceAudio()
|
|
||||||
}
|
|
||||||
val streetName = routeModel.currentStep().street
|
val streetName = routeModel.currentStep().street
|
||||||
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
if (checkLocationDeviation(location, snappedLocation, streetName)) {
|
||||||
|
if (routeModel.navState.arrived) return
|
||||||
if (snapLocation(location, streetName)) {
|
if (guidanceAudio == 1) {
|
||||||
checkTraffic(currentDate, location)
|
handleGuidanceAudio()
|
||||||
updateSpeedCamera(location)
|
}
|
||||||
checkRoute(currentDate, location)
|
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
|
checkTraffic(currentDate, snappedLocation)
|
||||||
|
updateSpeedCamera(snappedLocation)
|
||||||
|
checkRoute(currentDate, snappedLocation)
|
||||||
updateNavigationScreen()
|
updateNavigationScreen()
|
||||||
checkArrival()
|
checkArrival()
|
||||||
}
|
}
|
||||||
|
val endTime = System.currentTimeMillis() - startTime
|
||||||
|
Log.d(TAG, "handleNavigationLocation: $endTime")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the surface renderer with snapped location and street name.
|
* Checks if the location deviation is acceptable.
|
||||||
* Checks if maximal route deviation is exceeded and reroutes if needed.
|
|
||||||
*/
|
*/
|
||||||
private fun snapLocation(location: Location, streetName: String): Boolean {
|
private fun checkLocationDeviation(location: Location, snappedLocation: Location, streetName: String): Boolean {
|
||||||
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
|
||||||
val distance = location.distanceTo(snappedLocation)
|
val distance = location.distanceTo(snappedLocation)
|
||||||
when {
|
when {
|
||||||
distance > MAXIMAL_ROUTE_DEVIATION -> {
|
distance > MAXIMAL_ROUTE_DEVIATION -> {
|
||||||
@@ -514,6 +516,9 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val currentStep = routeModel.route.currentStep()
|
||||||
|
val stepData = routeModel.currentStep()
|
||||||
|
|
||||||
navigationScreen.updateTrip(
|
navigationScreen.updateTrip(
|
||||||
isNavigating = routeModel.isNavigating(),
|
isNavigating = routeModel.isNavigating(),
|
||||||
isRerouting = false,
|
isRerouting = false,
|
||||||
@@ -526,7 +531,8 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
shouldShowNextStep = false,
|
shouldShowNextStep = false,
|
||||||
shouldShowLanes = true,
|
shouldShowLanes = true,
|
||||||
junctionImage = null,
|
junctionImage = null,
|
||||||
backGroundColor = routeModel.backGroundColor()
|
backGroundColor = routeModel.backGroundColor(),
|
||||||
|
message = stepData.message,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -786,18 +792,15 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
|
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
|
||||||
val camera = sortedList.firstOrNull() ?: return
|
val camera = sortedList.firstOrNull() ?: return
|
||||||
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
||||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
val bearingSpeedCamera = try {
|
||||||
try {
|
camera.tags.direction.toFloat()
|
||||||
camera.tags.direction!!.toFloat()
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
0F
|
||||||
0F
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
|
|
||||||
}
|
}
|
||||||
if (camera.distance < 80) {
|
if (camera.distance < 80 && (System.currentTimeMillis() - lastAlert > 5000)) {
|
||||||
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
|
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
|
||||||
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
|
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
|
||||||
|
lastAlert = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import android.location.Location
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.car.app.AppManager
|
import androidx.car.app.AppManager
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.Session
|
|
||||||
import androidx.car.app.SurfaceCallback
|
import androidx.car.app.SurfaceCallback
|
||||||
import androidx.car.app.SurfaceContainer
|
import androidx.car.app.SurfaceContainer
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -156,8 +155,6 @@ class SurfaceRenderer(
|
|||||||
Log.i(TAG, "Surface available $surfaceContainer")
|
Log.i(TAG, "Surface available $surfaceContainer")
|
||||||
lifecycleOwner = CustomLifecycleOwner()
|
lifecycleOwner = CustomLifecycleOwner()
|
||||||
lifecycleOwner.performRestore(null)
|
lifecycleOwner.performRestore(null)
|
||||||
// technically, we only really need any one of these instead of all 3
|
|
||||||
// add them to be consistent with the actual lifecycle.
|
|
||||||
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||||
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
@@ -301,7 +298,7 @@ class SurfaceRenderer(
|
|||||||
) {
|
) {
|
||||||
val cameraDuration =
|
val cameraDuration =
|
||||||
duration(
|
duration(
|
||||||
viewStyle == ViewStyle.PREVIEW,
|
viewStyle,
|
||||||
position!!.bearing,
|
position!!.bearing,
|
||||||
lastBearing,
|
lastBearing,
|
||||||
lastLocationUpdate
|
lastLocationUpdate
|
||||||
@@ -351,13 +348,11 @@ class SurfaceRenderer(
|
|||||||
viewStyle = ViewStyle.PAN_VIEW
|
viewStyle = ViewStyle.PAN_VIEW
|
||||||
}
|
}
|
||||||
val newZoom = if (zoomSign < 0) {
|
val newZoom = if (zoomSign < 0) {
|
||||||
cameraPosition.value!!.zoom - 0.2
|
cameraPosition.value!!.zoom - 1
|
||||||
} else {
|
} else {
|
||||||
cameraPosition.value!!.zoom + 0.2
|
cameraPosition.value!!.zoom + 1
|
||||||
}
|
|
||||||
if (viewStyle == ViewStyle.VIEW) {
|
|
||||||
tilt = calculateTilt(newZoom, tilt)
|
|
||||||
}
|
}
|
||||||
|
tilt = calculateTilt(viewStyle, newZoom, tilt)
|
||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
cameraPosition.value!!.bearing,
|
cameraPosition.value!!.bearing,
|
||||||
newZoom,
|
newZoom,
|
||||||
@@ -411,6 +406,7 @@ class SurfaceRenderer(
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
routeData.value = routeGeoJson
|
routeData.value = routeGeoJson
|
||||||
viewStyle = ViewStyle.VIEW
|
viewStyle = ViewStyle.VIEW
|
||||||
|
updateLocation(lastLocation, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,7 +475,7 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
viewStyle = ViewStyle.VIEW
|
viewStyle = ViewStyle.VIEW
|
||||||
val zoom = calculateZoom(0.0)
|
val zoom = calculateZoom(0.0)
|
||||||
tilt = calculateTilt(zoom, tilt)
|
tilt = calculateTilt(viewStyle, zoom, tilt)
|
||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
tilt = tilt,
|
tilt = tilt,
|
||||||
zoom = zoom,
|
zoom = zoom,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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
|
||||||
@@ -33,6 +32,7 @@ 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.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
|
||||||
@@ -49,11 +49,8 @@ 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
|
||||||
import org.maplibre.compose.layers.SymbolLayer
|
import org.maplibre.compose.layers.SymbolLayer
|
||||||
import org.maplibre.compose.location.LocationPuck
|
|
||||||
import org.maplibre.compose.location.LocationPuckColors
|
import org.maplibre.compose.location.LocationPuckColors
|
||||||
import org.maplibre.compose.location.LocationPuckSizes
|
import org.maplibre.compose.location.LocationPuckSizes
|
||||||
import org.maplibre.compose.location.UserLocationState
|
|
||||||
import org.maplibre.compose.map.GestureOptions
|
|
||||||
import org.maplibre.compose.map.MapOptions
|
import org.maplibre.compose.map.MapOptions
|
||||||
import org.maplibre.compose.map.MaplibreMap
|
import org.maplibre.compose.map.MaplibreMap
|
||||||
import org.maplibre.compose.map.OrnamentOptions
|
import org.maplibre.compose.map.OrnamentOptions
|
||||||
@@ -115,6 +112,7 @@ fun MapLibre(
|
|||||||
AmenityLayer(route)
|
AmenityLayer(route)
|
||||||
} else {
|
} else {
|
||||||
RouteLayer(route, traffic!!)
|
RouteLayer(route, traffic!!)
|
||||||
|
StartEndLayer(route)
|
||||||
//RouteLayerPoint(route )
|
//RouteLayerPoint(route )
|
||||||
}
|
}
|
||||||
SpeedCameraLayer(speedCameras)
|
SpeedCameraLayer(speedCameras)
|
||||||
@@ -123,6 +121,30 @@ fun MapLibre(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StartEndLayer(routeData: String?) {
|
||||||
|
if (!routeData.isNullOrEmpty()) {
|
||||||
|
val end = createEndCollection(routeData)
|
||||||
|
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(end))
|
||||||
|
val img = image(painterResource(R.drawable.sports_score_48px), drawAsSdf = true)
|
||||||
|
SymbolLayer(
|
||||||
|
id = "end-layer",
|
||||||
|
source = routes,
|
||||||
|
iconColor = const(Color.Black),
|
||||||
|
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
|
||||||
fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
|
fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
|
||||||
if (!routeData.isNullOrEmpty()) {
|
if (!routeData.isNullOrEmpty()) {
|
||||||
@@ -308,8 +330,8 @@ fun DrawNavigationImages(
|
|||||||
if (speed != null) {
|
if (speed != null) {
|
||||||
CurrentSpeed(width, height, speed, maxSpeed)
|
CurrentSpeed(width, height, speed, maxSpeed)
|
||||||
}
|
}
|
||||||
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
|
if (speed != null && maxSpeed > 0) { // && (speed * 3.6) > maxSpeed) {
|
||||||
MaxSpeed(width, height, maxSpeed)
|
MaxSpeed(width, height, maxSpeed, speed)
|
||||||
}
|
}
|
||||||
//DebugInfo(width, height, lat!!)
|
//DebugInfo(width, height, lat!!)
|
||||||
}
|
}
|
||||||
@@ -325,9 +347,9 @@ fun NavigationImage(
|
|||||||
|
|
||||||
val imageSize = (height / 8)
|
val imageSize = (height / 8)
|
||||||
val navigationColor = if (darkMode)
|
val navigationColor = if (darkMode)
|
||||||
remember { NavigationColorDark }
|
|
||||||
else
|
|
||||||
remember { NavigationColorLight }
|
remember { NavigationColorLight }
|
||||||
|
else
|
||||||
|
remember { NavigationColorDark }
|
||||||
|
|
||||||
val textMeasurerStreet = rememberTextMeasurer()
|
val textMeasurerStreet = rememberTextMeasurer()
|
||||||
val street = streetName.toString()
|
val street = streetName.toString()
|
||||||
@@ -397,7 +419,7 @@ private fun CurrentSpeed(
|
|||||||
maxSpeed: Int
|
maxSpeed: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val radius = 34
|
val radius = 36
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
@@ -414,12 +436,12 @@ private fun CurrentSpeed(
|
|||||||
val kmh = if (isMetricSystem()) "km/h" else "mph"
|
val kmh = if (isMetricSystem()) "km/h" else "mph"
|
||||||
|
|
||||||
val styleSpeed = TextStyle(
|
val styleSpeed = TextStyle(
|
||||||
fontSize = 22.sp,
|
fontSize = 24.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
)
|
)
|
||||||
val styleKm = TextStyle(
|
val styleKm = TextStyle(
|
||||||
fontSize = 12.sp,
|
fontSize = 14.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
)
|
)
|
||||||
val textLayoutSpeed = remember(speed, maxSpeed) {
|
val textLayoutSpeed = remember(speed, maxSpeed) {
|
||||||
@@ -464,6 +486,7 @@ private fun MaxSpeed(
|
|||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
maxSpeed: Int,
|
maxSpeed: Int,
|
||||||
|
curSpeed: Float,
|
||||||
) {
|
) {
|
||||||
val radius = 24
|
val radius = 24
|
||||||
Box(
|
Box(
|
||||||
@@ -484,6 +507,11 @@ private fun MaxSpeed(
|
|||||||
val textLayoutSpeed = remember(speed) {
|
val textLayoutSpeed = remember(speed) {
|
||||||
textMeasurerSpeed.measure(speed, styleSpeed)
|
textMeasurerSpeed.measure(speed, styleSpeed)
|
||||||
}
|
}
|
||||||
|
val signColor = if (curSpeed * 3.6 > maxSpeed) {
|
||||||
|
Color.Red
|
||||||
|
} else {
|
||||||
|
Color.Green
|
||||||
|
}
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
drawCircle(
|
drawCircle(
|
||||||
center = Offset(
|
center = Offset(
|
||||||
@@ -491,7 +519,7 @@ private fun MaxSpeed(
|
|||||||
y = center.y
|
y = center.y
|
||||||
),
|
),
|
||||||
radius = radius * 1.3.toFloat(),
|
radius = radius * 1.3.toFloat(),
|
||||||
color = Color.Red,
|
color = signColor,
|
||||||
)
|
)
|
||||||
drawCircle(
|
drawCircle(
|
||||||
center = Offset(
|
center = Offset(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.kouros.navigation.car.navigation
|
|||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.car.app.AppManager
|
import androidx.car.app.AppManager
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
@@ -27,6 +28,7 @@ import androidx.car.app.navigation.model.Trip
|
|||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.screen.createCarIcon
|
import com.kouros.navigation.car.screen.createCarIcon
|
||||||
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.data.route.ManeuverType
|
import com.kouros.navigation.data.route.ManeuverType
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
@@ -265,7 +267,7 @@ class RouteCarModel : RouteModel() {
|
|||||||
R.string.exit_action_title, R.string.exit_action_title,
|
R.string.exit_action_title, R.string.exit_action_title,
|
||||||
FLAG_DEFAULT
|
FLAG_DEFAULT
|
||||||
)
|
)
|
||||||
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */5000)
|
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */4000)
|
||||||
.setSubtitle(subtitle)
|
.setSubtitle(subtitle)
|
||||||
.setIcon(icon)
|
.setIcon(icon)
|
||||||
.addAction(dismissAction).setCallback(object : AlertCallback {
|
.addAction(dismissAction).setCallback(object : AlertCallback {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.os.SystemClock
|
|||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import com.kouros.data.BuildConfig
|
import com.kouros.data.BuildConfig
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
import io.ticofab.androidgpxparser.parser.GPXParser
|
import io.ticofab.androidgpxparser.parser.GPXParser
|
||||||
import io.ticofab.androidgpxparser.parser.domain.Gpx
|
import io.ticofab.androidgpxparser.parser.domain.Gpx
|
||||||
import io.ticofab.androidgpxparser.parser.domain.TrackSegment
|
import io.ticofab.androidgpxparser.parser.domain.TrackSegment
|
||||||
@@ -49,16 +50,16 @@ class Simulation {
|
|||||||
simulationJob = lifecycleScope.launch {
|
simulationJob = lifecycleScope.launch {
|
||||||
for ((index, point) in points.withIndex()) {
|
for ((index, point) in points.withIndex()) {
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
|
curBearing = lastLocation.bearingTo(location(point[0], point[1]))
|
||||||
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
||||||
latitude = point[1]
|
latitude = point[1]
|
||||||
longitude = point[0]
|
longitude = point[0]
|
||||||
bearing = curBearing
|
bearing = curBearing
|
||||||
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||||
speed = 5.0f
|
speed = 10.0f
|
||||||
time = System.currentTimeMillis()
|
time = System.currentTimeMillis()
|
||||||
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||||
}
|
}
|
||||||
curBearing = lastLocation.bearingTo(fakeLocation)
|
|
||||||
// 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)
|
||||||
|
|||||||
@@ -120,11 +120,12 @@ class CategoryScreen(
|
|||||||
|
|
||||||
private fun createItem(it: Elements, category: String, index: Int): Row {
|
private fun createItem(it: Elements, category: String, index: Int): Row {
|
||||||
var name = ""
|
var name = ""
|
||||||
if (it.tags.name != null) {
|
name = it.tags.name
|
||||||
name = it.tags.name.toString()
|
if (name.isEmpty()) {
|
||||||
|
name = it.tags.operator
|
||||||
}
|
}
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
name = it.tags.operator.toString()
|
name = "Empty"
|
||||||
}
|
}
|
||||||
val row = Row.Builder()
|
val row = Row.Builder()
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ open class NavigationScreen(
|
|||||||
private var junctionImage: CarIcon? = null
|
private var junctionImage: CarIcon? = null
|
||||||
private var backGroundColor = CarColor.BLUE
|
private var backGroundColor = CarColor.BLUE
|
||||||
|
|
||||||
|
private var message = ""
|
||||||
|
|
||||||
private var showAlternativeRoute = false
|
private var showAlternativeRoute = false
|
||||||
val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
|
val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
|
||||||
recentPlaces.addAll(newPlaces)
|
recentPlaces.addAll(newPlaces)
|
||||||
@@ -220,9 +222,10 @@ open class NavigationScreen(
|
|||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setNavigationInfo(
|
.setNavigationInfo(
|
||||||
MessageInfo.Builder(
|
MessageInfo.Builder(
|
||||||
carContext.getString(R.string.arrived_exclamation_msg)
|
message
|
||||||
|
//carContext.getString(R.string.arrived_exclamation_msg)
|
||||||
)
|
)
|
||||||
.setText(street)
|
// .setText(street)
|
||||||
.setImage(
|
.setImage(
|
||||||
CarIcon.Builder(
|
CarIcon.Builder(
|
||||||
IconCompat.createWithResource(
|
IconCompat.createWithResource(
|
||||||
@@ -504,7 +507,8 @@ open class NavigationScreen(
|
|||||||
shouldShowNextStep: Boolean,
|
shouldShowNextStep: Boolean,
|
||||||
shouldShowLanes: Boolean,
|
shouldShowLanes: Boolean,
|
||||||
junctionImage: CarIcon?,
|
junctionImage: CarIcon?,
|
||||||
backGroundColor: CarColor
|
backGroundColor: CarColor,
|
||||||
|
message: String
|
||||||
) {
|
) {
|
||||||
this.isNavigating = isNavigating
|
this.isNavigating = isNavigating
|
||||||
this.isRerouting = isRerouting
|
this.isRerouting = isRerouting
|
||||||
@@ -518,6 +522,7 @@ open class NavigationScreen(
|
|||||||
this.shouldShowLanes = shouldShowLanes
|
this.shouldShowLanes = shouldShowLanes
|
||||||
this.junctionImage = junctionImage
|
this.junctionImage = junctionImage
|
||||||
this.backGroundColor = backGroundColor
|
this.backGroundColor = backGroundColor
|
||||||
|
this.message = message
|
||||||
|
|
||||||
navigationType = NavigationType.NAVIGATION
|
navigationType = NavigationType.NAVIGATION
|
||||||
invalidate()
|
invalidate()
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
var loading = true
|
var loading = true
|
||||||
|
|
||||||
|
var previewReady = false;
|
||||||
var flag = FLAG_DEFAULT
|
var flag = FLAG_DEFAULT
|
||||||
|
|
||||||
private val backPressedCallback = object : OnBackPressedCallback(false) {
|
private val backPressedCallback = object : OnBackPressedCallback(false) {
|
||||||
@@ -85,6 +86,7 @@ class RoutePreviewScreen(
|
|||||||
routeModel.startNavigation(route)
|
routeModel.startNavigation(route)
|
||||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||||
loading = false
|
loading = false
|
||||||
|
previewReady = true
|
||||||
if (routeModel.route.routes.size == 1 && showAlternativeRoute) {
|
if (routeModel.route.routes.size == 1 && showAlternativeRoute) {
|
||||||
routeType = RoutePreviewType.SINGLE_ROUTE
|
routeType = RoutePreviewType.SINGLE_ROUTE
|
||||||
showAlternativeRoute = false
|
showAlternativeRoute = false
|
||||||
@@ -167,7 +169,8 @@ class RoutePreviewScreen(
|
|||||||
if (routeModel.isNavigating() && routeModel.curRoute.waypoints.isNotEmpty()) {
|
if (routeModel.isNavigating() && routeModel.curRoute.waypoints.isNotEmpty()) {
|
||||||
createRouteText(routeModel.route.routes.first())
|
createRouteText(routeModel.route.routes.first())
|
||||||
} else {
|
} else {
|
||||||
CarText.Builder("Wait")
|
loading = true
|
||||||
|
CarText.Builder(carContext.getString(R.string.wait))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
val content = if (routeType == RoutePreviewType.MULTI_ROUTE) {
|
val content = if (routeType == RoutePreviewType.MULTI_ROUTE) {
|
||||||
@@ -191,8 +194,10 @@ class RoutePreviewScreen(
|
|||||||
})
|
})
|
||||||
val listContent = MessageTemplate.Builder(message)
|
val listContent = MessageTemplate.Builder(message)
|
||||||
.setHeader(header.build())
|
.setHeader(header.build())
|
||||||
.addAction(navigateAction)
|
|
||||||
|
|
||||||
|
if (previewReady) {
|
||||||
|
listContent.addAction(navigateAction)
|
||||||
|
}
|
||||||
if (showAlternativeRoute) {
|
if (showAlternativeRoute) {
|
||||||
listContent.addAction(selectRouteAction)
|
listContent.addAction(selectRouteAction)
|
||||||
}
|
}
|
||||||
@@ -212,14 +217,16 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
)
|
)
|
||||||
if (routeModel.route.routes.size > 1 && routeType == RoutePreviewType.MULTI_ROUTE && !routeSelected) {
|
if (routeModel.route.routes.size > 1 && routeType == RoutePreviewType.MULTI_ROUTE && !routeSelected) {
|
||||||
template.setActionStrip(createActionStrip {
|
if (previewReady) {
|
||||||
createAction(
|
template.setActionStrip(createActionStrip {
|
||||||
carContext, R.drawable.navigation_48px,
|
createAction(
|
||||||
onClickAction = {
|
carContext, R.drawable.navigation_48px,
|
||||||
onNavigate(routeModel.navState.currentRouteIndex)
|
onClickAction = {
|
||||||
}
|
onNavigate(routeModel.navState.currentRouteIndex)
|
||||||
)
|
}
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return template.build()
|
return template.build()
|
||||||
}
|
}
|
||||||
@@ -320,7 +327,9 @@ class RoutePreviewScreen(
|
|||||||
.setTitle(routeText)
|
.setTitle(routeText)
|
||||||
.setOnClickListener { onRouteSelected(index) }
|
.setOnClickListener { onRouteSelected(index) }
|
||||||
.addText(street)
|
.addText(street)
|
||||||
.addAction(navigateAction)
|
if (previewReady) {
|
||||||
|
row.addAction(navigateAction)
|
||||||
|
}
|
||||||
if (route.summary.trafficDelay > 60) {
|
if (route.summary.trafficDelay > 60) {
|
||||||
row.addText(createDelay(route))
|
row.addText(createDelay(route))
|
||||||
row.setImage(createCarIcon(carContext = carContext, R.drawable.traffic_jam_48px))
|
row.setImage(createCarIcon(carContext = carContext, R.drawable.traffic_jam_48px))
|
||||||
@@ -346,10 +355,12 @@ class RoutePreviewScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onNavigate(index: Int) {
|
private fun onNavigate(index: Int) {
|
||||||
destination.routeIndex = index
|
if (previewReady) {
|
||||||
destination.route = navigationViewModel.previewRoute.value.toString()
|
destination.routeIndex = index
|
||||||
setResult(destination)
|
destination.route = navigationViewModel.previewRoute.value.toString()
|
||||||
finish()
|
setResult(destination)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRouteSelected(index: Int) {
|
private fun onRouteSelected(index: Int) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
|
|
||||||
val NavigationColorLight = Color(0xFF17A119)
|
val NavigationColorLight = Color(0xFF17A119)
|
||||||
|
|
||||||
val NavigationColorDark = Color(0xFF4EDE10)
|
val NavigationColorDark = Color(0xFF03411F)
|
||||||
|
|
||||||
val RouteColor = Color(0xFF195D02)
|
val RouteColor = Color(0xFF195D02)
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ data class StepData (
|
|||||||
var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList(), 0, 0)),
|
var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList(), 0, 0)),
|
||||||
var exitNumber: Int = 0,
|
var exitNumber: Int = 0,
|
||||||
var message: String = "",
|
var message: String = "",
|
||||||
|
var roadNumbers: List<String> = emptyList(),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +125,11 @@ object Constants {
|
|||||||
/** The initial location to use as an anchor for searches. */
|
/** The initial location to use as an anchor for searches. */
|
||||||
val homeVogelhart = location(11.5793748, 48.185749)
|
val homeVogelhart = location(11.5793748, 48.185749)
|
||||||
val homeHohenwaldeck = location( 11.594322, 48.1164817)
|
val homeHohenwaldeck = location( 11.594322, 48.1164817)
|
||||||
|
val ioannina = location( 20.826237, 39.690174)
|
||||||
|
val subislawa = location(18.570808, 54.420647)
|
||||||
|
val a94 = location(11.872097,48.163449)
|
||||||
|
|
||||||
|
val a9 = location(11.621556, 48.204402,)
|
||||||
const val NEXT_STEP_THRESHOLD = 500.0
|
const val NEXT_STEP_THRESHOLD = 500.0
|
||||||
|
|
||||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||||
@@ -138,8 +144,12 @@ object Constants {
|
|||||||
|
|
||||||
const val TRAFFIC_UPDATE = 300
|
const val TRAFFIC_UPDATE = 300
|
||||||
|
|
||||||
|
const val SPEED_UPDATE_DISTANCE = 600F
|
||||||
|
|
||||||
const val INSTRUCTION_DISTANCE = 50
|
const val INSTRUCTION_DISTANCE = 50
|
||||||
|
|
||||||
|
const val SPEED_BEARING_DEVIATION = 60
|
||||||
|
|
||||||
const val GMS_CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED"
|
const val GMS_CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED"
|
||||||
|
|
||||||
const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED"
|
const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED"
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
package com.kouros.navigation.data.overpass
|
package com.kouros.navigation.data.overpass
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
data class Amenity(
|
||||||
|
val elements: List<Elements>,
|
||||||
|
|
||||||
data class Amenity (
|
|
||||||
|
|
||||||
@SerializedName("version" ) var version : Double? = null,
|
|
||||||
@SerializedName("generator" ) var generator : String? = null,
|
|
||||||
@SerializedName("osm3s" ) var osm3s : Osm3s? = Osm3s(),
|
|
||||||
@SerializedName("elements" ) var elements : ArrayList<Elements> = arrayListOf()
|
|
||||||
|
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.kouros.navigation.data.overpass
|
||||||
|
|
||||||
|
data class Bounds(
|
||||||
|
val maxlat: Double,
|
||||||
|
val maxlon: Double,
|
||||||
|
val minlat: Double,
|
||||||
|
val minlon: Double
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.kouros.navigation.data.overpass
|
||||||
|
|
||||||
|
data class ElementSearch(
|
||||||
|
val element: Elements,
|
||||||
|
val distance: Double,
|
||||||
|
val bearing: Float,
|
||||||
|
)
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
package com.kouros.navigation.data.overpass
|
package com.kouros.navigation.data.overpass
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
|
data class Elements(
|
||||||
data class Elements (
|
val bounds: Bounds,
|
||||||
|
val geometry: List<Geometry>,
|
||||||
@SerializedName("type" ) var type : String = "",
|
val id: Long = 0,
|
||||||
@SerializedName("id" ) var id : Long = 0,
|
val lat: Double= 0.0,
|
||||||
@SerializedName("lat" ) var lat : Double = 0.0,
|
val lon: Double = 0.0,
|
||||||
@SerializedName("lon" ) var lon : Double = 0.0,
|
val tags: Tags,
|
||||||
@SerializedName("tags" ) var tags : Tags = Tags(),
|
val type: String = "",
|
||||||
var distance : Double = 0.0
|
var distance : Double = 0.0
|
||||||
|
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.kouros.navigation.data.overpass
|
||||||
|
|
||||||
|
data class Geometry(
|
||||||
|
val lat: Double = 0.0,
|
||||||
|
val lon: Double = 0.0
|
||||||
|
)
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.kouros.navigation.data.overpass
|
package com.kouros.navigation.data.overpass
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.kouros.data.BuildConfig
|
||||||
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.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
@@ -9,12 +11,43 @@ import java.net.URL
|
|||||||
|
|
||||||
class Overpass {
|
class Overpass {
|
||||||
|
|
||||||
//val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
|
var overpassUrl = if (BuildConfig.DEBUG)
|
||||||
//val overpassUrl = "https://overpass-api.de/api"
|
"http://192.168.1.37/api/interpreter"
|
||||||
val overpassUrl = "https://kouros-online.de/overpass/interpreter"
|
else
|
||||||
|
"https://kouros-online.de/api/interpreter"
|
||||||
|
|
||||||
|
|
||||||
fun getAround(radius: Int, linestring: String): List<Elements> {
|
fun getSpeedLimit(radius: Float, linestring: String, street: String, roadNumbers: List<String>): List<Elements> {
|
||||||
|
val streetName = if (street.length > 10) {
|
||||||
|
street.substring(0, 10)
|
||||||
|
} else {
|
||||||
|
street
|
||||||
|
}
|
||||||
|
val name = if (streetName.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"[name~\"^${streetName}\"]"
|
||||||
|
}
|
||||||
|
|
||||||
|
val regex = Regex("""\d+|\D+""")
|
||||||
|
val search = "way[maxspeed](around:$radius,$linestring)$name[!destination][highway!=\"motorway_link\"]"
|
||||||
|
var waySearch = search
|
||||||
|
for ((index, r) in roadNumbers.withIndex()) {
|
||||||
|
val result = regex.findAll(r).map { it.groupValues.first() }.toList()
|
||||||
|
if (index > 0) {
|
||||||
|
waySearch = waySearch.plus(";").plus(search)
|
||||||
|
}
|
||||||
|
var refValue = result.first().trim().plus(" ")
|
||||||
|
for (res in result.subList(1, result.size)) {
|
||||||
|
refValue = refValue.plus(res)
|
||||||
|
}
|
||||||
|
// International reference starts with "E"
|
||||||
|
waySearch = if (r.first().toString() == "E") {
|
||||||
|
waySearch.plus("[int_ref~\"${refValue}\"]")
|
||||||
|
} else {
|
||||||
|
waySearch.plus("[ref~\"${refValue}\"]")
|
||||||
|
}
|
||||||
|
}
|
||||||
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
||||||
httpURLConnection.requestMethod = "POST"
|
httpURLConnection.requestMethod = "POST"
|
||||||
httpURLConnection.setRequestProperty(
|
httpURLConnection.setRequestProperty(
|
||||||
@@ -26,15 +59,14 @@ class Overpass {
|
|||||||
val searchQuery = """
|
val searchQuery = """
|
||||||
|[out:json];
|
|[out:json];
|
||||||
|(
|
|(
|
||||||
| way[highway](around:$radius,$linestring)
|
| $waySearch;
|
||||||
| ;
|
|
||||||
|);
|
|);
|
||||||
|out body;
|
|out body geom;
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
//println("way[highway](around:$radius,$linestring)")
|
|
||||||
return overpassApi(httpURLConnection, searchQuery)
|
return overpassApi(httpURLConnection, searchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getAmenities(
|
fun getAmenities(
|
||||||
type: String,
|
type: String,
|
||||||
category: String,
|
category: String,
|
||||||
@@ -59,7 +91,7 @@ class Overpass {
|
|||||||
| ($boundingBox);
|
| ($boundingBox);
|
||||||
|);
|
|);
|
||||||
|(._;>;);
|
|(._;>;);
|
||||||
|out body;
|
|out body geom;
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
return overpassApi(httpURLConnection, searchQuery)
|
return overpassApi(httpURLConnection, searchQuery)
|
||||||
}
|
}
|
||||||
@@ -70,17 +102,22 @@ class Overpass {
|
|||||||
outputStreamWriter.write(searchQuery)
|
outputStreamWriter.write(searchQuery)
|
||||||
outputStreamWriter.flush()
|
outputStreamWriter.flush()
|
||||||
// Check if the connection is successful
|
// Check if the connection is successful
|
||||||
|
httpURLConnection.requestMethod = "POST"
|
||||||
val responseCode = httpURLConnection.responseCode
|
val responseCode = httpURLConnection.responseCode
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
val response = httpURLConnection.inputStream.bufferedReader()
|
val response = httpURLConnection.inputStream.bufferedReader()
|
||||||
.use { it.readText() } // defaults to UTF-8
|
.use { it.readText() } // defaults to UTF-8
|
||||||
|
if (response.startsWith("<?xml")) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
val gson = GsonBuilder().serializeNulls().create()
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
val overpass = gson.fromJson(response, Amenity::class.java)
|
val overpass = gson.fromJson(response, Amenity::class.java)
|
||||||
return overpass.elements
|
return overpass.elements
|
||||||
|
} else {
|
||||||
|
Log.e("OverpassApi", responseCode.toString())
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("Speed $e")
|
Log.e("OverpassApi", e.toString())
|
||||||
}
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,28 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
|
|
||||||
data class Tags(
|
data class Tags(
|
||||||
@SerializedName("name") var name: String? = null,
|
val destination: String = "",
|
||||||
@SerializedName("amenity") var amenity: String? = null,
|
val highway: String = "",
|
||||||
@SerializedName("authentication:none") var authenticationNone: String? = null,
|
val lanes: String = "",
|
||||||
@SerializedName("capacity") var capacity: String? = null,
|
val lit: String = "",
|
||||||
@SerializedName("motorcar") var motorcar: String? = null,
|
val maxspeed: String = "0",
|
||||||
@SerializedName("network") var network: String? = null,
|
val name: String = "",
|
||||||
@SerializedName("opening_hours") var openingHours: String? = null,
|
val oneway: String = "",
|
||||||
@SerializedName("operator") var operator: String? = null,
|
val ref: String = "",
|
||||||
@SerializedName("operator:short") var operatorShort: String? = null,
|
@SerializedName("int_ref") val intRef: String = "",
|
||||||
@SerializedName("operator:wikidata") var operatorWikidata: String? = null,
|
val sidewalk: String = "",
|
||||||
@SerializedName("operator:wikipedia") var operatorWikipedia: String? = null,
|
val smoothness: String = "",
|
||||||
@SerializedName("ref") var ref: String? = null,
|
val surface: String = "",
|
||||||
@SerializedName("socket:type2") var socketType2: String? = null,
|
val amenity: String = "",
|
||||||
@SerializedName("socket:type2:output") var socketType2Output: String? = null,
|
val capacity: String = "",
|
||||||
@SerializedName("maxspeed") var maxspeed: String = "0",
|
val motorcar: String = "",
|
||||||
@SerializedName("direction") var direction: String? = null,
|
val network: String = "",
|
||||||
|
val openingHours: String = "",
|
||||||
|
val operator: String = "",
|
||||||
|
val operatorShort: String = "",
|
||||||
|
val operatorWikidata: String = "",
|
||||||
|
val operatorWikipedia: String = "",
|
||||||
|
val socketType2: String = "",
|
||||||
|
val socketType2Output: String = "",
|
||||||
|
val direction: String = "",
|
||||||
)
|
)
|
||||||
@@ -12,5 +12,6 @@ data class Step(
|
|||||||
val distance: Double = 0.0,
|
val distance: Double = 0.0,
|
||||||
val street : String = "",
|
val street : String = "",
|
||||||
val intersection: List<Intersection> = mutableListOf(),
|
val intersection: List<Intersection> = mutableListOf(),
|
||||||
val countryCode : String = ""
|
val countryCode : String = "",
|
||||||
|
val roadNumbers : List<String> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident
|
|||||||
private const val tomtomFields =
|
private const val tomtomFields =
|
||||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||||
|
|
||||||
val useLocal = false // BuildConfig.DEBUG
|
val useLocal = BuildConfig.DEBUG
|
||||||
|
|
||||||
val useLocalTraffic = BuildConfig.DEBUG
|
val useLocalTraffic = BuildConfig.DEBUG
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ class TomTomRepository : NavigationRepository() {
|
|||||||
}
|
}
|
||||||
if (useLocal) {
|
if (useLocal) {
|
||||||
return fetchUrl(
|
return fetchUrl(
|
||||||
"https://kouros-online.de/tomtom_routing.json",
|
"http://192.168.1.37/tomtom_routing.json",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ class TomTomRepository : NavigationRepository() {
|
|||||||
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
|
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
|
||||||
return if (useLocalTraffic) {
|
return if (useLocalTraffic) {
|
||||||
fetchUrl(
|
fetchUrl(
|
||||||
"https://kouros-online.de/tomtom_traffic.json",
|
"http://192.168.1.37/tomtom_traffic.json",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ class TomTomRoute {
|
|||||||
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
|
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
|
||||||
stepDuration =
|
stepDuration =
|
||||||
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
|
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
|
||||||
|
val roadNumbers = if (lastInstruction.roadNumbers != null) {
|
||||||
|
lastInstruction.roadNumbers
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
val step = Step(
|
val step = Step(
|
||||||
index = stepIndex,
|
index = stepIndex,
|
||||||
street = street,
|
street = street,
|
||||||
@@ -106,7 +111,8 @@ class TomTomRoute {
|
|||||||
duration = stepDuration,
|
duration = stepDuration,
|
||||||
maneuver = maneuver,
|
maneuver = maneuver,
|
||||||
intersection = intersections,
|
intersection = intersections,
|
||||||
countryCode = lastInstruction.countryCode
|
countryCode = lastInstruction.countryCode,
|
||||||
|
roadNumbers = roadNumbers
|
||||||
)
|
)
|
||||||
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
|
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
|
||||||
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
|
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package com.kouros.navigation.model
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
//import com.kouros.navigation.data.Preferences.boxStore
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationListener
|
import android.util.Log
|
||||||
import android.location.LocationManager
|
|
||||||
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
|
||||||
@@ -12,15 +11,18 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
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_UPDATE_DISTANCE
|
||||||
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
|
||||||
import com.kouros.navigation.data.SearchFilter
|
import com.kouros.navigation.data.SearchFilter
|
||||||
import com.kouros.navigation.data.nominatim.Search
|
import com.kouros.navigation.data.nominatim.Search
|
||||||
import com.kouros.navigation.data.nominatim.SearchResult
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
|
import com.kouros.navigation.data.overpass.ElementSearch
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
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.utils.Levenshtein
|
import com.kouros.navigation.utils.countryCodeSpeedLimit
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -31,9 +33,12 @@ 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 java.lang.reflect.Modifier
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
import kotlin.collections.first
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.comparisons.compareBy
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel for navigation-related data operations.
|
* ViewModel for navigation-related data operations.
|
||||||
@@ -86,6 +91,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing POI elements from Overpass API */
|
||||||
|
val speedElements = mutableListOf<Elements>()
|
||||||
|
|
||||||
|
|
||||||
/** LiveData containing speed camera locations */
|
/** LiveData containing speed camera locations */
|
||||||
val speedCameras: MutableLiveData<List<Elements>> by lazy {
|
val speedCameras: MutableLiveData<List<Elements>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
@@ -107,10 +116,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
val gson: com.google.gson.Gson = GsonBuilder().create()
|
val gson: com.google.gson.Gson = GsonBuilder().create()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves recent places from Preferences as a Flow.
|
* Retrieves recent places from Preferences as a Flow.
|
||||||
*/
|
*/
|
||||||
fun recentPlacesFlow(context: Context, location: Location,): Flow<Place> = callbackFlow {
|
fun recentPlacesFlow(context: Context, location: Location): Flow<Place> = callbackFlow {
|
||||||
for (place in recentPlaces.value!!) {
|
for (place in recentPlaces.value!!) {
|
||||||
trySend(place)
|
trySend(place)
|
||||||
}
|
}
|
||||||
@@ -399,24 +409,93 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||||
* Posts speed limit to maxSpeed LiveData.
|
* Posts speed limit to maxSpeed LiveData.
|
||||||
*/
|
*/
|
||||||
fun getMaxSpeed(location: Location, street: String) {
|
fun getSpeedLimit(
|
||||||
|
location: Location,
|
||||||
|
routeBearing: Float,
|
||||||
|
countryCode: String,
|
||||||
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val levenshtein = Levenshtein()
|
maxSpeed.postValue(calculateSpeedLimit(location, routeBearing, countryCode))
|
||||||
val lineString = "${location.latitude},${location.longitude}"
|
|
||||||
val amenities = Overpass().getAround(10, lineString)
|
|
||||||
amenities.forEach {
|
|
||||||
if (it.tags.name != null) {
|
|
||||||
val distance =
|
|
||||||
levenshtein.distance(it.tags.name!!, street)
|
|
||||||
if (distance < 5) {
|
|
||||||
val speed = it.tags.maxspeed.toInt()
|
|
||||||
maxSpeed.postValue(speed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||||
|
*/
|
||||||
|
fun calculateSpeedLimit(location: Location, routeBearing: Float, countryCode: String): Int {
|
||||||
|
var speed = 0
|
||||||
|
var element: Elements?
|
||||||
|
val search = mutableListOf<ElementSearch>()
|
||||||
|
|
||||||
|
speedElements.filter { it.type == "way"}.forEach {
|
||||||
|
var streetBearingSum = 0F
|
||||||
|
var streetBearingAvg = 0F
|
||||||
|
var distance = 0F
|
||||||
|
var maxDistance = 1000F
|
||||||
|
for ((geoIndex, geo) in it.geometry.withIndex()) {
|
||||||
|
val geometryLocation = location(geo.lon, geo.lat)
|
||||||
|
distance = geometryLocation.distanceTo(location)
|
||||||
|
if (distance < maxDistance) {
|
||||||
|
maxDistance = distance
|
||||||
|
}
|
||||||
|
if (geoIndex > 0) {
|
||||||
|
val prevLocation =
|
||||||
|
location(it.geometry[geoIndex - 1].lon, it.geometry[geoIndex - 1].lat)
|
||||||
|
val streetBearing = prevLocation.bearingTo(geometryLocation).absoluteValue
|
||||||
|
streetBearingSum = (streetBearingSum + streetBearing)
|
||||||
|
streetBearingAvg = streetBearingSum / geoIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val bearing = calculateBearing(it, streetBearingAvg, routeBearing)
|
||||||
|
if (bearing < SPEED_BEARING_DEVIATION) {
|
||||||
|
search.add(
|
||||||
|
ElementSearch(
|
||||||
|
it,
|
||||||
|
maxDistance.toDouble(),
|
||||||
|
streetBearingAvg.absoluteValue
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val result = search.sortedWith(compareBy<ElementSearch> { it.distance }.thenByDescending { it.bearing })
|
||||||
|
if (result.isNotEmpty()) {
|
||||||
|
element = result.first().element
|
||||||
|
//Log.d("NavigationViewModel", "Distance: ${result.first().distance} Bearing: ${result.first().bearing} RouteBearing $routeBearing")
|
||||||
|
speed = if (element.tags.maxspeed == "none" && element.tags.highway == "motorway") {
|
||||||
|
countryCodeSpeedLimit(countryCode)
|
||||||
|
} else {
|
||||||
|
element.tags.maxspeed.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return speed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateBearing(element: Elements, streetBearingAvg : Float, routeBearing: Float) : Float {
|
||||||
|
return if (element.tags.oneway.isNotEmpty()) {
|
||||||
|
(streetBearingAvg - routeBearing.absoluteValue).absoluteValue
|
||||||
|
} else {
|
||||||
|
0F
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries Overpass API for speed limit on current road.
|
||||||
|
* Posts speed elements to speedElements.
|
||||||
|
*/
|
||||||
|
fun updateSpeedLimit(
|
||||||
|
location: Location,
|
||||||
|
street: String,
|
||||||
|
roadNumbers: List<String>
|
||||||
|
) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val lineString = "${location.latitude},${location.longitude}"
|
||||||
|
val elements = Overpass().getSpeedLimit(SPEED_UPDATE_DISTANCE, lineString, street, roadNumbers)
|
||||||
|
speedElements.clear()
|
||||||
|
speedElements.addAll(elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a place as a favorite in Preferences.
|
* Saves a place as a favorite in Preferences.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
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.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.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class RouteCalculator(var routeModel: RouteModel) {
|
class RouteCalculator(var routeModel: RouteModel) {
|
||||||
|
|
||||||
@@ -107,14 +106,27 @@ class RouteCalculator(var routeModel: RouteModel) {
|
|||||||
return nowUtcMillis + timeToDestinationMillis
|
return nowUtcMillis + timeToDestinationMillis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the speed limit in the view model.
|
||||||
|
*/
|
||||||
fun updateSpeedLimit(location: Location, viewModel: NavigationViewModel) {
|
fun updateSpeedLimit(location: Location, viewModel: NavigationViewModel) {
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
// speed limit
|
// speed limit
|
||||||
val distance = lastSpeedLocation.distanceTo(location)
|
val distance = lastSpeedLocation.distanceTo(location)
|
||||||
if (distance > 500 || 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.getMaxSpeed(location, routeModel.route.currentStep().street)
|
viewModel.updateSpeedLimit(
|
||||||
|
location,
|
||||||
|
routeModel.route.currentStep().street,
|
||||||
|
routeModel.currentStep().roadNumbers
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
viewModel.getSpeedLimit(
|
||||||
|
location,
|
||||||
|
routeModel.navState.routeBearing,
|
||||||
|
routeModel.currentStep.countryCode
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ open class RouteModel {
|
|||||||
leftDistance = routeCalculator.travelLeftDistance(),
|
leftDistance = routeCalculator.travelLeftDistance(),
|
||||||
lane = currentLanes,
|
lane = currentLanes,
|
||||||
exitNumber = exitNumber,
|
exitNumber = exitNumber,
|
||||||
message = currentStep.maneuver.message
|
message = currentStep.maneuver.message,
|
||||||
|
roadNumbers = currentStep.roadNumbers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.location.Location
|
|||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import org.maplibre.geojson.FeatureCollection
|
import org.maplibre.geojson.FeatureCollection
|
||||||
|
import org.maplibre.geojson.LineString
|
||||||
import org.maplibre.geojson.Point
|
import org.maplibre.geojson.Point
|
||||||
import org.maplibre.spatialk.geojson.Feature
|
import org.maplibre.spatialk.geojson.Feature
|
||||||
import org.maplibre.spatialk.geojson.dsl.addFeature
|
import org.maplibre.spatialk.geojson.dsl.addFeature
|
||||||
@@ -19,7 +20,7 @@ import kotlin.math.pow
|
|||||||
|
|
||||||
object GeoUtils {
|
object GeoUtils {
|
||||||
|
|
||||||
fun snapLocation(location: Location, stepCoordinates: List<Point>) : Location {
|
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
|
||||||
val newLocation = Location(location)
|
val newLocation = Location(location)
|
||||||
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
||||||
if (stepCoordinates.size > 1) {
|
if (stepCoordinates.size > 1) {
|
||||||
@@ -34,7 +35,7 @@ object GeoUtils {
|
|||||||
return newLocation
|
return newLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decodePolyline(encoded: String, precision: Int = 6): List<List<Double>> {
|
fun decodePolyline(encoded: String, precision: Int = 6): List<List<Double>> {
|
||||||
val factor = 10.0.pow(precision)
|
val factor = 10.0.pow(precision)
|
||||||
var lat = 0
|
var lat = 0
|
||||||
var lng = 0
|
var lng = 0
|
||||||
@@ -91,18 +92,20 @@ object GeoUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createLineStringCollection(lineCoordinates: List<List<Double>>): String {
|
fun createLineStringCollection(lineCoordinates: List<List<Double>>): String {
|
||||||
// return createPointCollection(lineCoordinates, "Route")
|
// return createPointCollection(lineCoordinates, "Route")
|
||||||
val lineString = buildLineString {
|
val lineString = buildLineString {
|
||||||
lineCoordinates.forEach {
|
lineCoordinates.forEach {
|
||||||
add(org.maplibre.spatialk.geojson.Point(
|
add(
|
||||||
it[0],
|
org.maplibre.spatialk.geojson.Point(
|
||||||
it[1]
|
it[0],
|
||||||
))
|
it[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val feature = Feature(lineString, null)
|
val feature = Feature(lineString, null)
|
||||||
val featureCollection = org.maplibre.spatialk.geojson.FeatureCollection(feature)
|
val featureCollection = org.maplibre.spatialk.geojson.FeatureCollection(feature)
|
||||||
return featureCollection.toJson()
|
return featureCollection.toJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPointCollection(lineCoordinates: List<List<Double>>, category: String): String {
|
fun createPointCollection(lineCoordinates: List<List<Double>>, category: String): String {
|
||||||
@@ -114,9 +117,30 @@ object GeoUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return featureCollection.toJson()
|
return featureCollection.toJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createStartCollection(geoJson: String): String {
|
||||||
|
val featureCollection = FeatureCollection.fromJson(geoJson)
|
||||||
|
val geometry = featureCollection.features()!!.first().geometry()
|
||||||
|
val coordinates = (geometry as LineString)
|
||||||
|
val first = coordinates.coordinates().first()
|
||||||
|
val points = createPointCollection(
|
||||||
|
listOf(listOf(first.coordinates()[0], first.coordinates()[1])), "End"
|
||||||
|
)
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createEndCollection(geoJson: String): String {
|
||||||
|
val featureCollection = FeatureCollection.fromJson(geoJson)
|
||||||
|
val geometry = featureCollection.features()!!.first().geometry()
|
||||||
|
val coordinates = (geometry as LineString)
|
||||||
|
val last = coordinates.coordinates().last()
|
||||||
|
val points = createPointCollection(
|
||||||
|
listOf(listOf(last.coordinates()[0], last.coordinates()[1])), "End"
|
||||||
|
)
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the lat and len of a square around a point.
|
* Calculate the lat and len of a square around a point.
|
||||||
@@ -131,6 +155,7 @@ object GeoUtils {
|
|||||||
|
|
||||||
return "$lngMin,$latMin,$lngMax,$latMax"
|
return "$lngMin,$latMin,$lngMax,$latMax"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBoundingBox(
|
fun getBoundingBox(
|
||||||
lat: Double,
|
lat: Double,
|
||||||
lon: Double,
|
lon: Double,
|
||||||
@@ -144,4 +169,18 @@ object GeoUtils {
|
|||||||
|
|
||||||
return "$minLat,$minLon,$maxLat,$maxLon"
|
return "$minLat,$minLon,$maxLat,$maxLon"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isLocationInBoundingBox(
|
||||||
|
bottomLeftLat: Double,
|
||||||
|
bottomLeftLon: Double,
|
||||||
|
topRightLat: Double,
|
||||||
|
topRightLon: Double,
|
||||||
|
location: Location
|
||||||
|
): Boolean {
|
||||||
|
val isInside =
|
||||||
|
location.latitude in bottomLeftLat..topRightLat
|
||||||
|
&& location.longitude >= bottomLeftLon
|
||||||
|
&& location.longitude <= topRightLon
|
||||||
|
return isInside
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,11 @@ class Levenshtein {
|
|||||||
* @param limit the maximum result to compute before stopping, terminating calculation early.
|
* @param limit the maximum result to compute before stopping, terminating calculation early.
|
||||||
* @return the computed Levenshtein distance.
|
* @return the computed Levenshtein distance.
|
||||||
*/
|
*/
|
||||||
fun distance(first: CharSequence, second: CharSequence, limit: Int = Int.MAX_VALUE): Int {
|
fun distance(first: CharSequence, second: CharSequence, countryCode: String, limit: Int = Int.MAX_VALUE): Int {
|
||||||
|
if (countryCode == "GRC") return 0
|
||||||
if (first == second) return 0
|
if (first == second) return 0
|
||||||
if (first.isEmpty()) return second.length
|
if (first.isEmpty()) return 0
|
||||||
if (second.isEmpty()) return first.length
|
if (second.isEmpty()) return 0
|
||||||
|
|
||||||
// initial costs is the edit distance from an empty string, which corresponds to the characters to inserts.
|
// initial costs is the edit distance from an empty string, which corresponds to the characters to inserts.
|
||||||
// the array size is : length + 1 (empty string)
|
// the array size is : length + 1 (empty string)
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ package com.kouros.navigation.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.util.Log
|
|
||||||
import androidx.car.app.model.Distance
|
import androidx.car.app.model.Distance
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
|
||||||
import com.kouros.navigation.data.Constants.TILT
|
import com.kouros.navigation.data.Constants.TILT
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
@@ -94,15 +93,19 @@ fun calculateZoomFromBoundingBox(centerLocation: Location, previewDistance: Doub
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun calculateTilt(newZoom: Double, tilt: Double): Double =
|
fun calculateTilt(viewStyle: ViewStyle, newZoom: Double, tilt: Double): Double =
|
||||||
if (newZoom < 13) {
|
if (viewStyle == ViewStyle.VIEW) {
|
||||||
0.0
|
if (newZoom < 13) {
|
||||||
} else {
|
0.0
|
||||||
if (tilt == 0.0) {
|
|
||||||
TILT
|
|
||||||
} else {
|
} else {
|
||||||
tilt
|
if (tilt == 0.0) {
|
||||||
|
TILT
|
||||||
|
} else {
|
||||||
|
tilt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double): Double {
|
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double): Double {
|
||||||
@@ -134,13 +137,14 @@ fun Double.round(numFractionDigits: Int): Double {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun duration(
|
fun duration(
|
||||||
preview: Boolean,
|
viewStyle: ViewStyle,
|
||||||
bearing: Double,
|
bearing: Double,
|
||||||
lastBearing: Double,
|
lastBearing: Double,
|
||||||
lastLocationUpdate: LocalDateTime
|
lastLocationUpdate: LocalDateTime
|
||||||
): Duration {
|
): Duration {
|
||||||
if (preview) {
|
if (viewStyle == ViewStyle.PREVIEW ||
|
||||||
return 10.milliseconds
|
viewStyle == ViewStyle.PAN_VIEW) {
|
||||||
|
return 100.milliseconds
|
||||||
}
|
}
|
||||||
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
||||||
2.seconds
|
2.seconds
|
||||||
@@ -184,3 +188,11 @@ fun formattedDistance(distanceMode: Int, distance: Double): Pair<Double, Int> {
|
|||||||
}
|
}
|
||||||
return Pair(currentDistance, displayUnit)
|
return Pair(currentDistance, displayUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun countryCodeSpeedLimit(countryCode: String) : Int {
|
||||||
|
return when (countryCode) {
|
||||||
|
"DEU", "FRA", "AUT", "GRE", "NLD", "ITA", "SLO", "SVK", "CZE" -> 130
|
||||||
|
"POL", "BEL", "ESP", "PRT", "BGR", "HUN", "FIN" -> 120
|
||||||
|
else -> 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="48dp"
|
||||||
|
android:height="48dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M390,220L450,220L450,160L390,160L390,220ZM510,220L510,160L570,160L570,220L510,220ZM390,460L390,400L450,400L450,460L390,460ZM630,340L630,280L690,280L690,340L630,340ZM630,460L630,400L690,400L690,460L630,460ZM510,460L510,400L570,400L570,460L510,460ZM630,220L630,160L690,160L690,220L630,220ZM450,280L450,220L510,220L510,280L450,280ZM270,800L270,160L330,160L330,220L390,220L390,280L330,280L330,340L390,340L390,400L330,400L330,800L270,800ZM570,400L570,340L630,340L630,400L570,400ZM450,400L450,340L510,340L510,400L450,400ZM390,340L390,280L450,280L450,340L390,340ZM510,340L510,280L570,280L570,340L510,340ZM570,280L570,220L630,220L630,280L570,280Z"/>
|
||||||
|
</vector>
|
||||||
@@ -72,4 +72,5 @@
|
|||||||
<string name="electric">Electric</string>
|
<string name="electric">Electric</string>
|
||||||
<string name="engine_type">Engine type</string>
|
<string name="engine_type">Engine type</string>
|
||||||
<string name="alternative_routes">Alternative routes</string>
|
<string name="alternative_routes">Alternative routes</string>
|
||||||
|
<string name="wait">Wait</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -56,4 +56,5 @@
|
|||||||
<string name="electric">Electric</string>
|
<string name="electric">Electric</string>
|
||||||
<string name="engine_type">Engine type</string>
|
<string name="engine_type">Engine type</string>
|
||||||
<string name="alternative_routes">Alternative routes</string>
|
<string name="alternative_routes">Alternative routes</string>
|
||||||
|
<string name="wait">Wait</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -56,4 +56,5 @@
|
|||||||
<string name="electric">Electric</string>
|
<string name="electric">Electric</string>
|
||||||
<string name="engine_type">Engine type</string>
|
<string name="engine_type">Engine type</string>
|
||||||
<string name="alternative_routes">Alternative routes</string>
|
<string name="alternative_routes">Alternative routes</string>
|
||||||
|
<string name="wait">Wait</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -59,4 +59,5 @@
|
|||||||
<string name="electric">Electric</string>
|
<string name="electric">Electric</string>
|
||||||
<string name="engine_type">Engine type</string>
|
<string name="engine_type">Engine type</string>
|
||||||
<string name="alternative_routes">Alternative routes</string>
|
<string name="alternative_routes">Alternative routes</string>
|
||||||
|
<string name="wait">Wait</string>
|
||||||
</resources>
|
</resources>
|
||||||
File diff suppressed because one or more lines are too long
@@ -8,8 +8,11 @@ import com.kouros.navigation.data.route.Maneuver
|
|||||||
import com.kouros.navigation.data.route.Routes
|
import com.kouros.navigation.data.route.Routes
|
||||||
import com.kouros.navigation.data.route.Step
|
import com.kouros.navigation.data.route.Step
|
||||||
import com.kouros.navigation.data.route.Summary
|
import com.kouros.navigation.data.route.Summary
|
||||||
|
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.maplibre.geojson.FeatureCollection
|
||||||
|
import org.maplibre.geojson.LineString
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
import org.mockito.kotlin.doNothing
|
import org.mockito.kotlin.doNothing
|
||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
@@ -58,6 +61,18 @@ class RouteModelTest {
|
|||||||
return Route(routeEngine = 2, routes = listOf(routes), currentStepIndex = currentStepIndex)
|
return Route(routeEngine = 2, routes = listOf(routes), currentStepIndex = currentStepIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create Point Collection returns false when route has no legs`() {
|
||||||
|
val geoJson = routeModel.curRoute.routeGeoJson
|
||||||
|
val featureCollection = FeatureCollection.fromJson(geoJson)
|
||||||
|
val geometry = featureCollection.features()!!.first().geometry()
|
||||||
|
val coordinates = (geometry as LineString)
|
||||||
|
val first = coordinates.coordinates().first()
|
||||||
|
val last = coordinates.coordinates().first()
|
||||||
|
val points = createPointCollection(listOf(
|
||||||
|
listOf(first.coordinates()[0], first.coordinates()[1]), listOf(last.coordinates()[0], last.coordinates()[1])), "Start")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hasLegs returns true when route has legs`() {
|
fun `hasLegs returns true when route has legs`() {
|
||||||
val step0 = createStep(index = 0, numWaypoints = 2)
|
val step0 = createStep(index = 0, numWaypoints = 2)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.1.0"
|
agp = "9.1.1"
|
||||||
androidGpxParser = "2.3.1"
|
androidGpxParser = "2.3.1"
|
||||||
androidSdkTurf = "6.0.1"
|
androidSdkTurf = "6.0.1"
|
||||||
datastore = "1.2.1"
|
datastore = "1.2.1"
|
||||||
gradle = "9.1.0"
|
gradle = "9.1.1"
|
||||||
koinAndroid = "4.2.0"
|
koinAndroid = "4.2.0"
|
||||||
koinAndroidxCompose = "4.2.0"
|
koinAndroidxCompose = "4.2.0"
|
||||||
koinComposeViewmodel = "4.2.0"
|
koinComposeViewmodel = "4.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user