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

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

View File

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

View File

@@ -15,6 +15,7 @@
*/
package com.kouros.navigation.car.navigation
import android.location.Location
import android.text.SpannableString
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
@@ -98,17 +99,17 @@ class RouteCarModel() : RouteModel() {
}
fun travelEstimate(carContext: CarContext): TravelEstimate {
val timeLeft = travelLeftTime()
val timeLeft = routeCalculator.travelLeftTime()
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
val leftDistance = travelLeftDistance() / 1000
val leftDistance = routeCalculator.travelLeftDistance() / 1000
val displayUnit = if (leftDistance > 1.0) {
Distance.UNIT_KILOMETERS
} else {
Distance.UNIT_METERS
}
val arrivalTime = DateTimeWithZone.create(
arrivalTime(),
routeCalculator.arrivalTime(),
TimeZone.getTimeZone("Europe/Berlin")
)
val travelBuilder = TravelEstimate.Builder( // The estimated distance to the destination.
@@ -133,99 +134,99 @@ class RouteCarModel() : RouteModel() {
return travelBuilder.build()
}
fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) {
var laneImageAdded = false
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()}"
fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) {
var laneImageAdded = false
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)
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
if (!laneImageAdded) {
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
laneImageAdded = true
val laneDirection = iconMapper.addLanes(direction, stepData)
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
if (!laneImageAdded) {
step.setLanesImage(createCarIcon(iconMapper.createLaneIcon(carContext, stepData)))
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(
text: String
): SpannableString {
val spannableString = SpannableString(text)
return spannableString
}
fun createString(
text: String
): SpannableString {
val spannableString = SpannableString(text)
return spannableString
}
fun createCarText(carContext: CarContext, @StringRes stringRes: Int): CarText {
return CarText.create(carContext.getString(stringRes))
}
fun createCarText(carContext: CarContext, @StringRes stringRes: Int): CarText {
return CarText.create(carContext.getString(stringRes))
}
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
// fun createCarIcon(iconCompat: IconCompat): CarIcon {
// return CarIcon.Builder(iconCompat).build()
// }
fun createCarIcon(iconCompat: IconCompat): CarIcon {
return CarIcon.Builder(iconCompat).build()
}
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(
createAlert(
carContext,
maxSpeed,
createCarIcon(carContext, R.drawable.speed_camera_24px)
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(
createAlert(
carContext,
maxSpeed,
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(
carContext: CarContext,
maxSpeed: String?,
icon: CarIcon
): Alert {
val title = createCarText(carContext, R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!)
override fun onDismiss() {
}
}).build()
}
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) {
}
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()
}
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 {
var currentDistance = routeModel.leftStepDistance()
var currentDistance = routeModel.routeCalculator.leftStepDistance()
val displayUnit = if (currentDistance > 1000.0) {
currentDistance /= 1000.0
Distance.UNIT_KILOMETERS
@@ -496,7 +496,7 @@ class NavigationScreen(
|| maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|| maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|| maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
stopNavigation()
arrived = true

View File

@@ -13,7 +13,7 @@ class Overpass {
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
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
@@ -57,12 +57,13 @@ class Overpass {
| node[$type=$category]
| ($boundingBox);
|);
|(._;>;);
|out body;
""".trimMargin()
return overpassApi(httpURLConnection, searchQuery)
}
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String): List<Elements> {
try {
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(searchQuery)
@@ -75,8 +76,10 @@ class Overpass {
val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java)
return overpass.elements
}
} catch (e: Exception) {
}
return emptyList()
}

View File

@@ -35,16 +35,16 @@ class TomTomRoute {
var stepDuration = 0.0
val allIntersections = mutableListOf<Intersection>()
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 nextPointIndex = nextPointIndex(index, route)
val maneuver = RouteManeuver(
bearingBefore = 0,
bearingAfter = 0,
type = convertType(instruction.maneuver),
waypoints = points.subList(
lastPointIndex,
instruction.pointIndex,
route.guidance.instructions[nextPointIndex].pointIndex
),
exit = exitNumber(instruction),
location = location(
@@ -52,6 +52,7 @@ class TomTomRoute {
),
)
lastPointIndex = instruction.pointIndex
val intersections = mutableListOf<Intersection>()
route.sections.forEach { section ->
val lanes = mutableListOf<Lane>()
@@ -75,8 +76,8 @@ class TomTomRoute {
}
}
allIntersections.addAll(intersections)
stepDistance = route.guidance.instructions[nextPointIndex].routeOffsetInMeters - stepDistance
stepDuration = route.guidance.instructions[nextPointIndex].travelTimeInSeconds - stepDuration
stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration = route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val name = instruction.street
val step = Step(
index = stepIndex,
@@ -86,8 +87,8 @@ class TomTomRoute {
maneuver = maneuver,
intersection = intersections
)
stepDistance = route.guidance.instructions[nextPointIndex].routeOffsetInMeters.toDouble()
stepDuration = route.guidance.instructions[nextPointIndex].travelTimeInSeconds.toDouble()
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
steps.add(step)
stepIndex += 1
}
@@ -108,15 +109,6 @@ class TomTomRoute {
.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 {
var newType = 0
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
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 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.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.ROUTING_ENGINE
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.RouteEngine
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.Leg
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.location
import kotlinx.coroutines.CoroutineScope
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.roundToInt
open class RouteModel() {
var route = Route.Builder().buildEmpty()
val routeCalculator = RouteCalculator(this)
var iconMapper = IconMapper(this)
var navigating: Boolean = false
var destination: Place = Place()
var arrived: Boolean = false
var maneuverType: Int = 0
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 routeBearing: Float = 0F
@@ -63,6 +41,7 @@ open class RouteModel() {
val curLeg: Leg
get() = route.routes[currentRouteIndex].legs.first()
fun startNavigation(routeString: String, context: Context) {
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
route = Route.Builder()
@@ -88,36 +67,10 @@ open class RouteModel() {
@OptIn(DelicateCoroutinesApi::class)
fun updateLocation(curLocation: Location, viewModel: ViewModel) {
location = curLocation
findStep(curLocation)
updateSpeedLimit(curLocation, viewModel)
lastLocation = location
}
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
}
}
currentLocation = curLocation
routeCalculator.findStep(curLocation)
routeCalculator.updateSpeedLimit(curLocation, viewModel)
lastLocation = currentLocation
}
private fun currentLanes(location: Location): List<Lane> {
@@ -135,68 +88,21 @@ open class RouteModel() {
}
}
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 {
val distanceToNextStep = leftStepDistance()
val distanceToNextStep = routeCalculator.leftStepDistance()
// 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
val streetName = currentStep.name
val curManeuverType = currentStep.maneuver.type
val exitNumber = currentStep.maneuver.exit
val maneuverIcon = maneuverIcon(curManeuverType)
val maneuverIcon = iconMapper.maneuverIcon(curManeuverType)
maneuverType = curManeuverType
val lanes = currentLanes(location)
val lanes = currentLanes(currentLocation)
// Construct and return the final StepData object
return StepData(
@@ -204,17 +110,17 @@ open class RouteModel() {
distanceToNextStep,
maneuverType,
maneuverIcon,
arrivalTime(),
travelLeftDistance(),
routeCalculator.arrivalTime(),
routeCalculator.travelLeftDistance(),
lanes,
exitNumber
)
}
fun nextStep(): StepData {
val step = route.nextStep(2)
val step = route.nextStep(1)
val maneuverType = step.maneuver.type
val distanceLeft = leftStepDistance()
val distanceLeft = routeCalculator.leftStepDistance()
var text = ""
when (distanceLeft) {
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
return StepData(
text,
distanceLeft,
maneuverType,
maneuverIcon,
arrivalTime(),
travelLeftDistance(),
routeCalculator.arrivalTime(),
routeCalculator.travelLeftDistance(),
listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
step.maneuver.exit
)
}
fun travelLeftTime(): Double {
var timeLeft = 0.0
// time for next step until end step
for (i in route.currentStepIndex + 1..<curLeg.steps.size) {
val step = curLeg.steps[i]
timeLeft += step.duration
fun previousStreet(): String {
if (route.currentStepIndex > 0) {
return route.legs().first().steps[route.currentStepIndex - 1].name
}
// time for current step
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
return ""
}
fun isNavigating(): Boolean {
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.TrafficData
import com.kouros.navigation.utils.GeoUtils.createPointCollection
import com.kouros.navigation.utils.Levenshtein
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor
@@ -37,7 +38,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData()
}
val traffic: MutableLiveData<Map<String, String> > by lazy {
val traffic: MutableLiveData<Map<String, String>> by lazy {
MutableLiveData()
}
@@ -64,7 +65,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val placeLocation: MutableLiveData<SearchResult> by lazy {
MutableLiveData()
}
val contactAddress: MutableLiveData<List<Place>> by lazy {
MutableLiveData()
}
@@ -77,6 +78,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData()
}
val maxSpeed: MutableLiveData<Int> by lazy {
MutableLiveData()
}
val routingEngine: MutableLiveData<Int> by lazy {
MutableLiveData()
}
@@ -93,11 +97,17 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
query.close()
for (place in results) {
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()
if (place.distance > 1F) {
recentPlace.postValue(place)
return@launch
return@launch
}
}
} 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) {
try {
val placeBox = boxStore.boxFor(Place::class)
@@ -118,16 +128,16 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
query.close()
for (place in results) {
val plLocation = location(place.longitude, place.latitude)
if (place.latitude != 0.0) {
val distance =
repository.getRouteDistance(
location,
plLocation,
carOrientation,
getSearchFilter(context), context
)
place.distance = distance.toFloat()
}
if (place.latitude != 0.0) {
val distance =
repository.getRouteDistance(
location,
plLocation,
carOrientation,
getSearchFilter(context), context
)
place.distance = distance.toFloat()
}
}
places.postValue(results)
} catch (e: Exception) {
@@ -149,7 +159,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) {
val plLocation = location(place.longitude, place.latitude)
val distance =
repository.getRouteDistance(location, plLocation, carOrientation, getSearchFilter(context), context)
repository.getRouteDistance(
location,
plLocation,
carOrientation,
getSearchFilter(context),
context
)
place.distance = distance.toFloat()
}
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) {
try {
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) {
try {
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 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()
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()
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()
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()
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()
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) {
try {
previewRoute.postValue(
@@ -277,6 +309,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
}
}
}
fun searchPlaces(search: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
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) {
val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
val distAmenities = mutableListOf<Elements>()
amenities.forEach {
val plLocation =
location(longitude = it.lon!!, latitude = it.lat!!)
location(longitude = it.lon, latitude = it.lat)
val distance = plLocation.distanceTo(location)
it.distance = distance.toDouble()
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 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) {
@@ -350,7 +395,8 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun saveRecent(place: Place) {
if (place.category == Constants.FUEL_STATION
|| place.category == Constants.CHARGING_STATION
|| place.category == Constants.PHARMACY) {
|| place.category == Constants.PHARMACY
) {
return
}
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>()
try {
val placeBox = boxStore.boxFor(Place::class)
@@ -436,7 +486,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) {
val plLocation = location(place.longitude, place.latitude)
val distance =
repository.getRouteDistance(location, plLocation, carOrientation, getSearchFilter(context), context)
repository.getRouteDistance(
location,
plLocation,
carOrientation,
getSearchFilter(context),
context
)
place.distance = distance.toFloat()
}
} catch (e: Exception) {