TomTom Routing
This commit is contained in:
@@ -147,6 +147,10 @@ object Constants {
|
||||
|
||||
const val DESTINATION_ARRIVAL_DISTANCE = 40.0
|
||||
|
||||
const val NEAREST_LOCATION_DISTANCE = 10F
|
||||
|
||||
const val MAXIMUM_LOCATION_DISTANCE = 100000F
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -50,11 +50,10 @@ abstract class NavigationRepository {
|
||||
searchFilter: SearchFilter,
|
||||
context: Context
|
||||
): Double {
|
||||
//val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||
//val routeModel = RouteModel()
|
||||
//routeModel.startNavigation(route, context)
|
||||
// return routeModel.curRoute.summary.distance
|
||||
return 0.0
|
||||
val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||
val routeModel = RouteModel()
|
||||
routeModel.startNavigation(route, context)
|
||||
return routeModel.curRoute.summary.distance
|
||||
}
|
||||
|
||||
fun searchPlaces(search: String, location: Location): String {
|
||||
|
||||
@@ -26,7 +26,6 @@ data class Route(
|
||||
) {
|
||||
|
||||
data class Builder(
|
||||
|
||||
var routeEngine: Int = 0,
|
||||
var summary: Summary = Summary(),
|
||||
var routes: List<com.kouros.navigation.data.route.Routes> = emptyList(),
|
||||
@@ -76,8 +75,6 @@ data class Route(
|
||||
return Route(
|
||||
routeEngine = 0,
|
||||
routes = emptyList(),
|
||||
//waypoints = emptyList(),
|
||||
//routeGeoJson = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class OsrmRoute {
|
||||
}
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
name = step.name,
|
||||
street = step.name,
|
||||
distance = step.distance / 1000,
|
||||
duration = step.duration,
|
||||
maneuver = maneuver,
|
||||
|
||||
@@ -9,4 +9,5 @@ data class Maneuver(
|
||||
val waypoints: List<List<Double>>,
|
||||
val location: Location,
|
||||
val exit: Int = 0,
|
||||
val street: String = "",
|
||||
)
|
||||
|
||||
@@ -10,6 +10,6 @@ data class Step(
|
||||
val maneuver: Maneuver,
|
||||
val duration: Double = 0.0,
|
||||
val distance: Double = 0.0,
|
||||
val name : String = "",
|
||||
val street : String = "",
|
||||
val intersection: List<Intersection> = mutableListOf(),
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ data class Instruction(
|
||||
val roadNumbers: List<String>,
|
||||
val routeOffsetInMeters: Int,
|
||||
val signpostText: String,
|
||||
val street: String = "",
|
||||
val street: String? = "",
|
||||
val travelTimeInSeconds: Int,
|
||||
val turnAngleInDecimalDegrees: Int,
|
||||
val exitNumber: String? = "0",
|
||||
|
||||
@@ -3,6 +3,6 @@ package com.kouros.navigation.data.tomtom
|
||||
data class Route(
|
||||
val guidance: Guidance,
|
||||
val legs: List<Leg>,
|
||||
val sections: List<Section>,
|
||||
val sections: List<Section>?,
|
||||
val summary: SummaryX
|
||||
)
|
||||
@@ -18,6 +18,8 @@ val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incidentDetail
|
||||
private val tomtomFields =
|
||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||
|
||||
const val useAsset = false
|
||||
|
||||
class TomTomRepository : NavigationRepository() {
|
||||
override fun getRoute(
|
||||
context: Context,
|
||||
@@ -26,9 +28,11 @@ class TomTomRepository : NavigationRepository() {
|
||||
carOrientation: Float,
|
||||
searchFilter: SearchFilter
|
||||
): String {
|
||||
val routeJson = context.resources.openRawResource(R.raw.tomom_routing)
|
||||
val routeJsonString = routeJson.bufferedReader().use { it.readText() }
|
||||
return routeJsonString
|
||||
if (useAsset) {
|
||||
val routeJson = context.resources.openRawResource(R.raw.tomom_routing)
|
||||
val routeJsonString = routeJson.bufferedReader().use { it.readText() }
|
||||
return routeJsonString
|
||||
}
|
||||
val url =
|
||||
routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:${location.latitude},${location.longitude}" +
|
||||
"/json?vehicleHeading=90§ionType=traffic&report=effectiveSettings&routeType=eco" +
|
||||
@@ -44,7 +48,6 @@ class TomTomRepository : NavigationRepository() {
|
||||
}
|
||||
|
||||
override fun getTraffic(context: Context, location: Location, carOrientation: Float): String {
|
||||
val useAsset = true
|
||||
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
|
||||
return if (useAsset) {
|
||||
val trafficJson = context.resources.openRawResource(R.raw.tomtom_traffic)
|
||||
|
||||
@@ -37,24 +37,28 @@ class TomTomRoute {
|
||||
val steps = mutableListOf<Step>()
|
||||
var lastPointIndex = 0
|
||||
for (index in 1..< route.guidance.instructions.size) {
|
||||
val lastInstruction = route.guidance.instructions[index-1]
|
||||
val instruction = route.guidance.instructions[index]
|
||||
val street = lastInstruction.street ?: ""
|
||||
val maneuverStreet = instruction.street ?: ""
|
||||
val maneuver = RouteManeuver(
|
||||
bearingBefore = 0,
|
||||
bearingAfter = 0,
|
||||
type = convertType(instruction.maneuver),
|
||||
waypoints = points.subList(
|
||||
lastPointIndex,
|
||||
instruction.pointIndex,
|
||||
instruction.pointIndex+1,
|
||||
),
|
||||
exit = exitNumber(instruction),
|
||||
location = location(
|
||||
instruction.point.longitude, instruction.point.latitude
|
||||
),
|
||||
street = maneuverStreet
|
||||
)
|
||||
|
||||
lastPointIndex = instruction.pointIndex
|
||||
val intersections = mutableListOf<Intersection>()
|
||||
route.sections.forEach { section ->
|
||||
route.sections?.forEach { section ->
|
||||
val lanes = mutableListOf<Lane>()
|
||||
var startIndex = 0
|
||||
if (section.startPointIndex <= instruction.pointIndex - 3
|
||||
@@ -78,10 +82,9 @@ class TomTomRoute {
|
||||
allIntersections.addAll(intersections)
|
||||
stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance
|
||||
stepDuration = route.guidance.instructions[index].travelTimeInSeconds - stepDuration
|
||||
val name = instruction.street
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
name = name,
|
||||
street = street,
|
||||
distance = stepDistance,
|
||||
duration = stepDuration,
|
||||
maneuver = maneuver,
|
||||
|
||||
@@ -34,7 +34,7 @@ class ValhallaRoute {
|
||||
if (it.streetNames != null && it.streetNames.isNotEmpty()) {
|
||||
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)
|
||||
stepIndex += 1
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ import java.util.Collections
|
||||
import java.util.Locale
|
||||
import kotlin.collections.forEach
|
||||
|
||||
class IconMapper(var routeModel: RouteModel) {
|
||||
|
||||
class IconMapper() {
|
||||
|
||||
fun maneuverIcon(routeManeuverType: Int): Int {
|
||||
var currentTurnIcon = R.drawable.ic_turn_name_change
|
||||
@@ -102,7 +101,7 @@ class IconMapper(var routeModel: RouteModel) {
|
||||
}
|
||||
}
|
||||
|
||||
"left", "slight_left" -> {
|
||||
"left" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
|
||||
else
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.kouros.navigation.model
|
||||
|
||||
import android.location.Location
|
||||
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 java.util.concurrent.TimeUnit
|
||||
import kotlin.math.roundToInt
|
||||
@@ -12,38 +14,31 @@ class RouteCalculator(var routeModel: RouteModel) {
|
||||
|
||||
var lastSpeedIndex: Int = 0
|
||||
|
||||
|
||||
fun findStep(location: Location) {
|
||||
var nearestDistance = 100000f
|
||||
var nearestDistance = MAXIMUM_LOCATION_DISTANCE
|
||||
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()) {
|
||||
if (wayIndex >= step.waypointIndex) {
|
||||
val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance
|
||||
routeModel.route.currentStepIndex = step.index
|
||||
routeModel.navState.route.currentStepIndex = step.index
|
||||
step.waypointIndex = wayIndex
|
||||
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
||||
routeModel.routeBearing = routeModel.lastLocation.bearingTo(location)
|
||||
} else {
|
||||
if (nearestDistance != 100000f) {
|
||||
break;
|
||||
}
|
||||
routeModel.navState = routeModel.navState.copy(
|
||||
routeBearing = routeModel.navState.lastLocation.bearingTo(location)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (nearestDistance == 0F) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestDistance == 0F) {
|
||||
break
|
||||
if (nearestDistance < NEAREST_LOCATION_DISTANCE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun travelLeftTime(): Double {
|
||||
var timeLeft = 0.0
|
||||
// time for next step until end step
|
||||
@@ -102,10 +97,8 @@ class RouteCalculator(var routeModel: RouteModel) {
|
||||
if (distance > 500 || lastSpeedIndex < routeModel.route.currentStepIndex) {
|
||||
lastSpeedIndex = routeModel.route.currentStepIndex
|
||||
lastSpeedLocation = location
|
||||
viewModel.getMaxSpeed(location, routeModel.previousStreet())
|
||||
viewModel.getMaxSpeed(location, routeModel.route.currentStep().street)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -13,75 +13,81 @@ import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Routes
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
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)
|
||||
var navigating: Boolean = false
|
||||
var destination: Place = Place()
|
||||
var arrived: Boolean = false
|
||||
var maneuverType: Int = 0
|
||||
var travelMessage: String = ""
|
||||
val route: Route
|
||||
get() = navState.route
|
||||
|
||||
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
|
||||
get() = route.routes[currentRouteIndex]
|
||||
get() = navState.route.routes[navState.currentRouteIndex]
|
||||
|
||||
val curLeg: Leg
|
||||
get() = route.routes[currentRouteIndex].legs.first()
|
||||
|
||||
get() = navState.route.routes[navState.currentRouteIndex].legs.first()
|
||||
|
||||
fun startNavigation(routeString: String, context: Context) {
|
||||
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
|
||||
route = Route.Builder()
|
||||
.routeEngine(routeEngine)
|
||||
.route(routeString)
|
||||
.build()
|
||||
navState = navState.copy(
|
||||
route = Route.Builder()
|
||||
.routeEngine(routeEngine)
|
||||
.route(routeString)
|
||||
.build()
|
||||
)
|
||||
if (hasLegs()) {
|
||||
navigating = true
|
||||
navState = navState.copy(navigating = true)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
route = Route.Builder().buildEmpty()
|
||||
navigating = false
|
||||
arrived = false
|
||||
maneuverType = Maneuver.TYPE_UNKNOWN
|
||||
|
||||
navState = navState.copy(
|
||||
route = Route.Builder().buildEmpty(),
|
||||
navigating = false,
|
||||
arrived = false,
|
||||
maneuverType = Maneuver.TYPE_UNKNOWN
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun updateLocation(curLocation: Location, viewModel: ViewModel) {
|
||||
currentLocation = curLocation
|
||||
navState = navState.copy(currentLocation = curLocation)
|
||||
routeCalculator.findStep(curLocation)
|
||||
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>()
|
||||
if (route.legs().isNotEmpty()) {
|
||||
route.legs().first().intersection.forEach { it ->
|
||||
if (navState.route.legs().isNotEmpty()) {
|
||||
navState.route.legs().first().intersection.forEach {
|
||||
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 =
|
||||
lastLocation.bearingTo(location(it.location[0], it.location[1]))
|
||||
if (distance < 500 && (routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) {
|
||||
navState.lastLocation.bearingTo(location(it.location[0], it.location[1]))
|
||||
if (distance < 500 && (navState.routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) {
|
||||
lanes = it.lane
|
||||
}
|
||||
}
|
||||
@@ -90,25 +96,24 @@ open class RouteModel() {
|
||||
return lanes
|
||||
}
|
||||
|
||||
|
||||
fun currentStep(): StepData {
|
||||
val distanceToNextStep = routeCalculator.leftStepDistance()
|
||||
// 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
|
||||
val streetName = currentStep.name
|
||||
val streetName = currentStep.maneuver.street
|
||||
val curManeuverType = currentStep.maneuver.type
|
||||
val exitNumber = currentStep.maneuver.exit
|
||||
val maneuverIcon = iconMapper.maneuverIcon(curManeuverType)
|
||||
maneuverType = curManeuverType
|
||||
val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType)
|
||||
navState = navState.copy(maneuverType = curManeuverType)
|
||||
|
||||
val lanes = currentLanes(currentLocation)
|
||||
val lanes = currentLanes()
|
||||
|
||||
// Construct and return the final StepData object
|
||||
return StepData(
|
||||
streetName,
|
||||
distanceToNextStep,
|
||||
maneuverType,
|
||||
navState.maneuverType,
|
||||
maneuverIcon,
|
||||
routeCalculator.arrivalTime(),
|
||||
routeCalculator.travelLeftDistance(),
|
||||
@@ -118,22 +123,21 @@ open class RouteModel() {
|
||||
}
|
||||
|
||||
fun nextStep(): StepData {
|
||||
val step = route.nextStep(1)
|
||||
val step = navState.route.nextStep(1)
|
||||
val maneuverType = step.maneuver.type
|
||||
val distanceLeft = routeCalculator.leftStepDistance()
|
||||
var text = ""
|
||||
when (distanceLeft) {
|
||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (step.name.isNotEmpty()) {
|
||||
text = step.name
|
||||
if (step.street.isNotEmpty()) {
|
||||
text = step.street
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val maneuverIcon = iconMapper.maneuverIcon(maneuverType)
|
||||
val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
|
||||
// Construct and return the final StepData object
|
||||
return StepData(
|
||||
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 {
|
||||
return navigating
|
||||
return navState.navigating
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ object GeoUtils {
|
||||
return newLocation
|
||||
}
|
||||
|
||||
|
||||
fun decodePolyline(encoded: String, precision: Int = 6): List<List<Double>> {
|
||||
val factor = 10.0.pow(precision)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user