From 5141041b5c845e295c2a825abb58b3fa29ec7841 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Mon, 9 Feb 2026 13:36:05 +0100 Subject: [PATCH] TomTom Routing --- .../com/kouros/navigation/ui/MainActivity.kt | 11 +- .../kouros/navigation/ui/NavigationSheet.kt | 2 +- .../kouros/navigation/car/SurfaceRenderer.kt | 4 +- .../com/kouros/navigation/car/map/MapView.kt | 10 +- .../car/navigation/RouteCarModel.kt | 169 +++---- .../navigation/car/screen/NavigationScreen.kt | 4 +- .../navigation/data/overpass/Overpass.kt | 7 +- .../navigation/data/tomtom/TomTomRoute.kt | 24 +- .../com/kouros/navigation/model/IconMapper.kt | 276 +++++++++++ .../navigation/model/RouteCalculator.kt | 111 +++++ .../com/kouros/navigation/model/RouteModel.kt | 454 +----------------- .../com/kouros/navigation/model/ViewModel.kt | 120 +++-- 12 files changed, 612 insertions(+), 580 deletions(-) create mode 100644 common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt diff --git a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt index 64b9fba..af40bc1 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt @@ -88,7 +88,7 @@ class MainActivity : ComponentActivity() { val routeModel = RouteModel() var tilt = 50.0 val useMock = true - val type = 3 // simulate 2 test 3 gpx + val type = 1 // simulate 2 test 3 gpx var currentIndex = 0 val stepData: MutableLiveData by lazy { @@ -309,20 +309,16 @@ class MainActivity : ComponentActivity() { && lastLocation.longitude != location.position.longitude ) { 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) with(routeModel) { if (isNavigating()) { updateLocation(currentLocation, navigationViewModel) stepData.value = currentStep() - println("Step: ${stepData.value!!.instruction} ${stepData.value!!.leftStepDistance}") if (route.currentStepIndex + 1 <= curLeg.steps.size) { nextStepData.value = nextStep() } if (maneuverType in 39..42 - && leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE + && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE ) { // stopNavigation() arrived = true @@ -410,9 +406,6 @@ class MainActivity : ComponentActivity() { ) val step = routeModel.currentStep() println("Step: ${step.instruction} ${step.leftStepDistance}") - if (step.leftStepDistance == 70.0) { - println("") - } if (index + 1 <= routeModel.curLeg.steps.size) { //nextStepData.value = routeModel.nextStep() } diff --git a/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt b/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt index 77c7a2d..68f394b 100755 --- a/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt +++ b/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt @@ -34,7 +34,7 @@ fun NavigationSheet( val distance = (step.leftDistance / 1000).round(1) if (step.lane.isNotEmpty()) { - routeModel.addLanes( step) + routeModel.iconMapper.addLanes( step) } Column { diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index 2210e1f..6dd882e 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -76,6 +76,7 @@ class SurfaceRenderer( val trafficData = MutableLiveData(emptyMap()) 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 ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt index 2b82f83..f90064a 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt @@ -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, diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt index 1f3d191..edd0c8c 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt @@ -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(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(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::class.java) - .showAlert( - createAlert( - carContext, - maxSpeed, - createCarIcon(carContext, R.drawable.speed_camera_24px) + fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) { + carContext.getCarService(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() + } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index 10a76de..0f9c62a 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -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 diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt index a5bf310..59012a0 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt @@ -13,7 +13,7 @@ class Overpass { val overpassUrl = "https://kouros-online.de/overpass/interpreter" - fun getAround(radius: Int, linestring: String) : List { + fun getAround(radius: Int, linestring: String): List { 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 { + fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String): List { 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() } diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt index 020fbfb..eac55af 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt @@ -35,16 +35,16 @@ class TomTomRoute { var stepDuration = 0.0 val allIntersections = mutableListOf() val steps = mutableListOf() - 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() route.sections.forEach { section -> val lanes = mutableListOf() @@ -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) { diff --git a/common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt b/common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt new file mode 100644 index 0000000..9e01768 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt @@ -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(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() + stepData.lane.forEach { + if (it.indications.isNotEmpty()) { + Collections.sort(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 { + 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, 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 + } + } + } +} \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt new file mode 100644 index 0000000..efbf310 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt @@ -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.. 500 || lastSpeedIndex < routeModel.route.currentStepIndex) { + lastSpeedIndex = routeModel.route.currentStepIndex + lastSpeedLocation = location + viewModel.getMaxSpeed(location, routeModel.previousStreet()) + } + } + } + + +} \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index 2a32804..5ca9623 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -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 { @@ -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.. 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.. { - 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(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() - stepData.lane.forEach { - if (it.indications.isNotEmpty()) { - Collections.sort(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 { - 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, 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 - } - } - } - } \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt index e08e9cb..92582e9 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt @@ -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 > by lazy { + val traffic: MutableLiveData> by lazy { MutableLiveData() } @@ -64,7 +65,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() { val placeLocation: MutableLiveData by lazy { MutableLiveData() } - + val contactAddress: MutableLiveData> by lazy { MutableLiveData() } @@ -77,6 +78,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() { MutableLiveData() } + val maxSpeed: MutableLiveData by lazy { + MutableLiveData() + } val routingEngine: MutableLiveData 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 { + private fun rebuildTraffic(data: String): Map { val featureCollection = FeatureCollection.fromJson(data) val incidents = mutableMapOf() - 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() 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 { + 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 { + fun loadPlaces2( + context: Context, + location: Location, + carOrientation: Float + ): SnapshotStateList { val results = listOf() 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) {