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

@@ -47,7 +47,6 @@ import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom
@@ -62,7 +61,6 @@ import org.maplibre.compose.location.Location
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.style.BaseStyle
import org.maplibre.geojson.Point
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
@@ -222,6 +220,7 @@ class MainActivity : ComponentActivity() {
SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() }
} else {
NavigationSheet(
applicationContext,
routeModel, step!!, nextStep!!,
{ stopNavigation { closeSheet() } },
{ simulateNavigation() }
@@ -274,7 +273,7 @@ class MainActivity : ComponentActivity() {
closeSheet()
routeModel.stopNavigation()
routeData.value = ""
stepData.value = StepData("", 0.0, 0, 0, 0, 0.0)
stepData.value = StepData("", 0.0, 0, 0,0, 0.0)
}
fun simulateNavigation() {
@@ -310,8 +309,11 @@ class MainActivity : ComponentActivity() {
if (routeModel.isNavigating()) {
for ((index, waypoint) in routeModel.route.waypoints!!.withIndex()) {
var deviation = 0.0
//if (index in 0..350 ) {
mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
delay(500L) //
// }
}
}
}

View File

@@ -1,20 +1,20 @@
import android.content.Context
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.kouros.data.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.formatDateTime
@@ -23,6 +23,7 @@ import com.kouros.navigation.utils.round
@Composable
fun NavigationSheet(
applicationContext: Context,
routeModel: RouteModel,
step: StepData,
nextStep: StepData,
@@ -30,6 +31,14 @@ fun NavigationSheet(
simulateNavigation: () -> Unit,
) {
val distance = step.leftDistance.round(1)
step.lane.forEach {
if (it.indications.isNotEmpty()) {
routeModel.createLaneIcon(applicationContext, step)
}
}
Column {
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
Text(formatDateTime(step.arrivalTime), fontSize = 22.sp)

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,9 +35,15 @@ class OsrmRoute {
if (it2.location[0] != 0.0) {
val lanes = mutableListOf<Lane>()
it2.lanes.forEach { it3 ->
val lane = Lane(it3.valid, it3.indications)
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,23 +333,22 @@ 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 (it.indications.isNotEmpty() && it.valid) {
Collections.sort<String>(it.indications)
val resource = laneToResource(it.indications, stepData)
if (resource.isNotEmpty()) {
val id = resourceId( resource);
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))
}
}
return IconCompat.createWithBitmap(overlay(bitmaps = bitmaps))
}
fun overlay(bitmaps: List<Bitmap>): Bitmap {
val matrix = Matrix()
@@ -357,7 +356,7 @@ open class RouteModel() {
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