TomTom Routing

This commit is contained in:
Dimitris
2026-02-09 13:36:05 +01:00
parent e9474695bf
commit 5141041b5c
12 changed files with 612 additions and 580 deletions

View File

@@ -88,7 +88,7 @@ class MainActivity : ComponentActivity() {
val routeModel = RouteModel() val routeModel = RouteModel()
var tilt = 50.0 var tilt = 50.0
val useMock = true val useMock = true
val type = 3 // simulate 2 test 3 gpx val type = 1 // simulate 2 test 3 gpx
var currentIndex = 0 var currentIndex = 0
val stepData: MutableLiveData<StepData> by lazy { val stepData: MutableLiveData<StepData> by lazy {
@@ -309,20 +309,16 @@ class MainActivity : ComponentActivity() {
&& lastLocation.longitude != location.position.longitude && lastLocation.longitude != location.position.longitude
) { ) {
val currentLocation = location(location.position.longitude, location.position.latitude) val currentLocation = location(location.position.longitude, location.position.latitude)
// if (currentIndex == 0)
// navigationViewModel.loadTraffic(applicationContext, currentLocation, 0f)
// currentIndex = 1
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()
println("Step: ${stepData.value!!.instruction} ${stepData.value!!.leftStepDistance}")
if (route.currentStepIndex + 1 <= curLeg.steps.size) { if (route.currentStepIndex + 1 <= curLeg.steps.size) {
nextStepData.value = nextStep() nextStepData.value = nextStep()
} }
if (maneuverType in 39..42 if (maneuverType in 39..42
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) { ) {
// stopNavigation() // stopNavigation()
arrived = true arrived = true
@@ -410,9 +406,6 @@ class MainActivity : ComponentActivity() {
) )
val step = routeModel.currentStep() val step = routeModel.currentStep()
println("Step: ${step.instruction} ${step.leftStepDistance}") println("Step: ${step.instruction} ${step.leftStepDistance}")
if (step.leftStepDistance == 70.0) {
println("")
}
if (index + 1 <= routeModel.curLeg.steps.size) { if (index + 1 <= routeModel.curLeg.steps.size) {
//nextStepData.value = routeModel.nextStep() //nextStepData.value = routeModel.nextStep()
} }

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.addLanes( step) routeModel.iconMapper.addLanes( step)
} }
Column { Column {

View File

@@ -76,6 +76,7 @@ class SurfaceRenderer(
val trafficData = MutableLiveData(emptyMap<String, String>()) val trafficData = MutableLiveData(emptyMap<String, String>())
val speedCamerasData = MutableLiveData("") val speedCamerasData = MutableLiveData("")
val speed = MutableLiveData(0F) val speed = MutableLiveData(0F)
val maxSpeed = MutableLiveData(0)
var viewStyle = ViewStyle.VIEW var viewStyle = ViewStyle.VIEW
lateinit var centerLocation: Location lateinit var centerLocation: Location
var previewDistance = 0.0 var previewDistance = 0.0
@@ -209,11 +210,12 @@ class SurfaceRenderer(
val cameraDuration = val cameraDuration =
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing) duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
val currentSpeed: Float? by speed.observeAsState() val currentSpeed: Float? by speed.observeAsState()
val maxSpeed: Int? by maxSpeed.observeAsState()
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) { if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
DrawNavigationImages( DrawNavigationImages(
paddingValues, paddingValues,
currentSpeed, currentSpeed,
routeModel, maxSpeed!!,
width, width,
height height
) )

View File

@@ -294,16 +294,16 @@ fun BuildingLayer(tiles: Source) {
fun DrawNavigationImages( fun DrawNavigationImages(
padding: PaddingValues, padding: PaddingValues,
speed: Float?, speed: Float?,
routeModel: RouteModel, maxSpeed: Int,
width: Int, width: Int,
height: Int height: Int
) { ) {
NavigationImage(padding, width, height) NavigationImage(padding, width, height)
if (speed != null) { if (speed != null) {
CurrentSpeed(width, height, speed, routeModel.maxSpeed) CurrentSpeed(width, height, speed, maxSpeed)
} }
if (speed != null && routeModel.maxSpeed > 0 && (speed * 3.6) > routeModel.maxSpeed) { if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
MaxSpeed(width, height, routeModel.maxSpeed) MaxSpeed(width, height, maxSpeed)
} }
//DebugInfo(width, height, routeModel) //DebugInfo(width, height, routeModel)
} }
@@ -470,7 +470,7 @@ fun DebugInfo(
contentAlignment = Alignment.CenterStart contentAlignment = Alignment.CenterStart
) { ) {
val textMeasurerLocation = rememberTextMeasurer() val textMeasurerLocation = rememberTextMeasurer()
val location = routeModel.location.latitude.toString() val location = routeModel.currentLocation.latitude.toString()
val styleSpeed = TextStyle( val styleSpeed = TextStyle(
fontSize = 26.sp, fontSize = 26.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,

View File

@@ -15,6 +15,7 @@
*/ */
package com.kouros.navigation.car.navigation package com.kouros.navigation.car.navigation
import android.location.Location
import android.text.SpannableString import android.text.SpannableString
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
@@ -98,17 +99,17 @@ class RouteCarModel() : RouteModel() {
} }
fun travelEstimate(carContext: CarContext): TravelEstimate { fun travelEstimate(carContext: CarContext): TravelEstimate {
val timeLeft = travelLeftTime() val timeLeft = routeCalculator.travelLeftTime()
val timeToDestinationMillis = val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong()) TimeUnit.SECONDS.toMillis(timeLeft.toLong())
val leftDistance = travelLeftDistance() / 1000 val leftDistance = routeCalculator.travelLeftDistance() / 1000
val displayUnit = if (leftDistance > 1.0) { val displayUnit = if (leftDistance > 1.0) {
Distance.UNIT_KILOMETERS Distance.UNIT_KILOMETERS
} else { } else {
Distance.UNIT_METERS Distance.UNIT_METERS
} }
val arrivalTime = DateTimeWithZone.create( val arrivalTime = DateTimeWithZone.create(
arrivalTime(), routeCalculator.arrivalTime(),
TimeZone.getTimeZone("Europe/Berlin") TimeZone.getTimeZone("Europe/Berlin")
) )
val travelBuilder = TravelEstimate.Builder( // The estimated distance to the destination. val travelBuilder = TravelEstimate.Builder( // The estimated distance to the destination.
@@ -133,99 +134,99 @@ class RouteCarModel() : RouteModel() {
return travelBuilder.build() return travelBuilder.build()
} }
fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) { fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) {
var laneImageAdded = false var laneImageAdded = false
stepData.lane.forEach { stepData.lane.forEach {
if (it.indications.isNotEmpty() && it.valid) { if (it.indications.isNotEmpty() && it.valid) {
Collections.sort<String>(it.indications) Collections.sort<String>(it.indications)
var direction = "" var direction = ""
it.indications.forEach { it2 -> it.indications.forEach { it2 ->
direction = if (direction.isEmpty()) { direction = if (direction.isEmpty()) {
it2.trim() it2.trim()
} else { } else {
"${direction}_${it2.trim()}" "${direction}_${it2.trim()}"
}
} }
} val laneDirection = iconMapper.addLanes(direction, stepData)
val laneDirection = 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(createLaneIcon(carContext, stepData))) laneImageAdded = true
laneImageAdded = true }
val laneType =
Lane.Builder()
.addDirection(LaneDirection.create(laneDirection, false))
.build()
step.addLane(laneType)
} }
val laneType =
Lane.Builder()
.addDirection(LaneDirection.create(laneDirection, false))
.build()
step.addLane(laneType)
} }
} }
} }
}
fun createString( fun createString(
text: String text: String
): SpannableString { ): SpannableString {
val spannableString = SpannableString(text) val spannableString = SpannableString(text)
return spannableString return spannableString
} }
fun createCarText(carContext: CarContext, @StringRes stringRes: Int): CarText { fun createCarText(carContext: CarContext, @StringRes stringRes: Int): CarText {
return CarText.create(carContext.getString(stringRes)) return CarText.create(carContext.getString(stringRes))
} }
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon { fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build() return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
} }
// fun createCarIcon(iconCompat: IconCompat): CarIcon { fun createCarIcon(iconCompat: IconCompat): CarIcon {
// return CarIcon.Builder(iconCompat).build() return CarIcon.Builder(iconCompat).build()
// } }
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) { fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
carContext.getCarService<AppManager?>(AppManager::class.java) carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert( .showAlert(
createAlert( createAlert(
carContext, carContext,
maxSpeed, maxSpeed,
createCarIcon(carContext, R.drawable.speed_camera_24px) createCarIcon(carContext, R.drawable.speed_camera_24px)
)
) )
}
fun createAlert(
carContext: CarContext,
maxSpeed: String?,
icon: CarIcon
): Alert {
val title = createCarText(carContext, R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!)
val dismissAction: Action = createToastAction(
carContext,
R.string.exit_action_title, R.string.exit_action_title,
FLAG_DEFAULT
) )
} return Alert.Builder( /* alertId: */0, title, /* durationMillis: */5000)
.setSubtitle(subtitle)
.setIcon(icon)
.addAction(dismissAction).setCallback(object : AlertCallback {
override fun onCancel(reason: Int) {
}
fun createAlert( override fun onDismiss() {
carContext: CarContext, }
maxSpeed: String?, }).build()
icon: CarIcon }
): Alert {
val title = createCarText(carContext, R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!)
val dismissAction: Action = createToastAction( private fun createToastAction(
carContext, carContext: CarContext,
R.string.exit_action_title, R.string.exit_action_title, @StringRes titleRes: Int, @StringRes toastStringRes: Int,
FLAG_DEFAULT flags: Int
) ): Action {
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */5000) return Action.Builder()
.setSubtitle(subtitle) .setOnClickListener { }
.setIcon(icon) .setTitle(createCarText(carContext, titleRes))
.addAction(dismissAction).setCallback(object : AlertCallback { .setFlags(flags)
override fun onCancel(reason: Int) { .build()
} }
override fun onDismiss() {
}
}).build()
}
private fun createToastAction(
carContext: CarContext,
@StringRes titleRes: Int, @StringRes toastStringRes: Int,
flags: Int
): Action {
return Action.Builder()
.setOnClickListener { }
.setTitle(createCarText(carContext, titleRes))
.setFlags(flags)
.build()
}
} }

View File

@@ -237,7 +237,7 @@ class NavigationScreen(
} }
fun getRoutingInfo(): RoutingInfo { fun getRoutingInfo(): RoutingInfo {
var currentDistance = routeModel.leftStepDistance() var currentDistance = routeModel.routeCalculator.leftStepDistance()
val displayUnit = if (currentDistance > 1000.0) { val displayUnit = if (currentDistance > 1000.0) {
currentDistance /= 1000.0 currentDistance /= 1000.0
Distance.UNIT_KILOMETERS Distance.UNIT_KILOMETERS
@@ -496,7 +496,7 @@ class NavigationScreen(
|| maneuverType == Maneuver.TYPE_DESTINATION_LEFT || maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|| maneuverType == Maneuver.TYPE_DESTINATION_RIGHT || maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|| maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT) || maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) { ) {
stopNavigation() stopNavigation()
arrived = true arrived = true

View File

@@ -13,7 +13,7 @@ class Overpass {
val overpassUrl = "https://kouros-online.de/overpass/interpreter" val overpassUrl = "https://kouros-online.de/overpass/interpreter"
fun getAround(radius: Int, linestring: String) : List<Elements> { fun getAround(radius: Int, linestring: String): List<Elements> {
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST" httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty( httpURLConnection.setRequestProperty(
@@ -57,12 +57,13 @@ class Overpass {
| node[$type=$category] | node[$type=$category]
| ($boundingBox); | ($boundingBox);
|); |);
|(._;>;);
|out body; |out body;
""".trimMargin() """.trimMargin()
return overpassApi(httpURLConnection, searchQuery) return overpassApi(httpURLConnection, searchQuery)
} }
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> { fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String): List<Elements> {
try { try {
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream) val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(searchQuery) outputStreamWriter.write(searchQuery)
@@ -75,8 +76,10 @@ class Overpass {
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
} }
} catch (e: Exception) { } catch (e: Exception) {
} }
return emptyList() return emptyList()
} }

View File

@@ -35,16 +35,16 @@ class TomTomRoute {
var stepDuration = 0.0 var stepDuration = 0.0
val allIntersections = mutableListOf<Intersection>() val allIntersections = mutableListOf<Intersection>()
val steps = mutableListOf<Step>() val steps = mutableListOf<Step>()
for (index in 0..< route.guidance.instructions.size) { var lastPointIndex = 0
for (index in 1..< route.guidance.instructions.size) {
val instruction = route.guidance.instructions[index] val instruction = route.guidance.instructions[index]
val nextPointIndex = nextPointIndex(index, route)
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,
instruction.pointIndex, instruction.pointIndex,
route.guidance.instructions[nextPointIndex].pointIndex
), ),
exit = exitNumber(instruction), exit = exitNumber(instruction),
location = location( location = location(
@@ -52,6 +52,7 @@ class TomTomRoute {
), ),
) )
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>()
@@ -75,8 +76,8 @@ class TomTomRoute {
} }
} }
allIntersections.addAll(intersections) allIntersections.addAll(intersections)
stepDistance = route.guidance.instructions[nextPointIndex].routeOffsetInMeters - stepDistance stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration = route.guidance.instructions[nextPointIndex].travelTimeInSeconds - stepDuration stepDuration = route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val name = instruction.street val name = instruction.street
val step = Step( val step = Step(
index = stepIndex, index = stepIndex,
@@ -86,8 +87,8 @@ class TomTomRoute {
maneuver = maneuver, maneuver = maneuver,
intersection = intersections intersection = intersections
) )
stepDistance = route.guidance.instructions[nextPointIndex].routeOffsetInMeters.toDouble() stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
stepDuration = route.guidance.instructions[nextPointIndex].travelTimeInSeconds.toDouble() stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
steps.add(step) steps.add(step)
stepIndex += 1 stepIndex += 1
} }
@@ -108,15 +109,6 @@ class TomTomRoute {
.routes(routes) .routes(routes)
} }
private fun nextPointIndex(index: Int, route: com.kouros.navigation.data.tomtom.Route): Int {
val nextPointIndex = if (index < route.guidance.instructions.size - 1) {
index + 1
} else {
index + 0
}
return nextPointIndex
}
fun convertType(type: String): Int { fun convertType(type: String): Int {
var newType = 0 var newType = 0
when (type) { when (type) {

View File

@@ -0,0 +1,276 @@
package com.kouros.navigation.model
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import androidx.annotation.DrawableRes
import androidx.car.app.model.CarIcon
import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.data.StepData
import java.util.Collections
import java.util.Locale
import kotlin.collections.forEach
class IconMapper(var routeModel: RouteModel) {
fun maneuverIcon(routeManeuverType: Int): Int {
var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) {
Maneuver.TYPE_STRAIGHT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_DESTINATION,
Maneuver.TYPE_DESTINATION_RIGHT,
Maneuver.TYPE_DESTINATION_LEFT,
Maneuver.TYPE_DESTINATION_STRAIGHT
-> {
currentTurnIcon = R.drawable.ic_turn_destination
}
Maneuver.TYPE_TURN_NORMAL_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_normal_right
}
Maneuver.TYPE_TURN_NORMAL_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_normal_left
}
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
Maneuver.TYPE_KEEP_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_KEEP_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
}
return currentTurnIcon
}
fun addLanes(stepData: StepData) : Int {
stepData.lane.forEach {
if (it.indications.isNotEmpty() && it.valid) {
Collections.sort<String>(it.indications)
var direction = ""
it.indications.forEach { it2 ->
direction = if (direction.isEmpty()) {
it2.trim()
} else {
"${direction}_${it2.trim()}"
}
}
val laneDirection = addLanes(direction, stepData)
return laneDirection
}
}
return 0
}
fun addLanes(direction: String, stepData: StepData): Int {
val laneDirection = when (direction.lowercase(Locale.getDefault())) {
"left_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"left", "slight_left" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"left_slight", "slight_left" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right_slight", "slight_right" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
else -> {
LaneDirection.SHAPE_UNKNOWN
}
}
return laneDirection
}
fun createCarIcon(iconCompat: IconCompat): CarIcon {
return CarIcon.Builder(iconCompat).build()
}
fun createCarIconx(carContext: Context, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
fun createLaneIcon(context: Context, stepData: StepData): IconCompat {
val bitmaps = mutableListOf<Bitmap>()
stepData.lane.forEach {
if (it.indications.isNotEmpty()) {
Collections.sort<String>(it.indications)
val resource = laneToResource(it.indications, stepData)
if (resource.isNotEmpty()) {
val id = resourceId(resource);
val bitMap = BitmapFactory.decodeResource(context.resources, id)
bitmaps.add(bitMap)
}
}
}
return if (bitmaps.isEmpty()) {
IconCompat.createWithResource(context, R.drawable.ic_close_white_24dp)
} else {
IconCompat.createWithBitmap(overlay(bitmaps = bitmaps))
}
}
fun overlay(bitmaps: List<Bitmap>): Bitmap {
val matrix = Matrix()
if (bitmaps.size == 1) {
return bitmaps.first()
}
val bmOverlay = createBitmap(
bitmaps.first().getWidth() * (bitmaps.size * 1.5).toInt(),
bitmaps.first().getHeight(),
bitmaps.first().getConfig()!!
)
val canvas = Canvas(bmOverlay)
canvas.drawBitmap(bitmaps.first(), matrix, null)
var i = 0
bitmaps.forEach {
if (i > 0) {
matrix.setTranslate(i * 45F, 0F)
canvas.drawBitmap(it, matrix, null)
}
i++
}
return bmOverlay
}
private fun laneToResource(directions: List<String>, stepData: StepData): String {
var direction = ""
directions.forEach {
direction = if (direction.isEmpty()) {
it.trim()
} else {
"${direction}_${it.trim()}"
}
}
direction = direction.lowercase()
return when (direction) {
"left_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> "left_o_straight_x"
Maneuver.TYPE_STRAIGHT -> "left_x_straight_o"
else
-> "left_x_straight_x"
}
}
"right_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> "right_x_straight_x"
Maneuver.TYPE_STRAIGHT -> "right_x_straight_o"
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> "right_o_straight_o"
else
-> "right_x_straight_x"
}
}
"right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x"
"left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x"
"straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT) "${direction}_o" else "${direction}_x"
"right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_o" else "${direction}_x"
"left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_o" else "${direction}_x"
else -> {
""
}
}
}
fun resourceId(
variableName: String,
): Int {
return when (variableName) {
"left_x" -> R.drawable.left_x
"left_o" -> R.drawable.left_o
"left_o_right_x" -> R.drawable.left_o_right_x
"right_x" -> R.drawable.right_x
"right_o" -> R.drawable.right_o
"slight_right_x" -> R.drawable.slight_right_x
"slight_right_o" -> R.drawable.slight_right_o
"slight_left_x" -> R.drawable.left_x
"straight_x" -> R.drawable.straight_x
"right_o_straight_x" -> R.drawable.right_o_straight_x
"right_x_straight_x" -> R.drawable.right_x_straight_x
"right_x_straight_o" -> R.drawable.right_x_straight_x
"straight_o" -> R.drawable.straight_o
"left_o_straight_x" -> R.drawable.left_o_straight_x
"left_x_straight_o" -> R.drawable.left_x_straight_o
else -> {
R.drawable.left_x
}
}
}
}

View File

@@ -0,0 +1,111 @@
package com.kouros.navigation.model
import android.location.Location
import androidx.car.app.navigation.model.Step
import com.kouros.navigation.utils.location
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class RouteCalculator(var routeModel: RouteModel) {
var lastSpeedLocation: Location = location(0.0, 0.0)
var lastSpeedIndex: Int = 0
fun findStep(location: Location) {
var nearestDistance = 100000f
for ((index, step) in routeModel.curLeg.steps.withIndex()) {
if (index >= routeModel.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
step.waypointIndex = wayIndex
step.wayPointLocation = location(waypoint[0], waypoint[1])
routeModel.routeBearing = routeModel.lastLocation.bearingTo(location)
} else {
if (nearestDistance != 100000f) {
break;
}
}
}
if (nearestDistance == 0F) {
break
}
}
}
if (nearestDistance == 0F) {
break
}
}
}
fun travelLeftTime(): Double {
var timeLeft = 0.0
// time for next step until end step
for (i in routeModel.route.currentStepIndex + 1..<routeModel.curLeg.steps.size) {
val step = routeModel.curLeg.steps[i]
timeLeft += step.duration
}
// time for current step
val step = routeModel.route.currentStep()
val curTime = step.duration
val percent =
100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
val time = curTime * percent / 100
timeLeft += time
return timeLeft
}
/** Returns the current [Step] left distance in m. */
fun leftStepDistance(): Double {
val step = routeModel.route.currentStep()
var leftDistance = 0F
for (i in step.waypointIndex..<step.maneuver.waypoints.size - 1) {
val loc1 = location(step.maneuver.waypoints[i][0], step.maneuver.waypoints[i][1])
val loc2 =
location(step.maneuver.waypoints[i + 1][0], step.maneuver.waypoints[i + 1][1])
val distance = loc1.distanceTo(loc2)
leftDistance += distance
}
return (leftDistance / 10.0).roundToInt() * 10.0
}
/** Returns the left distance in m. */
fun travelLeftDistance(): Double {
var leftDistance = 0.0
for (i in routeModel.route.currentStepIndex + 1..<routeModel.curLeg.steps.size) {
val step = routeModel.route.legs()[0].steps[i]
leftDistance += step.distance
}
leftDistance += leftStepDistance()
return leftDistance
}
fun arrivalTime(): Long {
val timeLeft = travelLeftTime()
// Calculate the time to destination from the current time.
val nowUtcMillis = System.currentTimeMillis()
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
return nowUtcMillis + timeToDestinationMillis
}
fun updateSpeedLimit(location: Location, viewModel: ViewModel) {
if (routeModel.isNavigating()) {
// speed limit
val distance = lastSpeedLocation.distanceTo(location)
if (distance > 500 || lastSpeedIndex < routeModel.route.currentStepIndex) {
lastSpeedIndex = routeModel.route.currentStepIndex
lastSpeedLocation = location
viewModel.getMaxSpeed(location, routeModel.previousStreet())
}
}
}
}

View File

@@ -1,58 +1,36 @@
package com.kouros.navigation.model package com.kouros.navigation.model
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.location.Location import android.location.Location
import androidx.annotation.DrawableRes
import androidx.car.app.CarContext
import androidx.car.app.model.CarIcon
import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Step
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.Constants.ROUTING_ENGINE import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.route.Intersection
import com.kouros.navigation.data.route.Lane import com.kouros.navigation.data.route.Lane
import com.kouros.navigation.data.route.Leg 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.data.valhalla.ManeuverType
import com.kouros.navigation.utils.Levenshtein
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.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.Collections
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.roundToInt
open class RouteModel() { open class RouteModel() {
var route = Route.Builder().buildEmpty() var route = Route.Builder().buildEmpty()
val routeCalculator = RouteCalculator(this)
var iconMapper = IconMapper(this)
var navigating: Boolean = false var navigating: Boolean = false
var destination: Place = Place() var destination: Place = Place()
var arrived: Boolean = false var arrived: Boolean = false
var maneuverType: Int = 0 var maneuverType: Int = 0
var travelMessage: String = "" var travelMessage: String = ""
var lastSpeedLocation: Location = location(0.0, 0.0)
var lastSpeedIndex: Int = 0
var maxSpeed: Int = 0
var location: Location = location(0.0, 0.0) var currentLocation: Location = location(0.0, 0.0)
var lastLocation: Location = location(0.0, 0.0) var lastLocation: Location = location(0.0, 0.0)
var routeBearing: Float = 0F var routeBearing: Float = 0F
@@ -63,6 +41,7 @@ open class RouteModel() {
val curLeg: Leg val curLeg: Leg
get() = route.routes[currentRouteIndex].legs.first() get() = route.routes[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)
route = Route.Builder() route = Route.Builder()
@@ -88,36 +67,10 @@ open class RouteModel() {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun updateLocation(curLocation: Location, viewModel: ViewModel) { fun updateLocation(curLocation: Location, viewModel: ViewModel) {
location = curLocation currentLocation = curLocation
findStep(curLocation) routeCalculator.findStep(curLocation)
updateSpeedLimit(curLocation, viewModel) routeCalculator.updateSpeedLimit(curLocation, viewModel)
lastLocation = location lastLocation = currentLocation
}
private fun findStep(location: Location) {
var nearestDistance = 100000f
for ((index, step) in curLeg.steps.withIndex()) {
if (index >= 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
route.currentStepIndex = step.index
step.waypointIndex = wayIndex
step.wayPointLocation = location(waypoint[0], waypoint[1])
routeBearing = lastLocation.bearingTo(location)
}
}
if (nearestDistance == 0F) {
break
}
}
}
if (nearestDistance == 0F) {
break
}
}
} }
private fun currentLanes(location: Location): List<Lane> { private fun currentLanes(location: Location): List<Lane> {
@@ -135,68 +88,21 @@ open class RouteModel() {
} }
} }
return lanes return lanes
// var inter = Intersection()
// var nearestDistance = 100000.0f
// route.currentStep().intersection.forEach {
// if (it.lane.isNotEmpty()) {
// val distance = location.distanceTo(location(it.location[0], it.location[1]))
// val interBearing = location.bearingTo(location(it.location[0], it.location[1]))
// if (distance < nearestDistance) {
// nearestDistance = distance
// if (distance <= NEXT_STEP_THRESHOLD * 3) {
// if (route.routeEngine == RouteEngine.TOMTOM.ordinal
// || (interBearing.absoluteValue - route.currentStep().maneuver.bearingAfter.absoluteValue).absoluteValue < 20
// ) {
// inter = it
// }
// }
// }
// }
// }
// return inter.lane
} }
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
CoroutineScope(Dispatchers.IO).launch {
if (isNavigating()) {
val instruction = currentStep().instruction
val levenshtein = Levenshtein()
// speed limit
val distance = lastSpeedLocation.distanceTo(location)
if (distance > 500 || lastSpeedIndex < route.currentStepIndex) {
lastSpeedIndex = route.currentStepIndex
val elements = viewModel.getMaxSpeed(location)
elements.forEach {
if (it.tags.name != null) {
if (isNavigating()) {
val distance =
levenshtein.distance(it.tags.name!!, instruction)
if (distance < 5) {
val speed = it.tags.maxspeed.toInt()
maxSpeed = speed
lastSpeedLocation = location
}
}
}
}
}
}
}
}
fun currentStep(): StepData { fun currentStep(): StepData {
val distanceToNextStep = leftStepDistance() val distanceToNextStep = routeCalculator.leftStepDistance()
// Determine the maneuver type and corresponding icon // Determine the maneuver type and corresponding icon
val currentStep = route.nextStep(1) val currentStep = 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.name
val curManeuverType = currentStep.maneuver.type val curManeuverType = currentStep.maneuver.type
val exitNumber = currentStep.maneuver.exit val exitNumber = currentStep.maneuver.exit
val maneuverIcon = maneuverIcon(curManeuverType) val maneuverIcon = iconMapper.maneuverIcon(curManeuverType)
maneuverType = curManeuverType maneuverType = curManeuverType
val lanes = currentLanes(location) val lanes = currentLanes(currentLocation)
// Construct and return the final StepData object // Construct and return the final StepData object
return StepData( return StepData(
@@ -204,17 +110,17 @@ open class RouteModel() {
distanceToNextStep, distanceToNextStep,
maneuverType, maneuverType,
maneuverIcon, maneuverIcon,
arrivalTime(), routeCalculator.arrivalTime(),
travelLeftDistance(), routeCalculator.travelLeftDistance(),
lanes, lanes,
exitNumber exitNumber
) )
} }
fun nextStep(): StepData { fun nextStep(): StepData {
val step = route.nextStep(2) val step = route.nextStep(1)
val maneuverType = step.maneuver.type val maneuverType = step.maneuver.type
val distanceLeft = 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 -> {
@@ -227,336 +133,28 @@ open class RouteModel() {
} }
} }
val maneuverIcon = maneuverIcon(maneuverType) val maneuverIcon = iconMapper.maneuverIcon(maneuverType)
// Construct and return the final StepData object // Construct and return the final StepData object
return StepData( return StepData(
text, text,
distanceLeft, distanceLeft,
maneuverType, maneuverType,
maneuverIcon, maneuverIcon,
arrivalTime(), routeCalculator.arrivalTime(),
travelLeftDistance(), routeCalculator.travelLeftDistance(),
listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())), listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
step.maneuver.exit step.maneuver.exit
) )
} }
fun travelLeftTime(): Double { fun previousStreet(): String {
var timeLeft = 0.0 if (route.currentStepIndex > 0) {
// time for next step until end step return route.legs().first().steps[route.currentStepIndex - 1].name
for (i in route.currentStepIndex + 1..<curLeg.steps.size) {
val step = curLeg.steps[i]
timeLeft += step.duration
} }
// time for current step return ""
val step = route.currentStep()
val curTime = step.duration
val percent =
100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
val time = curTime * percent / 100
timeLeft += time
return timeLeft
}
fun arrivalTime(): Long {
val timeLeft = travelLeftTime()
// Calculate the time to destination from the current time.
val nowUtcMillis = System.currentTimeMillis()
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
return nowUtcMillis + timeToDestinationMillis
}
/** Returns the current [Step] left distance in m. */
fun leftStepDistance(): Double {
val step = route.currentStep()
println(step.index)
var leftDistance = 0F
for (i in step.waypointIndex..<step.maneuver.waypoints.size - 1) {
val loc1 = location(step.maneuver.waypoints[i][0], step.maneuver.waypoints[i][1])
val loc2 =
location(step.maneuver.waypoints[i + 1][0], step.maneuver.waypoints[i + 1][1])
val distance = loc1.distanceTo(loc2)
leftDistance += distance
}
return (leftDistance / 10.0).roundToInt() * 10.0
}
/** Returns the left distance in m. */
fun travelLeftDistance(): Double {
var leftDistance = 0.0
for (i in route.currentStepIndex + 1..<curLeg.steps.size) {
val step = route.legs()[0].steps[i]
leftDistance += step.distance
}
leftDistance += leftStepDistance()
return leftDistance
}
fun maneuverIcon(routeManeuverType: Int): Int {
var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) {
Maneuver.TYPE_STRAIGHT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_DESTINATION,
Maneuver.TYPE_DESTINATION_RIGHT,
Maneuver.TYPE_DESTINATION_LEFT,
Maneuver.TYPE_DESTINATION_STRAIGHT
-> {
currentTurnIcon = R.drawable.ic_turn_destination
}
Maneuver.TYPE_TURN_NORMAL_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_normal_right
}
Maneuver.TYPE_TURN_NORMAL_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_normal_left
}
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
Maneuver.TYPE_KEEP_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_KEEP_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
}
return currentTurnIcon
} }
fun isNavigating(): Boolean { fun isNavigating(): Boolean {
return navigating return navigating
} }
fun hasArrived(type: Int): Boolean {
return type == ManeuverType.DestinationRight.value
|| type == ManeuverType.Destination.value
|| type == ManeuverType.DestinationLeft.value
}
fun addLanes(stepData: StepData) {
stepData.lane.forEach {
if (it.indications.isNotEmpty() && it.valid) {
Collections.sort<String>(it.indications)
var direction = ""
it.indications.forEach { it2 ->
direction = if (direction.isEmpty()) {
it2.trim()
} else {
"${direction}_${it2.trim()}"
}
}
val laneDirection = addLanes(direction, stepData)
println(laneDirection)
// TODO:
}
}
}
fun addLanes(direction: String, stepData: StepData): Int {
val laneDirection = when (direction.lowercase(Locale.getDefault())) {
"left_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"left", "slight_left" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"left_slight", "slight_left" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right_slight", "slight_right" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
else -> {
LaneDirection.SHAPE_UNKNOWN
}
}
return laneDirection
}
fun createCarIcon(iconCompat: IconCompat): CarIcon {
return CarIcon.Builder(iconCompat).build()
}
fun createCarIconx(carContext: Context, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
fun createLaneIcon(context: Context, stepData: StepData): IconCompat {
val bitmaps = mutableListOf<Bitmap>()
stepData.lane.forEach {
if (it.indications.isNotEmpty()) {
Collections.sort<String>(it.indications)
val resource = laneToResource(it.indications, stepData)
if (resource.isNotEmpty()) {
val id = resourceId(resource);
val bitMap = BitmapFactory.decodeResource(context.resources, id)
bitmaps.add(bitMap)
}
}
}
return if (bitmaps.isEmpty()) {
IconCompat.createWithResource(context, R.drawable.ic_close_white_24dp)
} else {
IconCompat.createWithBitmap(overlay(bitmaps = bitmaps))
}
}
fun overlay(bitmaps: List<Bitmap>): Bitmap {
val matrix = Matrix()
if (bitmaps.size == 1) {
return bitmaps.first()
}
val bmOverlay = createBitmap(
bitmaps.first().getWidth() * (bitmaps.size * 1.5).toInt(),
bitmaps.first().getHeight(),
bitmaps.first().getConfig()!!
)
val canvas = Canvas(bmOverlay)
canvas.drawBitmap(bitmaps.first(), matrix, null)
var i = 0
bitmaps.forEach {
if (i > 0) {
matrix.setTranslate(i * 45F, 0F)
canvas.drawBitmap(it, matrix, null)
}
i++
}
return bmOverlay
}
private fun laneToResource(directions: List<String>, stepData: StepData): String {
var direction = ""
directions.forEach {
direction = if (direction.isEmpty()) {
it.trim()
} else {
"${direction}_${it.trim()}"
}
}
direction = direction.lowercase()
return when (direction) {
"left_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> "left_o_straight_x"
Maneuver.TYPE_STRAIGHT -> "left_x_straight_o"
else
-> "left_x_straight_x"
}
}
"right_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> "right_x_straight_x"
Maneuver.TYPE_STRAIGHT -> "right_x_straight_o"
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> "right_o_straight_o"
else
-> "right_x_straight_x"
}
}
"right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x"
"left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x"
"straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT) "${direction}_o" else "${direction}_x"
"right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_o" else "${direction}_x"
"left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_o" else "${direction}_x"
else -> {
""
}
}
}
fun resourceId(
variableName: String,
): Int {
return when (variableName) {
"left_x" -> R.drawable.left_x
"left_o" -> R.drawable.left_o
"left_o_right_x" -> R.drawable.left_o_right_x
"right_x" -> R.drawable.right_x
"right_o" -> R.drawable.right_o
"slight_right_x" -> R.drawable.slight_right_x
"slight_right_o" -> R.drawable.slight_right_o
"slight_left_x" -> R.drawable.left_x
"straight_x" -> R.drawable.straight_x
"right_o_straight_x" -> R.drawable.right_o_straight_x
"right_x_straight_x" -> R.drawable.right_x_straight_x
"right_x_straight_o" -> R.drawable.right_x_straight_x
"straight_o" -> R.drawable.straight_o
"left_o_straight_x" -> R.drawable.left_o_straight_x
"left_x_straight_o" -> R.drawable.left_x_straight_o
else -> {
R.drawable.left_x
}
}
}
} }

View File

@@ -22,6 +22,7 @@ import com.kouros.navigation.data.tomtom.Features
import com.kouros.navigation.data.tomtom.Traffic import com.kouros.navigation.data.tomtom.Traffic
import com.kouros.navigation.data.tomtom.TrafficData import com.kouros.navigation.data.tomtom.TrafficData
import com.kouros.navigation.utils.GeoUtils.createPointCollection import com.kouros.navigation.utils.GeoUtils.createPointCollection
import com.kouros.navigation.utils.Levenshtein
import com.kouros.navigation.utils.NavigationUtils import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
@@ -37,7 +38,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData() MutableLiveData()
} }
val traffic: MutableLiveData<Map<String, String> > by lazy { val traffic: MutableLiveData<Map<String, String>> by lazy {
MutableLiveData() MutableLiveData()
} }
@@ -64,7 +65,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val placeLocation: MutableLiveData<SearchResult> by lazy { val placeLocation: MutableLiveData<SearchResult> by lazy {
MutableLiveData() MutableLiveData()
} }
val contactAddress: MutableLiveData<List<Place>> by lazy { val contactAddress: MutableLiveData<List<Place>> by lazy {
MutableLiveData() MutableLiveData()
} }
@@ -77,6 +78,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData() MutableLiveData()
} }
val maxSpeed: MutableLiveData<Int> by lazy {
MutableLiveData()
}
val routingEngine: MutableLiveData<Int> by lazy { val routingEngine: MutableLiveData<Int> by lazy {
MutableLiveData() MutableLiveData()
} }
@@ -93,11 +97,17 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
query.close() query.close()
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = repository.getRouteDistance(location, plLocation, carOrientation, SearchFilter(), context) val distance = repository.getRouteDistance(
location,
plLocation,
carOrientation,
SearchFilter(),
context
)
place.distance = distance.toFloat() place.distance = distance.toFloat()
if (place.distance > 1F) { if (place.distance > 1F) {
recentPlace.postValue(place) recentPlace.postValue(place)
return@launch return@launch
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -106,7 +116,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadRecentPlaces(context: Context, location: Location, carOrientation : Float) { fun loadRecentPlaces(context: Context, location: Location, carOrientation: Float) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val placeBox = boxStore.boxFor(Place::class) val placeBox = boxStore.boxFor(Place::class)
@@ -118,16 +128,16 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
query.close() query.close()
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
if (place.latitude != 0.0) { if (place.latitude != 0.0) {
val distance = val distance =
repository.getRouteDistance( repository.getRouteDistance(
location, location,
plLocation, plLocation,
carOrientation, carOrientation,
getSearchFilter(context), context getSearchFilter(context), context
) )
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
} }
places.postValue(results) places.postValue(results)
} catch (e: Exception) { } catch (e: Exception) {
@@ -149,7 +159,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = val distance =
repository.getRouteDistance(location, plLocation, carOrientation, getSearchFilter(context), context) repository.getRouteDistance(
location,
plLocation,
carOrientation,
getSearchFilter(context),
context
)
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
favorites.postValue(results) favorites.postValue(results)
@@ -159,7 +175,12 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadRoute(context: Context, currentLocation: Location, location: Location, carOrientation : Float) { fun loadRoute(
context: Context,
currentLocation: Location,
location: Location,
carOrientation: Float
) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
route.postValue( route.postValue(
@@ -177,7 +198,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadTraffic(context: Context, currentLocation: Location, carOrientation : Float) { fun loadTraffic(context: Context, currentLocation: Location, carOrientation: Float) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val data = repository.getTraffic( val data = repository.getTraffic(
@@ -195,23 +216,34 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
private fun rebuildTraffic(data: String) : Map<String, String> { private fun rebuildTraffic(data: String): Map<String, String> {
val featureCollection = FeatureCollection.fromJson(data) val featureCollection = FeatureCollection.fromJson(data)
val incidents = mutableMapOf<String, String>() val incidents = mutableMapOf<String, String>()
val queuing = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Queuing traffic")} val queuing = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Queuing traffic") }
incidents["queuing"] = FeatureCollection.fromFeatures(queuing).toJson() incidents["queuing"] = FeatureCollection.fromFeatures(queuing).toJson()
val stationary = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Stationary traffic")} val stationary = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Stationary traffic") }
incidents["stationary"] = FeatureCollection.fromFeatures(stationary).toJson() incidents["stationary"] = FeatureCollection.fromFeatures(stationary).toJson()
val slow = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Slow traffic")} val slow = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Slow traffic") }
incidents["slow"] = FeatureCollection.fromFeatures(slow).toJson() incidents["slow"] = FeatureCollection.fromFeatures(slow).toJson()
val heavy = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Heavy traffic")} val heavy = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Heavy traffic") }
incidents["heavy"] = FeatureCollection.fromFeatures(heavy).toJson() incidents["heavy"] = FeatureCollection.fromFeatures(heavy).toJson()
val roadworks = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Roadworks")} val roadworks = featureCollection.features()!!
.filter { it.properties()!!.get("events").toString().contains("Roadworks") }
incidents["roadworks"] = FeatureCollection.fromFeatures(roadworks).toJson() incidents["roadworks"] = FeatureCollection.fromFeatures(roadworks).toJson()
return incidents return incidents
} }
fun loadPreviewRoute(context: Context, currentLocation: Location, location: Location, carOrientation: Float) {
fun loadPreviewRoute(
context: Context,
currentLocation: Location,
location: Location,
carOrientation: Float
) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
previewRoute.postValue( previewRoute.postValue(
@@ -277,6 +309,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
} }
fun searchPlaces(search: String, location: Location) { fun searchPlaces(search: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val placesJson = repository.searchPlaces(search, location) val placesJson = repository.searchPlaces(search, location)
@@ -320,13 +353,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun getSpeedCameras(location: Location, radius : Double) { fun getSpeedCameras(location: Location, radius: Double) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius) val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
val distAmenities = mutableListOf<Elements>() val distAmenities = mutableListOf<Elements>()
amenities.forEach { amenities.forEach {
val plLocation = val plLocation =
location(longitude = it.lon!!, latitude = it.lat!!) location(longitude = it.lon, latitude = it.lat)
val distance = plLocation.distanceTo(location) val distance = plLocation.distanceTo(location)
it.distance = distance.toDouble() it.distance = distance.toDouble()
distAmenities.add(it) distAmenities.add(it)
@@ -336,10 +369,22 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun getMaxSpeed(location: Location) : List<Elements> { fun getMaxSpeed(location: Location, street: String) {
viewModelScope.launch(Dispatchers.IO) {
val levenshtein = Levenshtein()
val lineString = "${location.latitude},${location.longitude}" val lineString = "${location.latitude},${location.longitude}"
val amenities = Overpass().getAround(10, lineString) val amenities = Overpass().getAround(10, lineString)
return amenities 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)
}
}
}
}
} }
fun saveFavorite(place: Place) { fun saveFavorite(place: Place) {
@@ -350,7 +395,8 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun saveRecent(place: Place) { fun saveRecent(place: Place) {
if (place.category == Constants.FUEL_STATION if (place.category == Constants.FUEL_STATION
|| place.category == Constants.CHARGING_STATION || place.category == Constants.CHARGING_STATION
|| place.category == Constants.PHARMACY) { || place.category == Constants.PHARMACY
) {
return return
} }
place.category = Constants.RECENT place.category = Constants.RECENT
@@ -423,7 +469,11 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
fun loadPlaces2(context: Context, location: Location, carOrientation: Float): SnapshotStateList<Place?> { fun loadPlaces2(
context: Context,
location: Location,
carOrientation: Float
): SnapshotStateList<Place?> {
val results = listOf<Place>() val results = listOf<Place>()
try { try {
val placeBox = boxStore.boxFor(Place::class) val placeBox = boxStore.boxFor(Place::class)
@@ -436,7 +486,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = val distance =
repository.getRouteDistance(location, plLocation, carOrientation, getSearchFilter(context), context) repository.getRouteDistance(
location,
plLocation,
carOrientation,
getSearchFilter(context),
context
)
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
} catch (e: Exception) { } catch (e: Exception) {