TomTom Routing

This commit is contained in:
Dimitris
2026-02-10 10:50:42 +01:00
parent 5141041b5c
commit 65ff41995d
26 changed files with 182 additions and 201 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId = "com.kouros.navigation" applicationId = "com.kouros.navigation"
minSdk = 33 minSdk = 33
targetSdk = 36 targetSdk = 36
versionCode = 36 versionCode = 38
versionName = "0.2.0.36" versionName = "0.2.0.38"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -87,8 +87,8 @@ class MainActivity : ComponentActivity() {
val routeModel = RouteModel() val routeModel = RouteModel()
var tilt = 50.0 var tilt = 50.0
val useMock = true val useMock = false
val type = 1 // simulate 2 test 3 gpx val type = 3 // simulate 2 test 3 gpx 4 testSingle
var currentIndex = 0 var currentIndex = 0
val stepData: MutableLiveData<StepData> by lazy { val stepData: MutableLiveData<StepData> by lazy {
@@ -109,14 +109,15 @@ class MainActivity : ComponentActivity() {
3 -> gpx( 3 -> gpx(
context = applicationContext context = applicationContext
) )
4 -> testSingle()
} }
} }
} }
} }
val cameraPosition = MutableLiveData( val cameraPosition = MutableLiveData(
CameraPosition( CameraPosition(
zoom = 15.0, zoom = 15.0, target = Position(latitude = 48.1857475, longitude = 11.5793627)
target = Position(latitude = 48.1857475, longitude = 11.5793627)
) )
) )
@@ -140,13 +141,11 @@ class MainActivity : ComponentActivity() {
} }
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.lastLocation fusedLocationClient.lastLocation.addOnSuccessListener { _: android.location.Location? ->
.addOnSuccessListener { _: android.location.Location? ->
if (useMock) { if (useMock) {
mock = MockLocation(locationManager) mock = MockLocation(locationManager)
mock.setMockLocation( mock.setMockLocation(
homeVogelhart.latitude, homeVogelhart.latitude, homeVogelhart.longitude
homeVogelhart.longitude
) )
navigationViewModel.route.observe(this, observer) navigationViewModel.route.observe(this, observer)
} }
@@ -196,8 +195,7 @@ class MainActivity : ComponentActivity() {
val sheetPeekHeightState = remember { mutableStateOf(256.dp) } val sheetPeekHeightState = remember { mutableStateOf(256.dp) }
val locationProvider = rememberDefaultLocationProvider( val locationProvider = rememberDefaultLocationProvider(
updateInterval = 0.5.seconds, updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest
desiredAccuracy = DesiredAccuracy.Highest
) )
val userLocationState = rememberUserLocationState(locationProvider) val userLocationState = rememberUserLocationState(locationProvider)
val locationState = locationProvider.location.collectAsState() val locationState = locationProvider.location.collectAsState()
@@ -263,12 +261,10 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun Settings(navController: NavController, modifier: Modifier = Modifier) { fun Settings(navController: NavController, modifier: Modifier = Modifier) {
Box( Box(
modifier = Modifier modifier = Modifier.fillMaxSize()
.fillMaxSize()
) { ) {
FloatingActionButton( FloatingActionButton(
modifier = Modifier modifier = Modifier.padding(start = 10.dp, top = 40.dp),
.padding(start = 10.dp, top = 40.dp),
onClick = { onClick = {
navController.navigate("settings") navController.navigate("settings")
}, },
@@ -284,44 +280,35 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun SheetContent( fun SheetContent(
locationState: Double, locationState: Double, step: StepData?, nextStep: StepData?, closeSheet: () -> Unit
step: StepData?,
nextStep: StepData?,
closeSheet: () -> Unit
) { ) {
if (!routeModel.isNavigating()) { if (!routeModel.isNavigating()) {
SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() } SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() }
} else { } else {
NavigationSheet( NavigationSheet(
applicationContext, applicationContext,
routeModel, step!!, nextStep!!, routeModel,
step!!,
nextStep!!,
{ stopNavigation { closeSheet() } }, { stopNavigation { closeSheet() } },
{ simulateNavigation() } { simulateNavigation() })
)
} }
// For recomposition! // For recomposition!
Text("$locationState", fontSize = 12.sp) Text("$locationState", fontSize = 12.sp)
} }
fun updateLocation(location: Location?) { fun updateLocation(location: Location?) {
if (location != null if (location != null && lastLocation.latitude != location.position.latitude && lastLocation.longitude != location.position.longitude) {
&& lastLocation.latitude != location.position.latitude
&& lastLocation.longitude != location.position.longitude
) {
val currentLocation = location(location.position.longitude, location.position.latitude) val currentLocation = location(location.position.longitude, location.position.latitude)
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing) val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
with(routeModel) { with(routeModel) {
if (isNavigating()) { if (isNavigating()) {
updateLocation(currentLocation, navigationViewModel) updateLocation(currentLocation, navigationViewModel)
stepData.value = currentStep() stepData.value = currentStep()
if (route.currentStepIndex + 1 <= curLeg.steps.size) {
nextStepData.value = nextStep() nextStepData.value = nextStep()
} if (navState.maneuverType in 39..42 && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE) {
if (maneuverType in 39..42
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
// stopNavigation() // stopNavigation()
arrived = true navState.copy(arrived = true)
routeData.value = "" routeData.value = ""
} }
} }
@@ -329,9 +316,7 @@ class MainActivity : ComponentActivity() {
val zoom = calculateZoom(location.speed) val zoom = calculateZoom(location.speed)
cameraPosition.postValue( cameraPosition.postValue(
cameraPosition.value!!.copy( cameraPosition.value!!.copy(
zoom = zoom, zoom = zoom, target = location.position, bearing = bearing
target = location.position,
bearing = bearing
), ),
) )
lastLocation = currentLocation lastLocation = currentLocation
@@ -361,13 +346,9 @@ class MainActivity : ComponentActivity() {
private fun checkMockLocationEnabled() { private fun checkMockLocationEnabled() {
try { try {
// Check if mock location is enabled for this app // Check if mock location is enabled for this app
val appOpsManager = val appOpsManager = getSystemService(APP_OPS_SERVICE) as AppOpsManager
getSystemService(APP_OPS_SERVICE) as AppOpsManager val mode = appOpsManager.checkOp(
val mode = AppOpsManager.OPSTR_MOCK_LOCATION, Process.myUid(), packageName
appOpsManager.checkOp(
AppOpsManager.OPSTR_MOCK_LOCATION,
Process.myUid(),
packageName
) )
if (mode != AppOpsManager.MODE_ALLOWED) { if (mode != AppOpsManager.MODE_ALLOWED) {
@@ -389,7 +370,7 @@ class MainActivity : ComponentActivity() {
val deviation = 0.0 val deviation = 0.0
if (index in 0..routeModel.curRoute.waypoints.size) { if (index in 0..routeModel.curRoute.waypoints.size) {
mock.setMockLocation(waypoint[1], waypoint[0]) mock.setMockLocation(waypoint[1], waypoint[0])
delay(500L) // Thread.sleep(500)
} }
} }
} }
@@ -401,24 +382,34 @@ class MainActivity : ComponentActivity() {
//if (index in 3..3) { //if (index in 3..3) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
routeModel.updateLocation( routeModel.updateLocation(
location(waypoint[0], waypoint[1]), location(waypoint[0], waypoint[1]), navigationViewModel
navigationViewModel
) )
val step = routeModel.currentStep() val step = routeModel.currentStep()
println("Step: ${step.instruction} ${step.leftStepDistance}") val nextStep = routeModel.nextStep()
if (index + 1 <= routeModel.curLeg.steps.size) { println("Step: ${step.instruction} ${step.leftStepDistance} ${nextStep.currentManeuverType}")
//nextStepData.value = routeModel.nextStep()
}
} }
//} //}
} }
} }
fun test2() { fun testSingle() {
CoroutineScope(Dispatchers.IO).launch { testSingleUpdate(48.185976, 11.578463) // Silcherstr. 23-13
// Balanstr. testSingleUpdate(48.186712, 11.578574) // Silcherstr. 27-33
mock.setMockLocation(48.119357, 11.599130) testSingleUpdate(48.186899, 11.580480) // Schmalkadenerstr. 24-28
} }
fun testSingleUpdate(latitude: Double, longitude: Double) {
if (1 == 1) {
mock.setMockLocation(latitude, longitude)
} else {
routeModel.updateLocation(
location(longitude, latitude), navigationViewModel
)
}
val step = routeModel.currentStep()
val nextStep = routeModel.nextStep()
println("Step: ${step.instruction} ${step.leftStepDistance} ${nextStep.currentManeuverType}")
Thread.sleep(1_000)
} }
fun gpx(context: Context) { fun gpx(context: Context) {

View File

@@ -34,7 +34,7 @@ fun NavigationSheet(
val distance = (step.leftDistance / 1000).round(1) val distance = (step.leftDistance / 1000).round(1)
if (step.lane.isNotEmpty()) { if (step.lane.isNotEmpty()) {
routeModel.iconMapper.addLanes( step) routeModel.navState.iconMapper.addLanes( step)
} }
Column { Column {

View File

@@ -11,7 +11,6 @@ buildscript {
val objectboxVersion by extra("5.0.1") // For KTS build scripts val objectboxVersion by extra("5.0.1") // For KTS build scripts
dependencies { dependencies {
// Android Gradle Plugin 8.0 or later supported
classpath(libs.gradle) classpath(libs.gradle)
classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion") classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion")
} }

View File

@@ -251,11 +251,11 @@ class NavigationSession : Session(), NavigationScreen.Listener {
fun updateLocation(location: Location) { fun updateLocation(location: Location) {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
navigationScreen.updateTrip(location) navigationScreen.updateTrip(location)
if (!routeModel.arrived) { if (!routeModel.navState.arrived) {
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
val distance = location.distanceTo(snapedLocation) val distance = location.distanceTo(snapedLocation)
if (distance > MAXIMAL_ROUTE_DEVIATION) { if (distance > MAXIMAL_ROUTE_DEVIATION) {
navigationScreen.calculateNewRoute(routeModel.destination) navigationScreen.calculateNewRoute(routeModel.navState.destination)
return return
} }
if (distance < MAXIMAL_SNAP_CORRECTION) { if (distance < MAXIMAL_SNAP_CORRECTION) {

View File

@@ -470,7 +470,7 @@ fun DebugInfo(
contentAlignment = Alignment.CenterStart contentAlignment = Alignment.CenterStart
) { ) {
val textMeasurerLocation = rememberTextMeasurer() val textMeasurerLocation = rememberTextMeasurer()
val location = routeModel.currentLocation.latitude.toString() val location = routeModel.navState.currentLocation.latitude.toString()
val styleSpeed = TextStyle( val styleSpeed = TextStyle(
fontSize = 26.sp, fontSize = 26.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,

View File

@@ -68,8 +68,8 @@ class RouteCarModel() : RouteModel() {
.setManeuver( .setManeuver(
maneuver.build() maneuver.build()
) )
if (destination.street != null) { if (navState.destination.street != null) {
step.setRoad(destination.street!!) step.setRoad(navState.destination.street!!)
} }
if (stepData.lane.isNotEmpty()) { if (stepData.lane.isNotEmpty()) {
addLanes(carContext, step, stepData) addLanes(carContext, step, stepData)
@@ -127,9 +127,9 @@ class RouteCarModel() : RouteModel() {
.setRemainingTimeColor(CarColor.GREEN) .setRemainingTimeColor(CarColor.GREEN)
.setRemainingDistanceColor(CarColor.BLUE) .setRemainingDistanceColor(CarColor.BLUE)
if (travelMessage.isNotEmpty()) { if (navState.travelMessage.isNotEmpty()) {
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px)) travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
travelBuilder.setTripText(CarText.create(travelMessage)) travelBuilder.setTripText(CarText.create(navState.travelMessage))
} }
return travelBuilder.build() return travelBuilder.build()
} }
@@ -147,10 +147,10 @@ class RouteCarModel() : RouteModel() {
"${direction}_${it2.trim()}" "${direction}_${it2.trim()}"
} }
} }
val laneDirection = iconMapper.addLanes(direction, stepData) val laneDirection = navState.iconMapper.addLanes(direction, stepData)
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) { if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
if (!laneImageAdded) { if (!laneImageAdded) {
step.setLanesImage(createCarIcon(iconMapper.createLaneIcon(carContext, stepData))) step.setLanesImage(createCarIcon(navState.iconMapper.createLaneIcon(carContext, stepData)))
laneImageAdded = true laneImageAdded = true
} }
val laneType = val laneType =

View File

@@ -44,10 +44,10 @@ class CategoryScreen(
val loc = location(0.0, 0.0) val loc = location(0.0, 0.0)
elements.forEach { elements.forEach {
if (loc.latitude == 0.0) { if (loc.latitude == 0.0) {
loc.longitude = it.lon!! loc.longitude = it.lon
loc.latitude = it.lat!! loc.latitude = it.lat
} }
coordinates.add(listOf(it.lon!!, it.lat!!)) coordinates.add(listOf(it.lon, it.lat))
} }
if (elements.isNotEmpty()) { if (elements.isNotEmpty()) {
val route = createPointCollection(coordinates, category) val route = createPointCollection(coordinates, category)
@@ -111,7 +111,7 @@ class CategoryScreen(
} }
val row = Row.Builder() val row = Row.Builder()
.setOnClickListener { .setOnClickListener {
val location = location(it.lon!!, it.lat!!) val location = location(it.lon, it.lat)
surfaceRenderer.setCategoryLocation(location, category) surfaceRenderer.setCategoryLocation(location, category)
} }
.setTitle(name) .setTitle(name)

View File

@@ -31,16 +31,12 @@ import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.tomtom.Features
import com.kouros.navigation.data.tomtom.Geometry
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.Period
import java.time.ZoneOffset import java.time.ZoneOffset
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.time.Duration
class NavigationScreen( class NavigationScreen(
carContext: CarContext, carContext: CarContext,
@@ -154,11 +150,11 @@ class NavigationScreen(
} }
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template { private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
if (routeModel.arrived) { if (routeModel.navState.arrived) {
val timer = object : CountDownTimer(8000, 1000) { val timer = object : CountDownTimer(8000, 1000) {
override fun onTick(millisUntilFinished: Long) {} override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() { override fun onFinish() {
routeModel.arrived = false routeModel.navState = routeModel.navState.copy(arrived = false)
navigationType = NavigationType.VIEW navigationType = NavigationType.VIEW
invalidate() invalidate()
} }
@@ -177,8 +173,8 @@ class NavigationScreen(
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
var street = "" var street = ""
if (routeModel.destination.street != null) { if (routeModel.navState.destination.street != null) {
street = routeModel.destination.street!! street = routeModel.navState.destination.street!!
} }
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setNavigationInfo( .setNavigationInfo(
@@ -316,7 +312,7 @@ class NavigationScreen(
navigateTo, navigateTo,
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
) )
routeModel.destination = recentPlace routeModel.navState = routeModel.navState.copy(destination = recentPlace)
} }
.build() .build()
} }
@@ -444,7 +440,7 @@ class NavigationScreen(
location, location,
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
) )
routeModel.destination = place routeModel.navState = routeModel.navState.copy(destination = place)
invalidate() invalidate()
} }
@@ -492,14 +488,14 @@ class NavigationScreen(
updateSpeedCamera(location) updateSpeedCamera(location)
with(routeModel) { with(routeModel) {
updateLocation(location, viewModel) updateLocation(location, viewModel)
if ((maneuverType == Maneuver.TYPE_DESTINATION if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|| maneuverType == Maneuver.TYPE_DESTINATION_LEFT || navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|| maneuverType == Maneuver.TYPE_DESTINATION_RIGHT || navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|| maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT) || navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) { ) {
stopNavigation() stopNavigation()
arrived = true navState = navState.copy(arrived = true)
surfaceRenderer.routeData.value = "" surfaceRenderer.routeData.value = ""
navigationType = NavigationType.ARRIVAL navigationType = NavigationType.ARRIVAL
invalidate() invalidate()

View File

@@ -124,7 +124,7 @@ class PlaceListScreen(
setSpan( setSpan(
DistanceSpan.create( DistanceSpan.create(
Distance.create( Distance.create(
it.distance.toDouble(), (it.distance/1000).toDouble(),
Distance.UNIT_KILOMETERS Distance.UNIT_KILOMETERS
) )
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE ), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE

View File

@@ -220,7 +220,7 @@ class RoutePreviewScreen(
} }
private fun onRouteSelected(index: Int) { private fun onRouteSelected(index: Int) {
routeModel.currentRouteIndex = index routeModel.navState = routeModel.navState.copy(currentRouteIndex = index)
surfaceRenderer.setPreviewRouteData(routeModel) surfaceRenderer.setPreviewRouteData(routeModel)
//setResult(destination) //setResult(destination)
//finish() //finish()

View File

@@ -147,6 +147,10 @@ object Constants {
const val DESTINATION_ARRIVAL_DISTANCE = 40.0 const val DESTINATION_ARRIVAL_DISTANCE = 40.0
const val NEAREST_LOCATION_DISTANCE = 10F
const val MAXIMUM_LOCATION_DISTANCE = 100000F
} }

View File

@@ -50,11 +50,10 @@ abstract class NavigationRepository {
searchFilter: SearchFilter, searchFilter: SearchFilter,
context: Context context: Context
): Double { ): Double {
//val route = getRoute(context, currentLocation, location, carOrientation, searchFilter) val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
//val routeModel = RouteModel() val routeModel = RouteModel()
//routeModel.startNavigation(route, context) routeModel.startNavigation(route, context)
// return routeModel.curRoute.summary.distance return routeModel.curRoute.summary.distance
return 0.0
} }
fun searchPlaces(search: String, location: Location): String { fun searchPlaces(search: String, location: Location): String {

View File

@@ -26,7 +26,6 @@ data class Route(
) { ) {
data class Builder( data class Builder(
var routeEngine: Int = 0, var routeEngine: Int = 0,
var summary: Summary = Summary(), var summary: Summary = Summary(),
var routes: List<com.kouros.navigation.data.route.Routes> = emptyList(), var routes: List<com.kouros.navigation.data.route.Routes> = emptyList(),
@@ -76,8 +75,6 @@ data class Route(
return Route( return Route(
routeEngine = 0, routeEngine = 0,
routes = emptyList(), routes = emptyList(),
//waypoints = emptyList(),
//routeGeoJson = "",
) )
} }
} }

View File

@@ -58,7 +58,7 @@ class OsrmRoute {
} }
val step = Step( val step = Step(
index = stepIndex, index = stepIndex,
name = step.name, street = step.name,
distance = step.distance / 1000, distance = step.distance / 1000,
duration = step.duration, duration = step.duration,
maneuver = maneuver, maneuver = maneuver,

View File

@@ -9,4 +9,5 @@ data class Maneuver(
val waypoints: List<List<Double>>, val waypoints: List<List<Double>>,
val location: Location, val location: Location,
val exit: Int = 0, val exit: Int = 0,
val street: String = "",
) )

View File

@@ -10,6 +10,6 @@ data class Step(
val maneuver: Maneuver, val maneuver: Maneuver,
val duration: Double = 0.0, val duration: Double = 0.0,
val distance: Double = 0.0, val distance: Double = 0.0,
val name : String = "", val street : String = "",
val intersection: List<Intersection> = mutableListOf(), val intersection: List<Intersection> = mutableListOf(),
) )

View File

@@ -14,7 +14,7 @@ data class Instruction(
val roadNumbers: List<String>, val roadNumbers: List<String>,
val routeOffsetInMeters: Int, val routeOffsetInMeters: Int,
val signpostText: String, val signpostText: String,
val street: String = "", val street: String? = "",
val travelTimeInSeconds: Int, val travelTimeInSeconds: Int,
val turnAngleInDecimalDegrees: Int, val turnAngleInDecimalDegrees: Int,
val exitNumber: String? = "0", val exitNumber: String? = "0",

View File

@@ -3,6 +3,6 @@ package com.kouros.navigation.data.tomtom
data class Route( data class Route(
val guidance: Guidance, val guidance: Guidance,
val legs: List<Leg>, val legs: List<Leg>,
val sections: List<Section>, val sections: List<Section>?,
val summary: SummaryX val summary: SummaryX
) )

View File

@@ -18,6 +18,8 @@ val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incidentDetail
private val tomtomFields = private val tomtomFields =
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}" "{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
const val useAsset = false
class TomTomRepository : NavigationRepository() { class TomTomRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
context: Context, context: Context,
@@ -26,9 +28,11 @@ class TomTomRepository : NavigationRepository() {
carOrientation: Float, carOrientation: Float,
searchFilter: SearchFilter searchFilter: SearchFilter
): String { ): String {
if (useAsset) {
val routeJson = context.resources.openRawResource(R.raw.tomom_routing) val routeJson = context.resources.openRawResource(R.raw.tomom_routing)
val routeJsonString = routeJson.bufferedReader().use { it.readText() } val routeJsonString = routeJson.bufferedReader().use { it.readText() }
return routeJsonString return routeJsonString
}
val url = val url =
routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:${location.latitude},${location.longitude}" + routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:${location.latitude},${location.longitude}" +
"/json?vehicleHeading=90&sectionType=traffic&report=effectiveSettings&routeType=eco" + "/json?vehicleHeading=90&sectionType=traffic&report=effectiveSettings&routeType=eco" +
@@ -44,7 +48,6 @@ class TomTomRepository : NavigationRepository() {
} }
override fun getTraffic(context: Context, location: Location, carOrientation: Float): String { override fun getTraffic(context: Context, location: Location, carOrientation: Float): String {
val useAsset = true
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0) val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
return if (useAsset) { return if (useAsset) {
val trafficJson = context.resources.openRawResource(R.raw.tomtom_traffic) val trafficJson = context.resources.openRawResource(R.raw.tomtom_traffic)

View File

@@ -37,24 +37,28 @@ class TomTomRoute {
val steps = mutableListOf<Step>() val steps = mutableListOf<Step>()
var lastPointIndex = 0 var lastPointIndex = 0
for (index in 1..< route.guidance.instructions.size) { for (index in 1..< route.guidance.instructions.size) {
val lastInstruction = route.guidance.instructions[index-1]
val instruction = route.guidance.instructions[index] val instruction = route.guidance.instructions[index]
val street = lastInstruction.street ?: ""
val maneuverStreet = instruction.street ?: ""
val maneuver = RouteManeuver( val maneuver = RouteManeuver(
bearingBefore = 0, bearingBefore = 0,
bearingAfter = 0, bearingAfter = 0,
type = convertType(instruction.maneuver), type = convertType(instruction.maneuver),
waypoints = points.subList( waypoints = points.subList(
lastPointIndex, lastPointIndex,
instruction.pointIndex, instruction.pointIndex+1,
), ),
exit = exitNumber(instruction), exit = exitNumber(instruction),
location = location( location = location(
instruction.point.longitude, instruction.point.latitude instruction.point.longitude, instruction.point.latitude
), ),
street = maneuverStreet
) )
lastPointIndex = instruction.pointIndex lastPointIndex = instruction.pointIndex
val intersections = mutableListOf<Intersection>() val intersections = mutableListOf<Intersection>()
route.sections.forEach { section -> route.sections?.forEach { section ->
val lanes = mutableListOf<Lane>() val lanes = mutableListOf<Lane>()
var startIndex = 0 var startIndex = 0
if (section.startPointIndex <= instruction.pointIndex - 3 if (section.startPointIndex <= instruction.pointIndex - 3
@@ -78,10 +82,9 @@ class TomTomRoute {
allIntersections.addAll(intersections) allIntersections.addAll(intersections)
stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration = route.guidance.instructions[index].travelTimeInSeconds - stepDuration stepDuration = route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val name = instruction.street
val step = Step( val step = Step(
index = stepIndex, index = stepIndex,
name = name, street = street,
distance = stepDistance, distance = stepDistance,
duration = stepDuration, duration = stepDuration,
maneuver = maneuver, maneuver = maneuver,

View File

@@ -34,7 +34,7 @@ class ValhallaRoute {
if (it.streetNames != null && it.streetNames.isNotEmpty()) { if (it.streetNames != null && it.streetNames.isNotEmpty()) {
name = it.streetNames[0] name = it.streetNames[0]
} }
val step = Step( index = stepIndex, name = name, distance = it.length, duration = it.time, maneuver = maneuver) val step = Step( index = stepIndex, street = name, distance = it.length, duration = it.time, maneuver = maneuver)
steps.add(step) steps.add(step)
stepIndex += 1 stepIndex += 1
} }

View File

@@ -17,8 +17,7 @@ import java.util.Collections
import java.util.Locale import java.util.Locale
import kotlin.collections.forEach import kotlin.collections.forEach
class IconMapper(var routeModel: RouteModel) { class IconMapper() {
fun maneuverIcon(routeManeuverType: Int): Int { fun maneuverIcon(routeManeuverType: Int): Int {
var currentTurnIcon = R.drawable.ic_turn_name_change var currentTurnIcon = R.drawable.ic_turn_name_change
@@ -102,7 +101,7 @@ class IconMapper(var routeModel: RouteModel) {
} }
} }
"left", "slight_left" -> { "left" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
else else

View File

@@ -2,6 +2,8 @@ package com.kouros.navigation.model
import android.location.Location import android.location.Location
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.NEAREST_LOCATION_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 import kotlin.math.roundToInt
@@ -12,37 +14,30 @@ class RouteCalculator(var routeModel: RouteModel) {
var lastSpeedIndex: Int = 0 var lastSpeedIndex: Int = 0
fun findStep(location: Location) { fun findStep(location: Location) {
var nearestDistance = 100000f var nearestDistance = MAXIMUM_LOCATION_DISTANCE
for ((index, step) in routeModel.curLeg.steps.withIndex()) { for ((index, step) in routeModel.curLeg.steps.withIndex()) {
if (index >= routeModel.route.currentStepIndex) { if (index >= routeModel.navState.route.currentStepIndex) {
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
if (wayIndex >= step.waypointIndex) { if (wayIndex >= step.waypointIndex) {
val distance = location.distanceTo(location(waypoint[0], waypoint[1])) val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
if (distance < nearestDistance) { if (distance < nearestDistance) {
nearestDistance = distance nearestDistance = distance
routeModel.route.currentStepIndex = step.index routeModel.navState.route.currentStepIndex = step.index
step.waypointIndex = wayIndex step.waypointIndex = wayIndex
step.wayPointLocation = location(waypoint[0], waypoint[1]) step.wayPointLocation = location(waypoint[0], waypoint[1])
routeModel.routeBearing = routeModel.lastLocation.bearingTo(location) routeModel.navState = routeModel.navState.copy(
} else { routeBearing = routeModel.navState.lastLocation.bearingTo(location)
if (nearestDistance != 100000f) { )
}
}
}
}
if (nearestDistance < NEAREST_LOCATION_DISTANCE) {
break; break;
} }
} }
} }
if (nearestDistance == 0F) {
break
}
}
}
if (nearestDistance == 0F) {
break
}
}
}
fun travelLeftTime(): Double { fun travelLeftTime(): Double {
var timeLeft = 0.0 var timeLeft = 0.0
@@ -102,10 +97,8 @@ class RouteCalculator(var routeModel: RouteModel) {
if (distance > 500 || lastSpeedIndex < routeModel.route.currentStepIndex) { if (distance > 500 || lastSpeedIndex < routeModel.route.currentStepIndex) {
lastSpeedIndex = routeModel.route.currentStepIndex lastSpeedIndex = routeModel.route.currentStepIndex
lastSpeedLocation = location lastSpeedLocation = location
viewModel.getMaxSpeed(location, routeModel.previousStreet()) viewModel.getMaxSpeed(location, routeModel.route.currentStep().street)
} }
} }
} }
} }

View File

@@ -13,75 +13,81 @@ import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.Routes import com.kouros.navigation.data.route.Routes
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
open class RouteModel() { open class RouteModel {
var route = Route.Builder().buildEmpty() // Immutable Data Class
data class NavigationState(
val route: Route = Route.Builder().buildEmpty(),
val iconMapper : IconMapper = IconMapper(),
val navigating: Boolean = false,
val arrived: Boolean = false,
val travelMessage: String = "",
val maneuverType: Int = 0,
val lastLocation: Location = location(0.0, 0.0),
val currentLocation: Location = location(0.0, 0.0),
val routeBearing: Float = 0F,
val currentRouteIndex: Int = 0,
val destination: Place = Place()
)
val routeCalculator = RouteCalculator(this) var navState = NavigationState()
var iconMapper = IconMapper(this) val route: Route
var navigating: Boolean = false get() = navState.route
var destination: Place = Place()
var arrived: Boolean = false
var maneuverType: Int = 0
var travelMessage: String = ""
var currentLocation: Location = location(0.0, 0.0) val routeCalculator : RouteCalculator = RouteCalculator(this)
var lastLocation: Location = location(0.0, 0.0)
var routeBearing: Float = 0F
var currentRouteIndex = 0
val curRoute: Routes val curRoute: Routes
get() = route.routes[currentRouteIndex] get() = navState.route.routes[navState.currentRouteIndex]
val curLeg: Leg val curLeg: Leg
get() = route.routes[currentRouteIndex].legs.first() get() = navState.route.routes[navState.currentRouteIndex].legs.first()
fun startNavigation(routeString: String, context: Context) { fun startNavigation(routeString: String, context: Context) {
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE) val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
navState = navState.copy(
route = Route.Builder() route = Route.Builder()
.routeEngine(routeEngine) .routeEngine(routeEngine)
.route(routeString) .route(routeString)
.build() .build()
)
if (hasLegs()) { if (hasLegs()) {
navigating = true navState = navState.copy(navigating = true)
} }
} }
private fun hasLegs(): Boolean { private fun hasLegs(): Boolean {
return route.routes.isNotEmpty() && route.routes[0].legs.isNotEmpty() return navState.route.routes.isNotEmpty() && navState.route.routes[0].legs.isNotEmpty()
} }
fun stopNavigation() { fun stopNavigation() {
route = Route.Builder().buildEmpty() navState = navState.copy(
navigating = false route = Route.Builder().buildEmpty(),
arrived = false navigating = false,
arrived = false,
maneuverType = Maneuver.TYPE_UNKNOWN maneuverType = Maneuver.TYPE_UNKNOWN
)
} }
@OptIn(DelicateCoroutinesApi::class)
fun updateLocation(curLocation: Location, viewModel: ViewModel) { fun updateLocation(curLocation: Location, viewModel: ViewModel) {
currentLocation = curLocation navState = navState.copy(currentLocation = curLocation)
routeCalculator.findStep(curLocation) routeCalculator.findStep(curLocation)
routeCalculator.updateSpeedLimit(curLocation, viewModel) routeCalculator.updateSpeedLimit(curLocation, viewModel)
lastLocation = currentLocation navState = navState.copy(lastLocation = navState.currentLocation)
} }
private fun currentLanes(location: Location): List<Lane> { private fun currentLanes(): List<Lane> {
var lanes = emptyList<Lane>() var lanes = emptyList<Lane>()
if (route.legs().isNotEmpty()) { if (navState.route.legs().isNotEmpty()) {
route.legs().first().intersection.forEach { it -> navState.route.legs().first().intersection.forEach {
if (it.lane.isNotEmpty()) { if (it.lane.isNotEmpty()) {
val distance = lastLocation.distanceTo(location(it.location[0], it.location[1])) val distance =
navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
val sectionBearing = val sectionBearing =
lastLocation.bearingTo(location(it.location[0], it.location[1])) navState.lastLocation.bearingTo(location(it.location[0], it.location[1]))
if (distance < 500 && (routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) { if (distance < 500 && (navState.routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) {
lanes = it.lane lanes = it.lane
} }
} }
@@ -90,25 +96,24 @@ open class RouteModel() {
return lanes return lanes
} }
fun currentStep(): StepData { fun currentStep(): StepData {
val distanceToNextStep = routeCalculator.leftStepDistance() val distanceToNextStep = routeCalculator.leftStepDistance()
// Determine the maneuver type and corresponding icon // Determine the maneuver type and corresponding icon
val currentStep = route.nextStep(0) val currentStep = navState.route.nextStep(0)
// Safely get the street name from the maneuver // Safely get the street name from the maneuver
val streetName = currentStep.name val streetName = currentStep.maneuver.street
val curManeuverType = currentStep.maneuver.type val curManeuverType = currentStep.maneuver.type
val exitNumber = currentStep.maneuver.exit val exitNumber = currentStep.maneuver.exit
val maneuverIcon = iconMapper.maneuverIcon(curManeuverType) val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType)
maneuverType = curManeuverType navState = navState.copy(maneuverType = curManeuverType)
val lanes = currentLanes(currentLocation) val lanes = currentLanes()
// Construct and return the final StepData object // Construct and return the final StepData object
return StepData( return StepData(
streetName, streetName,
distanceToNextStep, distanceToNextStep,
maneuverType, navState.maneuverType,
maneuverIcon, maneuverIcon,
routeCalculator.arrivalTime(), routeCalculator.arrivalTime(),
routeCalculator.travelLeftDistance(), routeCalculator.travelLeftDistance(),
@@ -118,22 +123,21 @@ open class RouteModel() {
} }
fun nextStep(): StepData { fun nextStep(): StepData {
val step = route.nextStep(1) val step = navState.route.nextStep(1)
val maneuverType = step.maneuver.type val maneuverType = step.maneuver.type
val distanceLeft = routeCalculator.leftStepDistance() val distanceLeft = routeCalculator.leftStepDistance()
var text = "" var text = ""
when (distanceLeft) { when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> { in 0.0..NEXT_STEP_THRESHOLD -> {
} }
else -> { else -> {
if (step.name.isNotEmpty()) { if (step.street.isNotEmpty()) {
text = step.name text = step.street
} }
} }
} }
val maneuverIcon = iconMapper.maneuverIcon(maneuverType) val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
// Construct and return the final StepData object // Construct and return the final StepData object
return StepData( return StepData(
text, text,
@@ -147,14 +151,7 @@ open class RouteModel() {
) )
} }
fun previousStreet(): String {
if (route.currentStepIndex > 0) {
return route.legs().first().steps[route.currentStepIndex - 1].name
}
return ""
}
fun isNavigating(): Boolean { fun isNavigating(): Boolean {
return navigating return navState.navigating
} }
} }

View File

@@ -31,7 +31,6 @@ 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)