CarInfo and CarSensors Osrm

This commit is contained in:
Dimitris
2026-01-03 14:04:50 +01:00
parent 1eab4f1aa3
commit fdf2ee9f48
37 changed files with 416 additions and 208 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 = 15 versionCode = 18
versionName = "0.1.3.15" versionName = "0.1.3.18"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -2,26 +2,14 @@ package com.kouros.navigation
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.di.appModule import com.kouros.navigation.di.appModule
import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.core.logger.Level import org.koin.core.logger.Level
import org.maplibre.compose.expressions.dsl.switch
class MainApplication : Application() { class MainApplication : Application() {
@@ -29,7 +17,6 @@ class MainApplication : Application() {
super.onCreate() super.onCreate()
ObjectBox.init(this); ObjectBox.init(this);
appContext = applicationContext appContext = applicationContext
setIntKeyValue(appContext!!, RouteEngine.VALHALLA.ordinal, ROUTE_ENGINE)
navigationViewModel = getRouteEngine(appContext!!) navigationViewModel = getRouteEngine(appContext!!)
startKoin { startKoin {
androidLogger(Level.DEBUG) androidLogger(Level.DEBUG)

View File

@@ -47,6 +47,7 @@ import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.MockLocation import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.ui.theme.NavigationTheme 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.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
@@ -61,6 +62,7 @@ import org.maplibre.compose.location.Location
import org.maplibre.compose.location.rememberDefaultLocationProvider import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.geojson.Point
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -102,7 +104,7 @@ class MainActivity : ComponentActivity() {
private var overpass = false private var overpass = false
lateinit var baseStyle : BaseStyle.Json lateinit var baseStyle: BaseStyle.Json
init { init {
navigationViewModel.route.observe(this, observer) navigationViewModel.route.observe(this, observer)
@@ -112,14 +114,14 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val darkModeSettings = getIntKeyValue(applicationContext, Constants.DARK_MODE_SETTINGS) val darkModeSettings = getIntKeyValue(applicationContext, Constants.DARK_MODE_SETTINGS)
baseStyle = BaseStyleModel().readStyle(applicationContext, darkModeSettings, false) baseStyle = BaseStyleModel().readStyle(applicationContext, darkModeSettings, false)
if (useMock) { if (useMock) {
checkMockLocationEnabled() checkMockLocationEnabled()
} }
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.lastLocation fusedLocationClient.lastLocation
.addOnSuccessListener { location : android.location.Location? -> .addOnSuccessListener { location: android.location.Location? ->
if (useMock) { if (useMock) {
mock = MockLocation(locationManager) mock = MockLocation(locationManager)
mock.setMockLocation( mock.setMockLocation(
@@ -235,6 +237,7 @@ class MainActivity : ComponentActivity() {
&& lastLocation.longitude != location.position.longitude && lastLocation.longitude != location.position.longitude
) { ) {
val currentLocation = location(location.position.longitude, location.position.latitude) val currentLocation = location(location.position.longitude, location.position.latitude)
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
with(routeModel) { with(routeModel) {
if (isNavigating()) { if (isNavigating()) {
updateLocation(currentLocation, navigationViewModel) updateLocation(currentLocation, navigationViewModel)
@@ -245,13 +248,12 @@ class MainActivity : ComponentActivity() {
if (routeState.maneuverType == 39 if (routeState.maneuverType == 39
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) { ) {
// stopNavigation() // stopNavigation()
routeState = routeState.copy(arrived = true) routeState = routeState.copy(arrived = true)
routeData.value = "" routeData.value = ""
} }
} }
} }
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
val zoom = calculateZoom(location.speed) val zoom = calculateZoom(location.speed)
cameraPosition.postValue( cameraPosition.postValue(
cameraPosition.value!!.copy( cameraPosition.value!!.copy(
@@ -303,14 +305,13 @@ class MainActivity : ComponentActivity() {
} }
} }
fun simulate() { fun simulate() {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
for ((index, step) in routeModel.legs.steps.withIndex()) { if (routeModel.isNavigating()) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((index, waypoint) in routeModel.route.waypoints!!.withIndex()) {
if (routeModel.isNavigating()) { var deviation = 0.0
mock.setMockLocation(waypoint[1], waypoint[0]) mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
delay(800L) // delay(500L) //
}
} }
} }
} }

View File

@@ -19,7 +19,6 @@ import androidx.car.app.hardware.common.OnCarDataAvailableListener
import androidx.car.app.hardware.info.CarHardwareLocation import androidx.car.app.hardware.info.CarHardwareLocation
import androidx.car.app.hardware.info.CarSensors import androidx.car.app.hardware.info.CarSensors
import androidx.car.app.hardware.info.Speed import androidx.car.app.hardware.info.Speed
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationListenerCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
@@ -33,10 +32,13 @@ import com.kouros.navigation.car.screen.SearchScreen
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Constants.TAG import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
@@ -52,7 +54,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var surfaceRenderer: SurfaceRenderer lateinit var surfaceRenderer: SurfaceRenderer
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? -> var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
updateLocation(location!!) val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
if (routingEngine == RouteEngine.VALHALLA.ordinal) {
updateLocation(location!!)
}
} }
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver { private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
@@ -75,8 +80,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
override fun onDestroy(owner: LifecycleOwner) { override fun onDestroy(owner: LifecycleOwner) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
carSensors.removeCarHardwareLocationListener(locationListener) carSensors.removeCarHardwareLocationListener(carLocationListener)
carInfo.removeSpeedListener(speedListener) carInfo.removeSpeedListener(carSpeedListener)
Log.i(TAG, "In onDestroy()") Log.i(TAG, "In onDestroy()")
val locationManager = val locationManager =
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
@@ -88,7 +93,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var baseStyle: BaseStyle.Json lateinit var baseStyle: BaseStyle.Json
val locationListener: OnCarDataAvailableListener<CarHardwareLocation?> = val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
OnCarDataAvailableListener { data -> OnCarDataAvailableListener { data ->
if (data.location.status == CarValue.STATUS_SUCCESS) { if (data.location.status == CarValue.STATUS_SUCCESS) {
val location = data.location.value val location = data.location.value
@@ -96,7 +101,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
} }
val speedListener = 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
surfaceRenderer.updateCarSpeed(speed!!) surfaceRenderer.updateCarSpeed(speed!!)
@@ -106,9 +111,19 @@ class NavigationSession : Session(), NavigationScreen.Listener {
val lifecycle: Lifecycle = lifecycle val lifecycle: Lifecycle = lifecycle
lifecycle.addObserver(mLifeCycleObserver) lifecycle.addObserver(mLifeCycleObserver)
} }
fun onRoutingEngineStateUpdated(routeEngine : Int) {
navigationViewModel = when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
else -> ViewModel(OsrmRepository())
}
}
override fun onCreateScreen(intent: Intent): Screen { override fun onCreateScreen(intent: Intent): Screen {
navigationViewModel = getRouteEngine(carContext) navigationViewModel = getRouteEngine(carContext)
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
routeModel = RouteCarModel() routeModel = RouteCarModel()
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS) val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
@@ -155,9 +170,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
carSensors.addCarHardwareLocationListener( carSensors.addCarHardwareLocationListener(
CarSensors.UPDATE_RATE_NORMAL, CarSensors.UPDATE_RATE_NORMAL,
carContext.mainExecutor, carContext.mainExecutor,
locationListener carLocationListener
) )
carInfo.addSpeedListener(carContext.mainExecutor, speedListener) carInfo.addSpeedListener(carContext.mainExecutor, carSpeedListener)
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@@ -221,15 +236,15 @@ class NavigationSession : Session(), NavigationScreen.Listener {
fun updateLocation(location: Location) { fun updateLocation(location: Location) {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) navigationScreen.updateTrip(location)
val distance = location.distanceTo(snapedLocation) val wayPointLocation = routeModel.route.currentStep().wayPointLocation
val distance = location.distanceTo(wayPointLocation)
if (distance > MAXIMAL_ROUTE_DEVIATION) { if (distance > MAXIMAL_ROUTE_DEVIATION) {
navigationScreen.calculateNewRoute(routeModel.routeState.destination) navigationScreen.calculateNewRoute(routeModel.routeState.destination)
return return
} }
navigationScreen.updateTrip(location)
if (distance < MAXIMAL_SNAP_CORRECTION) { if (distance < MAXIMAL_SNAP_CORRECTION) {
surfaceRenderer.updateLocation(snapedLocation) surfaceRenderer.updateLocation(wayPointLocation)
} else { } else {
surfaceRenderer.updateLocation(location) surfaceRenderer.updateLocation(location)
} }

View File

@@ -5,8 +5,6 @@ import android.graphics.Rect
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.location.Location import android.location.Location
import android.os.CountDownTimer
import android.os.Handler
import android.util.Log import android.util.Log
import androidx.car.app.AppManager import androidx.car.app.AppManager
import androidx.car.app.CarContext import androidx.car.app.CarContext
@@ -31,9 +29,12 @@ import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues 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.homeLocation import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateTilt import com.kouros.navigation.utils.calculateTilt
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
@@ -161,7 +162,9 @@ class SurfaceRenderer(
fun onConnectionStateUpdated(connectionState: Int) { fun onConnectionStateUpdated(connectionState: Int) {
when (connectionState) { when (connectionState) {
CarConnection.CONNECTION_TYPE_NATIVE -> ObjectBox.init(carContext) CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
CarConnection.CONNECTION_TYPE_NATIVE -> ObjectBox.init(carContext) // Automotive OS
} }
} }
@@ -256,44 +259,22 @@ class SurfaceRenderer(
) )
lastBearing = cameraPosition.value!!.bearing lastBearing = cameraPosition.value!!.bearing
lastLocation = location lastLocation = location
//speed.value = location.speed
if (!countDownTimerActive) {
countDownTimerActive = true
val mainThreadHandler = Handler(carContext.mainLooper)
val lastLocationTimer = lastLocation
checkUpdate(mainThreadHandler, lastLocationTimer)
}
} }
} }
} }
private fun checkUpdate(
mainThreadHandler: Handler,
lastLocationTimer: Location
) {
mainThreadHandler.post {
object : CountDownTimer(3000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
countDownTimerActive = false
if (lastLocation.time - lastLocationTimer.time < 1500) {
speed.postValue(0F)
}
}
}.start()
}
}
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) { private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
cameraPosition.postValue( synchronized(this) {
cameraPosition.value!!.copy( cameraPosition.postValue(
bearing = bearing, cameraPosition.value!!.copy(
zoom = zoom, bearing = bearing,
tilt = tilt, zoom = zoom,
padding = getPaddingValues(height, viewStyle), tilt = tilt,
target = target padding = getPaddingValues(height, viewStyle),
target = target
)
) )
) }
} }
fun setRouteData() { fun setRouteData() {
@@ -316,17 +297,22 @@ class SurfaceRenderer(
} }
fun setCategories(location: Location, route: String) { fun setCategories(location: Location, route: String) {
viewStyle = ViewStyle.AMENITY_VIEW synchronized(this) {
routeData.value = route viewStyle = ViewStyle.AMENITY_VIEW
updateCameraPosition( routeData.value = route
0.0, updateCameraPosition(
12.0, 0.0,
target = Position(location.longitude, location.latitude) 12.0,
) target = Position(location.longitude, location.latitude)
)
}
} }
fun updateCarLocation(location: Location) { fun updateCarLocation(location: Location) {
// updateLocation(location) val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
if (routingEngine == RouteEngine.OSRM.ordinal) {
updateLocation(location)
}
} }
fun updateCarSpeed(newSpeed: Float) { fun updateCarSpeed(newSpeed: Float) {

View File

@@ -177,12 +177,12 @@ fun AmenityLayer(routeData: String?) {
@Composable @Composable
fun SpeedCameraLayer(speedCameras: String?) { fun SpeedCameraLayer(speedCameras: String?) {
if (speedCameras != null && speedCameras.isNotEmpty()) { if (speedCameras != null && speedCameras.isNotEmpty()) {
val color = const(Color.DarkGray) val color = const(Color.Red)
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras)) val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
SymbolLayer( SymbolLayer(
id = "speed-camera-layer", id = "speed-camera-layer",
source = cameraSource, source = cameraSource,
iconImage = image(painterResource(R.drawable.speed_camera_48px), drawAsSdf = true), iconImage = image(painterResource(R.drawable.speed_camera_24px), drawAsSdf = true),
iconColor = color, iconColor = color,
iconSize = iconSize =
interpolate( interpolate(

View File

@@ -48,13 +48,10 @@ class RouteCarModel() : RouteModel() {
val stepData = currentStep() val stepData = currentStep()
val currentStepCueWithImage: SpannableString = val currentStepCueWithImage: SpannableString =
createString(stepData.instruction) createString(stepData.instruction)
val straightNormal = val straightNormal =
Lane.Builder() Lane.Builder()
.addDirection(LaneDirection.create(LaneDirection.SHAPE_STRAIGHT, false)) .addDirection(LaneDirection.create(LaneDirection.SHAPE_STRAIGHT, false))
.build() .build()
val step = val step =
Step.Builder(currentStepCueWithImage) Step.Builder(currentStepCueWithImage)
.setManeuver( .setManeuver(
@@ -65,7 +62,7 @@ class RouteCarModel() : RouteModel() {
.setRoad(routeState.destination.street!!) .setRoad(routeState.destination.street!!)
stepData.lane.forEach { stepData.lane.forEach {
if (it.indications.isNotEmpty() ) { if (it.indications.isNotEmpty() ) {
step.setLanesImage(createCarIcon(carContext, R.drawable.lanes)) step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
step.addLane(straightNormal) step.addLane(straightNormal)
} }
} }
@@ -139,6 +136,10 @@ class RouteCarModel() : RouteModel() {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build() return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
} }
fun createCarIcon(iconCompat: IconCompat): CarIcon {
return CarIcon.Builder(iconCompat).build()
}
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) { fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
carContext.getCarService<AppManager?>(AppManager::class.java) carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(createAlert(carContext, distance, maxSpeed)) .showAlert(createAlert(carContext, distance, maxSpeed))

View File

@@ -15,8 +15,6 @@ import androidx.car.app.model.Distance
import androidx.car.app.model.Header import androidx.car.app.model.Header
import androidx.car.app.model.MessageTemplate import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.navigation.model.Lane
import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.MapWithContentTemplate import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.navigation.model.MessageInfo import androidx.car.app.navigation.model.MessageInfo
@@ -92,12 +90,8 @@ class NavigationScreen(
var speedCameras = listOf<Elements>() var speedCameras = listOf<Elements>()
val speedObserver = Observer<List<Elements>> { cameras -> val speedObserver = Observer<List<Elements>> { cameras ->
speedCameras = cameras speedCameras = cameras
val coordinates = mutableListOf<List<Double>>() val coordinates = mutableListOf<List<Double>>()
val loc = location(0.0, 0.0)
cameras.forEach { cameras.forEach {
val loc =
location(longitude = it.lon!!, latitude = it.lat!!)
coordinates.add(listOf(it.lon!!, it.lat!!)) coordinates.add(listOf(it.lon!!, it.lat!!))
} }
val speedData = GeoUtils.createPointCollection(coordinates, "radar") val speedData = GeoUtils.createPointCollection(coordinates, "radar")
@@ -238,22 +232,13 @@ class NavigationScreen(
Distance.UNIT_METERS Distance.UNIT_METERS
} }
val nextStep = routeModel.nextStep(carContext = carContext) val nextStep = routeModel.nextStep(carContext = carContext)
if (nextStep != null) { return RoutingInfo.Builder()
return RoutingInfo.Builder() .setCurrentStep(
.setCurrentStep( routeModel.currentStep(carContext = carContext),
routeModel.currentStep(carContext = carContext), Distance.create(currentDistance, displayUnit)
Distance.create(currentDistance, displayUnit) )
) .setNextStep(nextStep)
.setNextStep(nextStep) .build()
.build()
} else {
return RoutingInfo.Builder()
.setCurrentStep(
routeModel.currentStep(carContext = carContext),
Distance.create(currentDistance, displayUnit)
)
.build()
}
} }
private fun createActionStripBuilder(): ActionStrip.Builder { private fun createActionStripBuilder(): ActionStrip.Builder {
@@ -350,7 +335,7 @@ class NavigationScreen(
return Action.Builder() return Action.Builder()
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px)) .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
.setOnClickListener { .setOnClickListener {
screenManager.push(SettingsScreen(carContext)) screenManager.push(SettingsScreen(carContext, viewModel))
} }
.build() .build()
} }
@@ -466,7 +451,7 @@ class NavigationScreen(
} }
fun updateTrip(location: Location) { fun updateTrip(location: Location) {
updateSpeedCamera(location) updateSpeedCamera(surfaceRenderer.lastLocation)
with(routeModel) { with(routeModel) {
updateLocation(location, viewModel) updateLocation(location, viewModel)
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION if (routeState.maneuverType == Maneuver.TYPE_DESTINATION

View File

@@ -12,11 +12,12 @@ import androidx.car.app.model.Toggle
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
import com.kouros.navigation.data.Constants.AVOID_TOLLWAY import com.kouros.navigation.data.Constants.AVOID_TOLLWAY
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
class NavigationSettings(private val carContext: CarContext) : Screen(carContext) { class NavigationSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
private var motorWayToggleState = false private var motorWayToggleState = false
@@ -52,7 +53,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
tollWayToggleState = !tollWayToggleState tollWayToggleState = !tollWayToggleState
}.setChecked(tollWayToggleState).build() }.setChecked(tollWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle)) listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
listBuilder.addItem(
buildRowForScreenTemplate(
RoutingSettings(carContext, viewModel),
R.string.routing_engine
)
)
return ListTemplate.Builder() return ListTemplate.Builder()
.setSingleList(listBuilder.build()) .setSingleList(listBuilder.build())
.setHeader( .setHeader(
@@ -70,4 +76,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
.setToggle(toggle) .setToggle(toggle)
.build() .build()
} }
private fun buildRowForScreenTemplate(screen: Screen, title: Int): Row {
return Row.Builder()
.setTitle(carContext.getString(title))
.setOnClickListener { screenManager.push(screen) }
.setBrowsable(true)
.build()
}
} }

View File

@@ -80,7 +80,6 @@ class RoutePreviewScreen(
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))
//.addEndHeaderAction(navigateAction)
.addEndHeaderAction( .addEndHeaderAction(
favoriteAction() favoriteAction()
) )

View File

@@ -0,0 +1,81 @@
package com.kouros.navigation.car.screen
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.SectionedItemList
import androidx.car.app.model.Template
import com.kouros.data.R
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
class RoutingSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
private var routingEngine = RouteEngine.VALHALLA.ordinal
init {
routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
}
override fun onGetTemplate(): Template {
val templateBuilder = ListTemplate.Builder()
val radioList =
ItemList.Builder()
.addItem(
buildRowForTemplate(
R.string.valhalla,
)
)
.addItem(
buildRowForTemplate(
R.string.osrm,
)
)
.setOnSelectedListener { index: Int ->
this.onSelected(index)
}
.setSelectedIndex(routingEngine)
.build()
return templateBuilder
.addSectionedList(SectionedItemList.create(
radioList,
carContext.getString(R.string.routing_engine)
))
.setHeader(
Header.Builder()
.setTitle(carContext.getString(R.string.routing_engine))
.setStartHeaderAction(Action.BACK)
.build()
)
.build()
}
private fun onSelected(index: Int) {
setIntKeyValue(carContext, index, ROUTING_ENGINE)
viewModel.routingEngine.value = index
CarToast.makeText(
carContext,
(carContext
.getString(R.string.routing_engine)
+ ":"
+ " " + index), CarToast.LENGTH_LONG
)
.show()
}
private fun buildRowForTemplate(title: Int): Row {
return Row.Builder()
.setTitle(carContext.getString(title))
.build()
}
}

View File

@@ -24,10 +24,12 @@ import androidx.car.app.model.ListTemplate
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 com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.model.ViewModel
/** A screen demonstrating selectable lists. */ /** A screen demonstrating selectable lists. */
class SettingsScreen( class SettingsScreen(
carContext: CarContext, carContext: CarContext,
private var viewModel: ViewModel,
) : Screen(carContext) { ) : Screen(carContext) {
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
@@ -40,7 +42,7 @@ class SettingsScreen(
) )
listBuilder.addItem( listBuilder.addItem(
buildRowForTemplate( buildRowForTemplate(
NavigationSettings(carContext), NavigationSettings(carContext, viewModel),
R.string.navigation_settings R.string.navigation_settings
) )
) )

View File

@@ -174,6 +174,8 @@ object Constants {
const val AVOID_TOLLWAY = "AvoidTollway" const val AVOID_TOLLWAY = "AvoidTollway"
const val ROUTING_ENGINE = "RoutingEngine"
const val NEXT_STEP_THRESHOLD = 100.0 const val NEXT_STEP_THRESHOLD = 100.0
const val MAXIMAL_SNAP_CORRECTION = 50.0 const val MAXIMAL_SNAP_CORRECTION = 50.0
@@ -182,7 +184,6 @@ object Constants {
const val DESTINATION_ARRIVAL_DISTANCE = 40.0 const val DESTINATION_ARRIVAL_DISTANCE = 40.0
val ROUTE_ENGINE = RouteEngine.VALHALLA.name
} }

View File

@@ -30,8 +30,6 @@ import kotlinx.serialization.json.Json
abstract class NavigationRepository { abstract class NavigationRepository {
private val placesUrl = "https://kouros-online.de/maps/placespwd";
private val nominatimUrl = "https://nominatim.openstreetmap.org/" private val nominatimUrl = "https://nominatim.openstreetmap.org/"
@@ -58,27 +56,6 @@ abstract class NavigationRepository {
return fetchUrl("${nominatimUrl}reverse?lat=${location.latitude}&lon=${location.longitude}&format=jsonv2&addressdetails=true&countrycodes=de", false) return fetchUrl("${nominatimUrl}reverse?lat=${location.latitude}&lon=${location.longitude}&format=jsonv2&addressdetails=true&countrycodes=de", false)
} }
fun getPlaces(): List<Place> {
val places: MutableList<Place> = ArrayList()
val placesStr = fetchUrl(placesUrl, true)
val jArray = JSONArray(placesStr)
for (i in 0..<jArray.length()) {
val json = jArray.getJSONObject(i)
val place = Place(
json.getString("id").toLong(),
json.getString("name"),
category = json.getString("category"),
latitude = json.getDouble("latitude"),
longitude = json.getDouble("longitude"),
postalCode = json.getString("postalCode"),
city = json.getString("city"),
street = json.getString("street"),
)
places.add(place)
}
return places
}
fun fetchUrl(url: String, authenticator : Boolean): String { fun fetchUrl(url: String, authenticator : Boolean): String {
try { try {
if (authenticator) { if (authenticator) {

View File

@@ -2,7 +2,6 @@ package com.kouros.navigation.data
import android.location.Location import android.location.Location
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.osrm.OsrmResponse import com.kouros.navigation.data.osrm.OsrmResponse
import com.kouros.navigation.data.osrm.OsrmRoute import com.kouros.navigation.data.osrm.OsrmRoute
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
@@ -11,8 +10,6 @@ import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.data.valhalla.ValhallaResponse import com.kouros.navigation.data.valhalla.ValhallaResponse
import com.kouros.navigation.data.valhalla.ValhallaRoute import com.kouros.navigation.data.valhalla.ValhallaRoute
import com.kouros.navigation.utils.GeoUtils.createCenterLocation import com.kouros.navigation.utils.GeoUtils.createCenterLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement

View File

@@ -4,9 +4,7 @@ import android.location.Location
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.SearchFilter import com.kouros.navigation.data.SearchFilter
//private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/" private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/"
private const val routeUrl = "http://192.168.1.37:5000/route/v1/driving/"
class OsrmRepository : NavigationRepository() { class OsrmRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(

View File

@@ -66,17 +66,34 @@ 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
} }
ManeuverType.turn.value -> { ManeuverType.turn.value,
ManeuverType.endOfRoad.value -> {
if (maneuver.modifier == "right") { if (maneuver.modifier == "right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
} }
} }
ManeuverType.turn.value -> { ManeuverType.turn.value,
ManeuverType.endOfRoad.value,
ManeuverType.onRamp.value
-> {
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
} }
} }
ManeuverType.fork.value
-> {
if (maneuver.modifier == "slight left") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
}
}
ManeuverType.fork.value
-> {
if (maneuver.modifier == "slight right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
}
}
} }
return newType return newType
} }
} }

View File

@@ -64,18 +64,20 @@ class Overpass {
} }
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> { fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream) try {
outputStreamWriter.write(searchQuery) val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.flush() outputStreamWriter.write(searchQuery)
// Check if the connection is successful outputStreamWriter.flush()
val responseCode = httpURLConnection.responseCode // Check if the connection is successful
if (responseCode == HttpURLConnection.HTTP_OK) { val responseCode = httpURLConnection.responseCode
val response = httpURLConnection.inputStream.bufferedReader() if (responseCode == HttpURLConnection.HTTP_OK) {
.use { it.readText() } // defaults to UTF-8 val response = httpURLConnection.inputStream.bufferedReader()
val gson = GsonBuilder().serializeNulls().create() .use { it.readText() } // defaults to UTF-8
val overpass = gson.fromJson(response, Amenity::class.java) val gson = GsonBuilder().serializeNulls().create()
// println("Overpass: $response") val overpass = gson.fromJson(response, Amenity::class.java)
return overpass.elements return overpass.elements
}
} catch (e: Exception) {
} }
return emptyList() return emptyList()
} }

View File

@@ -1,8 +1,12 @@
package com.kouros.navigation.data.route package com.kouros.navigation.data.route
import android.location.Location
import com.kouros.navigation.utils.location
data class Step( data class Step(
var index : Int = 0, var index : Int = 0,
var waypointIndex : Int = 0, var waypointIndex : Int = 0,
var wayPointLocation : Location = location(0.0,0.0),
val maneuver: Maneuver, val maneuver: Maneuver,
val duration: Double = 0.0, val duration: Double = 0.0,
val distance: Double = 0.0, val distance: Double = 0.0,

View File

@@ -1,8 +1,10 @@
package com.kouros.navigation.data.valhalla package com.kouros.navigation.data.valhalla
import androidx.car.app.navigation.model.Maneuver
import com.kouros.data.R
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.Maneuver 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.createLineStringCollection import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
@@ -18,10 +20,11 @@ class ValhallaRoute {
val steps = mutableListOf<Step>() val steps = mutableListOf<Step>()
var stepIndex = 0 var stepIndex = 0
routeJson.legs[0].maneuvers.forEach { routeJson.legs[0].maneuvers.forEach {
val maneuver = Maneuver( val maneuver = RouteManeuver(
bearingBefore = 0, bearingBefore = 0,
bearingAfter = it.bearingAfter, bearingAfter = it.bearingAfter,
type = it.type, //type = it.type,
type = convertType(it),
waypoints =waypoints.subList(it.beginShapeIndex, it.endShapeIndex+1) waypoints =waypoints.subList(it.beginShapeIndex, it.endShapeIndex+1)
) )
var name = "" var name = ""
@@ -40,4 +43,56 @@ class ValhallaRoute {
.legs(listOf(leg)) .legs(listOf(leg))
.waypoints(waypoints) .waypoints(waypoints)
} }
fun convertType(maneuver: Maneuvers): Int {
var newType = 0
when (maneuver.type) {
ManeuverType.None.value -> {
newType = Maneuver.TYPE_STRAIGHT
}
ManeuverType.Destination.value,
ManeuverType.DestinationRight.value,
ManeuverType.DestinationLeft.value,
-> {
newType = Maneuver.TYPE_DESTINATION
}
ManeuverType.Right.value -> {
newType = Maneuver.TYPE_TURN_NORMAL_RIGHT
}
ManeuverType.Left.value -> {
newType = Maneuver.TYPE_TURN_NORMAL_LEFT
}
ManeuverType.RampRight.value -> {
newType = Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT
}
ManeuverType.RampLeft.value -> {
newType = Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT
}
ManeuverType.ExitRight.value -> {
newType = Maneuver.TYPE_TURN_SLIGHT_RIGHT
}
ManeuverType.StayRight.value -> {
newType = Maneuver.TYPE_KEEP_RIGHT
}
ManeuverType.StayLeft.value -> {
newType = Maneuver.TYPE_KEEP_LEFT
}
ManeuverType.RoundaboutEnter.value -> {
newType = Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
}
ManeuverType.RoundaboutExit.value -> {
newType = Maneuver.TYPE_ROUNDABOUT_EXIT_CCW
}
}
return newType
}
} }

View File

@@ -1,28 +1,31 @@
package com.kouros.navigation.model package com.kouros.navigation.model
import android.content.Context 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 android.location.Location
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Step 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.data.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.Constants.ROUTE_ENGINE import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.valhalla.ManeuverType
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.StepData 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.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
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.invoke
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -40,6 +43,8 @@ open class RouteModel() {
val lastSpeedIndex: Int = 0, val lastSpeedIndex: Int = 0,
val maxSpeed: Int = 0, val maxSpeed: Int = 0,
val location: Location = location(0.0, 0.0), val location: Location = location(0.0, 0.0),
val lastLocation: Location = location(0.0, 0.0),
val bearing : Float = 0F
) )
var routeState = RouteState() var routeState = RouteState()
@@ -55,11 +60,14 @@ open class RouteModel() {
get() = routeState.route!!.legs!!.first() get() = routeState.route!!.legs!!.first()
fun startNavigation(routeString: String, context: Context) { fun startNavigation(routeString: String, context: Context) {
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE) val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
val newRoute = Route.Builder() var newRoute = Route.Builder()
.routeEngine(routeEngine) .routeEngine(routeEngine)
.route(routeString) .route(routeString)
.build() .build()
// TODO:
newRoute = newRoute.copy(centerLocation = createCenterLocation(newRoute.routeGeoJson))
println("Route ${newRoute.centerLocation}")
this.routeState = routeState.copy( this.routeState = routeState.copy(
route = newRoute, route = newRoute,
isNavigating = true isNavigating = true
@@ -80,6 +88,7 @@ open class RouteModel() {
routeState = routeState.copy(location = location) routeState = routeState.copy(location = location)
findStep(location) findStep(location)
updateSpeedLimit(location, viewModel) updateSpeedLimit(location, viewModel)
} }
private fun findStep(location: Location) { private fun findStep(location: Location) {
@@ -93,6 +102,9 @@ open class RouteModel() {
nearestDistance = distance nearestDistance = distance
route.currentStep = step.index route.currentStep = step.index
step.waypointIndex = wayIndex step.waypointIndex = wayIndex
step.wayPointLocation = location(waypoint[0], waypoint[1])
val bearing = routeState.lastLocation.bearingTo(location)
this.routeState = routeState.copy(lastLocation = location, bearing = bearing)
} }
} }
if (nearestDistance == 0F) { if (nearestDistance == 0F) {
@@ -104,11 +116,9 @@ open class RouteModel() {
break break
} }
} }
//println("Current Index ${route.currentStep} WayPoint: ${route.currentStep().waypointIndex}")
} }
private fun currentIntersection(location: Location): Intersection { private fun currentIntersection(location: Location): Intersection {
var inter = Intersection() var inter = Intersection()
var nearestDistance = 100000.0f var nearestDistance = 100000.0f
route.currentStep().intersection.forEach { route.currentStep().intersection.forEach {
@@ -120,6 +130,7 @@ open class RouteModel() {
} }
return inter return inter
} }
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking { fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// speed limit // speed limit
@@ -150,7 +161,7 @@ open class RouteModel() {
var maneuverType = if (hasArrived(currentStep.maneuver.type)) { var maneuverType = if (hasArrived(currentStep.maneuver.type)) {
currentStep.maneuver.type currentStep.maneuver.type
} else { } else {
ManeuverType.None.value Maneuver.TYPE_STRAIGHT
} }
// Get the single, correct maneuver for this state // Get the single, correct maneuver for this state
val relevantStep = if (shouldAdvance) { val relevantStep = if (shouldAdvance) {
@@ -187,6 +198,7 @@ open class RouteModel() {
when (distanceLeft) { when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> { in 0.0..NEXT_STEP_THRESHOLD -> {
} }
else -> { else -> {
if (step.name.isNotEmpty()) { if (step.name.isNotEmpty()) {
text = step.name text = step.name
@@ -262,68 +274,49 @@ open class RouteModel() {
} }
fun maneuverIcon(routeManeuverType: Int): (Pair<Int, Int>) { fun maneuverIcon(routeManeuverType: Int): (Pair<Int, Int>) {
var type = Maneuver.TYPE_DEPART
var currentTurnIcon = R.drawable.ic_turn_name_change var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) { when (routeManeuverType) {
ManeuverType.None.value -> { Maneuver.TYPE_STRAIGHT -> {
type = Maneuver.TYPE_STRAIGHT
currentTurnIcon = R.drawable.ic_turn_name_change currentTurnIcon = R.drawable.ic_turn_name_change
} }
ManeuverType.Destination.value, Maneuver.TYPE_DESTINATION,
ManeuverType.DestinationRight.value,
ManeuverType.DestinationLeft.value,
-> { -> {
type = Maneuver.TYPE_DESTINATION
currentTurnIcon = R.drawable.ic_turn_destination currentTurnIcon = R.drawable.ic_turn_destination
} }
ManeuverType.Right.value -> { Maneuver.TYPE_TURN_NORMAL_RIGHT -> {
type = Maneuver.TYPE_TURN_NORMAL_RIGHT
currentTurnIcon = R.drawable.ic_turn_normal_right currentTurnIcon = R.drawable.ic_turn_normal_right
} }
ManeuverType.Left.value -> { Maneuver.TYPE_TURN_NORMAL_LEFT -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = R.drawable.ic_turn_normal_left currentTurnIcon = R.drawable.ic_turn_normal_left
} }
ManeuverType.RampRight.value -> { Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT -> {
type = Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT
currentTurnIcon = R.drawable.ic_turn_slight_right currentTurnIcon = R.drawable.ic_turn_slight_right
} }
ManeuverType.RampLeft.value -> { Maneuver.TYPE_TURN_SLIGHT_RIGHT -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = R.drawable.ic_turn_normal_left
}
ManeuverType.ExitRight.value -> {
type = Maneuver.TYPE_TURN_SLIGHT_RIGHT
currentTurnIcon = R.drawable.ic_turn_slight_right currentTurnIcon = R.drawable.ic_turn_slight_right
} }
ManeuverType.StayRight.value -> { Maneuver.TYPE_KEEP_RIGHT -> {
type = Maneuver.TYPE_KEEP_RIGHT
currentTurnIcon = R.drawable.ic_turn_name_change currentTurnIcon = R.drawable.ic_turn_name_change
} }
Maneuver.TYPE_KEEP_LEFT -> {
ManeuverType.StayLeft.value -> {
type = Maneuver.TYPE_KEEP_LEFT
currentTurnIcon = R.drawable.ic_turn_name_change currentTurnIcon = R.drawable.ic_turn_name_change
} }
Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> {
ManeuverType.RoundaboutEnter.value -> {
type = Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
currentTurnIcon = R.drawable.ic_roundabout_ccw currentTurnIcon = R.drawable.ic_roundabout_ccw
} }
ManeuverType.RoundaboutExit.value -> { Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> {
type = Maneuver.TYPE_ROUNDABOUT_EXIT_CCW
currentTurnIcon = R.drawable.ic_roundabout_ccw currentTurnIcon = R.drawable.ic_roundabout_ccw
} }
} }
return Pair(type, currentTurnIcon) return Pair(routeManeuverType, currentTurnIcon)
} }
fun isNavigating(): Boolean { fun isNavigating(): Boolean {
@@ -336,4 +329,79 @@ open class RouteModel() {
|| type == ManeuverType.Destination.value || type == ManeuverType.Destination.value
|| type == ManeuverType.DestinationLeft.value || type == ManeuverType.DestinationLeft.value
} }
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)
}
}
}
}
}
return 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().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 * 40F, 0F)
canvas.drawBitmap(it, matrix, null)
}
i++
}
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 -> {""}
}
}
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}
}
}
} }

View File

@@ -67,6 +67,11 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData() MutableLiveData()
} }
val routingEngine: MutableLiveData<Int> by lazy {
MutableLiveData()
}
fun loadRecentPlace(location: Location, context: Context) { fun loadRecentPlace(location: Location, context: Context) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {

View File

@@ -4,7 +4,7 @@ import android.content.Context
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import androidx.core.content.edit import androidx.core.content.edit
import com.kouros.navigation.data.Constants.ROUTE_ENGINE import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.osrm.OsrmRepository
@@ -26,7 +26,7 @@ import kotlin.time.Duration.Companion.seconds
object NavigationUtils { object NavigationUtils {
fun getRouteEngine(context: Context): ViewModel { fun getRouteEngine(context: Context): ViewModel {
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE) val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
return when (routeEngine) { return when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository()) RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
else -> ViewModel(OsrmRepository()) else -> ViewModel(OsrmRepository())

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M701,600L614,550L754,446L840,496L701,600ZM160,800L160,720L360,720Q360,720 360,720Q360,720 360,720L360,482L240,413Q211,396 202.5,364.5Q194,333 211,304L271,200Q288,171 319.5,162.5Q351,154 380,171L761,391L517,573L440,529L440,720Q440,753 416.5,776.5Q393,800 360,800L160,800Z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

View File

@@ -30,4 +30,7 @@
<string name="reject_action_title">Reject</string> <string name="reject_action_title">Reject</string>
<string name="ok_action_title">OK</string> <string name="ok_action_title">OK</string>
<string name="search_action_title">Search</string> <string name="search_action_title">Search</string>
<string name="valhalla">Valhalla</string>
<string name="osrm">Osrm</string>
<string name="routing_engine">Routing engine</string>
</resources> </resources>