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"
minSdk = 33
targetSdk = 36
versionCode = 15
versionName = "0.1.3.15"
versionCode = 18
versionName = "0.1.3.18"
base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -2,26 +2,14 @@ package com.kouros.navigation
import android.app.Application
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.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.model.BaseStyleModel
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.setIntKeyValue
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.core.logger.Level
import org.maplibre.compose.expressions.dsl.switch
class MainApplication : Application() {
@@ -29,7 +17,6 @@ class MainApplication : Application() {
super.onCreate()
ObjectBox.init(this);
appContext = applicationContext
setIntKeyValue(appContext!!, RouteEngine.VALHALLA.ordinal, ROUTE_ENGINE)
navigationViewModel = getRouteEngine(appContext!!)
startKoin {
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.RouteModel
import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom
@@ -61,6 +62,7 @@ import org.maplibre.compose.location.Location
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.style.BaseStyle
import org.maplibre.geojson.Point
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
@@ -235,6 +237,7 @@ class MainActivity : ComponentActivity() {
&& lastLocation.longitude != location.position.longitude
) {
val currentLocation = location(location.position.longitude, location.position.latitude)
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
with(routeModel) {
if (isNavigating()) {
updateLocation(currentLocation, navigationViewModel)
@@ -251,7 +254,6 @@ class MainActivity : ComponentActivity() {
}
}
}
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
val zoom = calculateZoom(location.speed)
cameraPosition.postValue(
cameraPosition.value!!.copy(
@@ -305,12 +307,11 @@ class MainActivity : ComponentActivity() {
fun simulate() {
CoroutineScope(Dispatchers.IO).launch {
for ((index, step) in routeModel.legs.steps.withIndex()) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
if (routeModel.isNavigating()) {
mock.setMockLocation(waypoint[1], waypoint[0])
delay(800L) //
}
for ((index, waypoint) in routeModel.route.waypoints!!.withIndex()) {
var deviation = 0.0
mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
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.CarSensors
import androidx.car.app.hardware.info.Speed
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.core.location.LocationListenerCompat
import androidx.core.net.toUri
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.MAXIMAL_ROUTE_DEVIATION
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.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.ViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
import org.maplibre.compose.style.BaseStyle
@@ -52,8 +54,11 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var surfaceRenderer: SurfaceRenderer
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
if (routingEngine == RouteEngine.VALHALLA.ordinal) {
updateLocation(location!!)
}
}
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
@@ -75,8 +80,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
override fun onDestroy(owner: LifecycleOwner) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
carSensors.removeCarHardwareLocationListener(locationListener)
carInfo.removeSpeedListener(speedListener)
carSensors.removeCarHardwareLocationListener(carLocationListener)
carInfo.removeSpeedListener(carSpeedListener)
Log.i(TAG, "In onDestroy()")
val locationManager =
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
@@ -88,7 +93,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var baseStyle: BaseStyle.Json
val locationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
OnCarDataAvailableListener { data ->
if (data.location.status == CarValue.STATUS_SUCCESS) {
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) {
val speed = data.displaySpeedMetersPerSecond.value
surfaceRenderer.updateCarSpeed(speed!!)
@@ -106,9 +111,19 @@ class NavigationSession : Session(), NavigationScreen.Listener {
val lifecycle: Lifecycle = lifecycle
lifecycle.addObserver(mLifeCycleObserver)
}
fun onRoutingEngineStateUpdated(routeEngine : Int) {
navigationViewModel = when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
else -> ViewModel(OsrmRepository())
}
}
override fun onCreateScreen(intent: Intent): Screen {
navigationViewModel = getRouteEngine(carContext)
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
routeModel = RouteCarModel()
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
@@ -155,9 +170,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
carSensors.addCarHardwareLocationListener(
CarSensors.UPDATE_RATE_NORMAL,
carContext.mainExecutor,
locationListener
carLocationListener
)
carInfo.addSpeedListener(carContext.mainExecutor, speedListener)
carInfo.addSpeedListener(carContext.mainExecutor, carSpeedListener)
}
override fun onNewIntent(intent: Intent) {
@@ -221,15 +236,15 @@ class NavigationSession : Session(), NavigationScreen.Listener {
fun updateLocation(location: Location) {
if (routeModel.isNavigating()) {
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
val distance = location.distanceTo(snapedLocation)
navigationScreen.updateTrip(location)
val wayPointLocation = routeModel.route.currentStep().wayPointLocation
val distance = location.distanceTo(wayPointLocation)
if (distance > MAXIMAL_ROUTE_DEVIATION) {
navigationScreen.calculateNewRoute(routeModel.routeState.destination)
return
}
navigationScreen.updateTrip(location)
if (distance < MAXIMAL_SNAP_CORRECTION) {
surfaceRenderer.updateLocation(snapedLocation)
surfaceRenderer.updateLocation(wayPointLocation)
} else {
surfaceRenderer.updateLocation(location)
}

View File

@@ -5,8 +5,6 @@ import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.location.Location
import android.os.CountDownTimer
import android.os.Handler
import android.util.Log
import androidx.car.app.AppManager
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.rememberBaseStyle
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.ObjectBox
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateTilt
import com.kouros.navigation.utils.calculateZoom
@@ -161,7 +162,9 @@ class SurfaceRenderer(
fun onConnectionStateUpdated(connectionState: Int) {
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,35 +259,12 @@ class SurfaceRenderer(
)
lastBearing = cameraPosition.value!!.bearing
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) {
synchronized(this) {
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
@@ -295,6 +275,7 @@ class SurfaceRenderer(
)
)
}
}
fun setRouteData() {
routeData.value = routeModel.route.routeGeoJson
@@ -316,6 +297,7 @@ class SurfaceRenderer(
}
fun setCategories(location: Location, route: String) {
synchronized(this) {
viewStyle = ViewStyle.AMENITY_VIEW
routeData.value = route
updateCameraPosition(
@@ -324,9 +306,13 @@ class SurfaceRenderer(
target = Position(location.longitude, location.latitude)
)
}
}
fun updateCarLocation(location: Location) {
// updateLocation(location)
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
if (routingEngine == RouteEngine.OSRM.ordinal) {
updateLocation(location)
}
}
fun updateCarSpeed(newSpeed: Float) {

View File

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

View File

@@ -48,13 +48,10 @@ class RouteCarModel() : RouteModel() {
val stepData = currentStep()
val currentStepCueWithImage: SpannableString =
createString(stepData.instruction)
val straightNormal =
Lane.Builder()
.addDirection(LaneDirection.create(LaneDirection.SHAPE_STRAIGHT, false))
.build()
val step =
Step.Builder(currentStepCueWithImage)
.setManeuver(
@@ -65,7 +62,7 @@ class RouteCarModel() : RouteModel() {
.setRoad(routeState.destination.street!!)
stepData.lane.forEach {
if (it.indications.isNotEmpty() ) {
step.setLanesImage(createCarIcon(carContext, R.drawable.lanes))
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
step.addLane(straightNormal)
}
}
@@ -139,6 +136,10 @@ class RouteCarModel() : RouteModel() {
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?) {
carContext.getCarService<AppManager?>(AppManager::class.java)
.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.MessageTemplate
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.MapWithContentTemplate
import androidx.car.app.navigation.model.MessageInfo
@@ -92,12 +90,8 @@ class NavigationScreen(
var speedCameras = listOf<Elements>()
val speedObserver = Observer<List<Elements>> { cameras ->
speedCameras = cameras
val coordinates = mutableListOf<List<Double>>()
val loc = location(0.0, 0.0)
cameras.forEach {
val loc =
location(longitude = it.lon!!, latitude = it.lat!!)
coordinates.add(listOf(it.lon!!, it.lat!!))
}
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
@@ -238,7 +232,6 @@ class NavigationScreen(
Distance.UNIT_METERS
}
val nextStep = routeModel.nextStep(carContext = carContext)
if (nextStep != null) {
return RoutingInfo.Builder()
.setCurrentStep(
routeModel.currentStep(carContext = carContext),
@@ -246,14 +239,6 @@ class NavigationScreen(
)
.setNextStep(nextStep)
.build()
} else {
return RoutingInfo.Builder()
.setCurrentStep(
routeModel.currentStep(carContext = carContext),
Distance.create(currentDistance, displayUnit)
)
.build()
}
}
private fun createActionStripBuilder(): ActionStrip.Builder {
@@ -350,7 +335,7 @@ class NavigationScreen(
return Action.Builder()
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
.setOnClickListener {
screenManager.push(SettingsScreen(carContext))
screenManager.push(SettingsScreen(carContext, viewModel))
}
.build()
}
@@ -466,7 +451,7 @@ class NavigationScreen(
}
fun updateTrip(location: Location) {
updateSpeedCamera(location)
updateSpeedCamera(surfaceRenderer.lastLocation)
with(routeModel) {
updateLocation(location, viewModel)
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.navigation.data.Constants.AVOID_MOTORWAY
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.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
@@ -52,7 +53,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
tollWayToggleState = !tollWayToggleState
}.setChecked(tollWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
listBuilder.addItem(
buildRowForScreenTemplate(
RoutingSettings(carContext, viewModel),
R.string.routing_engine
)
)
return ListTemplate.Builder()
.setSingleList(listBuilder.build())
.setHeader(
@@ -70,4 +76,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
.setToggle(toggle)
.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()
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.route_preview))
//.addEndHeaderAction(navigateAction)
.addEndHeaderAction(
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.Template
import com.kouros.data.R
import com.kouros.navigation.model.ViewModel
/** A screen demonstrating selectable lists. */
class SettingsScreen(
carContext: CarContext,
private var viewModel: ViewModel,
) : Screen(carContext) {
override fun onGetTemplate(): Template {
@@ -40,7 +42,7 @@ class SettingsScreen(
)
listBuilder.addItem(
buildRowForTemplate(
NavigationSettings(carContext),
NavigationSettings(carContext, viewModel),
R.string.navigation_settings
)
)

View File

@@ -174,6 +174,8 @@ object Constants {
const val AVOID_TOLLWAY = "AvoidTollway"
const val ROUTING_ENGINE = "RoutingEngine"
const val NEXT_STEP_THRESHOLD = 100.0
const val MAXIMAL_SNAP_CORRECTION = 50.0
@@ -182,7 +184,6 @@ object Constants {
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 {
private val placesUrl = "https://kouros-online.de/maps/placespwd";
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)
}
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 {
try {
if (authenticator) {

View File

@@ -2,7 +2,6 @@ package com.kouros.navigation.data
import android.location.Location
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.OsrmRoute
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.ValhallaRoute
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 kotlinx.serialization.json.Json
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.SearchFilter
//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/"
private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/"
class OsrmRepository : NavigationRepository() {
override fun getRoute(

View File

@@ -66,17 +66,34 @@ class OsrmRoute {
ManeuverType.continue_.value -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
}
ManeuverType.turn.value -> {
ManeuverType.turn.value,
ManeuverType.endOfRoad.value -> {
if (maneuver.modifier == "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") {
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
}
}

View File

@@ -64,6 +64,7 @@ class Overpass {
}
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
try {
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(searchQuery)
outputStreamWriter.flush()
@@ -74,9 +75,10 @@ class Overpass {
.use { it.readText() } // defaults to UTF-8
val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java)
// println("Overpass: $response")
return overpass.elements
}
} catch (e: Exception) {
}
return emptyList()
}
}

View File

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

View File

@@ -1,8 +1,10 @@
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.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.Summary
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
@@ -18,10 +20,11 @@ class ValhallaRoute {
val steps = mutableListOf<Step>()
var stepIndex = 0
routeJson.legs[0].maneuvers.forEach {
val maneuver = Maneuver(
val maneuver = RouteManeuver(
bearingBefore = 0,
bearingAfter = it.bearingAfter,
type = it.type,
//type = it.type,
type = convertType(it),
waypoints =waypoints.subList(it.beginShapeIndex, it.endShapeIndex+1)
)
var name = ""
@@ -40,4 +43,56 @@ class ValhallaRoute {
.legs(listOf(leg))
.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
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.location.Location
import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Step
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.valhalla.ManeuverType
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.route.Intersection
import com.kouros.navigation.data.route.Lane
import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.valhalla.ManeuverType
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.location
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.invoke
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit
@@ -40,6 +43,8 @@ open class RouteModel() {
val lastSpeedIndex: Int = 0,
val maxSpeed: Int = 0,
val location: Location = location(0.0, 0.0),
val lastLocation: Location = location(0.0, 0.0),
val bearing : Float = 0F
)
var routeState = RouteState()
@@ -55,11 +60,14 @@ open class RouteModel() {
get() = routeState.route!!.legs!!.first()
fun startNavigation(routeString: String, context: Context) {
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE)
val newRoute = Route.Builder()
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
var newRoute = Route.Builder()
.routeEngine(routeEngine)
.route(routeString)
.build()
// TODO:
newRoute = newRoute.copy(centerLocation = createCenterLocation(newRoute.routeGeoJson))
println("Route ${newRoute.centerLocation}")
this.routeState = routeState.copy(
route = newRoute,
isNavigating = true
@@ -80,6 +88,7 @@ open class RouteModel() {
routeState = routeState.copy(location = location)
findStep(location)
updateSpeedLimit(location, viewModel)
}
private fun findStep(location: Location) {
@@ -93,6 +102,9 @@ open class RouteModel() {
nearestDistance = distance
route.currentStep = step.index
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) {
@@ -104,11 +116,9 @@ open class RouteModel() {
break
}
}
//println("Current Index ${route.currentStep} WayPoint: ${route.currentStep().waypointIndex}")
}
private fun currentIntersection(location: Location): Intersection {
var inter = Intersection()
var nearestDistance = 100000.0f
route.currentStep().intersection.forEach {
@@ -120,6 +130,7 @@ open class RouteModel() {
}
return inter
}
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
CoroutineScope(Dispatchers.IO).launch {
// speed limit
@@ -150,7 +161,7 @@ open class RouteModel() {
var maneuverType = if (hasArrived(currentStep.maneuver.type)) {
currentStep.maneuver.type
} else {
ManeuverType.None.value
Maneuver.TYPE_STRAIGHT
}
// Get the single, correct maneuver for this state
val relevantStep = if (shouldAdvance) {
@@ -187,6 +198,7 @@ open class RouteModel() {
when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
}
else -> {
if (step.name.isNotEmpty()) {
text = step.name
@@ -262,68 +274,49 @@ open class RouteModel() {
}
fun maneuverIcon(routeManeuverType: Int): (Pair<Int, Int>) {
var type = Maneuver.TYPE_DEPART
var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) {
ManeuverType.None.value -> {
type = Maneuver.TYPE_STRAIGHT
Maneuver.TYPE_STRAIGHT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
ManeuverType.Destination.value,
ManeuverType.DestinationRight.value,
ManeuverType.DestinationLeft.value,
Maneuver.TYPE_DESTINATION,
-> {
type = Maneuver.TYPE_DESTINATION
currentTurnIcon = R.drawable.ic_turn_destination
}
ManeuverType.Right.value -> {
type = Maneuver.TYPE_TURN_NORMAL_RIGHT
Maneuver.TYPE_TURN_NORMAL_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_normal_right
}
ManeuverType.Left.value -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
Maneuver.TYPE_TURN_NORMAL_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_normal_left
}
ManeuverType.RampRight.value -> {
type = Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
ManeuverType.RampLeft.value -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = R.drawable.ic_turn_normal_left
}
ManeuverType.ExitRight.value -> {
type = Maneuver.TYPE_TURN_SLIGHT_RIGHT
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
ManeuverType.StayRight.value -> {
type = Maneuver.TYPE_KEEP_RIGHT
Maneuver.TYPE_KEEP_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
ManeuverType.StayLeft.value -> {
type = Maneuver.TYPE_KEEP_LEFT
Maneuver.TYPE_KEEP_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
ManeuverType.RoundaboutEnter.value -> {
type = Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
ManeuverType.RoundaboutExit.value -> {
type = Maneuver.TYPE_ROUNDABOUT_EXIT_CCW
Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
}
return Pair(type, currentTurnIcon)
return Pair(routeManeuverType, currentTurnIcon)
}
fun isNavigating(): Boolean {
@@ -336,4 +329,79 @@ open class RouteModel() {
|| type == ManeuverType.Destination.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()
}
val routingEngine: MutableLiveData<Int> by lazy {
MutableLiveData()
}
fun loadRecentPlace(location: Location, context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {

View File

@@ -4,7 +4,7 @@ import android.content.Context
import android.location.Location
import android.location.LocationManager
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.RouteEngine
import com.kouros.navigation.data.osrm.OsrmRepository
@@ -26,7 +26,7 @@ import kotlin.time.Duration.Companion.seconds
object NavigationUtils {
fun getRouteEngine(context: Context): ViewModel {
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE)
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
return when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
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="ok_action_title">OK</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>