This commit is contained in:
Dimitris
2026-01-20 13:21:58 +01:00
parent e22865bd73
commit 7db7cba4fb
21 changed files with 3595 additions and 1218 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId = "com.kouros.navigation" applicationId = "com.kouros.navigation"
minSdk = 33 minSdk = 33
targetSdk = 36 targetSdk = 36
versionCode = 23 versionCode = 28
versionName = "0.1.3.23" versionName = "0.1.3.28"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -43,8 +43,8 @@ import com.kouros.data.R
import com.kouros.navigation.MainApplication.Companion.navigationViewModel import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.home2Location import com.kouros.navigation.data.Constants.homeHohenwaldeck
import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.MockLocation import com.kouros.navigation.model.MockLocation
@@ -62,7 +62,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.joda.time.DateTime import org.joda.time.DateTime
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.Location import org.maplibre.compose.location.Location
@@ -89,10 +88,12 @@ class MainActivity : ComponentActivity() {
val observer = Observer<String> { newRoute -> val observer = Observer<String> { newRoute ->
if (newRoute.isNotEmpty()) { if (newRoute.isNotEmpty()) {
routeModel.startNavigation(newRoute, applicationContext) routeModel.startNavigation(newRoute, applicationContext)
routeData.value = routeModel.route.routeGeoJson routeData.value = routeModel.curRoute.routeGeoJson
simulate() if (useMock) {
//test() simulate()
///gpx(applicationContext) //test()
///gpx(applicationContext)
}
} }
} }
val cameraPosition = MutableLiveData( val cameraPosition = MutableLiveData(
@@ -109,8 +110,6 @@ class MainActivity : ComponentActivity() {
private var loadRecentPlaces = false private var loadRecentPlaces = false
private var overpass = false
lateinit var baseStyle: BaseStyle.Json lateinit var baseStyle: BaseStyle.Json
init { init {
@@ -132,8 +131,8 @@ class MainActivity : ComponentActivity() {
if (useMock) { if (useMock) {
mock = MockLocation(locationManager) mock = MockLocation(locationManager)
mock.setMockLocation( mock.setMockLocation(
home2Location.latitude, homeHohenwaldeck.latitude,
home2Location.longitude homeHohenwaldeck.longitude
) )
} }
} }
@@ -157,7 +156,7 @@ class MainActivity : ComponentActivity() {
Content() Content()
// auto navigate // auto navigate
if (useMock) { if (useMock) {
//navigationViewModel.loadRoute(applicationContext, homeLocation, home2Location, 0F) navigationViewModel.loadRoute(applicationContext, homeHohenwaldeck, homeVogelhart, 0F)
} }
}, },
) )
@@ -255,7 +254,7 @@ class MainActivity : ComponentActivity() {
if (isNavigating()) { if (isNavigating()) {
updateLocation(currentLocation, navigationViewModel) updateLocation(currentLocation, navigationViewModel)
stepData.value = currentStep() stepData.value = currentStep()
if (route.currentStep + 1 <= legs.steps.size) { if (route.currentStep + 1 <= curLeg.steps.size) {
nextStepData.value = nextStep() nextStepData.value = nextStep()
} }
if (maneuverType in 39..42 if (maneuverType in 39..42
@@ -284,8 +283,8 @@ class MainActivity : ComponentActivity() {
} }
fun stopNavigation(closeSheet: () -> Unit) { fun stopNavigation(closeSheet: () -> Unit) {
val latitude = routeModel.route.waypoints!![0][1] val latitude = routeModel.curRoute.waypoints[0][1]
val longitude = routeModel.route.waypoints!![0][0] val longitude = routeModel.curRoute.waypoints[0][0]
closeSheet() closeSheet()
routeModel.stopNavigation() routeModel.stopNavigation()
if (useMock) { if (useMock) {
@@ -325,10 +324,10 @@ class MainActivity : ComponentActivity() {
fun simulate() { fun simulate() {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
for ((index, waypoint) in routeModel.route.waypoints!!.withIndex()) { for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
var deviation = 0.0 var deviation = 0.0
if (index in 0..routeModel.route.waypoints!!.size) { if (index in 0..routeModel.curRoute.waypoints.size) {
mock.setMockLocation(waypoint[1] + deviation, waypoint[0]) mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
delay(500L) // delay(500L) //
} }
@@ -338,7 +337,7 @@ class MainActivity : ComponentActivity() {
} }
fun test() { fun test() {
for ((index, step) in routeModel.legs.steps.withIndex()) { for ((index, step) in routeModel.curLeg.steps.withIndex()) {
if (index in 3..3) { if (index in 3..3) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
routeModel.updateLocation( routeModel.updateLocation(
@@ -349,7 +348,7 @@ class MainActivity : ComponentActivity() {
if (step.leftStepDistance == 70.0) { if (step.leftStepDistance == 70.0) {
println("") println("")
} }
if (index + 1 <= routeModel.legs.steps.size) { if (index + 1 <= routeModel.curLeg.steps.size) {
//nextStepData.value = routeModel.nextStep() //nextStepData.value = routeModel.nextStep()
} }
} }

View File

@@ -115,7 +115,6 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
} }
val carSpeedListener = OnCarDataAvailableListener<Speed> { data -> val carSpeedListener = OnCarDataAvailableListener<Speed> { data ->
if (data.displaySpeedMetersPerSecond.status == CarValue.STATUS_SUCCESS) { if (data.displaySpeedMetersPerSecond.status == CarValue.STATUS_SUCCESS) {
val speed = data.displaySpeedMetersPerSecond.value val speed = data.displaySpeedMetersPerSecond.value
@@ -184,7 +183,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION) val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION)
if (useCarLocation) { if (useCarLocation) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
carSensors.addCompassListener(CarSensors.UPDATE_RATE_FASTEST, carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL,
carContext.mainExecutor, carContext.mainExecutor,
carCompassListener) carCompassListener)
carSensors.addCarHardwareLocationListener( carSensors.addCarHardwareLocationListener(

View File

@@ -30,7 +30,7 @@ import com.kouros.navigation.car.map.getPaddingValues
import com.kouros.navigation.car.map.rememberBaseStyle import com.kouros.navigation.car.map.rememberBaseStyle
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants.ROUTING_ENGINE import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
@@ -55,11 +55,11 @@ class SurfaceRenderer(
var lastLocation = location(0.0, 0.0) var lastLocation = location(0.0, 0.0)
var carOrientation = 0F var carOrientation = 999F
private val cameraPosition = MutableLiveData( private val cameraPosition = MutableLiveData(
CameraPosition( CameraPosition(
zoom = 15.0, zoom = 15.0,
target = Position(latitude = homeLocation.latitude, longitude = homeLocation.longitude) target = Position(latitude = homeVogelhart.latitude, longitude = homeVogelhart.longitude)
) )
) )
private var visibleArea = MutableLiveData( private var visibleArea = MutableLiveData(
@@ -245,11 +245,14 @@ class SurfaceRenderer(
fun updateLocation(location: Location) { fun updateLocation(location: Location) {
synchronized(this) { synchronized(this) {
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) { if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
val bearing = bearing( val bearing = if (carOrientation == 999F)
bearing(
lastLocation, lastLocation,
location, location,
cameraPosition.value!!.bearing cameraPosition.value!!.bearing
) ) else {
carOrientation.toDouble()
}
val zoom = if (viewStyle == ViewStyle.VIEW) { val zoom = if (viewStyle == ViewStyle.VIEW) {
calculateZoom(location.speed.toDouble()) calculateZoom(location.speed.toDouble())
} else { } else {
@@ -281,16 +284,16 @@ class SurfaceRenderer(
} }
fun setRouteData() { fun setRouteData() {
routeData.value = routeModel.route.routeGeoJson routeData.value = routeModel.curRoute.routeGeoJson
viewStyle = ViewStyle.VIEW viewStyle = ViewStyle.VIEW
} }
fun setPreviewRouteData(routeModel: RouteModel) { fun setPreviewRouteData(routeModel: RouteModel) {
viewStyle = ViewStyle.PREVIEW viewStyle = ViewStyle.PREVIEW
with(routeModel) { with(routeModel) {
routeData.value = route.routeGeoJson routeData.value = curRoute.routeGeoJson
centerLocation = route.centerLocation centerLocation = curRoute.centerLocation
previewDistance = route.summary!!.distance previewDistance = curRoute.summary.distance
} }
updateCameraPosition( updateCameraPosition(
0.0, 0.0,

View File

@@ -158,21 +158,31 @@ fun RouteLayer(routeData: String?) {
@Composable @Composable
fun AmenityLayer(routeData: String?) { fun AmenityLayer(routeData: String?) {
if (routeData != null && routeData.isNotEmpty()) { if (routeData != null && routeData.isNotEmpty()) {
val color = if (routeData.contains(Constants.PHARMACY)) { var color = const(Color.Red)
const(Color.Red) var img = image(painterResource(R.drawable.local_pharmacy_48px), drawAsSdf = true)
} else if (routeData.contains(Constants.CHARGING_STATION)) { if (routeData.contains(Constants.CHARGING_STATION)) {
const(Color.Blue) color = const(Color.Green)
} else { img = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true)
const(Color.Black) } else if (routeData.contains(Constants.FUEL_STATION)){
color = const(Color.Black)
img = image(painterResource(R.drawable.local_gas_station_48px), drawAsSdf = true)
} }
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData)) val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
SymbolLayer( SymbolLayer(
id = "amenity-layer", id = "amenity-layer",
source = routes, source = routes,
iconImage = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true), iconImage = img,
iconColor = color, iconColor = color,
iconOpacity = const(2.0f), iconOpacity = const(2.0f),
iconSize = const(3.0f), iconSize =
interpolate(
type = exponential(1.2f),
input = zoom(),
5 to const(0.7f),
6 to const(1.0f),
7 to const(2.0f),
20 to const(4f),
),
) )
} }
} }

View File

@@ -450,7 +450,7 @@ class NavigationScreen(
invalidate() invalidate()
val mainThreadHandler = Handler(carContext.mainLooper) val mainThreadHandler = Handler(carContext.mainLooper)
mainThreadHandler.post { mainThreadHandler.post {
object : CountDownTimer(3000, 1000) { object : CountDownTimer(2000, 1000) {
override fun onTick(millisUntilFinished: Long) {} override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() { override fun onFinish() {
navigationType = NavigationType.NAVIGATION navigationType = NavigationType.NAVIGATION

View File

@@ -6,11 +6,10 @@ import androidx.annotation.DrawableRes
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.CarToast import androidx.car.app.CarToast
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.Action import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_DEFAULT import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.Action.FLAG_PRIMARY
import androidx.car.app.model.ActionStrip import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText import androidx.car.app.model.CarText
import androidx.car.app.model.DurationSpan import androidx.car.app.model.DurationSpan
@@ -18,19 +17,17 @@ import androidx.car.app.model.Header
import androidx.car.app.model.ItemList import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate import androidx.car.app.model.ListTemplate
import androidx.car.app.model.MessageTemplate import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.OnClickListener
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.navigation.model.NavigationTemplate
import androidx.car.app.navigation.model.RoutingInfo
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
@@ -79,9 +76,14 @@ class RoutePreviewScreen(
.setFlags(FLAG_DEFAULT) .setFlags(FLAG_DEFAULT)
.setIcon(navigateActionIcon) .setIcon(navigateActionIcon)
.setOnClickListener { this.onNavigate() } .setOnClickListener { this.onNavigate() }
.build() .build()
val itemListBuilder = ItemList.Builder()
var i = 0
routeModel.route.routes.forEach { it ->
itemListBuilder.addItem(createRow(i++, navigateAction))
}
val header = Header.Builder() val header = Header.Builder()
.setStartHeaderAction(Action.BACK) .setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.route_preview)) .setTitle(carContext.getString(R.string.route_preview))
@@ -93,30 +95,40 @@ class RoutePreviewScreen(
) )
.build() .build()
val message = if (routeModel.isNavigating() && routeModel.route.waypoints!!.isNotEmpty()) { val message =
createRouteText() if (routeModel.isNavigating() && routeModel.curRoute.waypoints!!.isNotEmpty()) {
} else { createRouteText(0)
CarText.Builder("Wait") } else {
.build() CarText.Builder("Wait")
} .build()
val messageTemplate = MessageTemplate.Builder(
message
)
.setHeader(header)
.addAction(navigateAction)
.setLoading(message.toString() == "Wait")
.build()
val timer = object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
//onNavigate()
} }
if (routeModel.route.routes.size == 1) {
val timer = object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
onNavigate()
}
}
timer.start()
} }
timer.start()
val content = if (routeModel.route.routes.size > 1) {
ListTemplate.Builder()
.setHeader(header)
.setSingleList(itemListBuilder.build())
.build()
} else {
MessageTemplate.Builder(
message
)
.setHeader(header)
.addAction(navigateAction)
.setLoading(message.toString() == "Wait")
.build()
}
return MapWithContentTemplate.Builder() return MapWithContentTemplate.Builder()
.setContentTemplate(messageTemplate) .setContentTemplate(content)
.setMapController( .setMapController(
MapController.Builder().setMapActionStrip( MapController.Builder().setMapActionStrip(
getMapActionStrip() getMapActionStrip()
@@ -176,10 +188,14 @@ class RoutePreviewScreen(
) )
.build() .build()
private fun createRouteText(): CarText { private fun createRouteText(index: Int): CarText {
val time = routeModel.route.summary!!.duration val time = routeModel.route.routes[index].summary.duration
println("Duration $time")
val length = val length =
BigDecimal(routeModel.route.summary!!.distance).setScale(1, RoundingMode.HALF_EVEN) BigDecimal(routeModel.route.routes[index].summary.distance).setScale(
1,
RoundingMode.HALF_EVEN
)
val firstRoute = SpannableString(" \u00b7 $length km") val firstRoute = SpannableString(" \u00b7 $length km")
firstRoute.setSpan( firstRoute.setSpan(
DurationSpan.create(time.toLong()), 0, 1, 0 DurationSpan.create(time.toLong()), 0, 1, 0
@@ -188,14 +204,27 @@ class RoutePreviewScreen(
.build() .build()
} }
private fun createRow(index: Int, action: Action): Row {
val route = createRouteText(index)
val titleText = "$index"
return Row.Builder()
.setTitle(route)
.setOnClickListener(OnClickListener { onRouteSelected(index) })
.addText(titleText)
.addAction(action)
.build()
}
private fun onNavigate() { private fun onNavigate() {
setResult(destination) setResult(destination)
finish() finish()
} }
private fun onRouteSelected(index: Int) { private fun onRouteSelected(index: Int) {
setResult(destination) routeModel.currentRouteIndex = index
finish() surfaceRenderer.setPreviewRouteData(routeModel)
//setResult(destination)
//finish()
} }
fun getMapActionStrip(): ActionStrip { fun getMapActionStrip(): ActionStrip {

View File

@@ -122,17 +122,8 @@ object Constants {
val categories = listOf("Tankstelle", "Apotheke", "Ladestationen") val categories = listOf("Tankstelle", "Apotheke", "Ladestationen")
/** The initial location to use as an anchor for searches. */ /** The initial location to use as an anchor for searches. */
val homeLocation: Location = Location(LocationManager.GPS_PROVIDER) val homeVogelhart = location(11.5793748, 48.185749)
val home2Location: Location = Location(LocationManager.GPS_PROVIDER) val homeHohenwaldeck = location( 11.594322, 48.1164817)
init {
// Vogelhartstr. 17
homeLocation.latitude = 48.185749
homeLocation.longitude = 11.5793748
// Hohenwaldeckstr. 27
home2Location.latitude = 48.1164817
home2Location.longitude = 11.594322
}
const val SHARED_PREF_KEY = "NavigationPrefs" const val SHARED_PREF_KEY = "NavigationPrefs"
@@ -151,7 +142,7 @@ object Constants {
const val MAXIMAL_SNAP_CORRECTION = 50.0 const val MAXIMAL_SNAP_CORRECTION = 50.0
const val MAXIMAL_ROUTE_DEVIATION = 100.0 const val MAXIMAL_ROUTE_DEVIATION = 80.0
const val DESTINATION_ARRIVAL_DISTANCE = 40.0 const val DESTINATION_ARRIVAL_DISTANCE = 40.0

View File

@@ -43,7 +43,7 @@ abstract class NavigationRepository {
val route = getRoute(currentLocation, location, carOrientation, searchFilter) val route = getRoute(currentLocation, location, carOrientation, searchFilter)
val routeModel = RouteModel() val routeModel = RouteModel()
routeModel.startNavigation(route, context) routeModel.startNavigation(route, context)
return routeModel.route.summary!!.distance return routeModel.curRoute.summary.distance
} }
fun searchPlaces(search: String, location: Location) : String { fun searchPlaces(search: String, location: Location) : String {

View File

@@ -21,33 +21,23 @@ import org.maplibre.geojson.Point
data class Route( data class Route(
val routeEngine: Int, val routeEngine: Int,
val summary: Summary?, val routes: List<com.kouros.navigation.data.route.Routes>,
val legs: List<Leg>?,
val routeGeoJson: String = "",
val centerLocation: Location = location(0.0, 0.0),
var currentStep: Int = 0, var currentStep: Int = 0,
val waypoints: List<List<Double>>?,
) { ) {
data class Builder( data class Builder(
var routeEngine: Int = 0, var routeEngine: Int = 0,
var summary: Summary? = null, var summary: Summary = Summary(),
var legs: List<Leg>? = null, var routes: List<com.kouros.navigation.data.route.Routes> = emptyList(),
var routeGeoJson: String = "",
var centerLocation: Location = location(0.0, 0.0),
var waypoints: List<List<Double>>? = null,
) { ) {
fun routeType(routeEngine: Int) = apply { this.routeEngine = routeEngine } fun routeType(routeEngine: Int) = apply { this.routeEngine = routeEngine }
fun summary(summary: Summary) = apply { this.summary = summary } fun routes(routes: List<com.kouros.navigation.data.route.Routes>) = apply {
fun legs(legs: List<Leg>) = apply { this.legs = legs } this.routes = routes
fun routeGeoJson(routeGeoJson: String) = apply {
this.routeGeoJson = routeGeoJson
centerLocation = createCenterLocation(routeGeoJson)
} }
fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine } fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine }
fun waypoints(waypoints: List<List<Double>>) = apply { this.waypoints = waypoints }
fun route(route: String) = apply { fun route(route: String) = apply {
if (route.isNotEmpty() && route != "[]") { if (route.isNotEmpty() && route != "[]") {
val gson = GsonBuilder().serializeNulls().create() val gson = GsonBuilder().serializeNulls().create()
@@ -74,26 +64,30 @@ data class Route(
fun build(): Route { fun build(): Route {
return Route( return Route(
routeEngine = this.routeEngine, routeEngine = this.routeEngine,
summary = this.summary, routes = this.routes,
legs = this.legs,
waypoints = this.waypoints,
routeGeoJson = this.routeGeoJson,
) )
} }
fun buildEmpty(): Route { fun buildEmpty(): Route {
return Route( return Route(
routeEngine = 0, routeEngine = 0,
summary = Summary(0.0, 0.0), //summary = Summary(0.0, 0.0),
legs = emptyList(), routes = emptyList(),
waypoints = emptyList(), // legs = emptyList(),
routeGeoJson = "", //waypoints = emptyList(),
//routeGeoJson = "",
) )
} }
} }
val legs: List<Leg>
get() = routes.first().legs
fun currentStep(): Step { fun currentStep(): Step {
return if (legs != null && legs.isNotEmpty()) {
return if (legs.isNotEmpty()) {
legs.first().steps[currentStep] legs.first().steps[currentStep]
} else { } else {
Step(maneuver = Maneuver(waypoints = emptyList(), location = location(0.0, 0.0))) Step(maneuver = Maneuver(waypoints = emptyList(), location = location(0.0, 0.0)))
@@ -102,7 +96,7 @@ data class Route(
fun nextStep(): Step { fun nextStep(): Step {
val nextIndex = currentStep + 1 val nextIndex = currentStep + 1
return if (nextIndex < legs!!.first().steps.size) { return if (nextIndex < legs.first().steps.size) {
legs.first().steps[nextIndex] legs.first().steps[nextIndex]
} else { } else {
throw IndexOutOfBoundsException("No next maneuver available.") throw IndexOutOfBoundsException("No next maneuver available.")

View File

@@ -21,7 +21,7 @@ class OsrmRepository : NavigationRepository() {
if (searchFilter.avoidTollway) { if (searchFilter.avoidTollway) {
exclude = "$exclude&exclude=toll" exclude = "$exclude&exclude=toll"
} }
val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=2" val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=0"
return fetchUrl(routeUrl + routeLocation + exclude, true) return fetchUrl(routeUrl + routeLocation + exclude, true)
} }
} }

View File

@@ -7,6 +7,7 @@ import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.Maneuver as RouteManeuver import com.kouros.navigation.data.route.Maneuver as RouteManeuver
import com.kouros.navigation.data.route.Step import com.kouros.navigation.data.route.Step
import com.kouros.navigation.data.route.Summary import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
import com.kouros.navigation.utils.GeoUtils.decodePolyline import com.kouros.navigation.utils.GeoUtils.decodePolyline
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
@@ -14,61 +15,75 @@ import com.kouros.navigation.utils.location
class OsrmRoute { class OsrmRoute {
fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) { fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) {
val waypoints = mutableListOf<List<Double>>()
val summary = Summary()
summary.distance = routeJson.routes.first().distance!! / 1000
summary.duration = routeJson.routes.first().duration!! / 1000
val steps = mutableListOf<Step>()
var stepIndex = 0
routeJson.routes.first().legs.first().steps.forEach {
val intersections = mutableListOf<Intersection>()
if (it.maneuver != null) {
val points = decodePolyline(it.geometry!!, 5)
waypoints.addAll(points)
val maneuver = RouteManeuver(
bearingBefore = it.maneuver.bearingBefore ?: 0,
bearingAfter = it.maneuver.bearingAfter ?: 0,
type = convertType(it.maneuver),
waypoints = points,
location = location(it.maneuver.location[0], it.maneuver.location[1])
)
it.intersections.forEach { it2 ->
if (it2.location[0] != 0.0) { val routes = mutableListOf<com.kouros.navigation.data.route.Routes>()
val lanes = mutableListOf<Lane>() var stepIndex = 0
it2.lanes.forEach { it3 -> routeJson.routes.forEach { route ->
if (it3.indications.isNotEmpty() && it3.indications.first() != "none") { val legs = mutableListOf<Leg>()
val lane = Lane( val waypoints = mutableListOf<List<Double>>()
location(it2.location[0], it2.location[1]), val summary = Summary(route.duration!!, route.distance!! / 1000)
it3.valid, route.legs.forEach { leg ->
it3.indications val steps = mutableListOf<Step>()
) leg.steps.forEach { step ->
lanes.add(lane) val intersections = mutableListOf<Intersection>()
if (step.maneuver != null) {
val points = decodePolyline(step.geometry!!, 5)
waypoints.addAll(points)
val maneuver = RouteManeuver(
bearingBefore = step.maneuver.bearingBefore ?: 0,
bearingAfter = step.maneuver.bearingAfter ?: 0,
type = convertType(step.maneuver),
waypoints = points,
location = location(
step.maneuver.location[0],
step.maneuver.location[1]
)
)
step.intersections.forEach { it2 ->
if (it2.location[0] != 0.0) {
val lanes = mutableListOf<Lane>()
it2.lanes.forEach { it3 ->
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))
} }
} }
intersections.add(Intersection(it2.location, lanes)) val step = Step(
index = stepIndex,
name = step.name!!,
distance = step.distance!! / 1000,
duration = step.duration!!,
maneuver = maneuver,
intersection = intersections
)
steps.add(step)
stepIndex += 1
} }
} }
val step = Step( legs.add(Leg(steps))
index = stepIndex,
name = it.name!!,
distance = it.distance!! / 1000,
duration = it.duration!!,
maneuver = maneuver,
intersection = intersections
)
steps.add(step)
stepIndex += 1
} }
val routeGeoJson = createLineStringCollection(waypoints)
val centerLocation = createCenterLocation(createLineStringCollection(waypoints))
val newRoute = com.kouros.navigation.data.route.Routes(
legs,
summary,
routeGeoJson,
centerLocation = centerLocation,
waypoints = waypoints
)
routes.add(newRoute)
} }
val leg = Leg(steps)
builder builder
.routeType(1) .routeType(1)
.summary(summary) .routes(routes)
.routeGeoJson(createLineStringCollection(waypoints))
.legs(listOf(leg))
.waypoints(waypoints.toList())
} }
fun convertType(maneuver: Maneuver): Int { fun convertType(maneuver: Maneuver): Int {
@@ -93,12 +108,24 @@ class OsrmRoute {
ManeuverType.continue_.value -> { ManeuverType.continue_.value -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
if (maneuver.modifier == "right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
}
if (maneuver.modifier == "left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT
}
} }
ManeuverType.newName.value -> { ManeuverType.newName.value -> {
if (maneuver.modifier == "straight") { if (maneuver.modifier == "straight") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
} }
if (maneuver.modifier == "slight right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
}
if (maneuver.modifier == "slight left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
}
} }
ManeuverType.turn.value, ManeuverType.turn.value,
@@ -110,7 +137,7 @@ class OsrmRoute {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT
} }
if (maneuver.modifier == "straight") { if (maneuver.modifier == "straight") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
} }
} }
@@ -120,6 +147,12 @@ class OsrmRoute {
if (maneuver.modifier == "left") { if (maneuver.modifier == "left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT
} }
if (maneuver.modifier == "slight right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
}
if (maneuver.modifier == "slight left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
}
} }
ManeuverType.offRamp.value ManeuverType.offRamp.value
@@ -127,6 +160,15 @@ class OsrmRoute {
if (maneuver.modifier == "slight right") { if (maneuver.modifier == "slight right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
} }
if (maneuver.modifier == "right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
}
if (maneuver.modifier == "slight left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
}
if (maneuver.modifier == "left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT
}
} }
ManeuverType.fork.value ManeuverType.fork.value
@@ -134,10 +176,6 @@ class OsrmRoute {
if (maneuver.modifier == "slight left") { if (maneuver.modifier == "slight left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
} }
}
ManeuverType.fork.value
-> {
if (maneuver.modifier == "slight right") { if (maneuver.modifier == "slight right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
} }
@@ -148,6 +186,21 @@ class OsrmRoute {
if (maneuver.modifier == "slight left") { if (maneuver.modifier == "slight left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
} }
if (maneuver.modifier == "slight right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
}
}
ManeuverType.roundAbout.value
-> {
if (maneuver.modifier == "right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
}
}
ManeuverType.exitRoundabout.value
-> {
if (maneuver.modifier == "right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_EXIT_CCW
}
} }
} }
return newType return newType

View File

@@ -1,5 +1,12 @@
package com.kouros.navigation.data.route package com.kouros.navigation.data.route
class Routes ( import android.location.Location
var legs : List<Leg> = arrayListOf() import com.kouros.navigation.utils.location
class Routes(
val legs: List<Leg> = arrayListOf(),
val summary: Summary,
val routeGeoJson: String,
val centerLocation: Location = location(0.0, 0.0),
val waypoints: List<List<Double>>,
) )

View File

@@ -1,6 +1,8 @@
package com.kouros.navigation.data.route package com.kouros.navigation.data.route
data class Summary( data class Summary(
// sec
var duration : Double = 0.0, var duration : Double = 0.0,
// km
var distance : Double = 0.0, var distance : Double = 0.0,
) )

View File

@@ -15,9 +15,7 @@ class ValhallaRoute {
fun mapJsonToValhalla(routeJson: ValhallaResponse, builder: Route.Builder) { fun mapJsonToValhalla(routeJson: ValhallaResponse, builder: Route.Builder) {
val waypoints = decodePolyline(routeJson.legs[0].shape) val waypoints = decodePolyline(routeJson.legs[0].shape)
val summary = Summary() val summary = Summary(routeJson.summaryValhalla.time, routeJson.summaryValhalla.length)
summary.distance = routeJson.summaryValhalla.length
summary.duration = routeJson.summaryValhalla.time
val steps = mutableListOf<Step>() val steps = mutableListOf<Step>()
var stepIndex = 0 var stepIndex = 0
routeJson.legs[0].maneuvers.forEach { routeJson.legs[0].maneuvers.forEach {
@@ -39,13 +37,13 @@ class ValhallaRoute {
steps.add(step) steps.add(step)
stepIndex += 1 stepIndex += 1
} }
val leg = Leg(steps)
builder builder
.routeType(1) .routeType(1)
.summary(summary) // TODO
.routeGeoJson(createLineStringCollection(waypoints)) .routes(emptyList())
.legs(listOf(leg)) //.summary(summary)
.waypoints(waypoints) //.routeGeoJson(createLineStringCollection(waypoints))
//.waypoints(waypoints)
} }
fun convertType(maneuver: Maneuvers): Int { fun convertType(maneuver: Maneuvers): Int {

View File

@@ -19,8 +19,8 @@ import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.route.Intersection import com.kouros.navigation.data.route.Intersection
import com.kouros.navigation.data.route.Lane import com.kouros.navigation.data.route.Lane
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.Routes
import com.kouros.navigation.data.valhalla.ManeuverType import com.kouros.navigation.data.valhalla.ManeuverType
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -48,9 +48,12 @@ open class RouteModel() {
var lastLocation: Location = location(0.0, 0.0) var lastLocation: Location = location(0.0, 0.0)
var bearing: Float = 0F var bearing: Float = 0F
var currentRouteIndex = 0
val curRoute: Routes
get() = route.routes[currentRouteIndex]
val legs: Leg val curLeg: Leg
get() = route.legs!!.first() get() = route.routes[currentRouteIndex].legs.first()
fun startNavigation(routeString: String, context: Context) { fun startNavigation(routeString: String, context: Context) {
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE) val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
@@ -58,8 +61,6 @@ open class RouteModel() {
.routeEngine(routeEngine) .routeEngine(routeEngine)
.route(routeString) .route(routeString)
.build() .build()
// TODO:
route = route.copy(centerLocation = createCenterLocation(route.routeGeoJson))
navigating = true navigating = true
} }
@@ -67,7 +68,7 @@ open class RouteModel() {
route = Route.Builder().buildEmpty() route = Route.Builder().buildEmpty()
navigating = false navigating = false
arrived = false arrived = false
maneuverType = 0 maneuverType = Maneuver.TYPE_UNKNOWN
} }
@@ -80,7 +81,7 @@ open class RouteModel() {
private fun findStep(location: Location) { private fun findStep(location: Location) {
var nearestDistance = 100000.0f var nearestDistance = 100000.0f
for ((index, step) in legs.steps.withIndex()) { for ((index, step) in curLeg.steps.withIndex()) {
if (index >= route.currentStep) { if (index >= route.currentStep) {
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
if (wayIndex >= step.waypointIndex) { if (wayIndex >= step.waypointIndex) {
@@ -149,7 +150,7 @@ open class RouteModel() {
val distanceToNextStep = leftStepDistance() val distanceToNextStep = leftStepDistance()
val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD
val shouldAdvance = val shouldAdvance =
isNearNextManeuver && route.currentStep < (route.legs!!.first().steps.size) isNearNextManeuver && route.currentStep < (route.legs.first().steps.size)
// Determine the maneuver type and corresponding icon // Determine the maneuver type and corresponding icon
var curManeuverType = if (hasArrived(currentStep.maneuver.type)) { var curManeuverType = if (hasArrived(currentStep.maneuver.type)) {
@@ -221,8 +222,8 @@ open class RouteModel() {
fun travelLeftTime(): Double { fun travelLeftTime(): Double {
var timeLeft = 0.0 var timeLeft = 0.0
// time for next step until end step // time for next step until end step
for (i in route.currentStep + 1..<legs.steps.size) { for (i in route.currentStep + 1..<curLeg.steps.size) {
val step = legs.steps[i] val step = curLeg.steps[i]
timeLeft += step.duration timeLeft += step.duration
} }
// time for current step // time for current step
@@ -261,7 +262,7 @@ open class RouteModel() {
/** Returns the left distance in km. */ /** Returns the left distance in km. */
fun travelLeftDistance(): Double { fun travelLeftDistance(): Double {
var leftDistance = 0.0 var leftDistance = 0.0
for (i in route.currentStep + 1..<legs.steps.size) { for (i in route.currentStep + 1..<curLeg.steps.size) {
val step = route.legs!![0].steps[i] val step = route.legs!![0].steps[i]
leftDistance += step.distance leftDistance += step.distance
} }

View File

@@ -302,6 +302,11 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
fun saveRecent(place: Place) { fun saveRecent(place: Place) {
if (place.category == Constants.FUEL_STATION
|| place.category == Constants.CHARGING_STATION
|| place.category == Constants.PHARMACY) {
return
}
place.category = Constants.RECENT place.category = Constants.RECENT
savePlace(place) savePlace(place)
} }

View File

@@ -92,16 +92,16 @@ fun calculateZoom(speed: Double?): Double {
in 61..70 -> 16.5 in 61..70 -> 16.5
else -> 16.0 else -> 16.0
} }
return zoom.toDouble() return zoom
} }
fun previewZoom(previewDistance: Double): Double { fun previewZoom(previewDistance: Double): Double {
when (previewDistance) { when (previewDistance) {
in 0.0..10.0 -> return 13.0 in 0.0..10.0 -> return 13.5
in 10.0..20.0 -> return 11.0 in 10.0..20.0 -> return 11.5
in 20.0..30.0 -> return 10.0 in 20.0..30.0 -> return 10.5
} }
return 9.0 return 9.5
} }
fun calculateTilt(newZoom: Double, tilt: Double): Double = fun calculateTilt(newZoom: Double, tilt: Double): Double =

View File

@@ -47,6 +47,6 @@
<string name="pharmacy">Apotheke</string> <string name="pharmacy">Apotheke</string>
<string name="charging_station">Ladestation</string> <string name="charging_station">Ladestation</string>
<string name="speed_camera">Speed camera</string> <string name="speed_camera">Speed camera</string>
<string name="use_car_location">Auto Location verwenden</string> <string name="use_car_location">Auto GPS verwenden</string>
</resources> </resources>

View File

@@ -1,7 +1,7 @@
[versions] [versions]
agp = "8.13.2" agp = "9.0.0"
androidSdkTurf = "6.0.1" androidSdkTurf = "6.0.1"
gradle = "8.13.2" gradle = "9.0.0"
koinAndroid = "4.1.1" koinAndroid = "4.1.1"
koinAndroidxCompose = "4.1.1" koinAndroidxCompose = "4.1.1"
koinComposeViewmodel = "4.1.1" koinComposeViewmodel = "4.1.1"