RouteCalculator
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 = 108
|
versionCode = 109
|
||||||
versionName = "0.3.0.108"
|
versionName = "0.3.0.109"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ class DeviceLocationManager(
|
|||||||
val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||||
if (lastLocation != null) {
|
if (lastLocation != null) {
|
||||||
if (setIndividualLocation) {
|
if (setIndividualLocation) {
|
||||||
onInitialLocation(lastLocation)
|
onInitialLocation(homeVogelhart)
|
||||||
onLocationUpdate(lastLocation)
|
onLocationUpdate(homeVogelhart)
|
||||||
} else {
|
} else {
|
||||||
onInitialLocation(lastLocation)
|
onInitialLocation(lastLocation)
|
||||||
onLocationUpdate(lastLocation)
|
onLocationUpdate(lastLocation)
|
||||||
|
|||||||
@@ -485,6 +485,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
val distance = location.distanceTo(snappedLocation)
|
val distance = location.distanceTo(snappedLocation)
|
||||||
when {
|
when {
|
||||||
distance > MAXIMAL_ROUTE_DEVIATION -> {
|
distance > MAXIMAL_ROUTE_DEVIATION -> {
|
||||||
|
Log.d(TAG, "Maximal route deviation")
|
||||||
stopNavigation()
|
stopNavigation()
|
||||||
navigationScreen.calculateNewRoute(routeModel.navState.destination)
|
navigationScreen.calculateNewRoute(routeModel.navState.destination)
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ class Simulation {
|
|||||||
) {
|
) {
|
||||||
if (routeModel.navState.route.isRouteValid()) {
|
if (routeModel.navState.route.isRouteValid()) {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
gpxSimulation(routeModel, lifecycleScope, updateLocation)
|
//gpxSimulation(routeModel, lifecycleScope, updateLocation)
|
||||||
//currentSimulation(routeModel, lifecycleScope, updateLocation)
|
currentSimulation(routeModel, lifecycleScope, updateLocation)
|
||||||
} else {
|
} else {
|
||||||
currentSimulation(routeModel, lifecycleScope, updateLocation)
|
currentSimulation(routeModel, lifecycleScope, updateLocation)
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ class Simulation {
|
|||||||
if (points.isEmpty()) return
|
if (points.isEmpty()) return
|
||||||
simulationJob?.cancel()
|
simulationJob?.cancel()
|
||||||
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
||||||
var curBearing = 0f
|
var curBearing: Float
|
||||||
simulationJob = lifecycleScope.launch {
|
simulationJob = lifecycleScope.launch {
|
||||||
for ((index, point) in points.withIndex()) {
|
for ((index, point) in points.withIndex()) {
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
@@ -90,7 +90,7 @@ class Simulation {
|
|||||||
simulationJob?.cancel()
|
simulationJob?.cancel()
|
||||||
simulationJob =lifecycleScope.launch() {
|
simulationJob =lifecycleScope.launch() {
|
||||||
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
||||||
var curBearing = 0f
|
val curBearing = 0f
|
||||||
val parser = GPXParser()
|
val parser = GPXParser()
|
||||||
val parsedGpx: Gpx? =
|
val parsedGpx: Gpx? =
|
||||||
parser.parse(route.byteInputStream())
|
parser.parse(route.byteInputStream())
|
||||||
@@ -118,9 +118,6 @@ class Simulation {
|
|||||||
// 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)
|
||||||
if (duration > 100) {
|
|
||||||
// delay(duration / 4)
|
|
||||||
}
|
|
||||||
delay(200)
|
delay(200)
|
||||||
lastTime = p.time
|
lastTime = p.time
|
||||||
lastLocation = fakeLocation
|
lastLocation = fakeLocation
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.kouros.navigation.data
|
package com.kouros.navigation.data
|
||||||
|
|
||||||
|
import android.location.Location
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.google.gson.annotations.Expose
|
import com.google.gson.annotations.Expose
|
||||||
import com.kouros.navigation.data.route.Lane
|
import com.kouros.navigation.data.route.Lane
|
||||||
@@ -28,7 +29,7 @@ data class Category(
|
|||||||
val name: String,
|
val name: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class StepMatch(val stepIndex: Int, val waypointIndex: Int, val location: Location)
|
||||||
|
|
||||||
data class Places(
|
data class Places(
|
||||||
val places: List<Place>,
|
val places: List<Place>,
|
||||||
@@ -144,7 +145,7 @@ object Constants {
|
|||||||
|
|
||||||
const val NEAREST_LOCATION_DISTANCE = 10F
|
const val NEAREST_LOCATION_DISTANCE = 10F
|
||||||
|
|
||||||
const val MAXIMUM_LOCATION_DISTANCE = 100000F
|
const val MAXIMUM_LOCATION_DISTANCE = Float.MAX_VALUE
|
||||||
|
|
||||||
const val TRAFFIC_UPDATE = 300
|
const val TRAFFIC_UPDATE = 300
|
||||||
|
|
||||||
|
|||||||
@@ -3,60 +3,84 @@ package com.kouros.navigation.model
|
|||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.util.Log
|
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
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.utils.bearingPositive
|
import com.kouros.navigation.data.StepMatch
|
||||||
|
import com.kouros.navigation.data.osrm.Waypoints
|
||||||
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.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
class RouteCalculator(var routeModel: RouteModel) {
|
class RouteCalculator(var routeModel: RouteModel) {
|
||||||
|
|
||||||
|
var bestMatch: StepMatch? = null
|
||||||
var lastSpeedLocation: Location = location(0.0, 0.0)
|
var lastSpeedLocation: Location = location(0.0, 0.0)
|
||||||
|
|
||||||
var lastSpeedIndex: Int = 0
|
var lastSpeedIndex: Int = 0
|
||||||
|
|
||||||
fun findStep(location: Location) {
|
fun findStep(location: Location) {
|
||||||
|
val route = routeModel.navState.route
|
||||||
|
val steps = routeModel.curLeg.steps
|
||||||
|
val startIndex = route.currentStepIndex
|
||||||
|
|
||||||
|
// Windowed search for performance (current + 2 steps)
|
||||||
|
//val endIndex = (startIndex + 2).coerceAtMost(steps.size - 1)
|
||||||
|
val endIndex = steps.size - 1
|
||||||
|
|
||||||
var nearestDistance = MAXIMUM_LOCATION_DISTANCE
|
var nearestDistance = MAXIMUM_LOCATION_DISTANCE
|
||||||
var count = 0
|
var searchCount = 0
|
||||||
var lastDistance = 0F
|
|
||||||
var increaseDistance = 0
|
for (i in startIndex..endIndex) {
|
||||||
for ((index, step) in routeModel.curLeg.steps.withIndex()) {
|
val step = steps[i]
|
||||||
count++
|
val waypoints = step.maneuver.waypoints
|
||||||
var distance = 0F
|
val startWayIndex = if (i == startIndex) step.waypointIndex else 0
|
||||||
if (index >= routeModel.navState.route.currentStepIndex) {
|
|
||||||
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
|
var lastDistance = Float.MAX_VALUE
|
||||||
count++
|
var increaseCount = 0
|
||||||
if (wayIndex >= step.waypointIndex) {
|
|
||||||
distance = location.distanceTo(waypoint)
|
for (j in startWayIndex until waypoints.size) {
|
||||||
if (distance < nearestDistance ) {
|
searchCount++
|
||||||
nearestDistance = distance
|
val waypoint = waypoints[j]
|
||||||
routeModel.navState.route.currentStepIndex = step.index
|
val distance = location.distanceTo(waypoint)
|
||||||
step.waypointIndex = wayIndex
|
|
||||||
step.wayPointLocation = waypoint
|
if (distance < nearestDistance) {
|
||||||
}
|
nearestDistance = distance
|
||||||
}
|
bestMatch = StepMatch(step.index, j, waypoint)
|
||||||
if (stopSearch(nearestDistance, distance) || increaseDistance > 20) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (distance > lastDistance) {
|
|
||||||
increaseDistance++
|
|
||||||
}
|
|
||||||
lastDistance = distance
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (stopSearch(nearestDistance, distance) || increaseDistance > 20) {
|
// Track if we are getting further away
|
||||||
break
|
if (distance > lastDistance) {
|
||||||
|
increaseCount++
|
||||||
|
} else {
|
||||||
|
increaseCount = 0
|
||||||
|
}
|
||||||
|
// Absolute early exit: found a close point and now moved far away
|
||||||
|
if (stopSearch(nearestDistance, distance, increaseCount)) {
|
||||||
|
bestMatch?.let { match ->
|
||||||
|
route.currentStepIndex = match.stepIndex
|
||||||
|
steps[match.stepIndex].waypointIndex = match.waypointIndex
|
||||||
|
steps[match.stepIndex].wayPointLocation = match.location
|
||||||
|
}
|
||||||
|
Log.d(TAG, "FindStep optimized: count=$searchCount, dist=$nearestDistance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastDistance = distance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d(TAG, "FindStep $nearestDistance $count $increaseDistance")
|
bestMatch?.let { match ->
|
||||||
|
route.currentStepIndex = match.stepIndex
|
||||||
|
steps[match.stepIndex].waypointIndex = match.waypointIndex
|
||||||
|
steps[match.stepIndex].wayPointLocation = match.location
|
||||||
|
|
||||||
|
}
|
||||||
|
Log.d(TAG, "FindStep not optimized: count=$searchCount, dist=$nearestDistance")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopSearch(nearestDistance: Float, distance: Float): Boolean {
|
private fun stopSearch(nearestDistance: Float, distance: Float, increaseCount: Int): Boolean {
|
||||||
return nearestDistance < NEAREST_LOCATION_DISTANCE && distance > NEAREST_LOCATION_DISTANCE * 10
|
return (nearestDistance < NEAREST_LOCATION_DISTANCE && distance > NEAREST_LOCATION_DISTANCE * 10)
|
||||||
|
|| increaseCount > 10
|
||||||
}
|
}
|
||||||
|
|
||||||
fun travelLeftTime(): Double {
|
fun travelLeftTime(): Double {
|
||||||
@@ -92,7 +116,7 @@ class RouteCalculator(var routeModel: RouteModel) {
|
|||||||
fun leftStepDistance(): Double {
|
fun leftStepDistance(): Double {
|
||||||
val step = routeModel.route.currentStep()
|
val step = routeModel.route.currentStep()
|
||||||
var leftDistance = 0F
|
var leftDistance = 0F
|
||||||
if (step.waypointIndex < step.maneuver.waypoints.size -1) {
|
if (step.waypointIndex < step.maneuver.waypoints.size - 1) {
|
||||||
leftDistance = step.maneuver.leftDistance[step.waypointIndex]
|
leftDistance = step.maneuver.leftDistance[step.waypointIndex]
|
||||||
val waypointLocation = step.maneuver.waypoints[step.waypointIndex]
|
val waypointLocation = step.maneuver.waypoints[step.waypointIndex]
|
||||||
if (routeModel.navState.lastLocation.latitude != 0.0) {
|
if (routeModel.navState.lastLocation.latitude != 0.0) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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.location
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -44,7 +45,7 @@ class RouteCalculatorTest {
|
|||||||
waypointIndex: Int = 0,
|
waypointIndex: Int = 0,
|
||||||
): Step {
|
): Step {
|
||||||
val waypoints = (0 until numWaypoints).map { i ->
|
val waypoints = (0 until numWaypoints).map { i ->
|
||||||
listOf(11.0 + index * 0.01 + i * 0.001, 48.0)
|
location(11.0 + index * 0.01 + i * 0.001, 48.0)
|
||||||
}
|
}
|
||||||
return Step(
|
return Step(
|
||||||
index = index,
|
index = index,
|
||||||
@@ -127,13 +128,14 @@ class RouteCalculatorTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val mockLocation: Location = mock()
|
val mockLocation: Location = mock()
|
||||||
// step0/wp0: 500F, step0/wp1: 5F — 5F < NEAREST_LOCATION_DISTANCE (10F) → break
|
// step0/wp0: 500F, step0/wp1: 5F, step1/wp0: 200F
|
||||||
whenever(mockLocation.distanceTo(any())).thenReturn(500F, 5F)
|
// 5F < NEAREST_LOCATION_DISTANCE (10F) AND 200F > 10 * 10F → break
|
||||||
|
whenever(mockLocation.distanceTo(any())).thenReturn(500F, 5F, 200F)
|
||||||
|
|
||||||
routeCalculator.findStep(mockLocation)
|
routeCalculator.findStep(mockLocation)
|
||||||
|
|
||||||
// step1 and step2 are never evaluated
|
// should stop after the 3rd call (step1/wp0 triggers stopSearch)
|
||||||
verify(mockLocation, times(2)).distanceTo(any())
|
verify(mockLocation, times(3)).distanceTo(any())
|
||||||
assertEquals(0, routeModel.navState.route.currentStepIndex)
|
assertEquals(0, routeModel.navState.route.currentStepIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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 com.kouros.navigation.utils.GeoUtils.createPointCollection
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
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.FeatureCollection
|
||||||
@@ -39,7 +40,7 @@ class RouteModelTest {
|
|||||||
waypointIndex: Int = 0,
|
waypointIndex: Int = 0,
|
||||||
): Step {
|
): Step {
|
||||||
val waypoints = (0 until numWaypoints).map { i ->
|
val waypoints = (0 until numWaypoints).map { i ->
|
||||||
listOf(11.0 + index * 0.01 + i * 0.001, 48.0)
|
location(11.0 + index * 0.01 + i * 0.001, 48.0)
|
||||||
}
|
}
|
||||||
return Step(
|
return Step(
|
||||||
index = index,
|
index = index,
|
||||||
|
|||||||
Reference in New Issue
Block a user