This commit is contained in:
Dimitris
2026-01-06 08:25:27 +01:00
parent fdf2ee9f48
commit 7efa2685be
24 changed files with 226 additions and 88 deletions

View File

@@ -55,9 +55,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
if (routingEngine == RouteEngine.VALHALLA.ordinal) {
// if (routingEngine == RouteEngine.VALHALLA.ordinal) {
updateLocation(location!!)
}
// }
}
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
@@ -97,7 +97,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
OnCarDataAvailableListener { data ->
if (data.location.status == CarValue.STATUS_SUCCESS) {
val location = data.location.value
surfaceRenderer.updateCarLocation(location!!)
if (location != null) {
updateLocation(location)
}
}
}

View File

@@ -36,9 +36,12 @@ import androidx.car.app.navigation.model.Step
import androidx.car.app.navigation.model.TravelEstimate
import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.RouteModel
import java.util.Collections
import java.util.TimeZone
import java.util.concurrent.TimeUnit
import kotlin.text.trim
/** A class that provides models for the routing demos. */
class RouteCarModel() : RouteModel() {
@@ -48,23 +51,16 @@ class RouteCarModel() : RouteModel() {
val stepData = currentStep()
val currentStepCueWithImage: SpannableString =
createString(stepData.instruction)
val straightNormal =
Lane.Builder()
.addDirection(LaneDirection.create(LaneDirection.SHAPE_STRAIGHT, false))
.build()
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
Maneuver.Builder(stepData.maneuverType)
Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon))
.build()
)
.setRoad(routeState.destination.street!!)
stepData.lane.forEach {
if (it.indications.isNotEmpty() ) {
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
step.addLane(straightNormal)
}
if (stepData.lane.isNotEmpty()) {
addLanes(carContext, step, stepData)
}
return step.build()
}
@@ -77,7 +73,7 @@ class RouteCarModel() : RouteModel() {
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
Maneuver.Builder(stepData.maneuverType)
Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon))
.build()
)
@@ -121,6 +117,90 @@ 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()}"
}
}
val laneDirection = when (direction) {
"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" -> {
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" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
"right_slight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_RIGHT-> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
}
else -> {
LaneDirection.SHAPE_UNKNOWN
}
}
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
if (!laneImageAdded) {
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
laneImageAdded = true
}
val laneType =
Lane.Builder()
.addDirection(LaneDirection.create(laneDirection, false))
.build()
step.addLane(laneType)
}
}
}
}
fun createString(
text: String
): SpannableString {

View File

@@ -20,6 +20,7 @@ import android.location.Location
import android.location.LocationManager
import android.net.Uri
import com.kouros.navigation.data.route.Lane
import com.kouros.navigation.utils.location
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import kotlinx.serialization.Serializable
@@ -58,7 +59,7 @@ data class StepData (
var leftStepDistance: Double,
var maneuverType: Int,
var currentManeuverType: Int,
var icon: Int,
@@ -66,7 +67,7 @@ data class StepData (
var leftDistance: Double,
var lane: List<Lane> = listOf(Lane(valid = false, indications = emptyList())),
var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
)
@@ -176,7 +177,7 @@ object Constants {
const val ROUTING_ENGINE = "RoutingEngine"
const val NEXT_STEP_THRESHOLD = 100.0
const val NEXT_STEP_THRESHOLD = 120.0
const val MAXIMAL_SNAP_CORRECTION = 50.0

View File

@@ -30,7 +30,9 @@ import kotlinx.serialization.json.Json
abstract class NavigationRepository {
private val nominatimUrl = "https://nominatim.openstreetmap.org/"
//private val nominatimUrl = "https://nominatim.openstreetmap.org/"
private val nominatimUrl = "https://kouros-online.de/nominatim/"
abstract fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String

View File

@@ -9,6 +9,7 @@ import com.kouros.navigation.data.route.Step
import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
import com.kouros.navigation.utils.GeoUtils.decodePolyline
import com.kouros.navigation.utils.location
class OsrmRoute {
@@ -34,8 +35,14 @@ class OsrmRoute {
if (it2.location[0] != 0.0) {
val lanes = mutableListOf<Lane>()
it2.lanes.forEach { it3 ->
val lane = Lane(it3.valid, it3.indications)
lanes.add(lane)
if (it3.indications.isNotEmpty() && it3.indications.first() != "none") {
val lane = Lane(
location(it2.location[0], it2.location[1]),
it3.valid,
it3.indications
)
lanes.add(lane)
}
}
intersections.add(Intersection(it2.location, lanes))
}

View File

@@ -1,6 +1,9 @@
package com.kouros.navigation.data.route
import android.location.Location
data class Lane (
val location: Location,
val valid: Boolean,
var indications: List<String>,
)

View File

@@ -28,6 +28,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.Collections
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
@@ -44,7 +45,7 @@ open class RouteModel() {
val maxSpeed: Int = 0,
val location: Location = location(0.0, 0.0),
val lastLocation: Location = location(0.0, 0.0),
val bearing : Float = 0F
val bearing: Float = 0F
)
var routeState = RouteState()
@@ -104,7 +105,8 @@ open class RouteModel() {
step.waypointIndex = wayIndex
step.wayPointLocation = location(waypoint[0], waypoint[1])
val bearing = routeState.lastLocation.bearingTo(location)
this.routeState = routeState.copy(lastLocation = location, bearing = bearing)
this.routeState =
routeState.copy(lastLocation = location, bearing = bearing)
}
}
if (nearestDistance == 0F) {
@@ -174,15 +176,15 @@ open class RouteModel() {
if (shouldAdvance) {
maneuverType = relevantStep.maneuver.type
}
val maneuverIconPair = maneuverIcon(maneuverType)
routeState = routeState.copy(maneuverType = maneuverIconPair.first)
val maneuverIcon = maneuverIcon(maneuverType)
routeState = routeState.copy(maneuverType = maneuverType)
// Construct and return the final StepData object
val intersection = currentIntersection(routeState.location)
return StepData(
streetName,
distanceToNextStep,
maneuverIconPair.first,
maneuverIconPair.second,
maneuverType,
maneuverIcon,
arrivalTime(),
travelLeftDistance(),
intersection.lane
@@ -206,13 +208,13 @@ open class RouteModel() {
}
}
val routing: (Pair<Int, Int>) = maneuverIcon(maneuverType)
val maneuverIcon = maneuverIcon(maneuverType)
// Construct and return the final StepData object
return StepData(
text,
distanceLeft,
routing.first,
routing.second,
maneuverType,
maneuverIcon,
arrivalTime(),
travelLeftDistance()
)
@@ -247,13 +249,15 @@ open class RouteModel() {
/** Returns the current [Step] left distance in m. */
fun leftStepDistance(): Double {
val step = route.currentStep()
var leftDistance = step.distance
val percent =
100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
leftDistance = leftDistance * percent / 100
// The remaining distance to the step, rounded to the nearest 10 units.
return (leftDistance * 1000 / 10.0).roundToInt() * 10.0
var leftDistance = 0.0
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 km. */
@@ -263,17 +267,11 @@ open class RouteModel() {
val step = route.legs!![0].steps[i]
leftDistance += step.distance
}
val step = route.currentStep()
val curDistance = step.distance
val percent =
100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
val distance = curDistance * percent / 100
leftDistance += distance
leftDistance += leftStepDistance() / 1000
return leftDistance
}
fun maneuverIcon(routeManeuverType: Int): (Pair<Int, Int>) {
fun maneuverIcon(routeManeuverType: Int): Int {
var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) {
Maneuver.TYPE_STRAIGHT -> {
@@ -304,9 +302,11 @@ open class RouteModel() {
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
}
@@ -316,7 +316,7 @@ open class RouteModel() {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
}
return Pair(routeManeuverType, currentTurnIcon)
return currentTurnIcon
}
fun isNavigating(): Boolean {
@@ -333,31 +333,30 @@ open class RouteModel() {
fun createLaneIcon(context: Context, stepData: StepData): IconCompat {
val bitmaps = mutableListOf<Bitmap>()
stepData.lane.forEach {
if (it.indications.isNotEmpty()) {
it.indications.forEach { it2 ->
val resource = laneToResource(it2, it, stepData)
if (it2 != "none") {
println("Direction $resource")
if (resource.isNotEmpty()) {
val id = resourceId( resource);
val bitMap = BitmapFactory.decodeResource(context.resources, id)
bitmaps.add(bitMap)
}
}
if (it.indications.isNotEmpty() && it.valid) {
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 IconCompat.createWithBitmap(overlay(bitmaps = bitmaps))
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,
bitmaps.first().getWidth() * (bitmaps.size) ,
bitmaps.first().getHeight(),
bitmaps.first().getConfig()!!
)
@@ -366,7 +365,7 @@ open class RouteModel() {
var i = 0
bitmaps.forEach {
if (i > 0) {
matrix.setTranslate(i * 40F, 0F)
matrix.setTranslate(i * 45F, 0F)
canvas.drawBitmap(it, matrix, null)
}
i++
@@ -374,33 +373,66 @@ open class RouteModel() {
return bmOverlay
}
private fun laneToResource(direction: String, lane: Lane, stepData: StepData): String {
println("Maneuver ${stepData.maneuverType}")
return when (val direction = direction.replace(" ", "_")) {
"left" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_valid" else "${direction}_not_valid"
"right" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_valid" else "${direction}_not_valid"
"straight" -> if (stepData.maneuverType == Maneuver.TYPE_STRAIGHT) "${direction}_valid" else "${direction}_not_valid"
"slight_right" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_valid" else "${direction}_not_valid"
"slight_left" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_valid" else "${direction}_not_valid"
else -> {""}
private fun laneToResource(directions: List<String>, stepData: StepData): String {
var direction = ""
directions.forEach {
direction = if (direction.isEmpty()) {
it.trim()
} else {
"${direction}_${it.trim()}"
}
}
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"
}
}
"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" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_o" else "${direction}_x"
"left_slight" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_o" else "${direction}_x"
else -> {
""
}
}
}
fun resourceId(
variableName: String,
): Int {
return when(variableName) {
"left_not_valid" -> R.drawable.left_not_valid
"left_valid" -> R.drawable.left_valid
"left_valid_right_not_valid" -> R.drawable.left_valid_right_not_valid
"right_not_valid" -> R.drawable.right_not_valid
"right_valid" -> R.drawable.right_valid
"slight_right_not_valid" -> R.drawable.slight_right_not_valid
"slight_right_valid" -> R.drawable.slight_right_valid
"straight_not_valid" -> R.drawable.straight_not_valid
"straight_not_valid_right_valid" -> R.drawable.straight_not_valid_right_valid
"straight_valid" -> R.drawable.straight_valid
else -> {R.drawable.ic_close_white_24dp}
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
"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.ic_close_white_24dp
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 914 B

After

Width:  |  Height:  |  Size: 914 B

View File

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 888 B

After

Width:  |  Height:  |  Size: 888 B

View File

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 895 B

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 888 B

After

Width:  |  Height:  |  Size: 888 B

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 723 B

After

Width:  |  Height:  |  Size: 723 B

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB