Speed limit

This commit is contained in:
Dimitris
2025-12-22 11:08:53 +01:00
parent 9e453dc955
commit e5c74f6fa2
36 changed files with 1421 additions and 167 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 = 11 versionCode = 13
versionName = "0.1.3.11" versionName = "0.1.3.13"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -2,8 +2,12 @@ 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.NavigationRepository
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
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.ViewModel
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
@@ -26,5 +30,7 @@ class MainApplication : Application() {
private set private set
var useContacts = false var useContacts = false
val navigationViewModel = ViewModel(ValhallaRepository())
} }
} }

View File

@@ -1,6 +1,8 @@
package com.kouros.navigation.di package com.kouros.navigation.di
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.singleOf
import org.koin.core.module.dsl.viewModelOf import org.koin.core.module.dsl.viewModelOf
@@ -8,5 +10,5 @@ import org.koin.dsl.module
val appModule = module { val appModule = module {
viewModelOf(::ViewModel) viewModelOf(::ViewModel)
singleOf(::NavigationRepository) singleOf(::ValhallaRepository)
} }

View File

@@ -49,7 +49,7 @@ class MockLocation (private var locationManager: LocationManager) {
this.longitude = longitude this.longitude = longitude
this.altitude = 0.0 this.altitude = 0.0
this.accuracy = 1.0f this.accuracy = 1.0f
this.speed = 0f this.speed = 10f
this.time = System.currentTimeMillis() this.time = System.currentTimeMillis()
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos() this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
@@ -72,7 +72,7 @@ class MockLocation (private var locationManager: LocationManager) {
this.longitude = longitude this.longitude = longitude
this.altitude = 0.0 this.altitude = 0.0
this.accuracy = 1.0f this.accuracy = 1.0f
this.speed = 0f this.speed = 10f
this.time = System.currentTimeMillis() this.time = System.currentTimeMillis()
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos() this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()

View File

@@ -15,18 +15,13 @@ import androidx.annotation.RequiresPermission
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.BottomSheetScaffoldState
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@@ -37,19 +32,18 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
import com.kouros.data.R import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.valhalla.ValhallaRepository
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.model.ViewModel import com.kouros.navigation.model.ViewModel
@@ -73,7 +67,7 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
val viewModel = ViewModel(NavigationRepository())
val routeModel = RouteModel() val routeModel = RouteModel()
var tilt = 50.0 var tilt = 50.0
val useMock = true val useMock = true
@@ -109,8 +103,7 @@ class MainActivity : ComponentActivity() {
private var overpass = false private var overpass = false
init { init {
viewModel.route.observe(this, observer) navigationViewModel.route.observe(this, observer)
} }
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]) @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
@@ -221,7 +214,7 @@ class MainActivity : ComponentActivity() {
closeSheet: () -> Unit closeSheet: () -> Unit
) { ) {
if (!routeModel.isNavigating()) { if (!routeModel.isNavigating()) {
SearchSheet(applicationContext, viewModel, lastLocation) { closeSheet() } SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() }
} else { } else {
NavigationSheet( NavigationSheet(
routeModel, step!!, nextStep!!, routeModel, step!!, nextStep!!,
@@ -241,7 +234,7 @@ class MainActivity : ComponentActivity() {
val currentLocation = location(location.position.longitude, location.position.latitude) val currentLocation = location(location.position.longitude, location.position.latitude)
with(routeModel) { with(routeModel) {
if (isNavigating()) { if (isNavigating()) {
updateLocation(currentLocation) updateLocation(currentLocation, navigationViewModel)
stepData.value = currentStep() stepData.value = currentStep()
if (route.currentManeuverIndex + 1 <= route.maneuvers.size) { if (route.currentManeuverIndex + 1 <= route.maneuvers.size) {
nextStepData.value = nextStep() nextStepData.value = nextStep()
@@ -266,7 +259,7 @@ class MainActivity : ComponentActivity() {
) )
lastLocation = currentLocation lastLocation = currentLocation
if (!loadRecentPlaces) { if (!loadRecentPlaces) {
viewModel.loadRecentPlaces(applicationContext, lastLocation) navigationViewModel.loadRecentPlaces(applicationContext, lastLocation)
loadRecentPlaces = true loadRecentPlaces = true
} }
} }
@@ -320,7 +313,7 @@ class MainActivity : ComponentActivity() {
fun test() { fun test() {
for ((index, loc) in routeModel.route.waypoints.withIndex()) { for ((index, loc) in routeModel.route.waypoints.withIndex()) {
if (index > 300) { if (index > 300) {
routeModel.updateLocation(location(loc[0], loc[1])) routeModel.updateLocation(location(loc[0], loc[1]), navigationViewModel)
routeModel.currentStep() routeModel.currentStep()
if (routeModel.route.currentManeuverIndex + 1 <= routeModel.route.maneuvers.size) { if (routeModel.route.currentManeuverIndex + 1 <= routeModel.route.maneuvers.size) {
nextStepData.value = routeModel.nextStep() nextStepData.value = routeModel.nextStep()

View File

@@ -1,62 +1,28 @@
package com.kouros.navigation.ui package com.kouros.navigation.ui
import android.R.attr.x
import android.R.attr.y
import android.content.Context import android.content.Context
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.window.layout.WindowMetricsCalculator import androidx.window.layout.WindowMetricsCalculator
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.map.BuildingLayer
import com.kouros.navigation.car.map.DarkMode import com.kouros.navigation.car.map.DarkMode
import com.kouros.navigation.car.map.MapLibre import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.NavigationImage import com.kouros.navigation.car.map.NavigationImage
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.expressions.dsl.exponential
import org.maplibre.compose.expressions.dsl.interpolate
import org.maplibre.compose.expressions.dsl.zoom
import org.maplibre.compose.layers.CircleLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationTrackingEffect import org.maplibre.compose.location.LocationTrackingEffect
import org.maplibre.compose.location.UserLocationState import org.maplibre.compose.location.UserLocationState
import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.map.OrnamentOptions
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.GeoJsonSource
import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds

View File

@@ -25,7 +25,9 @@ import com.kouros.navigation.car.screen.SearchScreen
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.TAG import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation import com.kouros.navigation.utils.GeoUtils.snapLocation
class NavigationSession : Session(), NavigationScreen.Listener { class NavigationSession : Session(), NavigationScreen.Listener {
@@ -67,6 +69,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
} }
val navigationViewModel = ViewModel(ValhallaRepository())
init { init {
val lifecycle: Lifecycle = lifecycle val lifecycle: Lifecycle = lifecycle
@@ -78,7 +81,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel) surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this) navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED && !useContacts == PackageManager.PERMISSION_GRANTED && !useContacts
@@ -116,6 +119,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location, location,
navigationViewModel,
// TODO: Uri // TODO: Uri
) )
) { obj: Any? -> ) { obj: Any? ->

View File

@@ -28,7 +28,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.map.DarkMode import com.kouros.navigation.car.map.DarkMode
import com.kouros.navigation.car.map.DrawImage import com.kouros.navigation.car.map.DrawNavigationImages
import com.kouros.navigation.car.map.MapLibre import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.cameraState import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues import com.kouros.navigation.car.map.getPaddingValues
@@ -191,7 +191,7 @@ class SurfaceRenderer(
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing) duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
val currentSpeed: Float? by speed.observeAsState() val currentSpeed: Float? by speed.observeAsState()
if (viewStyle == ViewStyle.VIEW) { if (viewStyle == ViewStyle.VIEW) {
DrawImage(paddingValues, currentSpeed, width, height) DrawNavigationImages(paddingValues, currentSpeed, routeModel.routeState.maxSpeed, width, height)
} }
LaunchedEffect(position, viewStyle) { LaunchedEffect(position, viewStyle) {
cameraState.animateTo( cameraState.animateTo(

View File

@@ -21,6 +21,7 @@ import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -33,13 +34,10 @@ import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.expressions.dsl.contains
import org.maplibre.compose.expressions.dsl.exponential import org.maplibre.compose.expressions.dsl.exponential
import org.maplibre.compose.expressions.dsl.image import org.maplibre.compose.expressions.dsl.image
import org.maplibre.compose.expressions.dsl.interpolate import org.maplibre.compose.expressions.dsl.interpolate
@@ -60,15 +58,7 @@ import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.getBaseSource import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.geojson.FeatureCollection
import org.maplibre.spatialk.geojson.BoundingBox.Companion.serializer
import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.GeoJson
import org.maplibre.spatialk.geojson.Geometry
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import org.maplibre.spatialk.geojson.dsl.FeatureBuilder
import org.maplibre.spatialk.geojson.dsl.FeatureCollectionBuilder
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
@Composable @Composable
@@ -187,9 +177,12 @@ fun BuildingLayer(tiles: Source) {
} }
@Composable @Composable
fun DrawImage(padding: PaddingValues, speed: Float?, width: Int, height: Int) { fun DrawNavigationImages(padding: PaddingValues, speed: Float?, maxSpeed: Int, width: Int, height: Int) {
NavigationImage(padding, width, height) NavigationImage(padding, width, height)
Speed(width, height, speed) CurrentSpeed(width, height, speed)
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
MaxSpeed(width, height, maxSpeed)
}
} }
@Composable @Composable
@@ -215,7 +208,7 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
} }
@Composable @Composable
private fun Speed( private fun CurrentSpeed(
width: Int, width: Int,
height: Int, height: Int,
speed: Float? speed: Float?
@@ -235,6 +228,7 @@ private fun Speed(
val kmh = "km/h" val kmh = "km/h"
val styleSpeed = TextStyle( val styleSpeed = TextStyle(
fontSize = 22.sp, fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = Color.White, color = Color.White,
) )
val styleKm = TextStyle( val styleKm = TextStyle(
@@ -278,6 +272,61 @@ private fun Speed(
} }
} }
@Composable
private fun MaxSpeed(
width: Int,
height: Int,
maxSpeed: Int,
) {
val radius = 20
Box(
modifier = Modifier
.padding(
start = width.dp - 350.dp,
top = height.dp - 80.dp
),
contentAlignment = Alignment.Center
) {
val textMeasurerSpeed = rememberTextMeasurer()
val speed = maxSpeed.toString()
val styleSpeed = TextStyle(
fontSize = 26.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
)
val textLayoutSpeed = remember(speed) {
textMeasurerSpeed.measure(speed, styleSpeed)
}
Canvas(modifier = Modifier.fillMaxSize()) {
drawCircle(
center = Offset(
x = center.x,
y = center.y
),
radius = radius * 1.3.toFloat(),
color = Color.Red,
)
drawCircle(
center = Offset(
x = center.x,
y = center.y
),
radius = radius.toFloat(),
color = Color.White,
)
drawText(
textMeasurer = textMeasurerSpeed,
text = speed,
style = styleSpeed,
topLeft = Offset(
x = center.x - textLayoutSpeed.size.width / 2,
y = center.y - textLayoutSpeed.size.height / 2,
)
)
}
}
}
@Composable @Composable
fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) { fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) {
val darkMode = getIntKeyValue(context, Constants.DARK_MODE_SETTINGS) val darkMode = getIntKeyValue(context, Constants.DARK_MODE_SETTINGS)

View File

@@ -74,7 +74,7 @@ class RouteCarModel() : RouteModel() {
return step return step
} }
fun travelEstimate(): TravelEstimate { fun travelEstimate(carContext: CarContext): TravelEstimate {
val timeLeft = travelLeftTime() val timeLeft = travelLeftTime()
val timeToDestinationMillis = val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong()) TimeUnit.SECONDS.toMillis(timeLeft.toLong())
@@ -88,7 +88,7 @@ class RouteCarModel() : RouteModel() {
arrivalTime(), arrivalTime(),
TimeZone.getTimeZone("Europe/Berlin") TimeZone.getTimeZone("Europe/Berlin")
) )
return TravelEstimate.Builder( // The estimated distance to the destination. val travelBuilder = TravelEstimate.Builder( // The estimated distance to the destination.
Distance.create( Distance.create(
leftDistance, leftDistance,
displayUnit displayUnit
@@ -102,9 +102,12 @@ class RouteCarModel() : RouteModel() {
) )
.setRemainingTimeColor(CarColor.YELLOW) .setRemainingTimeColor(CarColor.YELLOW)
.setRemainingDistanceColor(CarColor.RED) .setRemainingDistanceColor(CarColor.RED)
//.setTripText(createCarText(carContext,R.string.navigate))
//.setTripIcon(createCarIcon(carContext, R.drawable.navigation_48px)) if (routeState.travelMessage.isNotEmpty()) {
.build() travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
travelBuilder.setTripText(CarText.create(routeState.travelMessage))
}
return travelBuilder.build()
} }
fun createString( fun createString(
@@ -144,6 +147,7 @@ class RouteCarModel() : RouteModel() {
.addAction(dismissAction).setCallback(object : AlertCallback { .addAction(dismissAction).setCallback(object : AlertCallback {
override fun onCancel(reason: Int) { override fun onCancel(reason: Int) {
} }
override fun onDismiss() { override fun onDismiss() {
} }
}).build() }).build()

View File

@@ -18,11 +18,13 @@ import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants.CHARGING_STATION import com.kouros.navigation.data.Constants.CHARGING_STATION
import com.kouros.navigation.data.Constants.FUEL_STATION import com.kouros.navigation.data.Constants.FUEL_STATION
import com.kouros.navigation.data.Constants.PHARMACY import com.kouros.navigation.data.Constants.PHARMACY
import com.kouros.navigation.model.ViewModel
class CategoriesScreen( class CategoriesScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
private val location: Location, private val location: Location,
private val viewModel: ViewModel
) : Screen(carContext) { ) : Screen(carContext) {
var categories: List<Category> = listOf( var categories: List<Category> = listOf(
@@ -46,7 +48,8 @@ class CategoriesScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location, location,
it.id it.id,
viewModel
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {

View File

@@ -33,9 +33,9 @@ class CategoryScreen(
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
location: Location, location: Location,
private val category: String, private val category: String,
private val viewModel: ViewModel
) : Screen(carContext) { ) : Screen(carContext) {
val viewModel = ViewModel(NavigationRepository())
var elements = listOf<Elements>() var elements = listOf<Elements>()
val observer = Observer<List<Elements>> { newElements -> val observer = Observer<List<Elements>> { newElements ->
@@ -113,7 +113,6 @@ class CategoryScreen(
.setOnClickListener { .setOnClickListener {
val location = location(it.lon!!, it.lat!!) val location = location(it.lon!!, it.lat!!)
surfaceRenderer.setCategoryLocation(location, category) surfaceRenderer.setCategoryLocation(location, category)
println(it)
} }
.setTitle(name) .setTitle(name)
.setImage(carIcon(carContext, category)) .setImage(carIcon(carContext, category))

View File

@@ -11,6 +11,7 @@ import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.ActionStrip import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarColor import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText
import androidx.car.app.model.Distance 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
@@ -39,7 +40,8 @@ class NavigationScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var routeModel: RouteCarModel, private var routeModel: RouteCarModel,
private var listener: Listener private var listener: Listener,
private val viewModel: ViewModel
) : ) :
Screen(carContext) { Screen(carContext) {
@@ -52,7 +54,7 @@ class NavigationScreen(
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER) var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
var recentPlace = Place() var recentPlace = Place()
var navigationType = NavigationType.VIEW var navigationType = NavigationType.VIEW
val viewModel = ViewModel(NavigationRepository())
val observer = Observer<String> { route -> val observer = Observer<String> { route ->
if (route.isNotEmpty()) { if (route.isNotEmpty()) {
navigationType = NavigationType.NAVIGATION navigationType = NavigationType.NAVIGATION
@@ -88,7 +90,6 @@ 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
println("Speed cameras ${speedCameras.size}")
} }
init { init {
@@ -118,7 +119,7 @@ class NavigationScreen(
.setNavigationInfo( .setNavigationInfo(
getRoutingInfo() getRoutingInfo()
) )
.setDestinationTravelEstimate(routeModel.travelEstimate()) .setDestinationTravelEstimate(routeModel.travelEstimate(carContext))
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build()) .setMapActionStrip(mapActionStripBuilder().build())
.setBackgroundColor(CarColor.GREEN) .setBackgroundColor(CarColor.GREEN)
@@ -301,7 +302,7 @@ class NavigationScreen(
.setOnClickListener { .setOnClickListener {
val navigateTo = location(recentPlace.longitude, recentPlace.latitude) val navigateTo = location(recentPlace.longitude, recentPlace.latitude)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo) viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo)
routeModel.routeState.destination = recentPlace routeModel.routeState = routeModel.routeState.copy(destination = recentPlace)
} }
.build() .build()
} }
@@ -394,7 +395,7 @@ class NavigationScreen(
private fun startSearchScreen() { private fun startSearchScreen() {
screenManager screenManager
.pushForResult( .pushForResult(
SearchScreen(carContext, surfaceRenderer, surfaceRenderer.lastLocation) SearchScreen(carContext, surfaceRenderer, surfaceRenderer.lastLocation, viewModel)
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {
val place = obj as Place val place = obj as Place
@@ -417,7 +418,7 @@ class NavigationScreen(
viewModel.saveRecent(place) viewModel.saveRecent(place)
currentNavigationLocation = location currentNavigationLocation = location
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location) viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
routeModel.routeState.destination = place routeModel.routeState = routeModel.routeState.copy(destination = place)
invalidate() invalidate()
} }
@@ -430,8 +431,8 @@ class NavigationScreen(
} }
fun calculateNewRoute(destination: Place) { fun calculateNewRoute(destination: Place) {
navigationType = NavigationType.REROUTE
stopNavigation() stopNavigation()
navigationType = NavigationType.REROUTE
invalidate() invalidate()
val mainThreadHandler = Handler(carContext.mainLooper) val mainThreadHandler = Handler(carContext.mainLooper)
mainThreadHandler.post { mainThreadHandler.post {
@@ -451,14 +452,9 @@ class NavigationScreen(
} }
fun updateTrip(location: Location) { fun updateTrip(location: Location) {
if (lastCameraSearch++ % 100 == 0) { updateSpeedCamera(location)
viewModel.getSpeedCameras(location)
}
if (speedCameras.isNotEmpty()) {
updateDistance(location)
}
with(routeModel) { with(routeModel) {
updateLocation(location) updateLocation(location, viewModel)
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) { ) {
@@ -472,6 +468,15 @@ class NavigationScreen(
invalidate() invalidate()
} }
private fun updateSpeedCamera(location: Location) {
if (lastCameraSearch++ % 100 == 0) {
viewModel.getSpeedCameras(location)
}
if (speedCameras.isNotEmpty()) {
updateDistance(location)
}
}
private fun updateDistance( private fun updateDistance(
location: Location, location: Location,
) { ) {
@@ -485,10 +490,9 @@ class NavigationScreen(
} }
val sortedList = updatedCameras.sortedWith(compareBy { it.distance }) val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
val camera = sortedList.first() val camera = sortedList.first()
if (camera.distance < 100) { if (camera.distance < 80) {
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed) routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
} }
} }
} }

View File

@@ -37,10 +37,10 @@ class PlaceListScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
private val location: Location, private val location: Location,
private val category: String private val category: String,
private val viewModel: ViewModel
) : Screen(carContext) { ) : Screen(carContext) {
val viewModel = ViewModel(NavigationRepository())
var places = listOf<Place>() var places = listOf<Place>()
val observer = Observer<List<Place>> { newPlaces -> val observer = Observer<List<Place>> { newPlaces ->
@@ -102,7 +102,8 @@ class PlaceListScreen(
RoutePreviewScreen( RoutePreviewScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
place place,
viewModel
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {

View File

@@ -41,13 +41,12 @@ import java.math.RoundingMode
class RoutePreviewScreen( class RoutePreviewScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var destination: Place private var destination: Place,
private val viewModel: ViewModel
) : ) :
Screen(carContext) { Screen(carContext) {
private var isFavorite = false private var isFavorite = false
val vieModel = ViewModel(NavigationRepository())
val routeModel = RouteCarModel() val routeModel = RouteCarModel()
val navigationMessage = NavigationMessage(carContext) val navigationMessage = NavigationMessage(carContext)
@@ -60,9 +59,9 @@ class RoutePreviewScreen(
} }
init { init {
vieModel.previewRoute.observe(this, observer) viewModel.previewRoute.observe(this, observer)
val location = location(destination.longitude, destination.latitude) val location = location(destination.longitude, destination.latitude)
vieModel.loadPreviewRoute(carContext, surfaceRenderer.lastLocation, location) viewModel.loadPreviewRoute(carContext, surfaceRenderer.lastLocation, location)
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
@@ -149,7 +148,7 @@ class RoutePreviewScreen(
CarToast.LENGTH_SHORT CarToast.LENGTH_SHORT
) )
.show() .show()
vieModel.saveFavorite(destination) viewModel.saveFavorite(destination)
invalidate() invalidate()
} }
.build() .build()
@@ -157,7 +156,7 @@ class RoutePreviewScreen(
private fun deleteFavoriteAction(): Action = Action.Builder() private fun deleteFavoriteAction(): Action = Action.Builder()
.setOnClickListener { .setOnClickListener {
if (isFavorite) { if (isFavorite) {
vieModel.deleteFavorite(destination) viewModel.deleteFavorite(destination)
} }
isFavorite = !isFavorite isFavorite = !isFavorite
finish() finish()

View File

@@ -28,7 +28,8 @@ import com.kouros.navigation.model.ViewModel
class SearchScreen( class SearchScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var location: Location private var location: Location,
private val viewModel: ViewModel
) : Screen(carContext) { ) : Screen(carContext) {
var isSearchComplete: Boolean = false var isSearchComplete: Boolean = false
@@ -47,8 +48,6 @@ class SearchScreen(
invalidate() invalidate()
} }
val viewModel = ViewModel(NavigationRepository())
init { init {
viewModel.searchPlaces.observe(this, observer) viewModel.searchPlaces.observe(this, observer)
} }
@@ -75,6 +74,7 @@ class SearchScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location, location,
viewModel
) )
) { obj: Any? -> ) { obj: Any? ->
surfaceRenderer.viewStyle = ViewStyle.VIEW surfaceRenderer.viewStyle = ViewStyle.VIEW
@@ -90,7 +90,8 @@ class SearchScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
location, location,
it.id it.id,
viewModel
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {

View File

@@ -34,6 +34,5 @@ class ViewModelTest {
val route = repo.getRoute(fromLocation, toLocation, SearchFilter()) val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
model.startNavigation(route) model.startNavigation(route)
println(route)
} }
} }

View File

@@ -8,4 +8,6 @@ val RouteColor = Color(0xFF5582D0)
val SpeedColor = Color(0xFF262525) val SpeedColor = Color(0xFF262525)
val MaxSpeedColor = Color(0xFF262525)
val PlaceColor = Color(0xFF868005) val PlaceColor = Color(0xFF868005)

View File

@@ -27,34 +27,14 @@ import java.net.URL
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
class NavigationRepository { abstract class NavigationRepository {
private val placesUrl = "https://kouros-online.de/maps/placespwd"; private val placesUrl = "https://kouros-online.de/maps/placespwd";
private val routeUrl = "https://kouros-online.de/valhalla/route?json="
private val nominatimUrl = "https://nominatim.openstreetmap.org/" private val nominatimUrl = "https://nominatim.openstreetmap.org/"
// Road classes from highest to lowest are:
// motorway, trunk, primary, secondary, tertiary, unclassified, residential, service_other.
// exclude_toll abstract fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String
fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String {
SearchFilter
val vLocation = listOf(
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude, search_filter = searchFilter),
Locations(lat = location.latitude, lon = location.longitude, search_filter = searchFilter)
)
val valhallaLocation = ValhallaLocation(
locations = vLocation,
costing = "auto",
units = "km",
id = "my_work_route",
language = "de-DE"
)
val routeLocation = Json.encodeToString(valhallaLocation)
return fetchUrl(routeUrl + routeLocation, true)
}
fun getRouteDistance(currentLocation: Location, location: Location, searchFilter: SearchFilter): Double { fun getRouteDistance(currentLocation: Location, location: Location, searchFilter: SearchFilter): Double {
val route = getRoute(currentLocation, location, searchFilter) val route = getRoute(currentLocation, location, searchFilter)
@@ -98,7 +78,7 @@ class NavigationRepository {
return places return places
} }
private fun fetchUrl(url: String, authenticator : Boolean): String { fun fetchUrl(url: String, authenticator : Boolean): String {
try { try {
if (authenticator) { if (authenticator) {
Authenticator.setDefault(object : Authenticator() { Authenticator.setDefault(object : Authenticator() {

View File

@@ -75,8 +75,8 @@ data class Route(
fun route(route: String) = apply { fun route(route: String) = apply {
if (route.isNotEmpty() && route != "[]") { if (route.isNotEmpty() && route != "[]") {
val gson = GsonBuilder().serializeNulls().create() val gson = GsonBuilder().serializeNulls().create()
val valhalla = gson.fromJson(route, ValhallaJson::class.java) val routeJson = gson.fromJson(route, ValhallaJson::class.java)
trip = valhalla.trip trip = routeJson.trip
} }
} }

View File

@@ -0,0 +1,13 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class Intersections(
@SerializedName("out") var out: Int? = null,
@SerializedName("entry") var entry: ArrayList<Boolean> = arrayListOf(),
@SerializedName("bearings") var bearings: ArrayList<Int> = arrayListOf(),
@SerializedName("location") var location: ArrayList<Double> = arrayListOf(),
@SerializedName("lanes") var lanes: ArrayList<Lane> = arrayListOf(),
)

View File

@@ -0,0 +1,8 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class Lane(
@SerializedName("valid" ) var valid: Boolean,
@SerializedName("indications" ) var indications: List<String>,
)

View File

@@ -0,0 +1,14 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class Legs (
@SerializedName("steps" ) var steps : ArrayList<Steps> = arrayListOf(),
@SerializedName("weight" ) var weight : Double? = null,
@SerializedName("summary" ) var summary : String? = null,
@SerializedName("duration" ) var duration : Double? = null,
@SerializedName("distance" ) var distance : Double? = null
)

View File

@@ -0,0 +1,14 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class Maneuver (
@SerializedName("bearing_after" ) var bearingAfter : Int? = null,
@SerializedName("bearing_before" ) var bearingBefore : Int? = null,
@SerializedName("location" ) var location : ArrayList<Double> = arrayListOf(),
@SerializedName("modifier" ) var modifier : String? = null,
@SerializedName("type" ) var type : String? = null
)

View File

@@ -0,0 +1,12 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class OsrmJson (
@SerializedName("code" ) var code : String? = null,
@SerializedName("routes" ) var routes : ArrayList<Routes> = arrayListOf(),
@SerializedName("waypoints" ) var waypoints : ArrayList<Waypoints> = arrayListOf()
)

View File

@@ -0,0 +1,18 @@
package com.kouros.navigation.data.osrm
import android.location.Location
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.SearchFilter
private const val routeUrl = "https://router.project-osrm.org/route/v1/driving/"
class OsrmRepository : NavigationRepository() {
override fun getRoute(
currentLocation: Location,
location: Location,
searchFilter: SearchFilter
): String {
val routeLocation = "${currentLocation.latitude},${currentLocation.longitude};${location.latitude},${location.longitude}?steps=true"
return fetchUrl(routeUrl + routeLocation, true)
}
}

View File

@@ -0,0 +1,15 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class Routes (
@SerializedName("legs" ) var legs : ArrayList<Legs> = arrayListOf(),
@SerializedName("weight_name" ) var weightName : String? = null,
@SerializedName("geometry" ) var geometry : String? = null,
@SerializedName("weight" ) var weight : Double? = null,
@SerializedName("duration" ) var duration : Double? = null,
@SerializedName("distance" ) var distance : Double? = null
)

View File

@@ -0,0 +1,18 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class Steps (
@SerializedName("intersections" ) var intersections : ArrayList<Intersections> = arrayListOf(),
@SerializedName("driving_side" ) var drivingSide : String? = null,
@SerializedName("geometry" ) var geometry : String? = null,
@SerializedName("maneuver" ) var maneuver : Maneuver? = Maneuver(),
@SerializedName("name" ) var name : String? = null,
@SerializedName("mode" ) var mode : String? = null,
@SerializedName("weight" ) var weight : Double? = null,
@SerializedName("duration" ) var duration : Double? = null,
@SerializedName("distance" ) var distance : Double? = null
)

View File

@@ -0,0 +1,13 @@
package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName
data class Waypoints (
@SerializedName("hint" ) var hint : String? = null,
@SerializedName("location" ) var location : ArrayList<Double> = arrayListOf(),
@SerializedName("name" ) var name : String? = null,
@SerializedName("distance" ) var distance : Double? = null
)

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,8 @@ package com.kouros.navigation.data.overpass
import android.location.Location import android.location.Location
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.kouros.navigation.utils.GeoUtils.getBoundingBox2
import com.kouros.navigation.utils.GeoUtils.getOverpassBbox import com.kouros.navigation.utils.GeoUtils.getOverpassBbox
import com.kouros.navigation.utils.NavigationUtils import kotlinx.serialization.json.Json
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@@ -12,8 +11,33 @@ import java.net.URL
class Overpass { class Overpass {
val overpassUrl = "https://overpass.kumi.systems/api/interpreter" val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
fun getAmenities(type: String, category: String, location: Location) : List<Elements> { fun getAround(radius: Int, linestring: String) : List<Elements> {
val boundingBox = getOverpassBbox(location, 5.0) val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
"Accept",
"application/json"
)
httpURLConnection.setDoOutput(true);
// define search query
val searchQuery = """
|[out:json];
|(
| way[highway](around:$radius,$linestring)
| ;
|);
|out body;
""".trimMargin()
return overpassApi(httpURLConnection, searchQuery)
}
fun getAmenities(
type: String,
category: String,
location: Location,
radius: Double
): List<Elements> {
val boundingBox = getOverpassBbox(location, radius)
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST" httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty( httpURLConnection.setRequestProperty(
@@ -32,7 +56,10 @@ class Overpass {
|); |);
|out body; |out body;
""".trimMargin() """.trimMargin()
return overpassApi(httpURLConnection, searchQuery)
}
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
// Send the JSON we created // Send the JSON we created
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream) val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(searchQuery) outputStreamWriter.write(searchQuery)
@@ -44,7 +71,7 @@ class Overpass {
.use { it.readText() } // defaults to UTF-8 .use { it.readText() } // defaults to UTF-8
val gson = GsonBuilder().serializeNulls().create() val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java) val overpass = gson.fromJson(response, Amenity::class.java)
println("Overpass: $type $response") //println("Overpass: $response")
return overpass.elements return overpass.elements
} }
return emptyList() return emptyList()

View File

@@ -0,0 +1,29 @@
package com.kouros.navigation.data.valhalla
import android.location.Location
import com.kouros.navigation.data.Locations
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.SearchFilter
import com.kouros.navigation.data.ValhallaLocation
import kotlinx.serialization.json.Json
private const val routeUrl = "https://kouros-online.de/valhalla/route?json="
class ValhallaRepository : NavigationRepository() {
override fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String {
SearchFilter
val vLocation = listOf(
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude, search_filter = searchFilter),
Locations(lat = location.latitude, lon = location.longitude, search_filter = searchFilter)
)
val valhallaLocation = ValhallaLocation(
locations = vLocation,
costing = "auto",
units = "km",
id = "my_work_route",
language = "de-DE"
)
val routeLocation = Json.encodeToString(valhallaLocation)
return fetchUrl(routeUrl + routeLocation, true)
}
}

View File

@@ -4,16 +4,16 @@ 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 com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.ManeuverType import com.kouros.navigation.data.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.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import org.maplibre.turf.TurfMeasurement import kotlinx.coroutines.DelicateCoroutinesApi
import org.maplibre.turf.TurfMisc import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -21,13 +21,18 @@ open class RouteModel() {
data class RouteState( data class RouteState(
val route: Route? = null, val route: Route? = null,
val isNavigating: Boolean = false, val isNavigating: Boolean = false,
var destination: Place = Place(), val destination: Place = Place(),
val arrived: Boolean = false, val arrived: Boolean = false,
var maneuverType: Int = 0, val maneuverType: Int = 0,
var currentShapeIndex: Int = 0, var currentShapeIndex: Int = 0,
var distanceToStepEnd: Float = 0F, var distanceToStepEnd: Float = 0F,
var beginIndex: Int = 0, var beginIndex: Int = 0,
var endIndex: Int = 0 var endIndex: Int = 0,
val travelMessage: String = "",
// max speed for street (maneuver)
val lastSpeedIndex: Int = 0,
val maxSpeed: Int = 0,
) )
var routeState = RouteState() var routeState = RouteState()
@@ -61,7 +66,8 @@ open class RouteModel() {
) )
} }
fun updateLocation(location: Location) { @OptIn(DelicateCoroutinesApi::class)
fun updateLocation(location: Location, viewModel: ViewModel) {
var nearestDistance = 100000.0f var nearestDistance = 100000.0f
var newShapeIndex = -1 var newShapeIndex = -1
// find nearest waypoint and current shape index // find nearest waypoint and current shape index
@@ -77,6 +83,24 @@ open class RouteModel() {
// find maneuver // find maneuver
// calculate distance to step end // calculate distance to step end
findManeuver(newShapeIndex) findManeuver(newShapeIndex)
GlobalScope.launch(Dispatchers.IO) {
updateSpeedLimit(location, viewModel)
}
}
fun updateSpeedLimit(location: Location, viewModel: ViewModel) {
// speed limit for each maneuver index
if (routeState.lastSpeedIndex < route.currentManeuverIndex) {
routeState = routeState.copy(lastSpeedIndex = route.currentManeuverIndex)
val elements = viewModel.getMaxSpeed(location)
elements.forEach {
if (it.tags.name != null && it.tags.maxspeed != null) {
val speed = it.tags.maxspeed!!.toInt()
routeState = routeState.copy(maxSpeed = speed)
}
}
}
} }
private fun findManeuver(newShapeIndex: Int) { private fun findManeuver(newShapeIndex: Int) {
@@ -127,7 +151,7 @@ open class RouteModel() {
maneuverType = relevantManeuver.type maneuverType = relevantManeuver.type
} }
val maneuverIconPair = maneuverIcon(maneuverType) val maneuverIconPair = maneuverIcon(maneuverType)
routeState.maneuverType = maneuverIconPair.first routeState = routeState.copy(maneuverType = maneuverIconPair.first)
// Construct and return the final StepData object // Construct and return the final StepData object
return StepData( return StepData(
streetName, streetName,
@@ -149,6 +173,7 @@ open class RouteModel() {
when (distanceLeft) { when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> { in 0.0..NEXT_STEP_THRESHOLD -> {
} }
else -> { else -> {
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0] text = maneuver.streetNames[0]

View File

@@ -251,7 +251,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun getAmenities(category: String, location: Location) { fun getAmenities(category: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities("amenity", category, location) val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
val distAmenities = mutableListOf<Elements>() val distAmenities = mutableListOf<Elements>()
amenities.forEach { amenities.forEach {
val plLocation = val plLocation =
@@ -267,7 +267,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun getSpeedCameras(location: Location) { fun getSpeedCameras(location: Location) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities("highway", "speed_camera", location) val amenities = Overpass().getAmenities("highway", "speed_camera", location, 5.0)
val distAmenities = mutableListOf<Elements>() val distAmenities = mutableListOf<Elements>()
amenities.forEach { amenities.forEach {
val plLocation = val plLocation =
@@ -281,6 +281,12 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun getMaxSpeed(location: Location) : List<Elements> {
val lineString = "${location.latitude},${location.longitude}"
val amenities = Overpass().getAround(10, lineString)
return amenities
}
fun saveFavorite(place: Place) { fun saveFavorite(place: Place) {
place.category = Constants.FAVORITES place.category = Constants.FAVORITES
savePlace(place) savePlace(place)

View File

@@ -74,10 +74,10 @@ fun calculateZoom(speed: Double?): Double {
val speedKmh = (speed * 3.6).toInt() val speedKmh = (speed * 3.6).toInt()
val zoom = when (speedKmh) { val zoom = when (speedKmh) {
in 0..10 -> 18.0 in 0..10 -> 18.0
in 11..30 -> 17.0 in 11..30 -> 17.5
in 31..50 -> 16.0 in 31..50 -> 17.0
in 61..70 -> 15.0 in 61..70 -> 16.5
else -> 14 else -> 16.0
} }
return zoom.toDouble() return zoom.toDouble()
} }

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="M40,840L480,80L920,840L40,840ZM178,760L782,760L480,240L178,760ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM440,600L520,600L520,400L440,400L440,600ZM480,500L480,500L480,500Z"/>
</vector>